diff --git a/.cargo/config b/.cargo/config.toml similarity index 76% rename from .cargo/config rename to .cargo/config.toml index 91039e46e..2a74c63a7 100644 --- a/.cargo/config +++ b/.cargo/config.toml @@ -2,4 +2,4 @@ rustflags = ["-Ctarget-feature=+crt-static"] [target.aarch64-unknown-linux-gnu] -linker = "aarch64-linux-gnu-gcc" +linker = "aarch64-linux-gnu-gcc" \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed6bb0a63..388f63c30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,19 +172,36 @@ jobs: lfs: true - run: git lfs pull - uses: ./.github/workflows/tests/csharp/linux + +##### SWIFT TESTS ##### + tests_swift: + runs-on: "macos-14" + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - run: git lfs pull + - uses: ./.github/workflows/tests/swift + +##### KOTLIN BUILD ##### + build_kotlin: + runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - run: git lfs pull + - uses: ./.github/workflows/kotlin - #================================================= - # Disabled for now, Android Emulator doesn't boot. - mmorrissette 2024-02-21 - #================================================= - #tests_nuget_android: - # needs: [nugets_macos, setup_config] - # runs-on: "macos-12" - # steps: - # - uses: actions/checkout@v4 - # with: - # lfs: true - # - run: git lfs pull - # - uses: ./.github/workflows/tests/csharp/android + #tests_nuget_android: + # needs: [nugets_macos, setup_config] + # runs-on: "macos-14" + # steps: + # - uses: actions/checkout@v4 + # with: + # lfs: true + # - run: git lfs pull + # - uses: ./.github/workflows/tests/csharp/android tests_ios_integration: needs: [tests_nuget_ios, setup_config] diff --git a/.github/workflows/kotlin/action.yml b/.github/workflows/kotlin/action.yml new file mode 100644 index 000000000..352b85139 --- /dev/null +++ b/.github/workflows/kotlin/action.yml @@ -0,0 +1,76 @@ +name: Build Kotlin Package +runs: + using: composite + steps: + - name: Installing dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install nuget gcc-multilib software-properties-common + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Install rust + shell: bash + run: | + rustup update + + rustup target add x86_64-unknown-linux-gnu + rustup target add i686-unknown-linux-gnu + rustup target add aarch64-unknown-linux-gnu + + rustup target add aarch64-linux-android + rustup target add armv7-linux-androideabi + rustup target add i686-linux-android + rustup target add x86_64-linux-android + + - name: Installing Kotlin + shell: bash + run: sudo snap install --classic kotlin + + - name: Configure Android NDK + shell: bash + run: | + # Install NDK 25 + + ANDROID_ROOT="/usr/local/lib/android" + ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk" + SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" + echo "y" | $SDKMANAGER "ndk;25.2.9519653" + + export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle + + echo "[target.aarch64-linux-android] + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-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\" + linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi19-clang\" + [target.i686-linux-android] + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-ar\" + linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android19-clang\" + [target.x86_64-linux-android] + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar\" + linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang\"" >> ./.cargo/config + + - name: Build + working-directory: ./wrappers/kotlin + shell: bash + run: | + export ANDROID_ROOT="/usr/local/lib/android" + export ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk" + export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/25.2.9519653 $ANDROID_NDK + + make + make android + + chmod +x gradlew + ./gradlew test + ./gradlew build + + - name: Kotlin Package + uses: actions/upload-artifact@v4 + with: + name: kotlin + path: ./wrappers/kotlin/lib/build + diff --git a/.github/workflows/tests/kotlin/action.yml b/.github/workflows/tests/kotlin/action.yml new file mode 100644 index 000000000..ecc70b38e --- /dev/null +++ b/.github/workflows/tests/kotlin/action.yml @@ -0,0 +1,21 @@ +name: Build Kotlin Package +runs: + using: composite + steps: + - name: Run setup + working-directory: ./wrappers/kotlin + shell: bash + run: sh setup.sh + + - name: Build Wrapper + working-directory: ./wrappers/kotlin + shell: bash + run: make + + - name: Build Lib + working-directory: ./wrappers/kotlin + shell: bash + run: sh build.sh + + # TODO test and package into an artifact + diff --git a/.github/workflows/tests/swift/action.yml b/.github/workflows/tests/swift/action.yml new file mode 100644 index 000000000..19b0925bc --- /dev/null +++ b/.github/workflows/tests/swift/action.yml @@ -0,0 +1,8 @@ +name: Test Swift Package +runs: + using: composite + steps: + - name: Run tests + working-directory: ./wrappers/swift + shell: bash + run: sh generate.sh diff --git a/.gitignore b/.gitignore index f9e679942..678b9d47a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ packages .vs *.nupkg *.user +.build +wrappers/swift/bindings/* +wrappers/swift/output/* wrappers/csharp/nuget/dotnet/package/* wrappers/csharp/nuget/Android/Devolutions.Crypto.Android/package/* wrappers/csharp/nuget/iOS/Devolutions.Crypto.iOS/package/* @@ -24,3 +27,5 @@ bin obj .angular node_modules +__pycache__ +config.txt \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 167bfbd26..97e47fd6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -46,38 +46,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + [[package]] name = "arbitrary" version = "0.4.7" @@ -89,21 +95,62 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn 2.0.89", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -129,11 +176,29 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blahaj" @@ -142,7 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5106bf2680d585dc5f29711b8aa5dde353180b8e14af89b7f0424f760c84e7ce" dependencies = [ "arbitrary", - "hashbrown 0.15.1", + "hashbrown", "rand", ] @@ -187,6 +252,44 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cbc" version = "0.1.2" @@ -198,9 +301,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -245,9 +351,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -255,9 +361,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -267,27 +373,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "console_error_panic_hook" @@ -307,15 +413,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -361,7 +467,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] @@ -412,6 +518,8 @@ dependencies = [ "sha2", "strum", "subtle", + "thiserror", + "typed-builder", "wasm-bindgen", "wasm-bindgen-test", "x25519-dalek", @@ -455,6 +563,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "devolutions-crypto-uniffi" +version = "0.9.1" +dependencies = [ + "devolutions-crypto", + "uniffi", + "uniffi-builder-macro", +] + [[package]] name = "digest" version = "0.10.7" @@ -509,6 +626,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -533,10 +659,21 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] [[package]] name = "hashbrown" @@ -572,12 +709,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -598,24 +735,30 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libfuzzer-sys" @@ -639,9 +782,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -658,32 +801,74 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -714,6 +899,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -734,6 +925,12 @@ dependencies = [ "spki", ] +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "poly1305" version = "0.8.0" @@ -747,30 +944,33 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" dependencies = [ "unicode-ident", ] @@ -822,7 +1022,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] @@ -835,14 +1035,14 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -879,9 +1079,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] @@ -900,18 +1100,24 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "salsa20" @@ -922,6 +1128,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -934,6 +1149,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "scrypt" version = "0.11.0" @@ -950,12 +1185,15 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] [[package]] name = "serde" -version = "1.0.203" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -973,13 +1211,25 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", ] [[package]] @@ -993,6 +1243,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" @@ -1002,12 +1258,24 @@ dependencies = [ "rand_core", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "spki" version = "0.7.3" @@ -1018,6 +1286,12 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -1026,9 +1300,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] @@ -1043,14 +1317,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] name = "subtle" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1065,9 +1339,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -1076,38 +1350,247 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", +] + +[[package]] +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] +[[package]] +name = "typed-builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "uniffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "clap", + "uniffi_bindgen", + "uniffi_build", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi-bindgen" +version = "0.1.0" +dependencies = [ + "uniffi", +] + +[[package]] +name = "uniffi-builder-macro" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.89", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck 0.5.0", + "once_cell", + "paste", + "serde", + "textwrap", + "toml", + "uniffi_meta", + "uniffi_udl", +] + +[[package]] +name = "uniffi_build" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7cf32576e08104b7dc2a6a5d815f37616e66c6866c2a639fe16e6d2286b75b" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" +dependencies = [ + "quote", + "syn 2.0.89", +] + +[[package]] +name = "uniffi_core" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" +dependencies = [ + "anyhow", + "bytes", + "log", + "once_cell", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.89", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] [[package]] name = "unindent" @@ -1133,9 +1616,19 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "wasi" @@ -1145,34 +1638,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -1182,9 +1676,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1192,31 +1686,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-bindgen-test" -version = "0.3.42" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" dependencies = [ "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -1225,39 +1720,57 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.42" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", ] [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1271,57 +1784,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -1338,6 +1851,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -1355,5 +1889,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index cae4ccc31..1067791b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,13 @@ members = [ "ffi", "fuzz", "python", -] + "uniffi/uniffi-bindgen", + "uniffi/devolutions-crypto-uniffi", + "uniffi/devolutions-crypto-uniffi/uniffi-builder-macro", + ] + +[workspace.dependencies] +uniffi = "0.28" [package] name = "devolutions-crypto" @@ -42,6 +48,8 @@ subtle = "2" zeroize = { version = "1.8" } rand = "0.8" rand_core = { version = "0.6" } +thiserror = "1.0.64" +typed-builder = "0.20.0" ed25519-dalek = { version = "2", features = [ "rand_core" ] } x25519-dalek = { version = "2", features = [ "static_secrets" ] } @@ -74,4 +82,3 @@ wasm-bindgen-test = "0.3" default = [] fuzz = ["arbitrary", "blahaj/fuzzing"] wbindgen = ["wasm-bindgen", "serde-wasm-bindgen", "js-sys"] - diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 753a88c9c..ba947713f 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition = "2021" [lib] -name = "devolutions_crypto" +name = "devolutions_crypto_ffi" crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 98051d2a7..5157231b8 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -621,6 +621,8 @@ pub unsafe extern "C" fn GenerateSigningKeyPair( /// * `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. #[no_mangle] pub unsafe extern "C" fn GetSigningPublicKey( keypair: *const u8, @@ -900,7 +902,7 @@ pub unsafe extern "C" fn DeriveKeyArgon2( Err(e) => return e.error_code(), }; - let native_result = match utils::derive_key_argon2(&key, &argon2_parameters) { + let native_result = match utils::derive_key_argon2(key, &argon2_parameters) { Ok(x) => Zeroizing::new(x), Err(e) => return e.error_code(), }; @@ -948,8 +950,8 @@ pub unsafe extern "C" fn DeriveKeyPbkdf2( let result = slice::from_raw_parts_mut(result, result_length); let native_result = Zeroizing::new(utils::derive_key_pbkdf2( - &key, - &salt, + key, + salt, niterations, result_length, )); @@ -1042,7 +1044,7 @@ pub unsafe extern "C" fn GetDefaultArgon2Parameters( ) -> i64 { let argon2_parameters = slice::from_raw_parts_mut(argon2_parameters, argon2_parameters_length); - let argon2_parameters_raw: Vec = Argon2Parameters::default().into(); + let argon2_parameters_raw: Vec = (&Argon2Parameters::default()).into(); argon2_parameters.copy_from_slice(&argon2_parameters_raw); 0 } @@ -1088,9 +1090,9 @@ pub unsafe extern "C" fn Decode( }; let input = std::str::from_utf8_unchecked(slice::from_raw_parts(input, input_length)); - let mut output = slice::from_raw_parts_mut(output, output_length); + let output = slice::from_raw_parts_mut(output, output_length); - match general_purpose::STANDARD.decode_slice_unchecked(&input, &mut output) { + match general_purpose::STANDARD.decode_slice_unchecked(input, output) { Ok(res) => res as i64, Err(_e) => -1, } @@ -1118,9 +1120,9 @@ pub unsafe extern "C" fn Encode( }; let input = slice::from_raw_parts(input, input_length); - let mut output = slice::from_raw_parts_mut(output, output_length); + let output = slice::from_raw_parts_mut(output, output_length); - match general_purpose::STANDARD.encode_slice(&input, &mut output) { + match general_purpose::STANDARD.encode_slice(input, output) { Ok(res) => res as i64, Err(_err) => -1, } @@ -1148,9 +1150,9 @@ pub unsafe extern "C" fn DecodeUrl( }; let input = std::str::from_utf8_unchecked(slice::from_raw_parts(input, input_length)); - let mut output = slice::from_raw_parts_mut(output, output_length); + let output = slice::from_raw_parts_mut(output, output_length); - match general_purpose::URL_SAFE_NO_PAD.decode_slice_unchecked(&input, &mut output) { + match general_purpose::URL_SAFE_NO_PAD.decode_slice_unchecked(input, output) { Ok(res) => res as i64, Err(_e) => -1, } @@ -1178,9 +1180,9 @@ pub unsafe extern "C" fn EncodeUrl( }; let input = slice::from_raw_parts(input, input_length); - let mut output = slice::from_raw_parts_mut(output, output_length); + let output = slice::from_raw_parts_mut(output, output_length); - match general_purpose::URL_SAFE_NO_PAD.encode_slice(&input, &mut output) { + match general_purpose::URL_SAFE_NO_PAD.encode_slice(input, output) { Ok(res) => res as i64, Err(_err) => -1, } @@ -1241,7 +1243,7 @@ pub unsafe extern "C" fn Version(output: *mut u8, output_length: usize) -> i64 { }; let output = slice::from_raw_parts_mut(output, output_length); - output.copy_from_slice(&VERSION.as_bytes()); + output.copy_from_slice(VERSION.as_bytes()); output.len() as i64 } @@ -1254,18 +1256,22 @@ fn test_encrypt_length() { let one_full_block = b"0123456789abcdef"; let multiple_blocks = b"0123456789abcdefghijkl"; - let length_zero_enc: Vec = encrypt(length_zero, key, CiphertextVersion::Latest) - .unwrap() - .into(); - let length_one_block_enc: Vec = encrypt(length_one_block, key, CiphertextVersion::Latest) - .unwrap() - .into(); - let one_full_block_enc: Vec = encrypt(one_full_block, key, CiphertextVersion::Latest) - .unwrap() - .into(); - let multiple_blocks_enc: Vec = encrypt(multiple_blocks, key, CiphertextVersion::Latest) - .unwrap() - .into(); + let length_zero_enc: Vec = + devolutions_crypto::ciphertext::encrypt(length_zero, key, CiphertextVersion::Latest) + .unwrap() + .into(); + let length_one_block_enc: Vec = + devolutions_crypto::ciphertext::encrypt(length_one_block, key, CiphertextVersion::Latest) + .unwrap() + .into(); + let one_full_block_enc: Vec = + devolutions_crypto::ciphertext::encrypt(one_full_block, key, CiphertextVersion::Latest) + .unwrap() + .into(); + let multiple_blocks_enc: Vec = + devolutions_crypto::ciphertext::encrypt(multiple_blocks, key, CiphertextVersion::Latest) + .unwrap() + .into(); assert_eq!( length_zero_enc.len() as i64, @@ -1347,7 +1353,7 @@ fn test_get_default_argon2parameters() { let _params = Argon2Parameters::try_from(argon2_parameters).unwrap(); - let defaults: Vec = Argon2Parameters::default().into(); + let defaults: Vec = (&Argon2Parameters::default()).into(); let received: Vec = argon2_parameters.to_vec(); // The -16 is to remove the salt, since it is random diff --git a/python/Cargo.toml b/python/Cargo.toml index bfbaca3e3..66f92e104 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition = "2021" [lib] -name = "devolutions_crypto" +name = "devolutions_crypto_python" crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/python/pyproject.toml b/python/pyproject.toml index 2b120cc3e..812c6af10 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -6,4 +6,5 @@ requires = ["maturin>=0.13"] build-backend = "maturin" [tool.maturin] -bindings = "pyo3" \ No newline at end of file +bindings = "pyo3" +module-name = "devolutions_crypto" \ No newline at end of file diff --git a/src/argon2parameters.rs b/src/argon2parameters.rs index 4e937c4ae..6a5a54143 100644 --- a/src/argon2parameters.rs +++ b/src/argon2parameters.rs @@ -1,11 +1,12 @@ use std::{ convert::TryFrom, - io::{Cursor, Read}, + io::{Cursor, Read, Write}, }; use argon2::{Config, ThreadMode, Variant, Version}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use rand_core::{OsRng, RngCore}; +use typed_builder::TypedBuilder; #[cfg(feature = "wbindgen")] use wasm_bindgen::prelude::*; @@ -13,6 +14,26 @@ use wasm_bindgen::prelude::*; use super::Error; use super::Result; +pub mod defaults { + use argon2::Variant; + use argon2::Version; + use rand_core::{OsRng, RngCore}; + + pub const LENGTH: u32 = 32; + pub const LANES: u32 = 1; + pub const MEMORY: u32 = 4096; + pub const ITERATIONS: u32 = 2; + pub const VARIANT: Variant = Variant::Argon2id; + pub const VERSION: Version = Version::Version13; + pub const DC_VERSION: u32 = 1; + + pub fn salt() -> Vec { + let mut salt = vec![0u8; 16]; + OsRng.fill_bytes(salt.as_mut_slice()); + salt + } +} + /// 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. @@ -23,27 +44,37 @@ use super::Result; /// Note that calling `default()` will also generate a new random salt, /// so two calls to `default()` will not generate the same structure. #[cfg_attr(feature = "wbindgen", wasm_bindgen(inspectable))] -#[derive(Clone)] +#[derive(Clone, TypedBuilder)] pub struct Argon2Parameters { /// Length of the desired hash. Should be 32 in most case. + #[builder(default=defaults::LENGTH)] pub length: u32, /// Number of parallel jobs to run. Only use if always computed in a multithreaded environment. + #[builder(default=defaults::LANES)] pub lanes: u32, /// Memory used by the algorithm in KiB. Higher is better. + #[builder(default=defaults::MEMORY)] pub memory: u32, /// Number of iterations(time cost). Higher is better. + #[builder(default=defaults::ITERATIONS)] pub iterations: u32, /// The variant to use. You should almost always use Argon2Id. + #[builder(default=defaults::VARIANT)] variant: Variant, /// The version of Argon2 to use. Use the latest. + #[builder(default=defaults::VERSION)] version: Version, /// Version of this structure in DevolutionsCrypto. + #[builder(default=defaults::DC_VERSION)] dc_version: u32, /// Authenticated but not secret data. + #[builder(default)] associated_data: Vec, /// Secret key to sign the hash. Note that this is not serialized. + #[builder(default)] secret_key: Vec, /// A 16-bytes salt to use that is generated automatically. Should not be accessed directly. + #[builder(default = defaults::salt())] salt: Vec, } @@ -78,8 +109,8 @@ impl Default for Argon2Parameters { } } -impl From for Vec { - fn from(mut params: Argon2Parameters) -> Self { +impl From<&Argon2Parameters> for Vec { + fn from(params: &Argon2Parameters) -> Self { // Data is encoded this way: // All the u32 data -> enums(as u8) -> Vectors(length as u32 + vec)) // Note that the secret key is not serialized. @@ -98,11 +129,13 @@ impl From for Vec { data.write_u32::(params.associated_data.len() as u32) .unwrap(); - data.append(&mut params.associated_data); + + data.write_all(¶ms.associated_data).unwrap(); data.write_u32::(params.salt.len() as u32) .unwrap(); - data.append(&mut params.salt); + + data.write_all(¶ms.salt).unwrap(); data } @@ -195,7 +228,7 @@ fn test_argon2() { // Computes the first hash let hash1 = config.compute(b"Password1").unwrap(); - let config_vec: Vec = config.into(); + let config_vec: Vec = (&config).into(); assert_ne!(config_vec.len(), 0); diff --git a/src/error.rs b/src/error.rs index 7fecc7297..aa8e34dcb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ //! Possible errors in the library. use cbc::cipher::block_padding::UnpadError; -use std::fmt; #[cfg(feature = "wbindgen")] use wasm_bindgen::JsValue; @@ -11,39 +10,55 @@ use strum::IntoStaticStr; use hmac::digest::MacError; /// This crate's error type. -#[derive(Debug, IntoStaticStr)] +#[derive(Debug, IntoStaticStr, thiserror::Error)] pub enum Error { /// The provided data has an invalid length. Error code: -1 + #[error("The provided data has an invalid length")] InvalidLength, /// The key length is invalid. Error code: -2 + #[error("The key length is invalid.")] InvalidKeyLength, /// The length of the FFI output buffer is invalid. Error code: -3 + #[error("The length of the FFI output buffer is invalid.")] InvalidOutputLength, /// The signature of the data blob does not match 0x0d0c. Error code: -11 + #[error("The signature of the data blob does not match 0x0d0c.")] InvalidSignature, /// The MAC is invalid. Error code: -12 + #[error("The MAC is invalid.")] InvalidMac, /// The operation cannot be done with this type. Error code: -13 + #[error("The operation cannot be done with this type.")] InvalidDataType, /// The data type is unknown. Error code: -21 + #[error("The data type is unknown.")] UnknownType, /// The data subtype is unknown. Error code: -22 + #[error("The data subtype is unknown.")] UnknownSubtype, /// The data type version is unknown. Error code: -23 + #[error("The data type version is unknown.")] UnknownVersion, /// The data is invalid. Error code: -24 + #[error("The data is invalid.")] InvalidData, /// A null pointer has been passed to the FFI interface. Error code: -31 + #[error("A null pointer has been passed to the FFI interface.")] NullPointer, /// A cryptographic error occurred. Error code: -32 + #[error("A cryptographic error occurred.")] CryptoError, /// An error with the Random Number Generator occurred. Error code: -33 + #[error("An error with the Random Number Generator occurred.")] RandomError, /// A generic IO error has occurred. Error code: -34 - IoError(std::io::Error), + #[error("Generic IO error: {0}")] + IoError(#[from] std::io::Error), /// There is not enough shares to regenerate a secret: -41 + #[error("There wasn't enough share to regenerate the secret.")] NotEnoughShares, /// The version of the multiple data is inconsistent: -42 + #[error("The version is not the same for all the data.")] InconsistentVersion, } @@ -70,47 +85,6 @@ impl Error { Error::InconsistentVersion => -42, } } - - /// Returns a description of the error - pub fn description(&self) -> String { - match *self { - Error::InvalidLength => "The provided data has an invalid length.".to_string(), - Error::InvalidKeyLength => "The key length is invalid.".to_string(), - Error::InvalidOutputLength => { - "The length of the FFI output buffer is invalid.".to_string() - } - Error::InvalidSignature => { - "The signature of the data blob does not match 0x0d0c.".to_string() - } - Error::InvalidMac => "The MAC is invalid.".to_string(), - Error::InvalidDataType => "The operation cannot be done with this type.".to_string(), - Error::UnknownType => "The data type is unknown.".to_string(), - Error::UnknownSubtype => "The data subtype is unknown.".to_string(), - Error::InvalidData => "The data is invalid.".to_string(), - Error::UnknownVersion => "The data type version is unknown.".to_string(), - Error::NullPointer => { - "A null pointer has been passed to the FFI interface.".to_string() - } - Error::CryptoError => "A cryptographic error occurred.".to_string(), - Error::RandomError => "An error with the Random Number Generator occurred.".to_string(), - Error::IoError(ref error) => error.to_string(), - Error::NotEnoughShares => { - "There wasn't enough share to regenerate the secret.".to_string() - } - Error::InconsistentVersion => { - "The version is not the same for all the data.".to_string() - } - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - Error::IoError(ref error) => error.fmt(f), - _ => write!(f, "Error {}: {}", self.error_code(), self.description()), - } - } } impl From for Error { @@ -143,12 +117,6 @@ impl From for Error { } } -impl From for Error { - fn from(_error: std::io::Error) -> Error { - Error::RandomError - } -} - impl From for Error { fn from(_error: argon2::Error) -> Self { Error::CryptoError @@ -158,7 +126,7 @@ impl From for Error { #[cfg(feature = "wbindgen")] impl From for JsValue { fn from(error: Error) -> JsValue { - let js_error = js_sys::Error::new(&error.description()); + let js_error = js_sys::Error::new(&error.to_string()); js_error.set_name(error.into()); js_error.into() diff --git a/src/lib.rs b/src/lib.rs index d0bcf29b0..cda22c4bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,10 +227,17 @@ pub use enums::{ SignatureVersion, SigningKeyVersion, }; +pub use argon2::Variant as Argon2Variant; +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 const DEFAULT_KEY_SIZE: usize = 32; +pub const DEFAULT_PBKDF2_ITERATIONS: u32 = 10000; + #[cfg(feature = "wbindgen")] pub mod wasm; diff --git a/src/wasm.rs b/src/wasm.rs index f12ef0b63..d76e13ed5 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -82,7 +82,7 @@ impl Argon2Parameters { #[wasm_bindgen(getter)] pub fn bytes(&self) -> Vec { - self.clone().into() + self.into() } #[wasm_bindgen(js_name = "fromBytes")] diff --git a/uniffi/devolutions-crypto-uniffi/Cargo.toml b/uniffi/devolutions-crypto-uniffi/Cargo.toml new file mode 100644 index 000000000..ab404c42e --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "devolutions-crypto-uniffi" +edition = "2021" +version.workspace = true + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +devolutions-crypto = { path = "../../" } +uniffi-builder-macro = { path = "./uniffi-builder-macro" } +uniffi = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } diff --git a/uniffi/devolutions-crypto-uniffi/build.rs b/uniffi/devolutions-crypto-uniffi/build.rs new file mode 100644 index 000000000..3f64bede2 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::generate_scaffolding("src/devolutions_crypto.udl").unwrap(); +} diff --git a/uniffi/devolutions-crypto-uniffi/src/argon2parameters.rs b/uniffi/devolutions-crypto-uniffi/src/argon2parameters.rs new file mode 100644 index 000000000..d411db1f1 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/argon2parameters.rs @@ -0,0 +1,42 @@ +use crate::Result; + +use devolutions_crypto::{Argon2Variant, Argon2Version}; + +use uniffi_builder_macro::UniffiBuilder; + +#[UniffiBuilder(Argon2Parameters, devolutions_crypto::argon2parameters_defaults)] +pub struct Argon2ParametersBuilder { + length: u32, + lanes: u32, + memory: u32, + iterations: u32, + variant: Argon2Variant, + version: Argon2Version, + dc_version: u32, + + #[builder_default = Default::default()] + associated_data: Vec, + + #[builder_default = Default::default()] + secret_key: Vec, + + #[builder_default = devolutions_crypto::argon2parameters_defaults::salt()] + salt: Vec, +} + +pub struct Argon2Parameters(pub devolutions_crypto::Argon2Parameters); + +impl Argon2Parameters { + pub fn new_from_bytes(data: &[u8]) -> Result { + let data = data.try_into()?; + Ok(Self(data)) + } + + pub fn get_bytes(&self) -> Vec { + (&self.0).into() + } + + fn get_inner_builder() -> devolutions_crypto::Argon2ParametersBuilder { + devolutions_crypto::Argon2Parameters::builder() + } +} diff --git a/uniffi/devolutions-crypto-uniffi/src/ciphertext.rs b/uniffi/devolutions-crypto-uniffi/src/ciphertext.rs new file mode 100644 index 000000000..8ed09fc46 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/ciphertext.rs @@ -0,0 +1,59 @@ +use crate::CiphertextVersion; +use crate::Result; + +pub fn encrypt(data: &[u8], key: &[u8], version: CiphertextVersion) -> Result> { + Ok(devolutions_crypto::ciphertext::encrypt(data, key, version)?.into()) +} + +pub fn encrypt_with_aad( + data: &[u8], + key: &[u8], + aad: &[u8], + version: CiphertextVersion, +) -> Result> { + Ok(devolutions_crypto::ciphertext::encrypt_with_aad(data, key, aad, version)?.into()) +} + +#[uniffi::export] +pub fn decrypt(data: &[u8], key: &[u8]) -> Result> { + let data = devolutions_crypto::ciphertext::Ciphertext::try_from(data)?; + data.decrypt(key) +} + +#[uniffi::export] +fn decrypt_with_aad(data: &[u8], key: &[u8], aad: &[u8]) -> Result> { + let data = devolutions_crypto::ciphertext::Ciphertext::try_from(data)?; + data.decrypt_with_aad(key, aad) +} + +pub fn encrypt_asymmetric(data: &[u8], key: &[u8], version: CiphertextVersion) -> Result> { + let key = key.try_into()?; + Ok(devolutions_crypto::ciphertext::encrypt_asymmetric(data, &key, version)?.into()) +} + +pub fn encrypt_asymmetric_with_aad( + data: &[u8], + key: &[u8], + aad: &[u8], + version: CiphertextVersion, +) -> Result> { + let key = key.try_into()?; + Ok( + devolutions_crypto::ciphertext::encrypt_asymmetric_with_aad(data, &key, aad, version)? + .into(), + ) +} + +#[uniffi::export] +pub fn decrypt_asymmetric(data: &[u8], key: &[u8]) -> Result> { + let key = key.try_into()?; + let data = devolutions_crypto::ciphertext::Ciphertext::try_from(data)?; + data.decrypt_asymmetric(&key) +} + +#[uniffi::export] +fn decrypt_asymmetric_with_aad(data: &[u8], key: &[u8], aad: &[u8]) -> Result> { + let key = key.try_into()?; + let data = devolutions_crypto::ciphertext::Ciphertext::try_from(data)?; + data.decrypt_asymmetric_with_aad(&key, aad) +} diff --git a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl new file mode 100644 index 000000000..81a785044 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl @@ -0,0 +1,147 @@ +enum DataType { + "None", + "Key", + "Ciphertext", + "PasswordHash", + "Share", + "SigningKey", + "Signature", +}; + +enum CiphertextVersion { + "Latest", + "V1", + "V2", +}; + +enum PasswordHashVersion { + "Latest", + "V1", +}; + +enum KeyVersion { + "Latest", + "V1", +}; + +enum SigningKeyVersion { + "Latest", + "V1", +}; + +enum SecretSharingVersion { + "Latest", + "V1", +}; + +enum SignatureVersion { + "Latest", + "V1", +}; + +enum Argon2Version { + "Version10", + "Version13", +}; + +enum Argon2Variant { + "Argon2d", + "Argon2i", + "Argon2id", +}; + +[Error] +enum DevolutionsCryptoError { + "InvalidLength", + "InvalidKeyLength", + "InvalidOutputLength", + "InvalidSignature", + "InvalidMac", + "InvalidDataType", + "UnknownType", + "UnknownSubtype", + "UnknownVersion", + "InvalidData", + "NullPointer", + "CryptoError", + "RandomError", + "IoError", + "NotEnoughShares", + "InconsistentVersion", +}; + +interface Argon2ParametersBuilder { + constructor(); + [Self=ByArc] + Argon2ParametersBuilder length(u32 value); + [Self=ByArc] + Argon2ParametersBuilder lanes(u32 value); + [Self=ByArc] + Argon2ParametersBuilder memory(u32 value); + [Self=ByArc] + Argon2ParametersBuilder iterations(u32 value); + [Self=ByArc] + Argon2ParametersBuilder variant(Argon2Variant value); + [Self=ByArc] + Argon2ParametersBuilder version(Argon2Version value); + [Self=ByArc] + Argon2ParametersBuilder dc_version(u32 value); + [Self=ByArc] + Argon2ParametersBuilder associated_data(bytes value); + [Self=ByArc] + Argon2ParametersBuilder secret_key(bytes value); + [Self=ByArc] + Argon2ParametersBuilder salt(bytes value); + [Self=ByArc] + Argon2Parameters build(); +}; + +interface Argon2Parameters { + [Name=new_from_bytes, Throws=DevolutionsCryptoError] + constructor([ByRef] bytes data); + bytes get_bytes(); +}; + +dictionary KeyPair { + bytes public_key; + bytes private_key; +}; + +interface SigningKeyPair { + [Name=new_from_bytes, Throws=DevolutionsCryptoError] + constructor([ByRef] bytes data); + bytes get_public_key(); + bytes get_private_key(); +}; + +namespace devolutions_crypto { + // Ciphertext + [Throws=DevolutionsCryptoError] + bytes encrypt([ByRef] bytes data, [ByRef] bytes key, optional CiphertextVersion version = "Latest"); + + [Throws=DevolutionsCryptoError] + bytes encrypt_with_aad([ByRef] bytes data, [ByRef] bytes key, [ByRef] bytes aad, optional CiphertextVersion version = "Latest"); + + [Throws=DevolutionsCryptoError] + bytes encrypt_asymmetric([ByRef] bytes data, [ByRef] bytes key, optional CiphertextVersion version = "Latest"); + + [Throws=DevolutionsCryptoError] + bytes encrypt_asymmetric_with_aad([ByRef] bytes data, [ByRef] bytes key, [ByRef] bytes aad, optional CiphertextVersion version = "Latest"); + + // Keys + KeyPair generate_keypair(optional KeyVersion version = "Latest"); + + // Password Hash + bytes hash_password([ByRef] bytes password, optional u32 iterations = 10000, optional PasswordHashVersion version = "Latest"); + + // Secret Sharing + [Throws=DevolutionsCryptoError] + sequence generate_shared_key(u8 n_shares, u8 threshold, optional u32 length = 32, optional SecretSharingVersion version = "Latest"); + + // Signature + [Throws=DevolutionsCryptoError] + bytes sign([ByRef] bytes data, [ByRef] bytes keypair, optional SignatureVersion version = "Latest"); + + // Signing Key + SigningKeyPair generate_signing_keypair(optional SigningKeyVersion version = "Latest"); +}; \ No newline at end of file diff --git a/uniffi/devolutions-crypto-uniffi/src/key.rs b/uniffi/devolutions-crypto-uniffi/src/key.rs new file mode 100644 index 000000000..59eed6243 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/key.rs @@ -0,0 +1,28 @@ +use crate::KeyVersion; +use crate::Result; + +pub struct KeyPair { + pub private_key: Vec, + pub public_key: Vec, +} + +impl From for KeyPair { + fn from(value: devolutions_crypto::key::KeyPair) -> Self { + Self { + private_key: value.private_key.into(), + public_key: value.public_key.into(), + } + } +} + +pub fn generate_keypair(version: KeyVersion) -> KeyPair { + devolutions_crypto::key::generate_keypair(version).into() +} + +#[uniffi::export] +pub fn mix_key_exchange(private_key: &[u8], public_key: &[u8]) -> Result> { + let private_key = private_key.try_into()?; + let public_key = public_key.try_into()?; + + devolutions_crypto::key::mix_key_exchange(&private_key, &public_key) +} diff --git a/uniffi/devolutions-crypto-uniffi/src/lib.rs b/uniffi/devolutions-crypto-uniffi/src/lib.rs new file mode 100644 index 000000000..55c644d85 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/lib.rs @@ -0,0 +1,32 @@ +mod argon2parameters; +mod ciphertext; +mod key; +mod password_hash; +mod secret_sharing; +mod signature; +mod signing_key; +mod utils; + +pub use argon2parameters::*; +pub use ciphertext::*; +pub use key::*; +pub use password_hash::*; +pub use secret_sharing::*; +pub use signature::*; +pub use signing_key::*; +pub use utils::*; + +pub use devolutions_crypto::Argon2Variant; +pub use devolutions_crypto::Argon2Version; +pub use devolutions_crypto::CiphertextVersion; +pub use devolutions_crypto::DataType; +pub use devolutions_crypto::Error as DevolutionsCryptoError; +pub use devolutions_crypto::KeyVersion; +pub use devolutions_crypto::PasswordHashVersion; +pub use devolutions_crypto::SecretSharingVersion; +pub use devolutions_crypto::SignatureVersion; +pub use devolutions_crypto::SigningKeyVersion; + +pub use devolutions_crypto::Result; + +uniffi::include_scaffolding!("devolutions_crypto"); diff --git a/uniffi/devolutions-crypto-uniffi/src/password_hash.rs b/uniffi/devolutions-crypto-uniffi/src/password_hash.rs new file mode 100644 index 000000000..66cdc40ef --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/password_hash.rs @@ -0,0 +1,12 @@ +use crate::PasswordHashVersion; +use crate::Result; + +pub fn hash_password(password: &[u8], iterations: u32, version: PasswordHashVersion) -> Vec { + devolutions_crypto::password_hash::hash_password(password, iterations, version).into() +} + +#[uniffi::export] +pub fn verify_password(password: &[u8], hash: &[u8]) -> Result { + let hash: devolutions_crypto::password_hash::PasswordHash = hash.try_into()?; + Ok(hash.verify_password(password)) +} diff --git a/uniffi/devolutions-crypto-uniffi/src/secret_sharing.rs b/uniffi/devolutions-crypto-uniffi/src/secret_sharing.rs new file mode 100644 index 000000000..89e6ea58c --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/secret_sharing.rs @@ -0,0 +1,26 @@ +use crate::Result; +use crate::SecretSharingVersion; + +pub fn generate_shared_key( + n_shares: u8, + threshold: u8, + length: u32, + version: SecretSharingVersion, +) -> Result>> { + Ok(devolutions_crypto::secret_sharing::generate_shared_key( + n_shares, + threshold, + length as usize, + version, + )? + .into_iter() + .map(|s| s.into()) + .collect()) +} + +#[uniffi::export] +pub fn join_shares(shares: &[Vec]) -> Result> { + let shares: Result> = shares.iter().map(|s| s.as_slice().try_into()).collect(); + + devolutions_crypto::secret_sharing::join_shares(&shares?) +} diff --git a/uniffi/devolutions-crypto-uniffi/src/signature.rs b/uniffi/devolutions-crypto-uniffi/src/signature.rs new file mode 100644 index 000000000..c7ad08216 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/signature.rs @@ -0,0 +1,16 @@ +use crate::Result; +use crate::SignatureVersion; + +pub fn sign(data: &[u8], keypair: &[u8], version: SignatureVersion) -> Result> { + let keypair = keypair.try_into()?; + + Ok(devolutions_crypto::signature::sign(data, &keypair, version).into()) +} + +#[uniffi::export] +pub fn verify_signature(data: &[u8], public_key: &[u8], signature: &[u8]) -> Result { + let signature: devolutions_crypto::signature::Signature = signature.try_into()?; + let public_key = public_key.try_into()?; + + Ok(signature.verify(data, &public_key)) +} diff --git a/uniffi/devolutions-crypto-uniffi/src/signing_key.rs b/uniffi/devolutions-crypto-uniffi/src/signing_key.rs new file mode 100644 index 000000000..05a698ec8 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/signing_key.rs @@ -0,0 +1,41 @@ +use std::sync::Arc; + +use crate::DevolutionsCryptoError; +use crate::Result; +use crate::SigningKeyVersion; + +pub struct SigningKeyPair(devolutions_crypto::signing_key::SigningKeyPair); + +impl SigningKeyPair { + pub fn new_from_bytes(data: &[u8]) -> Result { + data.try_into() + } +} + +impl From for SigningKeyPair { + fn from(value: devolutions_crypto::signing_key::SigningKeyPair) -> Self { + Self(value) + } +} + +impl TryFrom<&[u8]> for SigningKeyPair { + type Error = DevolutionsCryptoError; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into()?)) + } +} + +impl SigningKeyPair { + pub fn get_public_key(&self) -> Vec { + self.0.get_public_key().into() + } + + pub fn get_private_key(&self) -> Vec { + self.0.clone().into() + } +} + +pub fn generate_signing_keypair(version: SigningKeyVersion) -> Arc { + Arc::new(devolutions_crypto::signing_key::generate_signing_keypair(version).into()) +} diff --git a/uniffi/devolutions-crypto-uniffi/src/utils.rs b/uniffi/devolutions-crypto-uniffi/src/utils.rs new file mode 100644 index 000000000..c97d71da6 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/src/utils.rs @@ -0,0 +1,53 @@ +use std::sync::Arc; + +use crate::{Argon2Parameters, DataType, Result}; + +#[uniffi::export(default(length = 32))] +pub fn generate_key(length: u32) -> Vec { + devolutions_crypto::utils::generate_key(length as usize) +} + +#[uniffi::export(default(iterations = 10000, length = 32))] +pub fn derive_key_pbkdf2( + key: &[u8], + salt: Option>, + iterations: u32, + length: u32, +) -> Vec { + devolutions_crypto::utils::derive_key_pbkdf2( + key, + &salt.unwrap_or_default(), + iterations, + length as usize, + ) +} + +#[uniffi::export] +pub fn derive_key_argon2(key: &[u8], parameters: &Arc) -> Result> { + devolutions_crypto::utils::derive_key_argon2(key, ¶meters.0) +} + +#[uniffi::export] +pub fn validate_header(data: &[u8], data_type: DataType) -> bool { + devolutions_crypto::utils::validate_header(data, data_type) +} + +#[uniffi::export] +pub fn base64_encode(data: &[u8]) -> String { + devolutions_crypto::utils::base64_encode(data) +} + +#[uniffi::export] +pub fn base64_decode(data: &str) -> Result> { + devolutions_crypto::utils::base64_decode(data) +} + +#[uniffi::export] +pub fn base64_encode_url(data: &[u8]) -> String { + devolutions_crypto::utils::base64_encode_url(data) +} + +#[uniffi::export] +pub fn base64_decode_url(data: &str) -> Result> { + devolutions_crypto::utils::base64_decode_url(data) +} diff --git a/uniffi/devolutions-crypto-uniffi/uniffi-builder-macro/Cargo.toml b/uniffi/devolutions-crypto-uniffi/uniffi-builder-macro/Cargo.toml new file mode 100644 index 000000000..e291025db --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/uniffi-builder-macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "uniffi-builder-macro" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" +proc-macro = true + +[dependencies] +quote = "1.0.37" +syn = { version = "2.0.79", features = ["full", "extra-traits"] } diff --git a/uniffi/devolutions-crypto-uniffi/uniffi-builder-macro/src/lib.rs b/uniffi/devolutions-crypto-uniffi/uniffi-builder-macro/src/lib.rs new file mode 100644 index 000000000..4a32cd5d2 --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/uniffi-builder-macro/src/lib.rs @@ -0,0 +1,146 @@ +use proc_macro::TokenStream; +use syn::spanned::Spanned; + +struct UniffiBuilderMacroInput { + output_struct: syn::Ident, + _comma: syn::Token![,], + defaults: syn::ExprPath, +} + +impl syn::parse::Parse for UniffiBuilderMacroInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { + output_struct: input.parse()?, + _comma: input.parse()?, + defaults: input.parse()?, + }) + } +} + +#[proc_macro_attribute] +#[allow(non_snake_case)] +pub fn UniffiBuilder(args: TokenStream, tokens: TokenStream) -> TokenStream { + // Get the name of the struct to output to + let args: UniffiBuilderMacroInput = match syn::parse(args) { + Ok(args) => args, + Err(e) => return e.to_compile_error().into(), + }; + + let output_struct_name = args.output_struct; + let defaults_module = args.defaults; + + // Parse the struct + let mut tree: syn::ItemStruct = match syn::parse(tokens) { + Ok(t) => t, + Err(e) => return e.to_compile_error().into(), + }; + + // Get the name of the struct + let struct_name = tree.ident.clone(); + + // Get the field and type of each fields + let ((field_names, field_types), field_attributes): ( + (Vec, Vec), + Vec>, + ) = tree + .fields + .iter() + .map(|f| { + let attribute = f + .attrs + .iter() + .find(|a| a.path().is_ident("builder_default")); + let attribute: Option = attribute.cloned(); + ((f.ident.clone().unwrap(), f.ty.clone()), attribute) + }) + .collect(); + + // Strip the fields from the fields that have custom default + let (field_names_for_default, field_default): (Vec, Vec) = tree + .fields + .iter() + .zip(field_attributes.iter()) + .map(|(f, attr)| { + let ident = f.ident.clone().unwrap(); + + let default: syn::Expr = if let Some(attr) = attr { + if let syn::Meta::NameValue(attr) = attr.meta.clone() { + let val = attr.value; + syn::parse_quote! { std::sync::Mutex::new({#val}) } + } else { + syn::Expr::Verbatim( + syn::Error::new( + attr.span(), + "The format for the attribute should be #[builder_default = code()]", + ) + .to_compile_error(), + ) + } + } else { + // Get the uppercased name of the field to get the corresponding default + let uppercased_ident: syn::Ident = + syn::parse_str(&ident.to_string().to_uppercase()).unwrap(); + syn::parse_quote! { std::sync::Mutex::new(#defaults_module::#uppercased_ident) } + }; + + (ident, default) + }) + .collect(); + + // Add mutex to the types + tree.fields.iter_mut().for_each(|field| { + let original_type = field.ty.clone(); + + // Strip the default attributes + field + .attrs + .retain(|a| !a.path().is_ident("builder_default")); + + field.ty = syn::parse_quote! { std::sync::Mutex<#original_type> }; + }); + + quote::quote! { + #tree + + impl Clone for #struct_name { + fn clone(&self) -> Self { + Self { + #(#field_names: std::sync::Mutex::new(self.#field_names.lock().unwrap().clone()),)* + } + } + } + + impl Default for #struct_name { + fn default() -> Self { + Self { + #(#field_names_for_default: #field_default,)* + } + } + } + + impl #struct_name { + pub fn new() -> Self { + Default::default() + } + + #( + pub fn #field_names(self: std::sync::Arc, value: #field_types) -> std::sync::Arc { + *self.#field_names.lock().unwrap() = value; + self + } + )* + + pub fn build(self: std::sync::Arc) -> std::sync::Arc<#output_struct_name> { + let builder = std::sync::Arc::<#struct_name>::unwrap_or_clone(self); + + #output_struct_name( + #output_struct_name::get_inner_builder() + #(.#field_names(builder.#field_names.into_inner().unwrap()))* + .build(), + ) + .into() + } + } + } + .into() +} diff --git a/uniffi/devolutions-crypto-uniffi/uniffi.toml b/uniffi/devolutions-crypto-uniffi/uniffi.toml new file mode 100644 index 000000000..1bf24baee --- /dev/null +++ b/uniffi/devolutions-crypto-uniffi/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin] +package_name = "org.devolutions.crypto" \ No newline at end of file diff --git a/uniffi/uniffi-bindgen/Cargo.toml b/uniffi/uniffi-bindgen/Cargo.toml new file mode 100644 index 000000000..173d744e9 --- /dev/null +++ b/uniffi/uniffi-bindgen/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "uniffi-bindgen" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "uniffi-bindgen" +path = "src/uniffi-bindgen.rs" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } diff --git a/uniffi/uniffi-bindgen/src/uniffi-bindgen.rs b/uniffi/uniffi-bindgen/src/uniffi-bindgen.rs new file mode 100644 index 000000000..f6cff6cf1 --- /dev/null +++ b/uniffi/uniffi-bindgen/src/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/wrappers/csharp/GeneratePackage.py b/wrappers/csharp/GeneratePackage.py index 84fa5481f..faefb942c 100644 --- a/wrappers/csharp/GeneratePackage.py +++ b/wrappers/csharp/GeneratePackage.py @@ -220,7 +220,7 @@ def build_windows(assembly_manifest, version, args): dllpath = "./" + folder + "/bin/DevolutionsCrypto-" + arch["name"] + ".dll" - shutil.copy("../../target/" + arch["value"] + "/release/devolutions_crypto.dll", dllpath) + shutil.copy("../../target/" + arch["value"] + "/release/devolutions_crypto_ffi.dll", dllpath) output = exec_command("./tools/rcedit-x64.exe " + dllpath + " --set-file-version " + version) print(output) @@ -294,18 +294,18 @@ def build_linux(assembly_manifest, version, args): if not args.no_32: architectures.append({"name" : "i686", "value" : "i686-unknown-linux-gnu", - "cargo_output": "../../target/i686-unknown-linux-gnu/release/libdevolutions_crypto.so", + "cargo_output": "../../target/i686-unknown-linux-gnu/release/libdevolutions_crypto_ffi.so", "filename" : "libDevolutionsCrypto-x86.so"}) if not args.no_64: architectures.append({"name" : "x86_64", "value" : "x86_64-unknown-linux-gnu", - "cargo_output": "../../target/x86_64-unknown-linux-gnu/release/libdevolutions_crypto.so", + "cargo_output": "../../target/x86_64-unknown-linux-gnu/release/libdevolutions_crypto_ffi.so", "filename" : "libDevolutionsCrypto-x64.so"}) architectures.append({"name" : "aarch64", "value" : "aarch64-unknown-linux-gnu", - "cargo_output": "../../target/aarch64-unknown-linux-gnu/release/libdevolutions_crypto.so", + "cargo_output": "../../target/aarch64-unknown-linux-gnu/release/libdevolutions_crypto_ffi.so", "filename" : "libDevolutionsCrypto-arm64.so"}) target_folder = "./linux" @@ -330,13 +330,13 @@ def build_mac_full(assembly_manifest, version, args): { "name" : "x86_64", "value" : "x86_64-apple-darwin", - "cargo_output": "../../target/x86_64-apple-darwin/release/libdevolutions_crypto.dylib", + "cargo_output": "../../target/x86_64-apple-darwin/release/libdevolutions_crypto_ffi.dylib", "filename" : "x86_64/libDevolutionsCrypto.dylib" }, { "name" : "aarch64", "value" : "aarch64-apple-darwin", - "cargo_output": "../../target/aarch64-apple-darwin/release/libdevolutions_crypto.dylib", + "cargo_output": "../../target/aarch64-apple-darwin/release/libdevolutions_crypto_ffi.dylib", "filename" : "aarch64/libDevolutionsCrypto.dylib" } ] @@ -381,13 +381,13 @@ def build_mac_modern(assembly_manifest, version, args): { "name" : "x86_64", "value" : "x86_64-apple-darwin", - "cargo_output": "../../target/x86_64-apple-darwin/release/libdevolutions_crypto.dylib", + "cargo_output": "../../target/x86_64-apple-darwin/release/libdevolutions_crypto_ffi.dylib", "filename" : "x86_64/libDevolutionsCrypto.dylib" }, { "name" : "aarch64", "value" : "aarch64-apple-darwin", - "cargo_output": "../../target/aarch64-apple-darwin/release/libdevolutions_crypto.dylib", + "cargo_output": "../../target/aarch64-apple-darwin/release/libdevolutions_crypto_ffi.dylib", "filename" : "aarch64/libDevolutionsCrypto.dylib" } ] @@ -425,12 +425,12 @@ def build_ios(assembly_manifest, version, args): {"name" : "x86_64", "value" : "x86_64-apple-ios", "manifest_path" : "./Cargo.toml", - "cargo_output": "../../target/x86_64-apple-ios/release/libdevolutions_crypto.dylib", + "cargo_output": "../../target/x86_64-apple-ios/release/libdevolutions_crypto_ffi.dylib", "filename" : "x86_64/libDevolutionsCrypto.dylib"}, {"name" : "aarch64", "value" : "aarch64-apple-ios", "manifest_path" : "./Cargo.toml", - "cargo_output": "../../target/aarch64-apple-ios/release/libdevolutions_crypto.dylib", + "cargo_output": "../../target/aarch64-apple-ios/release/libdevolutions_crypto_ffi.dylib", "filename" : "aarch64/libDevolutionsCrypto.dylib"}, ] @@ -506,19 +506,19 @@ def build_android(assembly_manifest, version, args): architectures = [ {"name" : "aarch64", "value" : "aarch64-linux-android", - "cargo_output": "../../target/aarch64-linux-android/release/libdevolutions_crypto.so", + "cargo_output": "../../target/aarch64-linux-android/release/libdevolutions_crypto_ffi.so", "filename" : "aarch64/libDevolutionsCrypto.so"}, {"name" : "armv7", "value" : "armv7-linux-androideabi", - "cargo_output": "../../target/armv7-linux-androideabi/release/libdevolutions_crypto.so", + "cargo_output": "../../target/armv7-linux-androideabi/release/libdevolutions_crypto_ffi.so", "filename" : "armv7/libDevolutionsCrypto.so"}, {"name" : "i686", "value" : "i686-linux-android", - "cargo_output": "../../target/i686-linux-android/release/libdevolutions_crypto.so", + "cargo_output": "../../target/i686-linux-android/release/libdevolutions_crypto_ffi.so", "filename" : "i686/libDevolutionsCrypto.so"}, {"name" : "x86_64", "value" : "x86_64-linux-android", - "cargo_output": "../../target/x86_64-linux-android/release/libdevolutions_crypto.so", + "cargo_output": "../../target/x86_64-linux-android/release/libdevolutions_crypto_ffi.so", "filename" : "x86_64/libDevolutionsCrypto.so"} ] diff --git a/wrappers/kotlin/.gitattributes b/wrappers/kotlin/.gitattributes new file mode 100644 index 000000000..f91f64602 --- /dev/null +++ b/wrappers/kotlin/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/wrappers/kotlin/.gitignore b/wrappers/kotlin/.gitignore new file mode 100644 index 000000000..0863556a1 --- /dev/null +++ b/wrappers/kotlin/.gitignore @@ -0,0 +1,7 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +lib/src/main/resources/* diff --git a/wrappers/kotlin/Makefile b/wrappers/kotlin/Makefile new file mode 100644 index 000000000..8ff4e7593 --- /dev/null +++ b/wrappers/kotlin/Makefile @@ -0,0 +1,72 @@ +ROOT = ../.. + +LIB_NAME = devolutions_crypto_uniffi +LIB_NAME_DASHES = devolutions-crypto-uniffi + +DEBUG_DIR = $(ROOT)/target/debug +DEBUG_DLL = $(DEBUG_DIR)/lib$(LIB_NAME).so + +KOTLIN_WRAPPER = ./lib/src/main/kotlin +KOTLIN_WRAPPER_FILE = $(KOTLIN_WRAPPER)/org/devolutions/crypto/devolutions_crypto.kt + +UNIFFI_PATH = $(ROOT)/uniffi +UNIFFI_BINDGEN = $(UNIFFI_PATH)/uniffi-bindgen +DC_UNIFFI = $(UNIFFI_PATH)/$(LIB_NAME_DASHES) +DC_UNIFFI_SOURCES = $(wildcard $(ROOT)/src/*) $(ROOT)/Cargo.toml $(wildcard $(DC_UNIFFI)/src/*) $(DC_UNIFFI)/Cargo.toml +UNIFFI_BINDGEN_EXE = $(DEBUG_DIR)/uniffi-bindgen + +RUST_ARCHS = aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android +ANDROID_ARCHS = android-arm64-v8a android-armeabi-v7a android-x86 android-x86-64 + +KT_DEBUG_PATH = ./lib/src/main/resources/linux-x86-64/lib$(LIB_NAME).so +KT_RELEASE_PATH = $(foreach var,$(ANDROID_ARCHS),./lib/src/main/resources/$(var)/lib$(LIB_NAME).so) + +.PHONY: android clean + +all: $(KOTLIN_WRAPPER_FILE) $(KT_DEBUG_PATH) + +android: $(KOTLIN_WRAPPER_FILE) $(KT_RELEASE_PATH) + +# Build the library in debug +$(DEBUG_DLL): $(DC_UNIFFI_SOURCES) + cargo build -p "$(LIB_NAME_DASHES)" + +# Build the release android libraries +$(ROOT)/target/%/release/lib$(LIB_NAME).so: $(DC_UNIFFI_SOURCES) + cargo build --release --target=$* -p "$(LIB_NAME_DASHES)" + +# Copy the libraries to KT directory +# TODO: Can we loop this? +$(KT_DEBUG_PATH): $(DEBUG_DLL) + mkdir -p $(@D) + cp $< $@ + +./lib/src/main/resources/android-arm64-v8a/lib$(LIB_NAME).so: $(ROOT)/target/aarch64-linux-android/release/lib$(LIB_NAME).so + mkdir -p $(@D) + cp $< $@ + +./lib/src/main/resources/android-armeabi-v7a/lib$(LIB_NAME).so: $(ROOT)/target/armv7-linux-androideabi/release/lib$(LIB_NAME).so + mkdir -p $(@D) + cp $< $@ + +./lib/src/main/resources/android-x86/lib$(LIB_NAME).so: $(ROOT)/target/i686-linux-android/release/lib$(LIB_NAME).so + mkdir -p $(@D) + cp $< $@ + +./lib/src/main/resources/android-x86-64/lib$(LIB_NAME).so: $(ROOT)/target/x86_64-linux-android/release/lib$(LIB_NAME).so + mkdir -p $(@D) + cp $< $@ + +# Build uniffi-bindgen +$(UNIFFI_BINDGEN_EXE): $(wildcard $(UNIFFI_BINDGEN)/src/*) $(UNIFFI_BINDGEN)/Cargo.toml + cargo build -p "uniffi-bindgen" + +# Generate the wrapper file +$(KOTLIN_WRAPPER_FILE): $(DEBUG_DLL) $(UNIFFI_BINDGEN_EXE) + $(UNIFFI_BINDGEN_EXE) generate --library "$(DEBUG_DLL)" --language kotlin -o $(KOTLIN_WRAPPER) + +clean: + cargo clean + rm -f $(KOTLIN_WRAPPER_FILE) + rm -f $(KT_RELEASE_PATH) + rm -f $(KT_DEBUG_PATH) diff --git a/wrappers/kotlin/gradle/libs.versions.toml b/wrappers/kotlin/gradle/libs.versions.toml new file mode 100644 index 000000000..43c157a91 --- /dev/null +++ b/wrappers/kotlin/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +commons-math3 = "3.6.1" +guava = "33.2.1-jre" +junit-jupiter-engine = "5.10.3" +jna = "5.12.0" + +[libraries] +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter-engine" } +jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version = "2.0.0" } diff --git a/wrappers/kotlin/gradle/wrapper/gradle-wrapper.jar b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..a4b76b953 Binary files /dev/null and b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/wrappers/kotlin/gradle/wrapper/gradle-wrapper.properties b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..df97d72b8 --- /dev/null +++ b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/wrappers/kotlin/gradlew b/wrappers/kotlin/gradlew new file mode 100644 index 000000000..f5feea6d6 --- /dev/null +++ b/wrappers/kotlin/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/wrappers/kotlin/gradlew.bat b/wrappers/kotlin/gradlew.bat new file mode 100644 index 000000000..9d21a2183 --- /dev/null +++ b/wrappers/kotlin/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/wrappers/kotlin/lib/build.gradle.kts b/wrappers/kotlin/lib/build.gradle.kts new file mode 100644 index 000000000..85bbc11fd --- /dev/null +++ b/wrappers/kotlin/lib/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Kotlin library project to get you started. + * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.10.2/userguide/building_java_projects.html in the Gradle documentation. + */ + +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + alias(libs.plugins.kotlin.jvm) + + // Apply the java-library plugin for API and implementation separation. + `java-library` +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use the Kotlin JUnit 5 integration. + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + + // Use the JUnit 5 integration. + testImplementation(libs.junit.jupiter.engine) + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + // This dependency is exported to consumers, that is to say found on their compile classpath. + api(libs.commons.math3) + + // This dependency is used internally, and not exposed to consumers on their own compile classpath. + implementation(libs.guava) + + implementation(libs.jna) +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() + + + // Configure test logging to display results in stdout + testLogging { + events("passed", "skipped", "failed") + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + showStandardStreams = true + } +} diff --git a/wrappers/kotlin/lib/src/main/resources/win32-x86-64/.gitkeep b/wrappers/kotlin/lib/src/main/resources/win32-x86-64/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/AsymmetricTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/AsymmetricTest.kt new file mode 100644 index 000000000..e49b216cf --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/AsymmetricTest.kt @@ -0,0 +1,93 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class AsymmetricTest { + @Test + fun generateKeypairTest() { + val keypair = generateKeypair() + + assert(!keypair.publicKey.isEmpty()) + assert(!keypair.privateKey.isEmpty()) + + assert(!keypair.privateKey.contentEquals(keypair.publicKey)) + } + + @Test + fun encryptDecryptAsymmetricTest() { + val data = "This is some test data".toByteArray(Charsets.UTF_8) + val keypair = generateKeypair() + + val encrypted = encryptAsymmetric(data, keypair.publicKey) + val decrypted = decryptAsymmetric(encrypted, keypair.privateKey) + + assert(!data.asList().isSubArray(encrypted.asList())) + assertContentEquals(data, decrypted) + } + + @Test + fun encryptDecryptAsymmetricWithAadTest() { + val data = "This is some test data".toByteArray(Charsets.UTF_8) + val aad = "This is some public data".toByteArray(Charsets.UTF_8) + + val keypair = generateKeypair() + + val encrypted = encryptAsymmetricWithAad(data, keypair.publicKey, aad) + val decrypted = decryptAsymmetricWithAad(encrypted, keypair.privateKey, aad) + + assert(!data.asList().isSubArray(encrypted.asList())) + assertContentEquals(data, decrypted) + } + + @Test + fun encryptDecryptAsymmetricWithWrongAadTest() { + val data = "This is some test data".toByteArray(Charsets.UTF_8) + val aad = "This is some public data".toByteArray(Charsets.UTF_8) + val wrongAad = "this is some public data".toByteArray(Charsets.UTF_8) + + val keypair = generateKeypair() + + val encrypted = encryptAsymmetricWithAad(data, keypair.publicKey, aad) + + assertFailsWith { + decryptAsymmetricWithAad(encrypted, keypair.privateKey, wrongAad) + } + } + + @Test + fun mixKeyExchangeTest() { + val bobKeypair = generateKeypair() + val aliceKeypair = generateKeypair() + + val bobShared = mixKeyExchange(bobKeypair.privateKey, aliceKeypair.publicKey) + val aliceShared = mixKeyExchange(aliceKeypair.privateKey, bobKeypair.publicKey) + + assertEquals(bobShared.size, 32) + assert(!bobShared.contentEquals(ByteArray(32) { 0 })) + assertContentEquals(bobShared, aliceShared) + } + + @Test + fun mixKeyExchangeNotEqualsTest() { + val bobKeypair = generateKeypair() + val aliceKeypair = generateKeypair() + val eveKeypair = generateKeypair() + + val bobAliceShared = mixKeyExchange(bobKeypair.privateKey, aliceKeypair.publicKey) + val aliceBobShared = mixKeyExchange(aliceKeypair.privateKey, bobKeypair.publicKey) + + val eveBobShared = mixKeyExchange(eveKeypair.privateKey, bobKeypair.publicKey) + val eveAliceShared = mixKeyExchange(eveKeypair.privateKey, aliceKeypair.publicKey) + + assert(!eveBobShared.contentEquals(bobAliceShared)) + assert(!eveBobShared.contentEquals(aliceBobShared)) + assert(!eveAliceShared.contentEquals(bobAliceShared)) + assert(!eveAliceShared.contentEquals(aliceBobShared)) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/ConformityTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/ConformityTest.kt new file mode 100644 index 000000000..daa424aab --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/ConformityTest.kt @@ -0,0 +1,130 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertFalse + +class ConformityTest { + @Test + fun deriveKeyPbkdf2Test() { + val derivedKey = deriveKeyPbkdf2("testpassword".toByteArray(), null) + val derivedKeyWithIterations = deriveKeyPbkdf2("testPa\$\$".toByteArray(), null, iterations = 100u) + val derivedKeyWithSalt = deriveKeyPbkdf2( + "testPa\$\$".toByteArray(), + base64Decode("tdTt5wgeqQYLvkiXKkFirqy2hMbzadBtL+jekVeNCRA="), + iterations = 100u + ) + + val expected = base64Decode("ImfGCyv6PwMYaJShGxR4MfVrjuUrsI0CSarJgOApwf8=") + val expectedWithIterations = base64Decode("ev/GiJLvOgIkkWrnIrHSi2fdZE5qJBIrW+DLeMLIXK4=") + val expectedWithSalt = base64Decode("ZaYRZeQiIPJ+Jl511AgHZjv4/HbCFq4eUP9yNa3gowI=") + + assertContentEquals(expected, derivedKey) + assertContentEquals(expectedWithIterations, derivedKeyWithIterations) + assertContentEquals(expectedWithSalt, derivedKeyWithSalt) + } + + @Test + fun deriveKeyArgon2Test() { + val password = "password".toByteArray() + val parameters = + Argon2Parameters.newFromBytes(base64Decode("AQAAACAAAAABAAAAIAAAAAEAAAACEwAAAAAQAAAAimFBkm3f8+f+YfLRnF5OoQ==")) + val result = deriveKeyArgon2(password, parameters) + + val expected = base64Decode("AcEN6Cb1Om6tomZScAM725qiXMzaxaHlj3iMiT/Ukq0=") + + assertContentEquals(expected, result) + } + + @Test + fun symmetricDecryptV1Test() { + val key = base64Decode("ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + val ciphertext = + base64Decode("DQwCAAAAAQCK1twEut+TeJfFbTWCRgHjyS6bOPOZUEQAeBtSFFRl2jHggM/34n68zIZWGbsZHkufVzU6mTN5N2Dx9bTplrycv5eNVevT4P9FdVHJ751D+A==") + + val result = decrypt(ciphertext, key) + + val expected = "test Ciph3rtext~".toByteArray() + + assertContentEquals(expected, result) + } + + @Test + fun symmetricDecryptWithAadV1Test() { + val key = base64Decode("ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + val ciphertext = + base64Decode("DQwCAAEAAQCeKfbTqYjfVCEPEiAJjiypBstPmZz0AnpliZKoR+WXTKdj2f/4ops0++dDBVZ+XdyE1KfqxViWVc9djy/HSCcPR4nDehtNI69heGCIFudXfQ==") + val aad = "this is some public data".toByteArray() + + val result = decryptWithAad(ciphertext, key, aad) + + val expected = "test Ciph3rtext~".toByteArray() + + assertContentEquals(expected, result) + } + + @Test + fun symmetricDecryptV2Test() { + val key = base64Decode("ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + val ciphertext = + base64Decode("DQwCAAAAAgAA0iPpI4IEzcrWAQiy6tqDqLbRYduGvlMC32mVH7tpIN2CXDUu5QHF91I7pMrmjt/61pm5CeR/IcU=") + + val result = decrypt(ciphertext, key) + + val expected = "test Ciph3rtext~2".toByteArray() + + assertContentEquals(expected, result) + } + + @Test + fun symmetricDecryptWithAadV2Test() { + val key = base64Decode("ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + val ciphertext = + base64Decode("DQwCAAEAAgA9bh989dao0Pvaz1NpJTI5m7M4br2qVjZtFwXXoXZOlkCjtqU/uif4pbNCcpEodzeP4YG1QvfKVQ==") + val aad = "this is some public data".toByteArray() + + val result = decryptWithAad(ciphertext, key, aad) + + val expected = "test Ciph3rtext~".toByteArray() + + assertContentEquals(expected, result) + } + + @Test + fun asymmetricDecryptWithAadV2Test() { + val privateKey = base64Decode("DQwBAAEAAQC9qf9UY1ovL/48ALGHL9SLVpVozbdjYsw0EPerUl3zYA==") + val ciphertext = + base64Decode("DQwCAAIAAgB1u62xYeyppWf83QdWwbwGUt5QuiAFZr+hIiFEvMRbXiNCE3RMBNbmgQkLr/vME0BeQa+uUTXZARvJcyNXHyAE4tSdw6o/psU/kw/Z/FbsPw==") + val aad = "this is some public data".toByteArray() + + val result = decryptAsymmetricWithAad(ciphertext, privateKey, aad) + + val expected = "testdata".toByteArray() + + assertContentEquals(expected, result) + } + + @Test + fun passwordHashingV1Test() { + val hash1 = + base64Decode("DQwDAAAAAQAQJwAAXCzLFoyeZhFSDYBAPiIWhCk04aoP/lalOoCl7D+skIY/i+3WT7dn6L8WvnfEq6flCd7i+IcKb3GEK4rCpzhDlw==") + val hash2 = + base64Decode("DQwDAAAAAQAKAAAAmH1BBckBJYDD0xfiwkAk1xwKgw8a57YQT0Igm+Faa9LFamTeEJgqn/qHc2R/8XEyK2iLPkVy+IErdGLLtLKJ2g==") + + assert(verifyPassword("password1".toByteArray(), hash1)) + assert(verifyPassword("password1".toByteArray(), hash2)) + } + + @Test + fun signatureV1Test() { + val publicKey = base64Decode("DQwFAAIAAQDeEvwlEigK5AXoTorhmlKP6+mbiUU2rYrVQ25JQ5xang==") + val signature = + base64Decode("DQwGAAAAAQD82uRk4sFC8vEni6pDNw/vOdN1IEDg9cAVfprWJZ/JBls9Gi61cUt5u6uBJtseNGZFT7qKLvp4NUZrAOL8FH0K") + + assert(verifySignature("this is a test".toByteArray(), publicKey, signature)) + assertFalse(verifySignature("this is wrong".toByteArray(), publicKey, signature)) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/HashingTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/HashingTest.kt new file mode 100644 index 000000000..ad8653807 --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/HashingTest.kt @@ -0,0 +1,26 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import kotlin.test.Test + +class HashingTest { + @Test + fun passwordHashTest() { + val password = "password".toByteArray(Charsets.UTF_8) + val hash = hashPassword(password, 10u) + + assert(verifyPassword(password, hash)) + } + + @Test + fun wrongPasswordTest() { + val password = "password".toByteArray(Charsets.UTF_8) + val hash = hashPassword(password, 10u) + + assert(!verifyPassword("pa\$\$word".toByteArray(), hash)) + assert(!verifyPassword("Password".toByteArray(), hash)) + assert(!verifyPassword("password1".toByteArray(), hash)) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SecretSharingTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SecretSharingTest.kt new file mode 100644 index 000000000..003c81be9 --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SecretSharingTest.kt @@ -0,0 +1,63 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import org.junit.jupiter.api.assertThrows +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class SecretSharingTest { + @Test + fun sharedSecretDefaultTest() { + val shares = generateSharedKey(5u, 3u) + + val shareGroup1 = shares.slice(0..2) + val shareGroup2 = shares.slice(1..3) + val shareGroup3 = shares.slice(2..4) + + val key1 = joinShares(shareGroup1) + val key2 = joinShares(shareGroup2) + val key3 = joinShares(shareGroup3) + + assertEquals(32, key1.size) + assert(!key1.contentEquals(ByteArray(32) { 0 })) + assertContentEquals(key1, key2) + assertContentEquals(key1, key3) + } + + @Test + fun sharedSecretLargerTest() { + val shares = generateSharedKey(5u, 3u, 41u) + + val shareGroup1 = shares.slice(0..2) + val shareGroup2 = shares.slice(1..3) + val shareGroup3 = shares.slice(2..4) + + val key1 = joinShares(shareGroup1) + val key2 = joinShares(shareGroup2) + val key3 = joinShares(shareGroup3) + + assertEquals(41, key1.size) + assert(!key1.contentEquals(ByteArray(41) { 0 })) + assertContentEquals(key1, key2) + assertContentEquals(key1, key3) + } + + @Test + fun sharedSecretWrongParamsTest() { + assertThrows { + generateSharedKey(3u, 5u) + } + } + + @Test + fun sharedSecrectNotEnoughShare() { + val shares = generateSharedKey(5u, 3u) + val sharesGroup = shares.slice(0..1) + assertThrows { + joinShares(sharesGroup) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SignatureTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SignatureTest.kt new file mode 100644 index 000000000..d64519cf2 --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SignatureTest.kt @@ -0,0 +1,29 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import kotlin.test.Test + +class SignatureTest { + @Test + fun signatureTest() { + val data = "this is a test".toByteArray() + val keypair = generateSigningKeypair() + + val signature = sign(data, keypair.getPrivateKey()) + + assert(verifySignature(data, keypair.getPublicKey(), signature)) + } + + @Test + fun wrongSignatureTest() { + val data = "this is test data".toByteArray() + val wrongData = "this is wrong data".toByteArray() + val keypair = generateSigningKeypair() + + val signature = sign(data, keypair.getPrivateKey()) + + assert(!verifySignature(wrongData, keypair.getPublicKey(), signature)) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SymmetricTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SymmetricTest.kt new file mode 100644 index 000000000..8c38d60ce --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/SymmetricTest.kt @@ -0,0 +1,51 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertFailsWith + +class SymmetricTest { + @Test + fun encryptDecryptTest() { + val data = "This is some test data".toByteArray(Charsets.UTF_8) + val key = generateKey() + + val encrypted = encrypt(data, key) + val decrypted = decrypt(encrypted, key) + + assert(!data.asList().isSubArray(encrypted.asList())) + assertContentEquals(data, decrypted) + } + + @Test + fun encryptDecryptWithAadTest() { + val data = "This is some test data".toByteArray(Charsets.UTF_8) + val aad = "This is some public data".toByteArray(Charsets.UTF_8) + + val key = generateKey() + + val encrypted = encryptWithAad(data, key, aad) + val decrypted = decryptWithAad(encrypted, key, aad) + + assert(!data.asList().isSubArray(encrypted.asList())) + assertContentEquals(data, decrypted) + } + + @Test + fun encryptDecryptWithWrongAadTest() { + val data = "This is some test data".toByteArray(Charsets.UTF_8) + val aad = "This is some public data".toByteArray(Charsets.UTF_8) + val wrongAad = "this is some public data".toByteArray(Charsets.UTF_8) + + val key = generateKey() + + val encrypted = encryptWithAad(data, key, aad) + + assertFailsWith { + decryptWithAad(encrypted, key, wrongAad) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/TestUtils.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/TestUtils.kt new file mode 100644 index 000000000..9ebeaf990 --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/TestUtils.kt @@ -0,0 +1,24 @@ +package org.devolutions.crypto + +// Code is ported from this post: +// https://www.geeksforgeeks.org/check-whether-an-array-is-subarray-of-another-array/ +fun List.isSubArray(data: List): Boolean { + var i = 0 + var j = 0 + + while (i < data.size && j < this.size) { + if (data[i] == this[j]) { + i += 1; + j += 1; + + if (j == this.size) { + return true + } + } else { + i = i - j + 1 + j = 0 + } + } + + return false +} diff --git a/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/UtilsTest.kt b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/UtilsTest.kt new file mode 100644 index 000000000..c545844f7 --- /dev/null +++ b/wrappers/kotlin/lib/src/test/kotlin/org/devolutions/crypto/UtilsTest.kt @@ -0,0 +1,187 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.devolutions.crypto + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class UtilsTest { + @Test + fun generateKeyDefaultTest() { + val key = generateKey() + + assertEquals(32, key.size) + assert(!key.contentEquals(ByteArray(32) { 0 })) + } + + @Test + fun generateKeyLongerTest() { + val key = generateKey(41u) + + assertEquals(41, key.size) + assert(!key.contentEquals(ByteArray(41) { 0 })) + } + + @Test + fun generateKeyActuallyRandomTest() { + val key1 = generateKey() + val key2 = generateKey() + + assert(!key1.contentEquals(key2)) + } + + @Test + fun deriveKeyPbkdfDefaultTest() { + val password = "password".toByteArray() + + val result = deriveKeyPbkdf2(password, null, 10u) + assertEquals(32, result.size) + assert(!result.contentEquals(ByteArray(32) { 0 })) + } + + @Test + fun deriveKeyPbkdfLargerTest() { + val password = "password".toByteArray() + + val result = deriveKeyPbkdf2(password, null, 10u, 41u) + assertEquals(41, result.size) + assert(!result.contentEquals(ByteArray(41) { 0 })) + } + + @Test + fun deriveKeyPbkdfDeterministicTest() { + val password = "password".toByteArray() + + val result1 = deriveKeyPbkdf2(password, null, 10u) + val result2 = deriveKeyPbkdf2(password, null, 10u) + + assertEquals(32, result1.size) + assert(!result1.contentEquals(ByteArray(32) { 0 })) + assertContentEquals(result1, result2) + } + + @Test + fun deriveKeyPbkdfDifferentTest() { + val password = "password".toByteArray() + val salt = "thisisasalt".toByteArray() + val iterations = 10u + + val result = deriveKeyPbkdf2(password, salt, iterations) + val differentPass = deriveKeyPbkdf2("pa\$\$word".toByteArray(), salt, iterations) + val differentSalt = deriveKeyPbkdf2(password, "this1sasalt".toByteArray(), iterations) + val differentIterations = deriveKeyPbkdf2(password, salt, 11u) + + assert(!result.contentEquals(differentPass)) + assert(!result.contentEquals(differentSalt)) + assert(!result.contentEquals(differentIterations)) + } + + @Test + fun deriveKeyArgon2Test() { + val parameters = Argon2ParametersBuilder().build() + val password = "password".toByteArray() + + val result = deriveKeyArgon2(password, parameters) + + assertEquals(32, result.size) + assert(!result.contentEquals(ByteArray(32) { 0 })) + } + + @Test + fun validateHeaderValidTest() { + val validCiphertext = base64Decode("DQwCAAAAAQA=") + val validPasswordHash = base64Decode("DQwDAAAAAQA=") + val validShare = base64Decode("DQwEAAAAAQA=") + val validPrivateKey = base64Decode("DQwBAAEAAQA=") + val validPublicKey = base64Decode("DQwBAAEAAQA=") + + assert(validateHeader(validCiphertext, DataType.CIPHERTEXT)) + assert(validateHeader(validPasswordHash, DataType.PASSWORD_HASH)) + assert(validateHeader(validShare, DataType.SHARE)) + assert(validateHeader(validPublicKey, DataType.KEY)) + assert(validateHeader(validPrivateKey, DataType.KEY)) + } + + @Test + fun validateHeaderInvalidTest() { + val validCiphertext = base64Decode("DQwCAAAAAQA=") + + assert(!validateHeader(validCiphertext, DataType.PASSWORD_HASH)) + + val invalidSignature = base64Decode("DAwBAAEAAQA=") + val invalidType = base64Decode("DQwIAAEAAQA=") + val invalidSubtype = base64Decode("DQwBAAgAAQA=") + val invalidVersion = base64Decode("DQwBAAEACAA=") + + assert(!validateHeader(invalidSignature, DataType.KEY)) + assert(!validateHeader(invalidType, DataType.KEY)) + assert(!validateHeader(invalidSubtype, DataType.KEY)) + assert(!validateHeader(invalidVersion, DataType.KEY)) + + val notLongEnough = base64Decode("DQwBAAEAAQ==") + + assert(!validateHeader(notLongEnough, DataType.KEY)) + } + + @Test + fun base64EncodeTest() { + val input = byteArrayOf(0x41, 0x42, 0x43, 0x44, 0x45) + val expected = "QUJDREU=" + val result = base64Encode(input) + + assertEquals(expected, result) + } + + @Test + fun base64DecodeTest() { + val input = "QUJDREU=" + val expected = byteArrayOf(0x41, 0x42, 0x43, 0x44, 0x45) + val result = base64Decode(input) + + assertContentEquals(expected, result) + } + + @Test + fun base64UrlEncodeTest() { + val input1 = "Ab6/".toByteArray() + val expected1 = "QWI2Lw" + val result1 = base64EncodeUrl(input1) + + assertEquals(expected1, result1) + + val input2 = "Ab6/75".toByteArray() + val expected2 = "QWI2Lzc1" + val result2 = base64EncodeUrl(input2) + + assertEquals(expected2, result2) + + val input3 = byteArrayOf(0xff.toByte(), 0xff.toByte(), 0xfe.toByte(), 0xff.toByte()) + val expected3 = "___-_w" + val result3 = base64EncodeUrl(input3) + + assertEquals(expected3, result3) + } + + @Test + fun base64UrlDecodeTest() { + val input1 = "QWI2Lw" + val expected1 = "Ab6/".toByteArray() + val result1 = base64DecodeUrl(input1) + + assertContentEquals(expected1, result1) + + val input2 = "QWI2Lzc1" + val expected2 = "Ab6/75".toByteArray() + val result2 = base64DecodeUrl(input2) + + assertContentEquals(expected2, result2) + + val input3 = "___-_w" + val expected3 = byteArrayOf(0xff.toByte(), 0xff.toByte(), 0xfe.toByte(), 0xff.toByte()) + val result3 = base64DecodeUrl(input3) + + assertContentEquals(expected3, result3) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/settings.gradle.kts b/wrappers/kotlin/settings.gradle.kts new file mode 100644 index 000000000..4b77915cf --- /dev/null +++ b/wrappers/kotlin/settings.gradle.kts @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.10.2/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + +rootProject.name = "org.devolutions.crypto" +include("lib") diff --git a/wrappers/kotlin/setup.sh b/wrappers/kotlin/setup.sh new file mode 100644 index 000000000..55c3a218e --- /dev/null +++ b/wrappers/kotlin/setup.sh @@ -0,0 +1,41 @@ +# run as root +rm -rf /usr/local/lib/android +mkdir -p /usr/local/lib/android/sdk + +ANDROID_ROOT="/usr/local/lib/android" +ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk" + +cd /usr/local/lib/android/sdk +wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip +unzip commandlinetools-linux-11076708_latest.zip + +cp -r "${ANDROID_SDK_ROOT}/cmdline-tools/." "${ANDROID_SDK_ROOT}/cmdline-tools/latest/" + + +SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" +echo "y" | $SDKMANAGER "ndk;25.2.9519653" + + + +export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle + + +rm /home/$SUDO_USER/.cargo/config + +echo "[target.aarch64-linux-android] + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-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\" + linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi19-clang\" + [target.i686-linux-android] + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-ar\" + linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android19-clang\" + [target.x86_64-linux-android] + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar\" + linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang\"" >> "/home/$SUDO_USER/.cargo/config" + +ln -sfn $ANDROID_SDK_ROOT/ndk/25.2.9519653 $ANDROID_NDK + +# install kotlin +snap install --classic kotlin \ No newline at end of file diff --git a/wrappers/swift/.gitignore b/wrappers/swift/.gitignore new file mode 100644 index 000000000..0023a5340 --- /dev/null +++ b/wrappers/swift/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/wrappers/swift/DevolutionsCryptoSwift/.gitignore b/wrappers/swift/DevolutionsCryptoSwift/.gitignore new file mode 100644 index 000000000..c6d7fb4a6 --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Sources/DevolutionsCryptoSwift/DevolutionsCryptoSwift.swift \ No newline at end of file diff --git a/wrappers/swift/DevolutionsCryptoSwift/Package.swift b/wrappers/swift/DevolutionsCryptoSwift/Package.swift new file mode 100644 index 000000000..d4249f84f --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.10 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "DevolutionsCryptoSwift", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "DevolutionsCryptoSwift", + targets: ["DevolutionsCryptoSwift"]) + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "DevolutionsCryptoSwift", + dependencies: ["DevolutionsCryptoFramework"]), + .testTarget( + name: "DevolutionsCryptoSwiftTests", + dependencies: ["DevolutionsCryptoSwift"]), + .binaryTarget( + name: "DevolutionsCryptoFramework", + path: "../output/DevolutionsCrypto.xcframework" + ), + ] +) diff --git a/wrappers/swift/DevolutionsCryptoSwift/Sources/DevolutionsCryptoSwift/.gitkeep b/wrappers/swift/DevolutionsCryptoSwift/Sources/DevolutionsCryptoSwift/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/AsymmetricTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/AsymmetricTests.swift new file mode 100644 index 000000000..b642eae2d --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/AsymmetricTests.swift @@ -0,0 +1,86 @@ +import DevolutionsCryptoSwift +import XCTest + +class AsymmetricTests: XCTestCase { + + func testGenerateKeypair() { + let keypair = generateKeypair() + + XCTAssertFalse(keypair.publicKey.isEmpty) + XCTAssertFalse(keypair.privateKey.isEmpty) + XCTAssertNotEqual(keypair.privateKey, keypair.publicKey) + } + + func testEncryptDecryptAsymmetric() throws { + let data = Data("This is some test data".utf8) + let keypair = generateKeypair() + + let encrypted = try encryptAsymmetric(data: data, key: keypair.publicKey) + let decrypted = try decryptAsymmetric(data: encrypted, key: keypair.privateKey) + + XCTAssertFalse(data.elementsEqual(encrypted)) + XCTAssertEqual(data, decrypted) + } + + func testEncryptDecryptAsymmetricWithAad() throws { + let data = Data("This is some test data".utf8) + let aad = Data("This is some public data".utf8) + let keypair = generateKeypair() + + let encrypted = try encryptAsymmetricWithAad(data: data, key: keypair.publicKey, aad: aad) + let decrypted = try decryptAsymmetricWithAad(data: encrypted, key: keypair.privateKey, aad: aad) + + XCTAssertFalse(data.elementsEqual(encrypted)) + XCTAssertEqual(data, decrypted) + } + + func testEncryptDecryptAsymmetricWithWrongAad() throws { + let data = Data("This is some test data".utf8) + let aad = Data("This is some public data".utf8) + let wrongAad = Data("this is some public data".utf8) + let keypair = generateKeypair() + + let encrypted = try encryptAsymmetricWithAad(data: data, key: keypair.publicKey, aad: aad) + + XCTAssertThrowsError( + try decryptAsymmetricWithAad(data: encrypted, key: keypair.privateKey, aad: wrongAad) + ) { error in + XCTAssertTrue(error is DevolutionsCryptoError) + } + } + + func testMixKeyExchange() throws { + let bobKeypair = generateKeypair() + let aliceKeypair = generateKeypair() + + let bobShared = try mixKeyExchange( + privateKey: bobKeypair.privateKey, publicKey: aliceKeypair.publicKey) + let aliceShared = try mixKeyExchange( + privateKey: aliceKeypair.privateKey, publicKey: bobKeypair.publicKey) + + XCTAssertEqual(bobShared.count, 32) + XCTAssertNotEqual(bobShared, Data(repeating: UInt8(0), count: 32)) + XCTAssertEqual(bobShared, aliceShared) + } + + func testMixKeyExchangeNotEquals() throws { + let bobKeypair = generateKeypair() + let aliceKeypair = generateKeypair() + let eveKeypair = generateKeypair() + + let bobAliceShared = try mixKeyExchange( + privateKey: bobKeypair.privateKey, publicKey: aliceKeypair.publicKey) + let aliceBobShared = try mixKeyExchange( + privateKey: aliceKeypair.privateKey, publicKey: bobKeypair.publicKey) + + let eveBobShared = try mixKeyExchange( + privateKey: eveKeypair.privateKey, publicKey: bobKeypair.publicKey) + let eveAliceShared = try mixKeyExchange( + privateKey: eveKeypair.privateKey, publicKey: aliceKeypair.publicKey) + + XCTAssertNotEqual(eveBobShared, bobAliceShared) + XCTAssertNotEqual(eveBobShared, aliceBobShared) + XCTAssertNotEqual(eveAliceShared, bobAliceShared) + XCTAssertNotEqual(eveAliceShared, aliceBobShared) + } +} diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/ConformityTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/ConformityTests.swift new file mode 100644 index 000000000..0e64fa3dc --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/ConformityTests.swift @@ -0,0 +1,130 @@ +import DevolutionsCryptoSwift +import XCTest + +class ConformityTests: XCTestCase { + + func testDeriveKeyPbkdf2() throws { + let derivedKey = deriveKeyPbkdf2(key: Data("testpassword".utf8), salt: nil) + let derivedKeyWithIterations = deriveKeyPbkdf2( + key: Data("testPa$$".utf8), salt: nil, iterations: 100) + let derivedKeyWithSalt = deriveKeyPbkdf2( + key: Data("testPa$$".utf8), + salt: try base64Decode(data: "tdTt5wgeqQYLvkiXKkFirqy2hMbzadBtL+jekVeNCRA="), + iterations: 100 + ) + + let expected = try base64Decode(data: "ImfGCyv6PwMYaJShGxR4MfVrjuUrsI0CSarJgOApwf8=") + let expectedWithIterations = try base64Decode( + data: "ev/GiJLvOgIkkWrnIrHSi2fdZE5qJBIrW+DLeMLIXK4=") + let expectedWithSalt = try base64Decode(data: "ZaYRZeQiIPJ+Jl511AgHZjv4/HbCFq4eUP9yNa3gowI=") + + XCTAssertEqual(derivedKey, expected) + XCTAssertEqual(derivedKeyWithIterations, expectedWithIterations) + XCTAssertEqual(derivedKeyWithSalt, expectedWithSalt) + } + + func testDeriveKeyArgon2() throws { + let password = Data("password".utf8) + let parameters = try Argon2Parameters.newFromBytes( + data: try base64Decode( + data: "AQAAACAAAAABAAAAIAAAAAEAAAACEwAAAAAQAAAAimFBkm3f8+f+YfLRnF5OoQ==") + ) + let result = try deriveKeyArgon2(key: password, parameters: parameters) + + let expected = try base64Decode(data: "AcEN6Cb1Om6tomZScAM725qiXMzaxaHlj3iMiT/Ukq0=") + + XCTAssertEqual(result, expected) + } + + func testSymmetricDecryptV1() throws { + let key = try base64Decode(data: "ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + let ciphertext = try base64Decode( + data: + "DQwCAAAAAQCK1twEut+TeJfFbTWCRgHjyS6bOPOZUEQAeBtSFFRl2jHggM/34n68zIZWGbsZHkufVzU6mTN5N2Dx9bTplrycv5eNVevT4P9FdVHJ751D+A==" + ) + let result = try decrypt(data: ciphertext, key: key) + let expected = Data("test Ciph3rtext~".utf8) + + XCTAssertEqual(result, expected) + } + + func testSymmetricDecryptWithAadV1() throws { + let key = try base64Decode(data: "ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + let ciphertext = try base64Decode( + data: + "DQwCAAEAAQCeKfbTqYjfVCEPEiAJjiypBstPmZz0AnpliZKoR+WXTKdj2f/4ops0++dDBVZ+XdyE1KfqxViWVc9djy/HSCcPR4nDehtNI69heGCIFudXfQ==" + ) + let aad = Data("this is some public data".utf8) + let result = try decryptWithAad(data: ciphertext, key: key, aad: aad) + let expected = Data("test Ciph3rtext~".utf8) + + XCTAssertEqual(result, expected) + } + + func testSymmetricDecryptV2() throws { + let key = try base64Decode(data: "ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + let ciphertext = try base64Decode( + data: + "DQwCAAAAAgAA0iPpI4IEzcrWAQiy6tqDqLbRYduGvlMC32mVH7tpIN2CXDUu5QHF91I7pMrmjt/61pm5CeR/IcU=") + let result = try decrypt(data: ciphertext, key: key) + let expected = Data("test Ciph3rtext~2".utf8) + + XCTAssertEqual(result, expected) + } + + func testSymmetricDecryptWithAadV2() throws { + let key = try base64Decode(data: "ozJVEme4+5e/4NG3C+Rl26GQbGWAqGc0QPX8/1xvaFM=") + let ciphertext = try base64Decode( + data: + "DQwCAAEAAgA9bh989dao0Pvaz1NpJTI5m7M4br2qVjZtFwXXoXZOlkCjtqU/uif4pbNCcpEodzeP4YG1QvfKVQ==") + let aad = Data("this is some public data".utf8) + let result = try decryptWithAad(data: ciphertext, key: key, aad: aad) + let expected = Data("test Ciph3rtext~".utf8) + + XCTAssertEqual(result, expected) + } + + func testAsymmetricDecryptWithAadV2() throws { + let privateKey = try base64Decode( + data: "DQwBAAEAAQC9qf9UY1ovL/48ALGHL9SLVpVozbdjYsw0EPerUl3zYA==") + let ciphertext = try base64Decode( + data: + "DQwCAAIAAgB1u62xYeyppWf83QdWwbwGUt5QuiAFZr+hIiFEvMRbXiNCE3RMBNbmgQkLr/vME0BeQa+uUTXZARvJcyNXHyAE4tSdw6o/psU/kw/Z/FbsPw==" + ) + let aad = Data("this is some public data".utf8) + let result = try decryptAsymmetricWithAad(data: ciphertext, key: privateKey, aad: aad) + let expected = Data("testdata".utf8) + + XCTAssertEqual(result, expected) + } + + func testPasswordHashingV1() throws { + let hash1 = try base64Decode( + data: + "DQwDAAAAAQAQJwAAXCzLFoyeZhFSDYBAPiIWhCk04aoP/lalOoCl7D+skIY/i+3WT7dn6L8WvnfEq6flCd7i+IcKb3GEK4rCpzhDlw==" + ) + let hash2 = try base64Decode( + data: + "DQwDAAAAAQAKAAAAmH1BBckBJYDD0xfiwkAk1xwKgw8a57YQT0Igm+Faa9LFamTeEJgqn/qHc2R/8XEyK2iLPkVy+IErdGLLtLKJ2g==" + ) + + XCTAssertTrue(try verifyPassword(password: Data("password1".utf8), hash: hash1)) + XCTAssertTrue(try verifyPassword(password: Data("password1".utf8), hash: hash2)) + } + + func testSignatureV1() throws { + let publicKey = try base64Decode( + data: "DQwFAAIAAQDeEvwlEigK5AXoTorhmlKP6+mbiUU2rYrVQ25JQ5xang==") + let signature = try base64Decode( + data: + "DQwGAAAAAQD82uRk4sFC8vEni6pDNw/vOdN1IEDg9cAVfprWJZ/JBls9Gi61cUt5u6uBJtseNGZFT7qKLvp4NUZrAOL8FH0K" + ) + + XCTAssertTrue( + try verifySignature( + data: Data("this is a test".utf8), publicKey: publicKey, signature: signature)) + XCTAssertFalse( + try verifySignature( + data: Data("this is wrong".utf8), publicKey: publicKey, signature: signature)) + } +} diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/HashingTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/HashingTests.swift new file mode 100644 index 000000000..36b86ffae --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/HashingTests.swift @@ -0,0 +1,20 @@ +import DevolutionsCryptoSwift +import XCTest + +class HashingTests: XCTestCase { + func testPasswordHash() throws { + let password = Data("password".utf8) + let hash = hashPassword(password: password, iterations: 10) + + XCTAssertTrue(try verifyPassword(password: password, hash: hash)) + } + + func testWrongPassword() throws { + let password = Data("password".utf8) + let hash = hashPassword(password: password, iterations: 10) + + XCTAssertFalse(try verifyPassword(password: Data("pa$$word".utf8), hash: hash)) + XCTAssertFalse(try verifyPassword(password: Data("Password".utf8), hash: hash)) + XCTAssertFalse(try verifyPassword(password: Data("password1".utf8), hash: hash)) + } +} diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SecretSharingTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SecretSharingTests.swift new file mode 100644 index 000000000..47ed2f7f6 --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SecretSharingTests.swift @@ -0,0 +1,53 @@ +import DevolutionsCryptoSwift +import XCTest + +class SecretSharingTests: XCTestCase { + func testSharedSecretDefault() throws { + let shares = try generateSharedKey(nShares: 5, threshold: 3) + + let shareGroup1 = Array(shares[0...2]) + let shareGroup2 = Array(shares[1...3]) + let shareGroup3 = Array(shares[2...4]) + + let key1 = try joinShares(shares: shareGroup1) + let key2 = try joinShares(shares: shareGroup2) + let key3 = try joinShares(shares: shareGroup3) + + XCTAssertEqual(key1.count, 32) + XCTAssertNotEqual(key1, Data(repeating: UInt8(0), count: 32)) + XCTAssertEqual(key1, key2) + XCTAssertEqual(key1, key3) + } + + func testSharedSecretLarger() throws { + let shares = try generateSharedKey(nShares: 5, threshold: 3, length: 41) + + let shareGroup1 = Array(shares[0...2]) + let shareGroup2 = Array(shares[1...3]) + let shareGroup3 = Array(shares[2...4]) + + let key1 = try joinShares(shares: shareGroup1) + let key2 = try joinShares(shares: shareGroup2) + let key3 = try joinShares(shares: shareGroup3) + + XCTAssertEqual(key1.count, 41) + XCTAssertNotEqual(key1, Data(repeating: UInt8(0), count: 41)) + XCTAssertEqual(key1, key2) + XCTAssertEqual(key1, key3) + } + + func testSharedSecretWrongParams() { + XCTAssertThrowsError(try generateSharedKey(nShares: 3, threshold: 5)) { error in + XCTAssertTrue(error is DevolutionsCryptoError) + } + } + + func testSharedSecretNotEnoughShares() { + let shares = try! generateSharedKey(nShares: 5, threshold: 3) + let sharesGroup = Array(shares[0...1]) + + XCTAssertThrowsError(try joinShares(shares: sharesGroup)) { error in + XCTAssertTrue(error is DevolutionsCryptoError) + } + } +} diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SignatureTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SignatureTests.swift new file mode 100644 index 000000000..4d77dd488 --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SignatureTests.swift @@ -0,0 +1,25 @@ +import DevolutionsCryptoSwift +import XCTest + +class SignatureTests: XCTestCase { + func testSignature() throws { + let data = Data("this is a test".utf8) + let keypair = generateSigningKeypair() + + let signature = try sign(data: data, keypair: keypair.getPrivateKey()) + + XCTAssertTrue( + try verifySignature(data: data, publicKey: keypair.getPublicKey(), signature: signature)) + } + + func testWrongSignature() throws { + let data = Data("this is test data".utf8) + let wrongData = Data("this is wrong data".utf8) + let keypair = generateSigningKeypair() + + let signature = try sign(data: data, keypair: keypair.getPrivateKey()) + + XCTAssertFalse( + try verifySignature(data: wrongData, publicKey: keypair.getPublicKey(), signature: signature)) + } +} diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SymmetricTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SymmetricTests.swift new file mode 100644 index 000000000..742895f19 --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/SymmetricTests.swift @@ -0,0 +1,42 @@ +import DevolutionsCryptoSwift +import XCTest + +class SymmetricTests: XCTestCase { + func testEncryptDecrypt() throws { + let data = Data("This is some test data".utf8) + let key = generateKey() + + let encrypted = try encrypt(data: data, key: key) + let decrypted = try decrypt(data: encrypted, key: key) + + XCTAssertFalse(data.elementsEqual(encrypted)) + XCTAssertEqual(data, decrypted) + } + + func testEncryptDecryptWithAad() throws { + let data = Data("This is some test data".utf8) + let aad = Data("This is some public data".utf8) + + let key = generateKey() + + let encrypted = try encryptWithAad(data: data, key: key, aad: aad) + let decrypted = try decryptWithAad(data: encrypted, key: key, aad: aad) + + XCTAssertFalse(data.elementsEqual(encrypted)) + XCTAssertEqual(data, decrypted) + } + + func testEncryptDecryptWithWrongAad() throws { + let data = Data("This is some test data".utf8) + let aad = Data("This is some public data".utf8) + let wrongAad = Data("this is some public data".utf8) + + let key = generateKey() + + let encrypted = try encryptWithAad(data: data, key: key, aad: aad) + + XCTAssertThrowsError(try decryptWithAad(data: encrypted, key: key, aad: wrongAad)) { error in + XCTAssertTrue(error is DevolutionsCryptoError) + } + } +} diff --git a/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/UtilsTests.swift b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/UtilsTests.swift new file mode 100644 index 000000000..a2cc86829 --- /dev/null +++ b/wrappers/swift/DevolutionsCryptoSwift/Tests/DevolutionsCryptoSwiftTests/UtilsTests.swift @@ -0,0 +1,152 @@ +import DevolutionsCryptoSwift +import XCTest + +class UtilsTests: XCTestCase { + func testGenerateKeyDefault() { + let key = generateKey() + XCTAssertEqual(key.count, 32) + XCTAssertNotEqual(key, Data(repeating: UInt8(0), count: 32)) + } + + func testGenerateKeyLonger() { + let key = generateKey(length: 41) + XCTAssertEqual(key.count, 41) + XCTAssertNotEqual(key, Data(repeating: UInt8(0), count: 41)) + } + + func testGenerateKeyActuallyRandom() { + let key1 = generateKey() + let key2 = generateKey() + XCTAssertNotEqual(key1, key2) + } + + func testDeriveKeyPbkdfDefault() { + let password = Data("password".utf8) + let result = deriveKeyPbkdf2(key: password, salt: nil, iterations: 10) + XCTAssertEqual(result.count, 32) + XCTAssertNotEqual(result, Data(repeating: UInt8(0), count: 32)) + } + + func testDeriveKeyPbkdfLarger() { + let password = Data("password".utf8) + let result = deriveKeyPbkdf2(key: password, salt: nil, iterations: 10, length: 41) + XCTAssertEqual(result.count, 41) + XCTAssertNotEqual(result, Data(repeating: UInt8(0), count: 41)) + } + + func testDeriveKeyPbkdfDeterministic() { + let password = Data("password".utf8) + let result1 = deriveKeyPbkdf2(key: password, salt: nil, iterations: 10) + let result2 = deriveKeyPbkdf2(key: password, salt: nil, iterations: 10) + + XCTAssertEqual(result1.count, 32) + XCTAssertNotEqual(result1, Data(repeating: UInt8(0), count: 32)) + XCTAssertEqual(result1, result2) + } + + func testDeriveKeyPbkdfDifferent() { + let password = Data("password".utf8) + let salt = Data("thisisasalt".utf8) + let iterations: UInt32 = 10 + + let result = deriveKeyPbkdf2(key: password, salt: salt, iterations: iterations) + let differentPass = deriveKeyPbkdf2( + key: Data("pa$$word".utf8), salt: salt, iterations: iterations) + let differentSalt = deriveKeyPbkdf2( + key: password, salt: Data("this1sasalt".utf8), iterations: iterations) + let differentIterations = deriveKeyPbkdf2(key: password, salt: salt, iterations: 11) + + XCTAssertNotEqual(result, differentPass) + XCTAssertNotEqual(result, differentSalt) + XCTAssertNotEqual(result, differentIterations) + } + + func testDeriveKeyArgon2() throws { + let parameters = Argon2ParametersBuilder().build() + let password = Data("password".utf8) + + let result = try deriveKeyArgon2(key: password, parameters: parameters) + XCTAssertEqual(result.count, 32) + XCTAssertNotEqual(result, Data(repeating: UInt8(0), count: 32)) + } + + func testValidateHeaderValid() throws { + let validCiphertext = try base64Decode(data: "DQwCAAAAAQA=") + let validPasswordHash = try base64Decode(data: "DQwDAAAAAQA=") + let validShare = try base64Decode(data: "DQwEAAAAAQA=") + let validPrivateKey = try base64Decode(data: "DQwBAAEAAQA=") + let validPublicKey = try base64Decode(data: "DQwBAAEAAQA=") + + XCTAssertTrue(validateHeader(data: validCiphertext, dataType: DataType.ciphertext)) + XCTAssertTrue(validateHeader(data: validPasswordHash, dataType: DataType.passwordHash)) + XCTAssertTrue(validateHeader(data: validShare, dataType: DataType.share)) + XCTAssertTrue(validateHeader(data: validPublicKey, dataType: DataType.key)) + XCTAssertTrue(validateHeader(data: validPrivateKey, dataType: DataType.key)) + } + + func testValidateHeaderInvalid() throws { + let validCiphertext = try base64Decode(data: "DQwCAAAAAQA=") + XCTAssertFalse(validateHeader(data: validCiphertext, dataType: DataType.passwordHash)) + + let invalidSignature = try base64Decode(data: "DAwBAAEAAQA=") + let invalidType = try base64Decode(data: "DQwIAAEAAQA=") + let invalidSubtype = try base64Decode(data: "DQwBAAgAAQA=") + let invalidVersion = try base64Decode(data: "DQwBAAEACAA=") + + XCTAssertFalse(validateHeader(data: invalidSignature, dataType: DataType.key)) + XCTAssertFalse(validateHeader(data: invalidType, dataType: DataType.key)) + XCTAssertFalse(validateHeader(data: invalidSubtype, dataType: DataType.key)) + XCTAssertFalse(validateHeader(data: invalidVersion, dataType: DataType.key)) + + let notLongEnough = try base64Decode(data: "DQwBAAEAAQ==") + XCTAssertFalse(validateHeader(data: notLongEnough, dataType: DataType.key)) + } + + func testBase64Encode() { + let input: Data = Data([0x41, 0x42, 0x43, 0x44, 0x45]) + let expected = "QUJDREU=" + let result = base64Encode(data: input) + XCTAssertEqual(result, expected) + } + + func testBase64Decode() throws { + let input = "QUJDREU=" + let expected: Data = Data([0x41, 0x42, 0x43, 0x44, 0x45]) + let result = try base64Decode(data: input) + XCTAssertEqual(result, expected) + } + + func testBase64UrlEncode() { + let input1 = Data("Ab6/".utf8) + let expected1 = "QWI2Lw" + let result1 = base64EncodeUrl(data: input1) + XCTAssertEqual(result1, expected1) + + let input2 = Data("Ab6/75".utf8) + let expected2 = "QWI2Lzc1" + let result2 = base64EncodeUrl(data: input2) + XCTAssertEqual(result2, expected2) + + let input3 = Data([0xff, 0xff, 0xfe, 0xff]) + let expected3 = "___-_w" + let result3 = base64EncodeUrl(data: input3) + XCTAssertEqual(result3, expected3) + } + + func testBase64UrlDecode() throws { + let input1 = "QWI2Lw" + let expected1 = Data("Ab6/".utf8) + let result1 = try base64DecodeUrl(data: input1) + XCTAssertEqual(result1, expected1) + + let input2 = "QWI2Lzc1" + let expected2 = Data("Ab6/75".utf8) + let result2 = try base64DecodeUrl(data: input2) + XCTAssertEqual(result2, expected2) + + let input3 = "___-_w" + let expected3 = Data([0xff, 0xff, 0xfe, 0xff]) + let result3 = try base64DecodeUrl(data: input3) + XCTAssertEqual(result3, expected3) + } +} diff --git a/wrappers/swift/generate.sh b/wrappers/swift/generate.sh new file mode 100755 index 000000000..f802b13cb --- /dev/null +++ b/wrappers/swift/generate.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# based on https://rhonabwy.com/2023/02/10/creating-an-xcframework + + +XCFRAMEWORK_FOLDER="./output/DevolutionsCrypto.xcframework" +LIBNAME="devolutions_crypto_uniffi" +LIBNAMEBUILD="devolutions-crypto-uniffi" + +rm -rf ./bindings +rm -rf ./output + +cargo build -p "$LIBNAMEBUILD" + +cargo run -p uniffi-bindgen generate --library "../../target/debug/lib$LIBNAME.dylib" --language swift -o bindings --no-format + +mkdir ./bindings/mac +mkdir ./bindings/ios-simulator + +rustup target add x86_64-apple-ios # iOS Simulator on Intel based mac +rustup target add aarch64-apple-ios-sim # iOS Simulator on Arm based mac +rustup target add aarch64-apple-ios # iOS & iPad +rustup target add aarch64-apple-darwin # Arm based mac +rustup target add x86_64-apple-darwin # Intel based mac + + +cargo build --release --target=x86_64-apple-ios -p "$LIBNAMEBUILD" +cargo build --release --target=aarch64-apple-ios-sim -p "$LIBNAMEBUILD" +cargo build --release --target=aarch64-apple-ios -p "$LIBNAMEBUILD" +cargo build --release --target=aarch64-apple-darwin -p "$LIBNAMEBUILD" +cargo build --release --target=x86_64-apple-darwin -p "$LIBNAMEBUILD" + + +mv "./bindings/devolutions_cryptoFFI.modulemap" ./bindings/module.modulemap + +# combine the platforms + +# ios simulator +lipo "../../target/x86_64-apple-ios/release/lib$LIBNAME.a" \ + "../../target/aarch64-apple-ios-sim/release/lib$LIBNAME.a" \ + -create -output "./bindings/ios-simulator/lib$LIBNAME.a" + +# mac +lipo ../../target/x86_64-apple-darwin/release/lib$LIBNAME.a \ + ../../target/aarch64-apple-darwin/release/lib$LIBNAME.a \ + -create -output ./bindings/mac/lib$LIBNAME.a + + +# no need to combine ios + +# create the XCFramework +xcodebuild -create-xcframework \ + -library "./bindings/ios-simulator/lib$LIBNAME.a" -headers ./bindings \ + -library "./bindings/mac/lib$LIBNAME.a" -headers ./bindings \ + -library "../../target/aarch64-apple-ios/release/lib$LIBNAME.a" -headers ./bindings \ + -output "$XCFRAMEWORK_FOLDER" + +# Compress XCFramework +ditto -c -k --sequesterRsrc --keepParent "$XCFRAMEWORK_FOLDER" "$XCFRAMEWORK_FOLDER.zip" + +# Compute checksum +swift package compute-checksum "$XCFRAMEWORK_FOLDER.zip" + +# Move swift file to package +cp "./bindings/devolutions_crypto.swift" ./DevolutionsCryptoSwift/Sources/DevolutionsCryptoSwift/DevolutionsCryptoSwift.swift + +# Tests +cd ./DevolutionsCryptoSwift + +swift test + +