From 3d05aa94c8d71578ac1fb0d3b51a4842c1cdd499 Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Fri, 16 Oct 2026 11:34:32 -0400 Subject: [PATCH 1/8] fix: reject low-order X25519 public keys --- src/ciphertext/ciphertext_v2.rs | 8 ++ src/key/key_v1.rs | 11 +- src/key/mod.rs | 4 +- src/online_ciphertext/mod.rs | 3 +- src/online_ciphertext/online_ciphertext_v1.rs | 14 ++- tests/low_order_points.rs | 119 ++++++++++++++++++ 6 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 tests/low_order_points.rs diff --git a/src/ciphertext/ciphertext_v2.rs b/src/ciphertext/ciphertext_v2.rs index d86e600a..c09e39b4 100644 --- a/src/ciphertext/ciphertext_v2.rs +++ b/src/ciphertext/ciphertext_v2.rs @@ -191,6 +191,10 @@ impl CiphertextV2Asymmetric { let key = ephemeral_private_key.diffie_hellman(&public_key); + if !key.was_contributory() { + return Err(Error::InvalidData); + } + let ciphertext = CiphertextV2Symmetric::encrypt(data, key.as_bytes(), aad, header)?; Ok(Self { @@ -209,6 +213,10 @@ impl CiphertextV2Asymmetric { let key = private_key.diffie_hellman(&self.public_key); + if !key.was_contributory() { + return Err(Error::InvalidData); + } + self.ciphertext.decrypt(key.as_bytes(), aad, header) } } diff --git a/src/key/key_v1.rs b/src/key/key_v1.rs index aa3526fa..032c4c42 100644 --- a/src/key/key_v1.rs +++ b/src/key/key_v1.rs @@ -108,8 +108,15 @@ pub fn generate_keypair() -> KeyV1Pair { } } -pub fn mix_key_exchange(private: &KeyV1Private, public: &KeyV1Public) -> Vec { - private.key.diffie_hellman(&public.key).as_bytes().to_vec() +pub fn mix_key_exchange(private: &KeyV1Private, public: &KeyV1Public) -> Result> { + let shared = private.key.diffie_hellman(&public.key); + + // Reject low-order public keys, which yield an all-zero shared secret + if !shared.was_contributory() { + return Err(Error::InvalidData); + } + + Ok(shared.as_bytes().to_vec()) } impl From<&KeyV1Public> for x25519_dalek::PublicKey { diff --git a/src/key/mod.rs b/src/key/mod.rs index 714c5b50..2e0a4c45 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -210,11 +210,11 @@ pub fn generate_keypair(version: KeyVersion) -> KeyPair { /// assert_eq!(bob_shared, alice_shared); /// ``` pub fn mix_key_exchange(private_key: &PrivateKey, public_key: &PublicKey) -> Result> { - Ok(match (&private_key.payload, &public_key.payload) { + match (&private_key.payload, &public_key.payload) { (PrivateKeyPayload::V1(private_key), PublicKeyPayload::V1(public_key)) => { key_v1::mix_key_exchange(private_key, public_key) } //_ => Err(DevoCryptoError::InvalidDataType), - }) + } } fn keypair_headers(version: KeyVersion) -> (Header, Header) { diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 748174cc..f846c9eb 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -128,7 +128,8 @@ impl OnlineCiphertextHeader { match self.payload { OnlineCiphertextHeaderPayload::V1(header) => match header { OnlineCiphertextV1Header::Asymmetric(header) => { - let cipher = OnlineCiphertextV1Decryptor::new_asymmetric(key, full_aad, header); + let cipher = + OnlineCiphertextV1Decryptor::new_asymmetric(key, full_aad, header)?; Ok(OnlineCiphertextDecryptor::V1(cipher)) } diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 65205d3c..013ea347 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -334,6 +334,10 @@ impl OnlineCiphertextV1Encryptor { let key = ephemeral_private_key.diffie_hellman(&public_key); + if !key.was_contributory() { + return Err(Error::InvalidData); + } + // Generate a new nonce let mut nonce = [0u8; 20]; rand::rngs::SysRng @@ -389,12 +393,16 @@ impl OnlineCiphertextV1Decryptor { private_key: &PrivateKey, mut aad: Vec, header: OnlineCiphertextV1HeaderAsymmetric, - ) -> Self { + ) -> Result { // Perform a ECDH exchange as per ECIES let private_key = x25519_dalek::StaticSecret::from(private_key); let key = private_key.diffie_hellman(&header.public_key); + if !key.was_contributory() { + return Err(Error::InvalidData); + } + // Derive the key let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes())); let cipher = XChaCha20Poly1305::new_from_slice(key.as_ref()) @@ -406,11 +414,11 @@ impl OnlineCiphertextV1Decryptor { let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); - Self { + Ok(Self { header: OnlineCiphertextV1Header::Asymmetric(header), aad, cipher, - } + }) } } diff --git a/tests/low_order_points.rs b/tests/low_order_points.rs new file mode 100644 index 00000000..25c76d96 --- /dev/null +++ b/tests/low_order_points.rs @@ -0,0 +1,119 @@ +use devolutions_crypto::Error; +use devolutions_crypto::ciphertext::{Ciphertext, CiphertextVersion, encrypt_asymmetric}; +use devolutions_crypto::key::{KeyVersion, PublicKey, generate_keypair, mix_key_exchange}; +use devolutions_crypto::online_ciphertext::{ + OnlineCiphertextEncryptor, OnlineCiphertextHeader, OnlineCiphertextVersion, +}; + +use std::convert::TryFrom; + +// Curve25519 points of small order, including non-canonical encodings +const LOW_ORDER_POINTS: [[u8; 32]; 7] = [ + hex("0000000000000000000000000000000000000000000000000000000000000000"), + hex("0100000000000000000000000000000000000000000000000000000000000000"), + hex("e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800"), + hex("5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157"), + hex("ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), + hex("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), + hex("eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), +]; + +const fn hex(s: &str) -> [u8; 32] { + const fn nibble(c: u8) -> u8 { + match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 10, + _ => panic!("invalid hex"), + } + } + + let s = s.as_bytes(); + let mut out = [0u8; 32]; + let mut i = 0; + while i < 32 { + out[i] = (nibble(s[i * 2]) << 4) | nibble(s[i * 2 + 1]); + i += 1; + } + out +} + +fn with_last_32(bytes: &[u8], point: &[u8; 32]) -> Vec { + let mut bytes = bytes.to_vec(); + let offset = bytes.len() - 32; + bytes[offset..].copy_from_slice(point); + bytes +} + +fn low_order_public_key(point: &[u8; 32]) -> PublicKey { + let template: Vec = generate_keypair(KeyVersion::Latest).public_key.into(); + PublicKey::try_from(with_last_32(&template, point).as_slice()).unwrap() +} + +#[test] +fn mix_key_exchange_rejects_low_order_points() { + let keypair = generate_keypair(KeyVersion::Latest); + + for point in &LOW_ORDER_POINTS { + let public_key = low_order_public_key(point); + let result = mix_key_exchange(&keypair.private_key, &public_key); + assert!(matches!(result, Err(Error::InvalidData))); + } +} + +#[test] +fn encrypt_asymmetric_rejects_low_order_points() { + for point in &LOW_ORDER_POINTS { + let public_key = low_order_public_key(point); + let result = encrypt_asymmetric(b"data", &public_key, CiphertextVersion::Latest); + assert!(matches!(result, Err(Error::InvalidData))); + } +} + +#[test] +fn decrypt_asymmetric_rejects_low_order_points() { + let keypair = generate_keypair(KeyVersion::Latest); + let ciphertext: Vec = + encrypt_asymmetric(b"data", &keypair.public_key, CiphertextVersion::Latest) + .unwrap() + .into(); + + for point in &LOW_ORDER_POINTS { + // The ephemeral public key follows the 8-byte header + let mut forged = ciphertext.clone(); + forged[8..40].copy_from_slice(point); + + let forged = Ciphertext::try_from(forged.as_slice()).unwrap(); + let result = forged.decrypt_asymmetric(&keypair.private_key); + assert!(matches!(result, Err(Error::InvalidData))); + } +} + +#[test] +fn online_ciphertext_rejects_low_order_points() { + let keypair = generate_keypair(KeyVersion::Latest); + let encryptor = OnlineCiphertextEncryptor::new_asymmetric( + &keypair.public_key, + b"", + 16, + OnlineCiphertextVersion::Latest, + ) + .unwrap(); + let header: Vec = (&encryptor.get_header()).into(); + + for point in &LOW_ORDER_POINTS { + let public_key = low_order_public_key(point); + let result = OnlineCiphertextEncryptor::new_asymmetric( + &public_key, + b"", + 16, + OnlineCiphertextVersion::Latest, + ); + assert!(matches!(result, Err(Error::InvalidData))); + + // The ephemeral public key is the last field of the header + let forged = + OnlineCiphertextHeader::try_from(with_last_32(&header, point).as_slice()).unwrap(); + let result = forged.into_decryptor_asymmetric(&keypair.private_key, b""); + assert!(matches!(result, Err(Error::InvalidData))); + } +} From 575f3d27cec71ab721e94c454c180099cb5ab47e Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Fri, 16 Oct 2026 11:54:29 -0400 Subject: [PATCH 2/8] ci: install wasm-pack with --locked --- .github/workflows/other/webassembly-build/action.yml | 2 +- .github/workflows/tests/webassembly/safari/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/other/webassembly-build/action.yml b/.github/workflows/other/webassembly-build/action.yml index c89744dd..5ffe7d0a 100644 --- a/.github/workflows/other/webassembly-build/action.yml +++ b/.github/workflows/other/webassembly-build/action.yml @@ -7,7 +7,7 @@ runs: shell: bash run: | rustup update - cargo install wasm-pack + cargo install wasm-pack --locked - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/tests/webassembly/safari/action.yml b/.github/workflows/tests/webassembly/safari/action.yml index 93028001..999a3ba2 100644 --- a/.github/workflows/tests/webassembly/safari/action.yml +++ b/.github/workflows/tests/webassembly/safari/action.yml @@ -9,7 +9,7 @@ runs: curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable echo "$HOME/.cargo/bin" >> $GITHUB_PATH source $HOME/.cargo/env - cargo install wasm-pack + cargo install wasm-pack --locked - name: Unit tests working-directory: ./ From 04d498b590c16ddba525fbe8e13f781a58554ae6 Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Mon, 19 Oct 2026 07:58:19 -0400 Subject: [PATCH 3/8] ci: bump actions off the deprecated Node 20 runtime download-artifact v4 -> v8, upload-artifact v4/v4.3.6 -> v7, setup-python v5 -> v7, setup-node v4 -> v7, setup-java v3 -> v6. Uploads previously pinned to v4.3.6 get include-hidden-files: true to keep including dotfiles, which v4.4+ excludes by default. --- .github/workflows/formatting/csharp/action.yml | 2 +- .github/workflows/formatting/rust/action.yml | 2 +- .github/workflows/kotlin/kotlin-linux/action.yml | 12 +++++++----- .github/workflows/kotlin/kotlin-macos/action.yml | 5 +++-- .github/workflows/kotlin/kotlin-windows/action.yml | 7 ++++--- .github/workflows/native/native-build-ios/action.yml | 5 +++-- .../workflows/native/native-build-linux/action.yml | 5 +++-- .../workflows/native/native-build-macos/action.yml | 5 +++-- .../workflows/native/native-build-windows/action.yml | 5 +++-- .github/workflows/nugets/nugets-windows/action.yml | 9 +++++---- .github/workflows/other/source-publish/action.yml | 3 ++- .github/workflows/other/webassembly-build/action.yml | 5 +++-- .github/workflows/python/build/linux/action.yml | 5 +++-- .github/workflows/python/build/macos/action.yml | 5 +++-- .github/workflows/python/build/windows/action.yml | 4 ++-- .github/workflows/swift/action.yml | 5 +++-- .github/workflows/tests/csharp/android/action.yml | 2 +- .github/workflows/tests/csharp/ios/action.yml | 2 +- .github/workflows/tests/csharp/linux/action.yml | 2 +- .github/workflows/tests/csharp/macos/action.yml | 2 +- .github/workflows/tests/csharp/windows/action.yml | 2 +- .github/workflows/tests/fuzz/quick/action.yml | 3 ++- .github/workflows/tests/python/linux/action.yml | 4 ++-- .github/workflows/tests/python/macos/action.yml | 4 ++-- .github/workflows/tests/python/windows/action.yml | 4 ++-- 25 files changed, 62 insertions(+), 47 deletions(-) diff --git a/.github/workflows/formatting/csharp/action.yml b/.github/workflows/formatting/csharp/action.yml index 8fcf4d1b..b5409018 100644 --- a/.github/workflows/formatting/csharp/action.yml +++ b/.github/workflows/formatting/csharp/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp diff --git a/.github/workflows/formatting/rust/action.yml b/.github/workflows/formatting/rust/action.yml index 92d076d8..187b3fac 100644 --- a/.github/workflows/formatting/rust/action.yml +++ b/.github/workflows/formatting/rust/action.yml @@ -3,7 +3,7 @@ description: Ensure that the Rust code format is compliant runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.7" diff --git a/.github/workflows/kotlin/kotlin-linux/action.yml b/.github/workflows/kotlin/kotlin-linux/action.yml index 6d7bc029..aba54255 100644 --- a/.github/workflows/kotlin/kotlin-linux/action.yml +++ b/.github/workflows/kotlin/kotlin-linux/action.yml @@ -4,12 +4,12 @@ runs: using: composite steps: - name: Download Version File - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v6 with: distribution: 'temurin' java-version: '17' @@ -22,7 +22,7 @@ runs: echo "version=$version" >> $GITHUB_OUTPUT - name: Download Native Libs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: kotlin-* merge-multiple: true @@ -131,14 +131,16 @@ runs: - name: Upload Kotlin JVM package - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: kotlin-jvm path: ./wrappers/kotlin/lib/build - name: Upload Kotlin Android package - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: kotlin-android path: ./wrappers/kotlin-android/lib/build diff --git a/.github/workflows/kotlin/kotlin-macos/action.yml b/.github/workflows/kotlin/kotlin-macos/action.yml index 0d7288ff..cb66f1ae 100644 --- a/.github/workflows/kotlin/kotlin-macos/action.yml +++ b/.github/workflows/kotlin/kotlin-macos/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Version File - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt @@ -35,7 +35,8 @@ runs: ./gradlew generatePomFileForMavenPublication - name: Upload Kotlin package - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: kotlin-macos path: ./wrappers/kotlin/lib/src/main/resources/ diff --git a/.github/workflows/kotlin/kotlin-windows/action.yml b/.github/workflows/kotlin/kotlin-windows/action.yml index e5bc2a9a..3b134ba6 100644 --- a/.github/workflows/kotlin/kotlin-windows/action.yml +++ b/.github/workflows/kotlin/kotlin-windows/action.yml @@ -4,12 +4,12 @@ runs: using: composite steps: - name: Download Version File - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v6 with: distribution: 'temurin' java-version: '17' @@ -34,7 +34,8 @@ runs: - name: Upload Kotlin package - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: kotlin-windows path: ./wrappers/kotlin/lib/src/main/resources/ diff --git a/.github/workflows/native/native-build-ios/action.yml b/.github/workflows/native/native-build-ios/action.yml index 96256f4b..059da79b 100644 --- a/.github/workflows/native/native-build-ios/action.yml +++ b/.github/workflows/native/native-build-ios/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp @@ -115,7 +115,8 @@ runs: mkdir -p ./ios/ios cp -R ./tmp/ios/. ./ios/ios - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: native-ios path: ./wrappers/csharp/ios diff --git a/.github/workflows/native/native-build-linux/action.yml b/.github/workflows/native/native-build-linux/action.yml index 3c95e337..60bc0a6e 100644 --- a/.github/workflows/native/native-build-linux/action.yml +++ b/.github/workflows/native/native-build-linux/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp @@ -156,8 +156,9 @@ runs: dotnet build ./src/devolutions-crypto.csproj --framework netstandard2.0 -p:DefineConstants=BROWSER -p:Version="$VERSION_MANAGED" --configuration Release --output ./browser-wasm/bin - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: native-android-linux path: | wrappers/csharp/android/** diff --git a/.github/workflows/native/native-build-macos/action.yml b/.github/workflows/native/native-build-macos/action.yml index 38c3a864..d9a90708 100644 --- a/.github/workflows/native/native-build-macos/action.yml +++ b/.github/workflows/native/native-build-macos/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp @@ -68,7 +68,8 @@ runs: mv wrappers/csharp/macos-temp/* wrappers/csharp/macos/macos/ rmdir wrappers/csharp/macos-temp - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: native-macos path: wrappers/csharp/macos/ diff --git a/.github/workflows/native/native-build-windows/action.yml b/.github/workflows/native/native-build-windows/action.yml index ffa94dea..af1b6c2a 100644 --- a/.github/workflows/native/native-build-windows/action.yml +++ b/.github/workflows/native/native-build-windows/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp @@ -52,8 +52,9 @@ runs: dotnet build ./src/devolutions-crypto.csproj --framework netstandard2.0 -p:Version="$VERSION_MANAGED" --configuration Release --output ./dotnet-core/bin dotnet build ./src/devolutions-crypto.csproj --framework net48 -p:Version="$VERSION_MANAGED" --configuration Release --output ./dotnet-framework/bin - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: native-windows path: | wrappers/csharp/windows/** diff --git a/.github/workflows/nugets/nugets-windows/action.yml b/.github/workflows/nugets/nugets-windows/action.yml index d7c2e6ad..55db1904 100644 --- a/.github/workflows/nugets/nugets-windows/action.yml +++ b/.github/workflows/nugets/nugets-windows/action.yml @@ -4,17 +4,17 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.7" - name: Download Native Libs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: native-* merge-multiple: true @@ -43,7 +43,8 @@ runs: shell: bash run: mv wrappers/csharp/nuget/dotnet-core/package/*.nupkg . - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: nugets-windows path: ./*.nupkg diff --git a/.github/workflows/other/source-publish/action.yml b/.github/workflows/other/source-publish/action.yml index feaf460d..22e85b4d 100644 --- a/.github/workflows/other/source-publish/action.yml +++ b/.github/workflows/other/source-publish/action.yml @@ -3,8 +3,9 @@ description: This package the source code used for this build. runs: using: composite steps: - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: source path: | **/* diff --git a/.github/workflows/other/webassembly-build/action.yml b/.github/workflows/other/webassembly-build/action.yml index 5ffe7d0a..3c2e1303 100644 --- a/.github/workflows/other/webassembly-build/action.yml +++ b/.github/workflows/other/webassembly-build/action.yml @@ -9,7 +9,7 @@ runs: rustup update cargo install wasm-pack --locked - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: 24 @@ -32,7 +32,8 @@ runs: npm install npm test - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: wasm path: wrappers/wasm/dist/** diff --git a/.github/workflows/python/build/linux/action.yml b/.github/workflows/python/build/linux/action.yml index 7295d0dd..3379e340 100644 --- a/.github/workflows/python/build/linux/action.yml +++ b/.github/workflows/python/build/linux/action.yml @@ -2,7 +2,7 @@ name: Build Linux Python runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -44,7 +44,8 @@ runs: run: | mv target/wheels/*.whl . - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: python-wheels-linux path: ./*.whl diff --git a/.github/workflows/python/build/macos/action.yml b/.github/workflows/python/build/macos/action.yml index 2b759b9b..bd63512f 100644 --- a/.github/workflows/python/build/macos/action.yml +++ b/.github/workflows/python/build/macos/action.yml @@ -2,7 +2,7 @@ name: Build macOS Python runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.14" @@ -36,7 +36,8 @@ runs: run: | mv target/wheels/*.whl . - - uses: actions/upload-artifact@v4.3.6 + - uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: python-wheels-macos path: ./*.whl diff --git a/.github/workflows/python/build/windows/action.yml b/.github/workflows/python/build/windows/action.yml index 6be6cbd4..014007ba 100644 --- a/.github/workflows/python/build/windows/action.yml +++ b/.github/workflows/python/build/windows/action.yml @@ -2,7 +2,7 @@ name: Build Windows Python runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.14" architecture: "x64" @@ -35,7 +35,7 @@ runs: shell: bash run: mv target/wheels/*.whl . - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: python-wheels-windows path: ./*.whl diff --git a/.github/workflows/swift/action.yml b/.github/workflows/swift/action.yml index aa4be342..9e3d7f39 100644 --- a/.github/workflows/swift/action.yml +++ b/.github/workflows/swift/action.yml @@ -3,7 +3,7 @@ runs: using: composite steps: - name: Download Config - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: config.txt path: ./wrappers/csharp @@ -14,7 +14,8 @@ runs: run: sh generate.sh - name: Upload package - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: swift path: ./wrappers/swift/package diff --git a/.github/workflows/tests/csharp/android/action.yml b/.github/workflows/tests/csharp/android/action.yml index 675d6592..205278ba 100644 --- a/.github/workflows/tests/csharp/android/action.yml +++ b/.github/workflows/tests/csharp/android/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Nugets Libs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: nugets-* merge-multiple: true diff --git a/.github/workflows/tests/csharp/ios/action.yml b/.github/workflows/tests/csharp/ios/action.yml index 1b30e5c0..dc22270e 100644 --- a/.github/workflows/tests/csharp/ios/action.yml +++ b/.github/workflows/tests/csharp/ios/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Nugets Libs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: nugets-* merge-multiple: true diff --git a/.github/workflows/tests/csharp/linux/action.yml b/.github/workflows/tests/csharp/linux/action.yml index 171e61ef..495d1955 100644 --- a/.github/workflows/tests/csharp/linux/action.yml +++ b/.github/workflows/tests/csharp/linux/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Native Libs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: nugets-* merge-multiple: true diff --git a/.github/workflows/tests/csharp/macos/action.yml b/.github/workflows/tests/csharp/macos/action.yml index c91b9534..542d03d7 100644 --- a/.github/workflows/tests/csharp/macos/action.yml +++ b/.github/workflows/tests/csharp/macos/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Nugets Libs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: nugets-* merge-multiple: true diff --git a/.github/workflows/tests/csharp/windows/action.yml b/.github/workflows/tests/csharp/windows/action.yml index 17870d05..d82f290a 100644 --- a/.github/workflows/tests/csharp/windows/action.yml +++ b/.github/workflows/tests/csharp/windows/action.yml @@ -4,7 +4,7 @@ runs: using: composite steps: - name: Download Nugets - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: nugets-* merge-multiple: true diff --git a/.github/workflows/tests/fuzz/quick/action.yml b/.github/workflows/tests/fuzz/quick/action.yml index d54f4253..509fcbe1 100644 --- a/.github/workflows/tests/fuzz/quick/action.yml +++ b/.github/workflows/tests/fuzz/quick/action.yml @@ -62,8 +62,9 @@ runs: - name: Upload crash artifacts if: failure() - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v7 with: + include-hidden-files: true name: fuzz-crash-artifacts path: fuzz/artifacts/ if-no-files-found: ignore diff --git a/.github/workflows/tests/python/linux/action.yml b/.github/workflows/tests/python/linux/action.yml index 6e691082..58fc867e 100644 --- a/.github/workflows/tests/python/linux/action.yml +++ b/.github/workflows/tests/python/linux/action.yml @@ -2,12 +2,12 @@ name: Test Python Wheels for Linux runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" - name: Download Python Wheels - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: python-wheels-linux path: ./wheels diff --git a/.github/workflows/tests/python/macos/action.yml b/.github/workflows/tests/python/macos/action.yml index 39373192..ec836773 100644 --- a/.github/workflows/tests/python/macos/action.yml +++ b/.github/workflows/tests/python/macos/action.yml @@ -2,12 +2,12 @@ name: Test Python Wheels for macOS runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" - name: Download Python Wheels - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: python-wheels-macos path: ./wheels diff --git a/.github/workflows/tests/python/windows/action.yml b/.github/workflows/tests/python/windows/action.yml index 3228032d..3b382240 100644 --- a/.github/workflows/tests/python/windows/action.yml +++ b/.github/workflows/tests/python/windows/action.yml @@ -2,12 +2,12 @@ name: Test Python Wheels for Windows runs: using: composite steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" - name: Download Python Wheels - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: python-wheels-windows path: ./wheels From dafc27a03a4c68efdf979f565231d8e3c47e8d3a Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Mon, 19 Oct 2026 07:58:58 -0400 Subject: [PATCH 4/8] chore: fix cargo manifest warnings Drop package.homepage, which duplicates package.repository, and add description/license/repository to the python and uniffi crates that maturin packages. --- Cargo.toml | 1 - python/Cargo.toml | 3 +++ uniffi/devolutions-crypto-uniffi/Cargo.toml | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 07bb3848..e4fa95df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ authors = ["Mathieu Morrissette ", "Sebastien Duqu edition = "2024" readme = "README_RUST.md" license = "MIT OR Apache-2.0" -homepage = "https://github.com/Devolutions/devolutions-crypto" repository = "https://github.com/Devolutions/devolutions-crypto" description = "An abstraction layer for the cryptography used by Devolutions" resolver = "2" diff --git a/python/Cargo.toml b/python/Cargo.toml index a94c026d..a75af861 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -2,6 +2,9 @@ name = "devolutions-crypto-python" version.workspace = true edition = "2024" +description = "Python bindings for devolutions-crypto" +license = "MIT OR Apache-2.0" +repository = "https://github.com/Devolutions/devolutions-crypto" [[bin]] name = "uniffi-bindgen" diff --git a/uniffi/devolutions-crypto-uniffi/Cargo.toml b/uniffi/devolutions-crypto-uniffi/Cargo.toml index 513e7f0f..21ffab96 100644 --- a/uniffi/devolutions-crypto-uniffi/Cargo.toml +++ b/uniffi/devolutions-crypto-uniffi/Cargo.toml @@ -1,6 +1,9 @@ [package] name = "devolutions-crypto-uniffi" edition = "2024" +description = "UniFFI bindings for devolutions-crypto" +license = "MIT OR Apache-2.0" +repository = "https://github.com/Devolutions/devolutions-crypto" version.workspace = true [lib] From a5c771a963c9476f14425d35b424603f12918bd3 Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Mon, 19 Oct 2026 08:01:09 -0400 Subject: [PATCH 5/8] chore(deps): bump yanked chacha20 0.10.1 -> 0.10.2 cargo-deny flagged chacha20 0.10.1 as yanked. --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abe1db56..e389f123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,9 +360,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", "cipher", From 0ddf9199e3f74831350316a8241cda1fb8d81dac Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Mon, 19 Oct 2026 08:05:30 -0400 Subject: [PATCH 6/8] test(csharp): fix nullable warnings in nuget test projects Enable nullable reference types in the dotnet-core test project (the shared test files are annotated) and align MSTest with the local test project (3.7.1). MSTest 3.0.0 lacks [NotNull] on Assert.IsNotNull, which caused the CS8602/CS8604 warnings on android. Mark the iOS AppDelegate launchOptions parameter nullable to match the base member. --- .../csharp/tests/unit-tests/nugets/android/android.csproj | 2 +- .../tests/unit-tests/nugets/dotnet-core/dotnet-core.csproj | 7 ++++--- wrappers/csharp/tests/unit-tests/nugets/iOS/AppDelegate.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj b/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj index c9fde318..0516aaa1 100644 --- a/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj +++ b/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj @@ -28,6 +28,6 @@ - + \ No newline at end of file diff --git a/wrappers/csharp/tests/unit-tests/nugets/dotnet-core/dotnet-core.csproj b/wrappers/csharp/tests/unit-tests/nugets/dotnet-core/dotnet-core.csproj index 3d0ead7b..dec7e990 100644 --- a/wrappers/csharp/tests/unit-tests/nugets/dotnet-core/dotnet-core.csproj +++ b/wrappers/csharp/tests/unit-tests/nugets/dotnet-core/dotnet-core.csproj @@ -3,6 +3,7 @@ net8.0 dotnet_core + enable false @@ -24,9 +25,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/wrappers/csharp/tests/unit-tests/nugets/iOS/AppDelegate.cs b/wrappers/csharp/tests/unit-tests/nugets/iOS/AppDelegate.cs index b33ca117..45244175 100644 --- a/wrappers/csharp/tests/unit-tests/nugets/iOS/AppDelegate.cs +++ b/wrappers/csharp/tests/unit-tests/nugets/iOS/AppDelegate.cs @@ -15,7 +15,7 @@ public override UIWindow? Window set; } - public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) + public override bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions) { // create a new window instance based on the screen size Window = new UIWindow(UIScreen.MainScreen.Bounds); From 6743c05aeca97d2e5fcff59f27cc32bd995af2cb Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Mon, 19 Oct 2026 08:05:32 -0400 Subject: [PATCH 7/8] test(csharp): move android nuget tests to net10.0-android net9.0-android is out of support (NETSDK1202). CI already uses the .NET 10 SDK. --- wrappers/csharp/tests/unit-tests/nugets/android/android.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj b/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj index 0516aaa1..c80183a2 100644 --- a/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj +++ b/wrappers/csharp/tests/unit-tests/nugets/android/android.csproj @@ -1,6 +1,6 @@ - net9.0-android + net10.0-android 21 Exe enable From 0c48cd117e691609f9483344e2fb0069b4877a75 Mon Sep 17 00:00:00 2001 From: mmorrissette-devolutions Date: Mon, 19 Oct 2026 08:08:59 -0400 Subject: [PATCH 8/8] build(kotlin-android): migrate off deprecated Gradle DSL Replace kotlinOptions with compilerOptions and move the library targetSdk to testOptions/lint, as the library-level targetSdk is deprecated and removed in AGP 9. --- wrappers/kotlin-android/lib/build.gradle.kts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wrappers/kotlin-android/lib/build.gradle.kts b/wrappers/kotlin-android/lib/build.gradle.kts index 00dc57fa..b5b1dd47 100644 --- a/wrappers/kotlin-android/lib/build.gradle.kts +++ b/wrappers/kotlin-android/lib/build.gradle.kts @@ -25,8 +25,8 @@ java { } tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "17" + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } @@ -46,11 +46,15 @@ android { defaultConfig { minSdk = 21 + } + + testOptions { targetSdk = 34 } lint { abortOnError = false + targetSdk = 34 } }