diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 72f5418bfd..fb5654f5c0 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -39,8 +39,10 @@ jobs: - '.github/workflows/build_and_test.yml' - 'bin/**' - 'rust/*' - - 'rust/!(bridge)/**' + - 'rust/!(bridge|protocol)/**' - 'rust/bridge/shared/**' + - 'rust/protocol/*' + - 'rust/protocol/!(cross-version-testing)/**' - 'rust-toolchain' - 'Cargo.toml' - 'Cargo.lock' @@ -49,7 +51,7 @@ jobs: - '.clippy.toml' - '.rustfmt.license-template' - '.rustfmt.toml' - - 'rust/**' # deliberately re-include the rust/bridge/ directories + - 'rust/**' # deliberately re-include rust/bridge/* and rust/protocol/cross-version-testing java: - *all - '.dockerignore' @@ -119,6 +121,11 @@ jobs: run: cargo fmt --all -- --check if: matrix.version == 'nightly' + - name: Rustfmt check for cross-version-testing + run: cargo fmt --all -- --check + working-directory: rust/protocol/cross-version-testing + if: matrix.version == 'nightly' + - name: Check bridge versioning run: ./bin/update_versions.py if: matrix.version == 'nightly' @@ -149,6 +156,11 @@ jobs: run: cargo clippy --workspace --all-targets --all-features -- -D warnings if: matrix.version == 'nightly' + - name: Rust docs + run: cargo doc --workspace --all-features + env: + RUSTFLAGS: -D warnings + # We check the fuzz targets on stable because they don't have lockfiles, # and crates don't generally support arbitrary nightly versions. # See https://github.com/dtolnay/proc-macro2/issues/307 for an example. @@ -306,5 +318,3 @@ jobs: - name: Run pod lint # No import validation because it tries to build unsupported platforms (like 32-bit iOS). run: pod lib lint --verbose --platforms=ios --include-podspecs=SignalCoreKit/SignalCoreKit.podspec --skip-import-validation - env: - SKIP_CATALYST: 1 diff --git a/.github/workflows/ios_artifacts.yml b/.github/workflows/ios_artifacts.yml index ae0c9331ca..1f0e8d7a0f 100644 --- a/.github/workflows/ios_artifacts.yml +++ b/.github/workflows/ios_artifacts.yml @@ -43,16 +43,6 @@ jobs: env: CARGO_BUILD_TARGET: aarch64-apple-ios-sim - - name: Build for x86_64-apple-ios-macabi - run: swift/build_ffi.sh --release --build-std - env: - CARGO_BUILD_TARGET: x86_64-apple-ios-macabi - - - name: Build for aarch64-apple-ios-macabi - run: swift/build_ffi.sh --release --build-std - env: - CARGO_BUILD_TARGET: aarch64-apple-ios-macabi - - run: tar -c --auto-compress --no-mac-metadata -f ${{ steps.archive-name.outputs.name }} target/*/release/libsignal_ffi.a - run: 'shasum -a 256 ${{ steps.archive-name.outputs.name }} | tee -a $GITHUB_STEP_SUMMARY ${{ steps.archive-name.outputs.name }}.sha256' diff --git a/.github/workflows/jni_artifacts.yml b/.github/workflows/jni_artifacts.yml index 13b61dc824..562a7f5a8a 100644 --- a/.github/workflows/jni_artifacts.yml +++ b/.github/workflows/jni_artifacts.yml @@ -1,20 +1,21 @@ -name: Publish JNI Artifacts to GitHub Release +name: Upload Java libraries to Sonatype +run-name: ${{ github.workflow }} (${{ github.ref_name }}) on: - push: - tags: - - v* - workflow_dispatch: {} # no parameters + workflow_dispatch: + inputs: + dry_run: + description: "Just build, don't publish" + default: false + required: false + type: boolean env: CARGO_TERM_COLOR: always jobs: build: - name: Build - - # Only run this in the public repository unless manually invoked. - if: ${{ github.event_name == 'workflow_dispatch' || !endsWith(github.repository, '-private') }} + name: Build for local development runs-on: ${{ matrix.os }} @@ -24,21 +25,14 @@ jobs: strategy: matrix: - os: [ubuntu-20.04, windows-latest, macos-latest] + os: [windows-latest, macos-latest] include: - - os: ubuntu-20.04 - library: libsignal_jni.so - os: windows-latest library: signal_jni.dll - os: macos-latest library: libsignal_jni.dylib additional-rust-target: aarch64-apple-darwin - - env: - # Keep this settings in sync with java/build_jni.sh, which supports Android as well. - CARGO_PROFILE_RELEASE_DEBUG: 1 - CARGO_PROFILE_RELEASE_LTO: thin - CARGO_PROFILE_RELEASE_OPT_LEVEL: s + # Ubuntu binaries are built using Docker, below steps: - uses: actions/checkout@v3 @@ -54,26 +48,97 @@ jobs: run: choco install nasm shell: cmd + - name: Build for host (should be x86_64) + run: java/build_jni.sh desktop + shell: bash + - name: Install Protoc uses: arduino/setup-protoc@v1 with: version: '3.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - - run: cargo build --release -p libsignal-jni - - - run: cargo build --release -p libsignal-jni --target aarch64-apple-darwin + - name: Build for alternate target (arm64) + run: java/build_jni.sh desktop if: matrix.os == 'macos-latest' + env: + CARGO_BUILD_TARGET: ${{ matrix.additional-rust-target }} - name: Merge library slices (for macOS) # Using target/release/ for both the input and output wouldn't normally be ideal # from a build system perspective, but we're going to immediately upload the merged library. - run: lipo -create target/release/${{ matrix.library }} target/aarch64-apple-darwin/release/${{ matrix.library }} -output target/release/${{ matrix.library }} + run: lipo -create target/release/${{ matrix.library }} target/${{ matrix.additional-rust-target }}/release/${{ matrix.library }} -output target/release/${{ matrix.library }} if: matrix.os == 'macos-latest' - - name: Upload - uses: ncipollo/release-action@4c75f0f2e4ae5f3c807cf0904605408e319dcaac # v1.11.1 + - name: Upload library + uses: actions/upload-artifact@v3 + with: + name: libsignal_jni (${{matrix.os}}) + path: target/release/${{ matrix.library }} + + verify-rust: + name: Verify JNI bindings + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - run: rustup toolchain install $(cat rust-toolchain) --profile minimal + + - name: Verify that the JNI bindings are up to date + run: rust/bridge/jni/bin/gen_java_decl.py --verify + + publish: + name: Build for production and publish + + runs-on: ubuntu-latest + + needs: [build, verify-rust] + + steps: + - uses: actions/checkout@v3 + + - name: Download built libraries + id: download + uses: actions/download-artifact@v3 + with: + path: artifacts + + - name: Copy libraries + run: mv ${{ steps.download.outputs.download-path }}/*/* java/shared/resources && find java/shared/resources + + - run: make + if: ${{ inputs.dry_run }} + working-directory: java + + - name: Upload libsignal-android + if: ${{ inputs.dry_run }} + uses: actions/upload-artifact@v3 + with: + name: libsignal-android + path: java/android/build/outputs/aar/libsignal-android-release.aar + + - name: Upload libsignal-client + if: ${{ inputs.dry_run }} + uses: actions/upload-artifact@v3 + with: + name: libsignal-client + path: java/client/build/libs/libsignal-client-*.jar + + - name: Upload libsignal-server + if: ${{ inputs.dry_run }} + uses: actions/upload-artifact@v3 with: - allowUpdates: true - artifactErrorsFailBuild: true - artifacts: target/release/${{ matrix.library }} + name: libsignal-server + path: java/server/build/libs/libsignal-server-*.jar + + - run: make publish_java + if: ${{ !inputs.dry_run }} + working-directory: java + env: + ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USER }} + ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEYID }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} + # ASCII-armored PGP secret key + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} diff --git a/.github/workflows/slow_tests.yml b/.github/workflows/slow_tests.yml index 08dede9dbd..723264a6c7 100644 --- a/.github/workflows/slow_tests.yml +++ b/.github/workflows/slow_tests.yml @@ -136,7 +136,7 @@ jobs: working-directory: node swift-cocoapod: - name: Swift CocoaPod (with Catalyst) + name: Swift CocoaPod (all architectures) runs-on: macOS-latest needs: [check-up-to-date] if: ${{ always() && (needs.check-up-to-date.outputs.has-changes || github.event_name != 'schedule') }} @@ -167,24 +167,38 @@ jobs: env: CARGO_BUILD_TARGET: aarch64-apple-ios-sim - - name: Build for x86_64-apple-ios-macabi - run: swift/build_ffi.sh --release --build-std - env: - CARGO_BUILD_TARGET: x86_64-apple-ios-macabi - - - name: Build for aarch64-apple-ios-macabi - run: swift/build_ffi.sh --release --build-std - env: - CARGO_BUILD_TARGET: aarch64-apple-ios-macabi - - name: Run pod lint # No import validation because it tries to build unsupported platforms (like 32-bit iOS). run: pod lib lint --verbose --platforms=ios --include-podspecs=SignalCoreKit/SignalCoreKit.podspec --skip-import-validation + rust-protocol-cross-version-testing: + name: libsignal-protocol Cross-version Tests + runs-on: ubuntu-latest + needs: [check-up-to-date] + if: ${{ always() && (needs.check-up-to-date.outputs.has-changes || github.event_name != 'schedule') }} + + steps: + - uses: actions/checkout@v3 + + - run: sudo apt-get update && sudo apt-get install gcc-multilib g++-multilib + + - run: rustup +stable target add i686-unknown-linux-gnu + + - name: Run tests + run: cargo +stable test + working-directory: rust/protocol/cross-version-testing + + - name: Run tests (32-bit) + run: cargo +stable test --target i686-unknown-linux-gnu + working-directory: rust/protocol/cross-version-testing + + # We don't run Clippy because GitHub silently updates `stable` and that can introduce new lints, + # and we don't have a guarantee that any particular pinned nightly can build older libsignals. + report_failures: name: Report Failures runs-on: ubuntu-latest - needs: [java-docker, android-emulator-tests, node-docker, node-windows-arm64, swift-cocoapod] + needs: [java-docker, android-emulator-tests, node-docker, node-windows-arm64, swift-cocoapod, rust-protocol-cross-version-testing] if: ${{ failure() && github.event_name == 'schedule' }} permissions: diff --git a/Cargo.lock b/Cargo.lock index c72ec98d2a..56ba2922ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aes" version = "0.7.5" @@ -33,37 +43,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", - "ctr 0.8.0", "opaque-debug", ] +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", + "zeroize", +] + [[package]] name = "aes-gcm" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ - "aead", - "aes", - "cipher", + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", "ctr 0.7.0", - "ghash", + "ghash 0.4.4", "subtle", ] [[package]] name = "aes-gcm-siv" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfde8146762f3c5f3c5cd41aa17a71f3188df09d5857192b658510d850e16068" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ - "aead", - "aes", - "cipher", - "ctr 0.7.0", - "polyval", + "aead 0.5.2", + "aes 0.8.3", + "cipher 0.4.4", + "ctr 0.9.2", + "polyval 0.6.1", "subtle", "zeroize", ] @@ -120,6 +141,7 @@ dependencies = [ "blake2", "cpufeatures", "password-hash", + "zeroize", ] [[package]] @@ -189,8 +211,9 @@ dependencies = [ "bitflags 2.3.3", "boring", "boring-sys", - "chacha20poly1305", + "chacha20poly1305 0.10.1", "chrono", + "ciborium", "displaydoc", "hex", "hex-literal", @@ -206,6 +229,7 @@ dependencies = [ "sha2", "snow", "static_assertions", + "subtle", "uuid", "variant_count", "x25519-dalek", @@ -219,9 +243,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", @@ -279,9 +303,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -364,21 +388,14 @@ dependencies = [ ] [[package]] -name = "block-modes" -version = "0.8.1" +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "block-padding", - "cipher", + "generic-array", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "boring" version = "2.1.0" @@ -430,11 +447,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cc" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", @@ -468,21 +494,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + [[package]] name = "chacha20poly1305" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", + "aead 0.4.3", + "chacha20 0.8.2", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", "zeroize", ] @@ -538,6 +588,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -700,6 +761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -709,16 +771,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -1007,9 +1069,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1022,9 +1084,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -1032,15 +1094,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -1049,15 +1111,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -1066,21 +1128,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -1122,7 +1184,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", - "polyval", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval 0.6.1", + "zeroize", ] [[package]] @@ -1139,9 +1212,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.18" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -1149,7 +1222,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.0.0", "slab", "tokio", "tokio-util", @@ -1364,6 +1437,16 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -1392,16 +1475,18 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -1442,9 +1527,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -1505,7 +1590,6 @@ dependencies = [ "signal-pin", "signal-quic", "static_assertions", - "typenum", "usernames", "uuid", "zkgroup", @@ -1525,7 +1609,7 @@ dependencies = [ [[package]] name = "libsignal-ffi" -version = "0.31.0" +version = "0.32.1" dependencies = [ "async-trait", "attest", @@ -1548,7 +1632,7 @@ dependencies = [ [[package]] name = "libsignal-jni" -version = "0.31.0" +version = "0.32.1" dependencies = [ "async-trait", "cfg-if", @@ -1564,7 +1648,7 @@ dependencies = [ [[package]] name = "libsignal-node" -version = "0.31.0" +version = "0.32.1" dependencies = [ "async-trait", "cmake", @@ -1581,17 +1665,16 @@ dependencies = [ name = "libsignal-protocol" version = "0.1.0" dependencies = [ - "aes", + "aes 0.8.3", "aes-gcm-siv", "arrayref", "async-trait", - "block-modes", "criterion", + "ctr 0.9.2", "curve25519-dalek", "displaydoc", "env_logger", "futures-util", - "generic-array", "hex", "hex-literal", "hkdf", @@ -1607,9 +1690,9 @@ dependencies = [ "rand", "sha2", "signal-crypto", + "static_assertions", "subtle", "thiserror", - "typenum", "uuid", "x25519-dalek", ] @@ -1664,9 +1747,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" @@ -1706,14 +1789,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1911,22 +1994,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -1994,7 +2077,18 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.4.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash 0.5.1", ] [[package]] @@ -2006,7 +2100,19 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.4.0", +] + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash 0.5.1", ] [[package]] @@ -2214,7 +2320,7 @@ dependencies = [ "libm", "log", "octets", - "ring", + "ring 0.16.20", "slab", "smallvec", "winapi", @@ -2349,12 +2455,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2396,7 +2516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki", "sct", ] @@ -2416,8 +2536,8 @@ version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -2461,12 +2581,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -2522,10 +2642,10 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.10.1" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", @@ -2567,18 +2687,19 @@ dependencies = [ name = "signal-crypto" version = "0.1.0" dependencies = [ - "aes", - "block-modes", + "aes 0.8.3", + "cbc", "criterion", + "ctr 0.9.2", "displaydoc", - "generic-array", - "ghash", + "ghash 0.5.0", "hex", + "hex-literal", "hmac", "rand", "serde", "serde_json", - "sha-1", + "sha1", "sha2", "subtle", "thiserror", @@ -2649,7 +2770,7 @@ dependencies = [ "futures", "mio", "quiche", - "ring", + "ring 0.16.20", "thiserror", "tokio", "tokio-stream", @@ -2676,12 +2797,13 @@ dependencies = [ [[package]] name = "snow" -version = "0.9.0" -source = "git+https://github.com/mcginty/snow.git?rev=586292364a30ecc74c785228b41e60b3ef03e773#586292364a30ecc74c785228b41e60b3ef03e773" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" dependencies = [ "aes-gcm", "blake2", - "chacha20poly1305", + "chacha20poly1305 0.9.1", "curve25519-dalek", "rand_core", "rustc_version", @@ -2705,6 +2827,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2881,9 +3009,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", @@ -3010,11 +3138,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3022,9 +3149,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", @@ -3033,9 +3160,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -3095,12 +3222,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "unzip3" version = "1.0.0" @@ -3182,11 +3325,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -3469,8 +3611,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" -source = "git+https://github.com/signalapp/curve25519-dalek?tag=signal-curve25519-4.0.0#463e5c7cba32561ffee8a281c4455ff3c25660d4" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core", @@ -3506,6 +3649,7 @@ dependencies = [ "curve25519-dalek", "displaydoc", "hex", + "hex-literal", "lazy_static", "poksho", "serde", @@ -3515,7 +3659,6 @@ dependencies = [ name = "zkgroup" version = "0.9.0" dependencies = [ - "aead", "aes-gcm-siv", "base64", "bincode", @@ -3523,6 +3666,7 @@ dependencies = [ "curve25519-dalek", "displaydoc", "hex", + "hex-literal", "lazy_static", "libsignal-protocol", "poksho", diff --git a/Cargo.toml b/Cargo.toml index b8aa987f95..3d8420e127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,7 @@ resolver = "2" # so that our dev-dependency features don't leak into products [patch.crates-io] # Use our fork of curve25519-dalek for zkgroup support. curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.0.0' } -x25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.0.0' } boring = { git = 'https://github.com/signalapp/boring', branch = 'libsignal' } -# This revision of snow is where curve25519-dalek v4.0.0 changes were merged in -snow = { git = 'https://github.com/mcginty/snow.git', rev = '586292364a30ecc74c785228b41e60b3ef03e773' } [profile.dev.package.argon2] opt-level = 2 # signal-signal-pin unit tests are too slow with an unoptimized argon2 diff --git a/LibSignalClient.podspec b/LibSignalClient.podspec index 051c6cec05..2db163c295 100644 --- a/LibSignalClient.podspec +++ b/LibSignalClient.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = 'LibSignalClient' - s.version = '0.31.0' + s.version = '0.32.1' s.summary = 'A Swift wrapper library for communicating with the Signal messaging service.' s.homepage = 'https://github.com/signalapp/libsignal' diff --git a/RELEASE.md b/RELEASE.md index 044190821c..ceae431f73 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -60,13 +60,9 @@ git push origin libsignal-v0.x.y ## 6. Submit to package repositories as needed -### Android: Sonatype +### Android and Server: Sonatype -1. Wait for the "Publish JNI Artifacts to GitHub Release" action to complete. These artifacts, though not built reproducibly, will be included in the `libsignal-client` and `libsignal-server` jars to support running on macOS and Windows as well. -2. Set the environment variables `SONATYPE_USERNAME`, `SONATYPE_PASSWORD`, `KEYRING_FILE`, `SIGNING_KEY`, and `SIGNING_KEY_PASSSWORD`. -3. Run `make -C java publish_java` to build through Docker. - -Note that Sonatype is pretty slow; even after the build completes it might take a while for it to show up. +In the signalapp/libsignal repository on GitHub, run the "Upload Java libraries to Sonatype" action on the tag you just made. Once complete, go into Sonatype and release the builds. ### Node: NPM diff --git a/bin/build_helpers.sh b/bin/build_helpers.sh index 3600197950..a236578a70 100644 --- a/bin/build_helpers.sh +++ b/bin/build_helpers.sh @@ -26,7 +26,6 @@ check_rust() { fi if [[ -n "${CARGO_BUILD_TARGET:-}" ]] && ! (rustup target list --installed | grep -q "${CARGO_BUILD_TARGET:-}"); then - # TODO: We could remove this once Catalyst support is promoted to tier 2 if [[ -n "${BUILD_STD:-}" ]]; then echo "warning: Building using -Zbuild-std to support tier 3 target ${CARGO_BUILD_TARGET}." >&2 else diff --git a/bin/update_versions.py b/bin/update_versions.py index fb33f9e126..9bddb70d19 100755 --- a/bin/update_versions.py +++ b/bin/update_versions.py @@ -29,7 +29,7 @@ def update_version(file, pattern, new_version): PODSPEC_PATTERN = re.compile(r"^(.*\.version\s+=\s+')(.*)(')") -GRADLE_PATTERN = re.compile(r'^(def\s+version_number\s+=\s+")(.*)(")') +GRADLE_PATTERN = re.compile(r'^(\s+version\s+=\s+")(.*)(")') NODE_PATTERN = re.compile(r'^(\s+"version": ")(.*)(")') CARGO_PATTERN = re.compile(r'^(version = ")(.*)(")') diff --git a/bin/verify_duplicate_crates b/bin/verify_duplicate_crates index 81952f58e5..a73eb73e9f 100755 --- a/bin/verify_duplicate_crates +++ b/bin/verify_duplicate_crates @@ -5,28 +5,21 @@ # SPDX-License-Identifier: AGPL-3.0-only # +# To keep code size down, we try to avoid depending on multiple versions of crates. +# # For now, these dependencies have conflicting requirements. # You can use the `cargo tree` command below to see where they come from, # and then document them here. # # bitflags: -# either a dev or a build time dependency. -# syn: -# syn is used in proc-macros only, so it isn't going to cost us in code size. +# mostly provides a macro, with only a small bit of code that actually +# makes it into the final build. EXPECTED="\ bitflags v1.3.2 -bitflags v2.3.3 - -ctr v0.7.0 - -ctr v0.8.0 - -syn v1.0.109 - -syn v2.0.28" +bitflags v2.3.3" -if [[ "$(cargo tree -d -e normal --workspace --locked --depth 0)" != "${EXPECTED}" ]]; then - cargo tree -d -e normal --workspace --locked +if [[ "$(cargo tree --duplicates --edges normal,no-proc-macro --workspace --locked --depth 0)" != "${EXPECTED}" ]]; then + cargo tree --duplicates --edges normal,no-proc-macro --workspace --locked exit 1 fi diff --git a/java/Makefile b/java/Makefile index 9d0e623b3c..e589a874f5 100644 --- a/java/Makefile +++ b/java/Makefile @@ -26,28 +26,22 @@ java_test: java_build -v `cd .. && pwd`/:/home/libsignal/src $(DOCKER_EXTRA) $(DOCKER_IMAGE) \ sh -c "cd src/java; ./gradlew test" -SONATYPE_USERNAME ?= -SONATYPE_PASSWORD ?= -KEYRING_FILE ?= -SIGNING_KEY ?= -SIGNING_KEY_PASSSWORD ?= - publish_java: DOCKER_EXTRA = $(shell [ -L build ] && P=$$(readlink build) && echo -v $$P/:$$P ) -publish_java: KEYRING_VOLUME := $(dir $(KEYRING_FILE)) -publish_java: KEYRING_FILE_ROOT := $(notdir $(KEYRING_FILE)) publish_java: docker_image - @[ -n "$(SONATYPE_USERNAME)" ] || ( echo "SONATYPE_USERNAME is not set" && false ) - @[ -n "$(SONATYPE_PASSWORD)" ] || ( echo "SONATYPE_PASSWORD is not set" && false ) - @[ -n "$(KEYRING_FILE)" ] || ( echo "KEYRING_FILE is not set" && false ) - @[ -n "$(SIGNING_KEY)" ] || ( echo "SIGNING_KEY is not set" && false ) - @[ -n "$(SIGNING_KEY_PASSWORD)" ] || ( echo "SIGNING_KEY_PASSWORD is not set" && false ) $(DOCKER) run --rm --user $$(id -u):$$(id -g) \ -v `cd .. && pwd`/:/home/libsignal/src $(DOCKER_EXTRA) \ - -v $(KEYRING_VOLUME):/home/libsignal/keyring \ + -e ORG_GRADLE_PROJECT_sonatypeUsername \ + -e ORG_GRADLE_PROJECT_sonatypePassword \ + -e ORG_GRADLE_PROJECT_signingKeyId \ + -e ORG_GRADLE_PROJECT_signingPassword \ + -e ORG_GRADLE_PROJECT_signingKey \ $(DOCKER_IMAGE) \ - sh -c "cd src/java; ./gradlew clean publish \ - -PsonatypeUsername='$(SONATYPE_USERNAME)' \ - -PsonatypePassword='$(SONATYPE_PASSWORD)' \ - -Psigning.secretKeyRingFile='/home/libsignal/keyring/$(KEYRING_FILE_ROOT)' \ - -Psigning.keyId='$(SIGNING_KEY)' \ - -Psigning.password='$(SIGNING_KEY_PASSWORD)'" + sh -c "cd src/java; ./gradlew publish closeSonatypeStagingRepository" + +# We could run these through Docker, but they would have the same result anyway. + +clean: + ./gradlew clean + +format_java: + ./gradlew spotlessApply diff --git a/java/android/benchmarks/src/androidTest/java/ECCBenchmark.java b/java/android/benchmarks/src/androidTest/java/ECCBenchmark.java index 713263876a..750b47df5f 100644 --- a/java/android/benchmarks/src/androidTest/java/ECCBenchmark.java +++ b/java/android/benchmarks/src/androidTest/java/ECCBenchmark.java @@ -1,10 +1,7 @@ -/** - * Copyright (C) 2022 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -import org.signal.libsignal.protocol.ecc.*; +// +// Copyright 2022 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// import androidx.benchmark.BenchmarkState; import androidx.benchmark.junit4.BenchmarkRule; @@ -12,32 +9,32 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.signal.libsignal.protocol.ecc.*; @RunWith(AndroidJUnit4.class) public class ECCBenchmark { - @Rule - public final BenchmarkRule benchmarkRule = new BenchmarkRule(); + @Rule public final BenchmarkRule benchmarkRule = new BenchmarkRule(); - private final ECKeyPair alicePair = Curve.generateKeyPair(); - private final ECKeyPair bobPair = Curve.generateKeyPair(); - private final byte[] arbitraryData = new byte[] { 0x53, 0x69, 0x67, 0x6E, 0x61, 0x6C }; + private final ECKeyPair alicePair = Curve.generateKeyPair(); + private final ECKeyPair bobPair = Curve.generateKeyPair(); + private final byte[] arbitraryData = new byte[] {0x53, 0x69, 0x67, 0x6E, 0x61, 0x6C}; - @Test - public void benchmarkKeyAgreement() { - final BenchmarkState state = benchmarkRule.getState(); + @Test + public void benchmarkKeyAgreement() { + final BenchmarkState state = benchmarkRule.getState(); - while (state.keepRunning()) { - alicePair.getPrivateKey().calculateAgreement(bobPair.getPublicKey()); - } + while (state.keepRunning()) { + alicePair.getPrivateKey().calculateAgreement(bobPair.getPublicKey()); } + } - @Test - public void benchmarkSignature() { - final BenchmarkState state = benchmarkRule.getState(); + @Test + public void benchmarkSignature() { + final BenchmarkState state = benchmarkRule.getState(); - while (state.keepRunning()) { - final byte[] signature = alicePair.getPrivateKey().calculateSignature(arbitraryData); - alicePair.getPublicKey().verifySignature(arbitraryData, signature); - } + while (state.keepRunning()) { + final byte[] signature = alicePair.getPrivateKey().calculateSignature(arbitraryData); + alicePair.getPublicKey().verifySignature(arbitraryData, signature); } + } } diff --git a/java/android/build.gradle b/java/android/build.gradle index ea231ad4c7..64b298f853 100644 --- a/java/android/build.gradle +++ b/java/android/build.gradle @@ -5,8 +5,6 @@ plugins { } archivesBaseName = "libsignal-android" -version = version_number -group = group_info repositories { google() @@ -106,17 +104,9 @@ afterEvaluate { } } } - repositories { - maven { - url = getReleaseRepositoryUrl() - credentials { - username = getRepositoryUsername() - password = getRepositoryPassword() - } - } - } } + setUpSigningKey(signing) signing { required { isReleaseBuild() && gradle.taskGraph.hasTask(":android:publish") } sign publishing.publications.mavenJava diff --git a/java/android/src/main/java/org/signal/libsignal/protocol/logging/AndroidSignalProtocolLogger.java b/java/android/src/main/java/org/signal/libsignal/protocol/logging/AndroidSignalProtocolLogger.java index a9bf8d4e65..2e6d0d4ea4 100644 --- a/java/android/src/main/java/org/signal/libsignal/protocol/logging/AndroidSignalProtocolLogger.java +++ b/java/android/src/main/java/org/signal/libsignal/protocol/logging/AndroidSignalProtocolLogger.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.logging; import android.util.Log; @@ -10,14 +10,16 @@ public class AndroidSignalProtocolLogger implements SignalProtocolLogger { - private static final SparseIntArray PRIORITY_MAP = new SparseIntArray(5) {{ - put(SignalProtocolLogger.INFO, Log.INFO); - put(SignalProtocolLogger.ASSERT, Log.ASSERT); - put(SignalProtocolLogger.DEBUG, Log.DEBUG); - put(SignalProtocolLogger.VERBOSE, Log.VERBOSE); - put(SignalProtocolLogger.WARN, Log.WARN); - - }}; + private static final SparseIntArray PRIORITY_MAP = + new SparseIntArray(5) { + { + put(SignalProtocolLogger.INFO, Log.INFO); + put(SignalProtocolLogger.ASSERT, Log.ASSERT); + put(SignalProtocolLogger.DEBUG, Log.DEBUG); + put(SignalProtocolLogger.VERBOSE, Log.VERBOSE); + put(SignalProtocolLogger.WARN, Log.WARN); + } + }; @Override public void log(int priority, String tag, String message) { diff --git a/java/build.gradle b/java/build.gradle index 2e0f381b4e..521fde39f4 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -1,13 +1,17 @@ plugins { + id "base" + id "signing" id "de.undercouch.download" version "5.0.2" + id "com.diffplug.spotless" version "6.20.0" + id "io.github.gradle-nexus.publish-plugin" version "1.3.0" } -def version_number = "0.31.0-gluon-2" +allprojects { + version = "0.32.1-gluon-2" + group = "org.signal" +} subprojects { - ext.version_number = version_number - ext.group_info = "org.signal" - if (JavaVersion.current().isJava8Compatible()) { allprojects { tasks.withType(Javadoc) { @@ -23,6 +27,20 @@ subprojects { options.compilerArgs += ["-Xlint:deprecation", "-Xlint:fallthrough", "-Xlint:unchecked"] } } + + apply plugin: "com.diffplug.spotless" + spotless { + java { + target('**/*.java') + targetExclude('**/Native.java') + importOrder() + removeUnusedImports() + + googleJavaFormat() + formatAnnotations() + licenseHeaderFile rootProject.file('license_header.txt') + } + } } task makeJniLibrariesDesktop(type:Exec) { @@ -38,7 +56,7 @@ task cargoClean(type:Exec) { commandLine 'cargo', 'clean' } -task clean(type: Delete) { +task cleanJni(type: Delete) { description 'Clean JNI libs' delete fileTree('./android/src/main/jniLibs') { include '**/*.so' @@ -49,25 +67,27 @@ task clean(type: Delete) { include '**/*.dll' } } -clean.dependsOn(cargoClean) -task downloadNonLinuxLibraries(type: Download) { - // Must be explicitly enabled. - enabled false +clean.dependsOn([cargoClean, cleanJni]) - def extraResources = ['signal_jni.dll', 'libsignal_jni.dylib'] +// PUBLISHING - src(extraResources.collect { - 'https://github.com/' + getGithubReleasesAccountName() + '/libsignal/releases/download/v' + version_number + '/' + it - }) - dest 'shared/resources' +ext.setUpSigningKey = { signingExt -> + def signingKeyId = findProperty("signingKeyId") + def signingKey = findProperty("signingKey") + def signingPassword = findProperty("signingPassword") + if (signingKeyId && signingKey && signingPassword) { + signingExt.useInMemoryPgpKeys(signingKeyId.trim(), signingKey.trim(), signingPassword.trim()) + } } -// PUBLISHING - -def getGithubReleasesAccountName() { - return hasProperty("githubReleasesAccount") ? githubReleasesAccount - : "libsignal" +nexusPublishing { + repositories { + sonatype { + username = project.findProperty('sonatypeUsername') ?: "" + password = project.findProperty('sonatypePassword') ?: "" + } + } } def isSkipSigning() { @@ -77,16 +97,3 @@ def isSkipSigning() { def isReleaseBuild() { return version.contains("SNAPSHOT") == false } - -def getReleaseRepositoryUrl() { - return hasProperty('sonatypeRepo') ? sonatypeRepo - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" -} - -def getRepositoryUsername() { - return hasProperty('sonatypeUsername') ? sonatypeUsername : "" -} - -def getRepositoryPassword() { - return hasProperty('sonatypePassword') ? sonatypePassword : "" -} diff --git a/java/build_jni.sh b/java/build_jni.sh index 7d676b1dd3..1b58965094 100755 --- a/java/build_jni.sh +++ b/java/build_jni.sh @@ -15,8 +15,6 @@ cd "${SCRIPT_DIR}"/.. ANDROID_LIB_DIR=java/android/src/main/jniLibs DESKTOP_LIB_DIR=java/shared/resources -# Keep these settings in sync with .github/workflows/jni_artifacts.yml, -# which builds for Windows as well. export CARGO_PROFILE_RELEASE_DEBUG=1 # enable line tables # On Linux, cdylibs don't include public symbols from their dependencies, # even if those symbols have been re-exported in the Rust source. @@ -28,7 +26,9 @@ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s # optimize for size over speed if [ "$1" = 'desktop' ]; then echo_then_run cargo build -p libsignal-jni --release - copy_built_library target/release signal_jni "${DESKTOP_LIB_DIR}/" + if [[ -z "${CARGO_BUILD_TARGET:-}" ]]; then + copy_built_library target/release signal_jni "${DESKTOP_LIB_DIR}/" + fi elif [ "$1" = 'android' ]; then # Use small BoringSSL curve tables to reduce binary size on Android. @@ -48,6 +48,7 @@ then export TARGET_AR="${ANDROID_TOOLCHAIN_DIR}/llvm-ar" export RUSTFLAGS="-C link-arg=-fuse-ld=lld ${RUSTFLAGS:-}" + export RUSTFLAGS="--cfg aes_armv8 --cfg polyval_armv8 ${RUSTFLAGS:-}" # Enable ARMv8 cryptography acceleration when available # Uncomment the following to force the 64-bit curve25519-dalek backend on 32-bit targets. # export RUSTFLAGS="--cfg curve25519_dalek_bits=\"64\" ${RUSTFLAGS:-}" diff --git a/java/client/build.gradle b/java/client/build.gradle index 1a2b003d74..3f8f53a979 100644 --- a/java/client/build.gradle +++ b/java/client/build.gradle @@ -7,8 +7,6 @@ plugins { sourceCompatibility = 1.8 archivesBaseName = "libsignal-client" -version = version_number -group = group_info repositories { mavenCentral() @@ -57,15 +55,15 @@ java { withJavadocJar() } +tasks.named('jar') { + manifest { + attributes('Automatic-Module-Name': 'org.signal.libsignal') + } +} + processResources { // TODO: Build a different variant of the JNI library for server. dependsOn ':makeJniLibrariesDesktop' - dependsOn ':downloadNonLinuxLibraries' - gradle.taskGraph.whenReady { graph -> - if (graph.hasTask(":client:publish")) { - tasks.getByPath(":downloadNonLinuxLibraries").enabled = true - } - } } // MARK: Publishing @@ -102,17 +100,9 @@ publishing { } } } - repositories { - maven { - url = getReleaseRepositoryUrl() - credentials { - username = getRepositoryUsername() - password = getRepositoryPassword() - } - } - } } +setUpSigningKey(signing) signing { required { !isSkipSigning() && isReleaseBuild() && gradle.taskGraph.hasTask(":client:publish") } sign publishing.publications.mavenJava diff --git a/java/client/src/main/java/org/signal/libsignal/cds2/Cds2Client.java b/java/client/src/main/java/org/signal/libsignal/cds2/Cds2Client.java index b5a6b4e752..c0b0cfce5b 100644 --- a/java/client/src/main/java/org/signal/libsignal/cds2/Cds2Client.java +++ b/java/client/src/main/java/org/signal/libsignal/cds2/Cds2Client.java @@ -5,21 +5,22 @@ package org.signal.libsignal.cds2; -import org.signal.libsignal.sgxsession.SgxClient; +import java.time.Instant; import org.signal.libsignal.attest.AttestationDataException; import org.signal.libsignal.internal.Native; - -import java.time.Instant; +import org.signal.libsignal.sgxsession.SgxClient; /** - * Cds2Client provides bindings to interact with Signal's v2 Contact Discovery Service.

+ * Cds2Client provides bindings to interact with Signal's v2 Contact Discovery Service. * - * {@inheritDoc} + *

{@inheritDoc} * - * A future update to Cds2Client will implement additional parts of the contact discovery protocol. + *

A future update to Cds2Client will implement additional parts of the contact discovery + * protocol. */ public class Cds2Client extends SgxClient { - public Cds2Client(byte[] mrenclave, byte[] attestationMsg, Instant currentInstant) throws AttestationDataException { + public Cds2Client(byte[] mrenclave, byte[] attestationMsg, Instant currentInstant) + throws AttestationDataException { super(Native.Cds2ClientState_New(mrenclave, attestationMsg, currentInstant.toEpochMilli())); } } diff --git a/java/client/src/main/java/org/signal/libsignal/chat/DeviceClient.java b/java/client/src/main/java/org/signal/libsignal/chat/DeviceClient.java index f04b987fa4..70049b6631 100644 --- a/java/client/src/main/java/org/signal/libsignal/chat/DeviceClient.java +++ b/java/client/src/main/java/org/signal/libsignal/chat/DeviceClient.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.chat; import com.google.protobuf.InvalidProtocolBufferException; @@ -20,7 +25,8 @@ public DeviceClient(String target) { this.unsafeHandle = Native.DeviceClient_New(target); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.ProfileClient_Destroy(this.unsafeHandle); } @@ -29,9 +35,12 @@ public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - public GetDevicesResponse getDevices(GetDevicesRequest request, String authorization) throws SignalChatCommunicationFailureException { + public GetDevicesResponse getDevices(GetDevicesRequest request, String authorization) + throws SignalChatCommunicationFailureException { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - byte[] serializedResponse = Native.DeviceClient_GetDevices(guard.nativeHandle(), request.toByteArray(), authorization); + byte[] serializedResponse = + Native.DeviceClient_GetDevices( + guard.nativeHandle(), request.toByteArray(), authorization); return GetDevicesResponse.parseFrom(serializedResponse); } catch (InvalidProtocolBufferException e) { throw new SignalChatCommunicationFailureException(e); diff --git a/java/client/src/main/java/org/signal/libsignal/chat/ProfileClient.java b/java/client/src/main/java/org/signal/libsignal/chat/ProfileClient.java index 5a6a36dc85..7503621bf5 100644 --- a/java/client/src/main/java/org/signal/libsignal/chat/ProfileClient.java +++ b/java/client/src/main/java/org/signal/libsignal/chat/ProfileClient.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.chat; import com.google.protobuf.InvalidProtocolBufferException; @@ -20,7 +25,8 @@ public ProfileClient(String target) { this.unsafeHandle = Native.ProfileClient_New(target); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.ProfileClient_Destroy(this.unsafeHandle); } @@ -29,9 +35,11 @@ public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - public GetVersionedProfileResponse getVersionedProfile(GetVersionedProfileRequest request) throws SignalChatCommunicationFailureException { + public GetVersionedProfileResponse getVersionedProfile(GetVersionedProfileRequest request) + throws SignalChatCommunicationFailureException { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - byte[] serializedResponse = Native.ProfileClient_GetVersionedProfile(guard.nativeHandle(), request.toByteArray()); + byte[] serializedResponse = + Native.ProfileClient_GetVersionedProfile(guard.nativeHandle(), request.toByteArray()); return GetVersionedProfileResponse.parseFrom(serializedResponse); } catch (InvalidProtocolBufferException e) { throw new SignalChatCommunicationFailureException(e); diff --git a/java/client/src/main/java/org/signal/libsignal/chat/SignalChatCommunicationFailureException.java b/java/client/src/main/java/org/signal/libsignal/chat/SignalChatCommunicationFailureException.java index 30bb4bac69..feb2d73745 100644 --- a/java/client/src/main/java/org/signal/libsignal/chat/SignalChatCommunicationFailureException.java +++ b/java/client/src/main/java/org/signal/libsignal/chat/SignalChatCommunicationFailureException.java @@ -1,6 +1,16 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.chat; public class SignalChatCommunicationFailureException extends Exception { - public SignalChatCommunicationFailureException(String msg) { super(msg); } - public SignalChatCommunicationFailureException(Throwable t) { super(t); } + public SignalChatCommunicationFailureException(String msg) { + super(msg); + } + + public SignalChatCommunicationFailureException(Throwable t) { + super(t); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256Ctr32.java b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256Ctr32.java index fe4b291c99..12fc545efc 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256Ctr32.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256Ctr32.java @@ -16,7 +16,8 @@ public Aes256Ctr32(byte[] key, byte[] nonce, int initialCtr) throws InvalidKeyEx this.unsafeHandle = Native.Aes256Ctr32_New(key, nonce, initialCtr); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.Aes256Ctr32_Destroy(this.unsafeHandle); } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmDecryption.java b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmDecryption.java index 7ae18db17d..23c5d2395e 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmDecryption.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmDecryption.java @@ -14,11 +14,13 @@ public class Aes256GcmDecryption implements NativeHandleGuard.Owner { private long unsafeHandle; - public Aes256GcmDecryption(byte[] key, byte[] nonce, byte[] associatedData) throws InvalidKeyException { + public Aes256GcmDecryption(byte[] key, byte[] nonce, byte[] associatedData) + throws InvalidKeyException { this.unsafeHandle = Native.Aes256GcmDecryption_New(key, nonce, associatedData); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.Aes256GcmDecryption_Destroy(this.unsafeHandle); } @@ -47,5 +49,4 @@ public boolean verifyTag(byte[] tag) { return tagOk; } } - } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmEncryption.java b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmEncryption.java index d0623e4883..52f2fc5481 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmEncryption.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmEncryption.java @@ -12,11 +12,13 @@ public class Aes256GcmEncryption implements NativeHandleGuard.Owner { private long unsafeHandle; - public Aes256GcmEncryption(byte[] key, byte[] nonce, byte[] associatedData) throws InvalidKeyException { + public Aes256GcmEncryption(byte[] key, byte[] nonce, byte[] associatedData) + throws InvalidKeyException { this.unsafeHandle = Native.Aes256GcmEncryption_New(key, nonce, associatedData); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.Aes256GcmEncryption_Destroy(this.unsafeHandle); } @@ -45,5 +47,4 @@ public byte[] computeTag() { return tag; } } - } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmSiv.java b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmSiv.java index 73792b16e1..693ede9a7f 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmSiv.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/Aes256GcmSiv.java @@ -7,8 +7,8 @@ import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; -import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidKeyException; +import org.signal.libsignal.protocol.InvalidMessageException; class Aes256GcmSiv implements NativeHandleGuard.Owner { private final long unsafeHandle; @@ -17,7 +17,8 @@ public Aes256GcmSiv(byte[] key) throws InvalidKeyException { this.unsafeHandle = Native.Aes256GcmSiv_New(key); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.Aes256GcmSiv_Destroy(this.unsafeHandle); } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicHash.java b/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicHash.java index cab32d1db0..53b9ecd828 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicHash.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicHash.java @@ -19,7 +19,8 @@ public long unsafeNativeHandleWithoutGuard() { return unsafeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.CryptographicHash_Destroy(this.unsafeHandle); } @@ -41,5 +42,4 @@ public byte[] finish() { return Native.CryptographicHash_Finalize(guard.nativeHandle()); } } - } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicMac.java b/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicMac.java index f1b435ba38..d09515214a 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicMac.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/CryptographicMac.java @@ -15,7 +15,8 @@ public CryptographicMac(String algo, byte[] key) { this.unsafeHandle = Native.CryptographicMac_New(algo, key); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.CryptographicMac_Destroy(this.unsafeHandle); } @@ -41,5 +42,4 @@ public byte[] finish() { return Native.CryptographicMac_Finalize(guard.nativeHandle()); } } - } diff --git a/java/client/src/main/java/org/signal/libsignal/crypto/jce/Mac.java b/java/client/src/main/java/org/signal/libsignal/crypto/jce/Mac.java index 4fe05d584d..5e71a0fbb9 100644 --- a/java/client/src/main/java/org/signal/libsignal/crypto/jce/Mac.java +++ b/java/client/src/main/java/org/signal/libsignal/crypto/jce/Mac.java @@ -7,8 +7,8 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import org.signal.libsignal.crypto.CryptographicMac; import javax.crypto.spec.SecretKeySpec; +import org.signal.libsignal.crypto.CryptographicMac; public class Mac { diff --git a/java/client/src/main/java/org/signal/libsignal/hsmenclave/EnclaveCommunicationFailureException.java b/java/client/src/main/java/org/signal/libsignal/hsmenclave/EnclaveCommunicationFailureException.java index 66205c591e..9bffdffd6b 100644 --- a/java/client/src/main/java/org/signal/libsignal/hsmenclave/EnclaveCommunicationFailureException.java +++ b/java/client/src/main/java/org/signal/libsignal/hsmenclave/EnclaveCommunicationFailureException.java @@ -1,6 +1,16 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.hsmenclave; public class EnclaveCommunicationFailureException extends Exception { - public EnclaveCommunicationFailureException(String msg) { super(msg); } - public EnclaveCommunicationFailureException(Throwable t) { super(t); } + public EnclaveCommunicationFailureException(String msg) { + super(msg); + } + + public EnclaveCommunicationFailureException(Throwable t) { + super(t); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/hsmenclave/HsmEnclaveClient.java b/java/client/src/main/java/org/signal/libsignal/hsmenclave/HsmEnclaveClient.java index 9717185dbc..27b04373c7 100644 --- a/java/client/src/main/java/org/signal/libsignal/hsmenclave/HsmEnclaveClient.java +++ b/java/client/src/main/java/org/signal/libsignal/hsmenclave/HsmEnclaveClient.java @@ -5,29 +5,28 @@ package org.signal.libsignal.hsmenclave; -import org.signal.libsignal.internal.Native; -import org.signal.libsignal.internal.NativeHandleGuard; -import org.signal.libsignal.protocol.InvalidKeyException; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; +import org.signal.libsignal.internal.Native; +import org.signal.libsignal.internal.NativeHandleGuard; /** * HsmEnclaveClient provides bindings to interact with Signal's HSM-backed enclave. * - * Interaction with the enclave is done over a websocket, which is handled by the client. Once the websocket - * has been initiated, the client establishes a connection in the following manner: + *

Interaction with the enclave is done over a websocket, which is handled by the client. Once + * the websocket has been initiated, the client establishes a connection in the following manner: * *

* - * After a connection has been established, a client may send or receive messages. To send a message, they - * formulate the plaintext, then pass it to HsmEnclaveClient.establishedSend() to get the ciphertext message - * to pass along. When a message is received (as ciphertext), it is passed to HsmEnclaveClient.establishedRecv(), - * which decrypts and verifies it, passing the plaintext back to the client for processing. + * After a connection has been established, a client may send or receive messages. To send a + * message, they formulate the plaintext, then pass it to HsmEnclaveClient.establishedSend() to get + * the ciphertext message to pass along. When a message is received (as ciphertext), it is passed to + * HsmEnclaveClient.establishedRecv(), which decrypts and verifies it, passing the plaintext back to + * the client for processing. */ public class HsmEnclaveClient implements NativeHandleGuard.Owner { private final long unsafeHandle; @@ -47,7 +46,8 @@ public HsmEnclaveClient(byte[] public_key, List code_hashes) { this.unsafeHandle = Native.HsmEnclaveClient_New(public_key, concatHashes.toByteArray()); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.HsmEnclaveClient_Destroy(this.unsafeHandle); } diff --git a/java/client/src/main/java/org/signal/libsignal/hsmenclave/TrustedCodeMismatchException.java b/java/client/src/main/java/org/signal/libsignal/hsmenclave/TrustedCodeMismatchException.java index 149cd834bc..04519066e4 100644 --- a/java/client/src/main/java/org/signal/libsignal/hsmenclave/TrustedCodeMismatchException.java +++ b/java/client/src/main/java/org/signal/libsignal/hsmenclave/TrustedCodeMismatchException.java @@ -1,6 +1,16 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.hsmenclave; public class TrustedCodeMismatchException extends Exception { - public TrustedCodeMismatchException(String msg) { super(msg); } - public TrustedCodeMismatchException(Throwable t) { super(t); } + public TrustedCodeMismatchException(String msg) { + super(msg); + } + + public TrustedCodeMismatchException(Throwable t) { + super(t); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/media/Mp4Sanitizer.java b/java/client/src/main/java/org/signal/libsignal/media/Mp4Sanitizer.java index 20026193a5..29fa99cd4e 100644 --- a/java/client/src/main/java/org/signal/libsignal/media/Mp4Sanitizer.java +++ b/java/client/src/main/java/org/signal/libsignal/media/Mp4Sanitizer.java @@ -5,65 +5,69 @@ package org.signal.libsignal.media; -import org.signal.libsignal.internal.Native; - import java.io.IOException; import java.io.InputStream; +import org.signal.libsignal.internal.Native; /** * An MP4 format “sanitizer”. * - * Currently the sanitizer always performs the following functions: + *

Currently the sanitizer always performs the following functions: * *

* - *

“Presentation” metadata means any metadata which is required by an MP4 player to play the file. “Self-contained and contiguous” means - * that the returned metadata can be concatenated with the media data to form a valid MP4 file. + *

“Presentation” metadata means any metadata which is required by an MP4 player to play the + * file. “Self-contained and contiguous” means that the returned metadata can be concatenated with + * the media data to form a valid MP4 file. * - *

The original metadata may or may not need to be modified in order to perform these functions. In the case that the original metadata does - * not need to be modified, the returned sanitized metadata will be null to prevent needless data copying. + *

The original metadata may or may not need to be modified in order to perform these functions. + * In the case that the original metadata does not need to be modified, the returned sanitized + * metadata will be null to prevent needless data copying. * *

Unsupported MP4 features

* * The sanitizer does not currently support: * * */ public class Mp4Sanitizer { - /** - * Sanitize an MP4 input. - * - *

It's recommended that the given {@link InputStream} be capable of {@code skip}ping. If it is, then it must only skip fewer - * bytes than requested when the end of stream is reached. - * - * @param input An MP4 format input stream. - * @param length The exact length of the input stream. - * @return The sanitized metadata. - * @throws IOException If an IO error on the input occurs. - * @throws ParseException If the input could not be parsed. - */ - public static SanitizedMetadata sanitize(InputStream input, long length) throws IOException, ParseException { - long sanitizedMetadataHandle = Native.Mp4Sanitizer_Sanitize(input, length); - try { - byte[] sanitizedMetadata = Native.SanitizedMetadata_GetMetadata(sanitizedMetadataHandle); - if (sanitizedMetadata.length == 0) { - sanitizedMetadata = null; - } - long dataOffset = Native.SanitizedMetadata_GetDataOffset(sanitizedMetadataHandle); - long dataLength = Native.SanitizedMetadata_GetDataLen(sanitizedMetadataHandle); - return new SanitizedMetadata(sanitizedMetadata, dataOffset, dataLength); - } finally { - Native.SanitizedMetadata_Destroy(sanitizedMetadataHandle); - } + /** + * Sanitize an MP4 input. + * + *

It's recommended that the given {@link InputStream} be capable of {@code skip}ping. If it + * is, then it must only skip fewer bytes than requested when the end of stream is reached. + * + * @param input An MP4 format input stream. + * @param length The exact length of the input stream. + * @return The sanitized metadata. + * @throws IOException If an IO error on the input occurs. + * @throws ParseException If the input could not be parsed. + */ + public static SanitizedMetadata sanitize(InputStream input, long length) + throws IOException, ParseException { + long sanitizedMetadataHandle = Native.Mp4Sanitizer_Sanitize(input, length); + try { + byte[] sanitizedMetadata = Native.SanitizedMetadata_GetMetadata(sanitizedMetadataHandle); + if (sanitizedMetadata.length == 0) { + sanitizedMetadata = null; + } + long dataOffset = Native.SanitizedMetadata_GetDataOffset(sanitizedMetadataHandle); + long dataLength = Native.SanitizedMetadata_GetDataLen(sanitizedMetadataHandle); + return new SanitizedMetadata(sanitizedMetadata, dataOffset, dataLength); + } finally { + Native.SanitizedMetadata_Destroy(sanitizedMetadataHandle); } + } } diff --git a/java/client/src/main/java/org/signal/libsignal/media/ParseException.java b/java/client/src/main/java/org/signal/libsignal/media/ParseException.java index 0521ac085b..7c7854ac46 100644 --- a/java/client/src/main/java/org/signal/libsignal/media/ParseException.java +++ b/java/client/src/main/java/org/signal/libsignal/media/ParseException.java @@ -6,15 +6,20 @@ package org.signal.libsignal.media; /** - * Signals that the given media input could not be parsed for some reason. Developer-readable details are provided in the message. + * Signals that the given media input could not be parsed for some reason. Developer-readable + * details are provided in the message. */ public class ParseException extends Exception { - // This constructor is called by native code. - @SuppressWarnings("unused") - public ParseException(String msg) { super(msg); } + // This constructor is called by native code. + @SuppressWarnings("unused") + public ParseException(String msg) { + super(msg); + } - // This constructor is called by native code. - @SuppressWarnings("unused") - public ParseException(Throwable t) { super(t); } + // This constructor is called by native code. + @SuppressWarnings("unused") + public ParseException(Throwable t) { + super(t); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/media/SanitizedMetadata.java b/java/client/src/main/java/org/signal/libsignal/media/SanitizedMetadata.java index 7251f1e9cf..649fc75a42 100644 --- a/java/client/src/main/java/org/signal/libsignal/media/SanitizedMetadata.java +++ b/java/client/src/main/java/org/signal/libsignal/media/SanitizedMetadata.java @@ -7,37 +7,40 @@ public class SanitizedMetadata { - private byte[] sanitizedMetadata; - private long dataOffset; - private long dataLength; + private byte[] sanitizedMetadata; + private long dataOffset; + private long dataLength; - public SanitizedMetadata(byte[] sanitizedMetadata, long dataOffset, long dataLength) { - this.sanitizedMetadata = sanitizedMetadata; - this.dataOffset = dataOffset; - this.dataLength = dataLength; - } + public SanitizedMetadata(byte[] sanitizedMetadata, long dataOffset, long dataLength) { + this.sanitizedMetadata = sanitizedMetadata; + this.dataOffset = dataOffset; + this.dataLength = dataLength; + } - /** - * Get the sanitized metadata, if any. - * @return The sanitized metadata, or {@code null} if it didn't need to be sanitized. - */ - public byte[] getSanitizedMetadata() { - return sanitizedMetadata; - } + /** + * Get the sanitized metadata, if any. + * + * @return The sanitized metadata, or {@code null} if it didn't need to be sanitized. + */ + public byte[] getSanitizedMetadata() { + return sanitizedMetadata; + } - /** - * Get the offset of the media data in the processed input. - * @return The offset of the media data in the processed input. - */ - public long getDataOffset() { - return dataOffset; - } + /** + * Get the offset of the media data in the processed input. + * + * @return The offset of the media data in the processed input. + */ + public long getDataOffset() { + return dataOffset; + } - /** - * Get the length of the media data in the processed input. - * @return The length of the media data in the processed input. - */ - public long getDataLength() { - return dataLength; - } + /** + * Get the length of the media data in the processed input. + * + * @return The length of the media data in the processed input. + */ + public long getDataLength() { + return dataLength; + } } diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataMessageException.java b/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataMessageException.java index 01aa341b0e..0503768d85 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataMessageException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataMessageException.java @@ -1,5 +1,9 @@ -package org.signal.libsignal.metadata; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +package org.signal.libsignal.metadata; public class InvalidMetadataMessageException extends Exception { public InvalidMetadataMessageException(String s) { @@ -9,5 +13,4 @@ public InvalidMetadataMessageException(String s) { public InvalidMetadataMessageException(Exception s) { super(s); } - } diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataVersionException.java b/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataVersionException.java index da52bd88d1..6ea2d1ee01 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataVersionException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/InvalidMetadataVersionException.java @@ -1,5 +1,9 @@ -package org.signal.libsignal.metadata; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +package org.signal.libsignal.metadata; public class InvalidMetadataVersionException extends Exception { public InvalidMetadataVersionException(String s) { diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolDuplicateMessageException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolDuplicateMessageException.java index 5ad672c08c..ab25605f62 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolDuplicateMessageException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolDuplicateMessageException.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolException.java index 20775f016f..0b9db6f438 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolException.java @@ -1,10 +1,14 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; +import java.util.Optional; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.ServiceId; -import java.util.Optional; - public abstract class ProtocolException extends Exception { private final Optional content; @@ -13,15 +17,15 @@ public abstract class ProtocolException extends Exception { public ProtocolException(Exception e, String sender, int senderDevice) { super(e); - this.content = Optional.empty(); - this.sender = sender; + this.content = Optional.empty(); + this.sender = sender; this.senderDevice = senderDevice; } ProtocolException(Exception e, UnidentifiedSenderMessageContent content) { super(e); - this.content = Optional.of(content); - this.sender = content.getSenderCertificate().getSender(); + this.content = Optional.of(content); + this.sender = content.getSenderCertificate().getSender(); this.senderDevice = content.getSenderCertificate().getSenderDeviceId(); } @@ -33,9 +37,7 @@ public String getSender() { return sender; } - /** - * Returns an Aci if the sender is a valid UUID, {@code null} otherwise. - */ + /** Returns an Aci if the sender is a valid UUID, {@code null} otherwise. */ public ServiceId.Aci getSenderAci() { try { return ServiceId.Aci.parseFromString(getSender()); diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyException.java index a6ee3c63a0..bfaa5cb17e 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyException.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyIdException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyIdException.java index 9ba708ec01..52bfa4671b 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyIdException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidKeyIdException.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidMessageException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidMessageException.java index 0986341d88..c6316f6f96 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidMessageException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidMessageException.java @@ -1,14 +1,21 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.InvalidMessageException; public class ProtocolInvalidMessageException extends ProtocolException { - public ProtocolInvalidMessageException(InvalidMessageException e, String sender, int senderDevice) { + public ProtocolInvalidMessageException( + InvalidMessageException e, String sender, int senderDevice) { super(e, sender, senderDevice); } - ProtocolInvalidMessageException(InvalidMessageException e, UnidentifiedSenderMessageContent content) { + ProtocolInvalidMessageException( + InvalidMessageException e, UnidentifiedSenderMessageContent content) { super(e, content); } } diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidVersionException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidVersionException.java index 4b3998f9eb..629863ac63 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidVersionException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolInvalidVersionException.java @@ -1,14 +1,21 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.InvalidVersionException; public class ProtocolInvalidVersionException extends ProtocolException { - public ProtocolInvalidVersionException(InvalidVersionException e, String sender, int senderDevice) { + public ProtocolInvalidVersionException( + InvalidVersionException e, String sender, int senderDevice) { super(e, sender, senderDevice); } - ProtocolInvalidVersionException(InvalidVersionException e, UnidentifiedSenderMessageContent content) { + ProtocolInvalidVersionException( + InvalidVersionException e, UnidentifiedSenderMessageContent content) { super(e, content); } } diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolLegacyMessageException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolLegacyMessageException.java index b28635b7e9..56c6d920ba 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolLegacyMessageException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolLegacyMessageException.java @@ -1,14 +1,21 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.LegacyMessageException; public class ProtocolLegacyMessageException extends ProtocolException { - public ProtocolLegacyMessageException(LegacyMessageException e, String sender, int senderDeviceId) { + public ProtocolLegacyMessageException( + LegacyMessageException e, String sender, int senderDeviceId) { super(e, sender, senderDeviceId); } - ProtocolLegacyMessageException(LegacyMessageException e, UnidentifiedSenderMessageContent content) { + ProtocolLegacyMessageException( + LegacyMessageException e, UnidentifiedSenderMessageContent content) { super(e, content); } } diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolNoSessionException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolNoSessionException.java index 7552f52ff9..ddd04fddc4 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolNoSessionException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolNoSessionException.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolUntrustedIdentityException.java b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolUntrustedIdentityException.java index 6f4fa878b4..1c87f0ec9e 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolUntrustedIdentityException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/ProtocolUntrustedIdentityException.java @@ -1,14 +1,21 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.UntrustedIdentityException; public class ProtocolUntrustedIdentityException extends ProtocolException { - public ProtocolUntrustedIdentityException(UntrustedIdentityException e, String sender, int senderDevice) { + public ProtocolUntrustedIdentityException( + UntrustedIdentityException e, String sender, int senderDevice) { super(e, sender, senderDevice); } - ProtocolUntrustedIdentityException(UntrustedIdentityException e, UnidentifiedSenderMessageContent content) { + ProtocolUntrustedIdentityException( + UntrustedIdentityException e, UnidentifiedSenderMessageContent content) { super(e, content); } } diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java b/java/client/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java index 489f94aa57..dfdc205043 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java @@ -1,14 +1,22 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.signal.libsignal.internal.Native; +import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.metadata.certificate.CertificateValidator; -import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.DuplicateMessageException; -import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.InvalidMacException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidRegistrationIdException; import org.signal.libsignal.protocol.InvalidVersionException; @@ -20,79 +28,72 @@ import org.signal.libsignal.protocol.UntrustedIdentityException; import org.signal.libsignal.protocol.groups.GroupCipher; import org.signal.libsignal.protocol.message.CiphertextMessage; -import org.signal.libsignal.protocol.message.PlaintextContent; import org.signal.libsignal.protocol.message.PreKeySignalMessage; -import org.signal.libsignal.protocol.message.SenderKeyMessage; import org.signal.libsignal.protocol.message.SignalMessage; import org.signal.libsignal.protocol.state.SessionRecord; import org.signal.libsignal.protocol.state.SignalProtocolStore; -import org.signal.libsignal.internal.Native; -import org.signal.libsignal.internal.NativeHandleGuard; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - public class SealedSessionCipher { private static final String TAG = SealedSessionCipher.class.getSimpleName(); private final SignalProtocolStore signalProtocolStore; - private final String localE164Address; - private final String localUuidAddress; - private final int localDeviceId; + private final String localE164Address; + private final String localUuidAddress; + private final int localDeviceId; - public SealedSessionCipher(SignalProtocolStore signalProtocolStore, - UUID localUuid, - String localE164Address, - int localDeviceId) - { + public SealedSessionCipher( + SignalProtocolStore signalProtocolStore, + UUID localUuid, + String localE164Address, + int localDeviceId) { this.signalProtocolStore = signalProtocolStore; - this.localUuidAddress = localUuid.toString(); - this.localE164Address = localE164Address; - this.localDeviceId = localDeviceId; + this.localUuidAddress = localUuid.toString(); + this.localE164Address = localE164Address; + this.localDeviceId = localDeviceId; } - public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext) - throws InvalidKeyException, UntrustedIdentityException - { + public byte[] encrypt( + SignalProtocolAddress destinationAddress, + SenderCertificate senderCertificate, + byte[] paddedPlaintext) + throws InvalidKeyException, UntrustedIdentityException { try (NativeHandleGuard addressGuard = new NativeHandleGuard(destinationAddress)) { - CiphertextMessage message = Native.SessionCipher_EncryptMessage( - paddedPlaintext, - addressGuard.nativeHandle(), - this.signalProtocolStore, - this.signalProtocolStore); - UnidentifiedSenderMessageContent content = new UnidentifiedSenderMessageContent( - message, - senderCertificate, - UnidentifiedSenderMessageContent.CONTENT_HINT_DEFAULT, - Optional.empty()); + CiphertextMessage message = + Native.SessionCipher_EncryptMessage( + paddedPlaintext, + addressGuard.nativeHandle(), + this.signalProtocolStore, + this.signalProtocolStore, + Instant.now().toEpochMilli()); + UnidentifiedSenderMessageContent content = + new UnidentifiedSenderMessageContent( + message, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_DEFAULT, + Optional.empty()); return encrypt(destinationAddress, content); } } - public byte[] encrypt(SignalProtocolAddress destinationAddress, UnidentifiedSenderMessageContent content) - throws InvalidKeyException, UntrustedIdentityException - { - try ( - NativeHandleGuard addressGuard = new NativeHandleGuard(destinationAddress); - NativeHandleGuard contentGuard = new NativeHandleGuard(content); - ) { + public byte[] encrypt( + SignalProtocolAddress destinationAddress, UnidentifiedSenderMessageContent content) + throws InvalidKeyException, UntrustedIdentityException { + try (NativeHandleGuard addressGuard = new NativeHandleGuard(destinationAddress); + NativeHandleGuard contentGuard = new NativeHandleGuard(content)) { return Native.SealedSessionCipher_Encrypt( - addressGuard.nativeHandle(), - contentGuard.nativeHandle(), - this.signalProtocolStore); + addressGuard.nativeHandle(), contentGuard.nativeHandle(), this.signalProtocolStore); } } - public byte[] multiRecipientEncrypt(List recipients, UnidentifiedSenderMessageContent content) - throws - InvalidKeyException, InvalidRegistrationIdException, NoSessionException, - UntrustedIdentityException - { + public byte[] multiRecipientEncrypt( + List recipients, UnidentifiedSenderMessageContent content) + throws InvalidKeyException, + InvalidRegistrationIdException, + NoSessionException, + UntrustedIdentityException { List recipientSessions = - this.signalProtocolStore.loadExistingSessions(recipients); + this.signalProtocolStore.loadExistingSessions(recipients); // Unsafely access the native handles for the recipients and sessions, // because try-with-resources syntax doesn't support a List of resources. @@ -111,11 +112,12 @@ public byte[] multiRecipientEncrypt(List recipients, Unid } try (NativeHandleGuard contentGuard = new NativeHandleGuard(content)) { - byte[] result = Native.SealedSessionCipher_MultiRecipientEncrypt( - recipientHandles, - recipientSessionHandles, - contentGuard.nativeHandle(), - this.signalProtocolStore); + byte[] result = + Native.SealedSessionCipher_MultiRecipientEncrypt( + recipientHandles, + recipientSessionHandles, + contentGuard.nativeHandle(), + this.signalProtocolStore); // Manually keep the lists of recipients and sessions from being garbage collected // while we're using their native handles. Native.keepAlive(recipients); @@ -141,37 +143,45 @@ static byte[] multiRecipientMessageForSingleRecipient(byte[] message) { } public DecryptionResult decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp) - throws - InvalidMetadataMessageException, InvalidMetadataVersionException, - ProtocolInvalidMessageException, ProtocolInvalidKeyException, - ProtocolNoSessionException, ProtocolLegacyMessageException, - ProtocolInvalidVersionException, ProtocolDuplicateMessageException, - ProtocolInvalidKeyIdException, ProtocolUntrustedIdentityException, - SelfSendException - { + throws InvalidMetadataMessageException, + InvalidMetadataVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyException, + ProtocolNoSessionException, + ProtocolLegacyMessageException, + ProtocolInvalidVersionException, + ProtocolDuplicateMessageException, + ProtocolInvalidKeyIdException, + ProtocolUntrustedIdentityException, + SelfSendException { UnidentifiedSenderMessageContent content; try { - content = new UnidentifiedSenderMessageContent( - Native.SealedSessionCipher_DecryptToUsmc(ciphertext, this.signalProtocolStore)); + content = + new UnidentifiedSenderMessageContent( + Native.SealedSessionCipher_DecryptToUsmc(ciphertext, this.signalProtocolStore)); validator.validate(content.getSenderCertificate(), timestamp); } catch (Exception e) { throw new InvalidMetadataMessageException(e); } - boolean isLocalE164 = localE164Address != null && localE164Address.equals(content.getSenderCertificate().getSenderE164().orElse(null)); + boolean isLocalE164 = + localE164Address != null + && localE164Address.equals(content.getSenderCertificate().getSenderE164().orElse(null)); boolean isLocalUuid = localUuidAddress.equals(content.getSenderCertificate().getSenderUuid()); - if ((isLocalE164 || isLocalUuid) && content.getSenderCertificate().getSenderDeviceId() == localDeviceId) { + if ((isLocalE164 || isLocalUuid) + && content.getSenderCertificate().getSenderDeviceId() == localDeviceId) { throw new SelfSendException(); } try { - return new DecryptionResult(content.getSenderCertificate().getSenderUuid(), - content.getSenderCertificate().getSenderE164(), - content.getSenderCertificate().getSenderDeviceId(), - content.getType(), - content.getGroupId(), - decrypt(content)); + return new DecryptionResult( + content.getSenderCertificate().getSenderUuid(), + content.getSenderCertificate().getSenderE164(), + content.getSenderCertificate().getSenderDeviceId(), + content.getType(), + content.getGroupId(), + decrypt(content)); } catch (InvalidMessageException e) { throw new ProtocolInvalidMessageException(e, content); } catch (InvalidKeyException e) { @@ -200,15 +210,26 @@ public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) { } private byte[] decrypt(UnidentifiedSenderMessageContent message) - throws InvalidVersionException, InvalidMessageException, InvalidKeyException, DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, LegacyMessageException, NoSessionException - { - SignalProtocolAddress sender = new SignalProtocolAddress(message.getSenderCertificate().getSenderUuid(), message.getSenderCertificate().getSenderDeviceId()); + throws InvalidVersionException, + InvalidMessageException, + InvalidKeyException, + DuplicateMessageException, + InvalidKeyIdException, + UntrustedIdentityException, + LegacyMessageException, + NoSessionException { + SignalProtocolAddress sender = + new SignalProtocolAddress( + message.getSenderCertificate().getSenderUuid(), + message.getSenderCertificate().getSenderDeviceId()); switch (message.getType()) { case CiphertextMessage.WHISPER_TYPE: - return new SessionCipher(signalProtocolStore, sender).decrypt(new SignalMessage(message.getContent())); - case CiphertextMessage.PREKEY_TYPE: - return new SessionCipher(signalProtocolStore, sender).decrypt(new PreKeySignalMessage(message.getContent())); + return new SessionCipher(signalProtocolStore, sender) + .decrypt(new SignalMessage(message.getContent())); + case CiphertextMessage.PREKEY_TYPE: + return new SessionCipher(signalProtocolStore, sender) + .decrypt(new PreKeySignalMessage(message.getContent())); case CiphertextMessage.SENDERKEY_TYPE: return new GroupCipher(signalProtocolStore, sender).decrypt(message.getContent()); case CiphertextMessage.PLAINTEXT_CONTENT_TYPE: @@ -219,19 +240,25 @@ private byte[] decrypt(UnidentifiedSenderMessageContent message) } public static class DecryptionResult { - private final String senderUuid; + private final String senderUuid; private final Optional senderE164; - private final int deviceId; - private final int messageType; + private final int deviceId; + private final int messageType; private final Optional groupId; - private final byte[] paddedMessage; + private final byte[] paddedMessage; - private DecryptionResult(String senderUuid, Optional senderE164, int deviceId, int messageType, Optional groupId, byte[] paddedMessage) { - this.senderUuid = senderUuid; - this.senderE164 = senderE164; - this.deviceId = deviceId; - this.messageType = messageType; - this.groupId = groupId; + private DecryptionResult( + String senderUuid, + Optional senderE164, + int deviceId, + int messageType, + Optional groupId, + byte[] paddedMessage) { + this.senderUuid = senderUuid; + this.senderE164 = senderE164; + this.deviceId = deviceId; + this.messageType = messageType; + this.groupId = groupId; this.paddedMessage = paddedMessage; } @@ -242,7 +269,7 @@ public String getSenderUuid() { /** * Returns an ACI if the sender is a valid UUID, {@code null} otherwise. * - * In a future release DecryptionResult will only support ACIs. + *

In a future release DecryptionResult will only support ACIs. */ public ServiceId.Aci getSenderAci() { try { diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/SelfSendException.java b/java/client/src/main/java/org/signal/libsignal/metadata/SelfSendException.java index c3c0c3051a..3175934188 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/SelfSendException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/SelfSendException.java @@ -1,5 +1,8 @@ -package org.signal.libsignal.metadata; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +package org.signal.libsignal.metadata; -public class SelfSendException extends Exception { -} +public class SelfSendException extends Exception {} diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/CertificateValidator.java b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/CertificateValidator.java index db0d3ccd8d..dc857277ec 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/CertificateValidator.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/CertificateValidator.java @@ -1,11 +1,15 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata.certificate; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - +import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; -import org.signal.libsignal.protocol.InvalidKeyException; public class CertificateValidator { private final ECPublicKey trustRoot; @@ -18,14 +22,14 @@ public ECPublicKey getTrustRoot() { return this.trustRoot; } - public void validate(SenderCertificate certificate, long validationTime) throws InvalidCertificateException { - try ( - NativeHandleGuard certificateGuard = new NativeHandleGuard(certificate); - NativeHandleGuard trustRootGuard = new NativeHandleGuard(trustRoot); - ) { - if (!Native.SenderCertificate_Validate(certificateGuard.nativeHandle(), trustRootGuard.nativeHandle(), validationTime)) { - throw new InvalidCertificateException("Validation failed"); - } + public void validate(SenderCertificate certificate, long validationTime) + throws InvalidCertificateException { + try (NativeHandleGuard certificateGuard = new NativeHandleGuard(certificate); + NativeHandleGuard trustRootGuard = new NativeHandleGuard(trustRoot)) { + if (!Native.SenderCertificate_Validate( + certificateGuard.nativeHandle(), trustRootGuard.nativeHandle(), validationTime)) { + throw new InvalidCertificateException("Validation failed"); + } } catch (Exception e) { throw new InvalidCertificateException(e); } @@ -34,7 +38,8 @@ public void validate(SenderCertificate certificate, long validationTime) throws // VisibleForTesting void validate(ServerCertificate certificate) throws InvalidCertificateException { try { - if (!Curve.verifySignature(trustRoot, certificate.getCertificate(), certificate.getSignature())) { + if (!Curve.verifySignature( + trustRoot, certificate.getCertificate(), certificate.getSignature())) { throw new InvalidCertificateException("Signature failed"); } } catch (InvalidKeyException e) { diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/InvalidCertificateException.java b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/InvalidCertificateException.java index f2ec14634f..5816f56cbd 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/InvalidCertificateException.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/InvalidCertificateException.java @@ -1,5 +1,9 @@ -package org.signal.libsignal.metadata.certificate; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +package org.signal.libsignal.metadata.certificate; public class InvalidCertificateException extends Exception { public InvalidCertificateException(String s) { diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java index 9310eac10f..6b28fc9896 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java @@ -1,21 +1,23 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata.certificate; +import java.util.Optional; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - -import org.signal.libsignal.protocol.ecc.ECPublicKey; -import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.ServiceId; - -import java.util.Optional; +import org.signal.libsignal.protocol.ecc.ECPublicKey; public class SenderCertificate implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.SenderCertificate_Destroy(this.unsafeHandle); + Native.SenderCertificate_Destroy(this.unsafeHandle); } public long unsafeNativeHandleWithoutGuard() { @@ -36,7 +38,8 @@ public SenderCertificate(long unsafeHandle) { public ServerCertificate getSigner() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - return new ServerCertificate(Native.SenderCertificate_GetServerCertificate(guard.nativeHandle())); + return new ServerCertificate( + Native.SenderCertificate_GetServerCertificate(guard.nativeHandle())); } } @@ -71,7 +74,7 @@ public String getSender() { /** * Returns an ACI if the sender is a valid UUID, {@code null} otherwise. * - * In a future release SenderCertificate will only support ACIs. + *

In a future release SenderCertificate will only support ACIs. */ public ServiceId.Aci getSenderAci() { try { diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/ServerCertificate.java b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/ServerCertificate.java index a6e00a061e..1100678361 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/certificate/ServerCertificate.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/certificate/ServerCertificate.java @@ -1,19 +1,21 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata.certificate; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - -import org.signal.libsignal.protocol.ecc.ECPublicKey; -import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.ecc.ECPublicKey; public class ServerCertificate implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.ServerCertificate_Destroy(this.unsafeHandle); + Native.ServerCertificate_Destroy(this.unsafeHandle); } public ServerCertificate(long unsafeHandle) { diff --git a/java/client/src/main/java/org/signal/libsignal/metadata/protocol/UnidentifiedSenderMessageContent.java b/java/client/src/main/java/org/signal/libsignal/metadata/protocol/UnidentifiedSenderMessageContent.java index 259db97f6a..263dbe7b40 100644 --- a/java/client/src/main/java/org/signal/libsignal/metadata/protocol/UnidentifiedSenderMessageContent.java +++ b/java/client/src/main/java/org/signal/libsignal/metadata/protocol/UnidentifiedSenderMessageContent.java @@ -1,26 +1,30 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata.protocol; +import java.util.Optional; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.metadata.InvalidMetadataMessageException; import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.protocol.message.CiphertextMessage; -import java.util.Optional; - public class UnidentifiedSenderMessageContent implements NativeHandleGuard.Owner { // Must be kept in sync with sealed_sender.proto. - public static final int CONTENT_HINT_DEFAULT = 0; + public static final int CONTENT_HINT_DEFAULT = 0; public static final int CONTENT_HINT_RESENDABLE = 1; - public static final int CONTENT_HINT_IMPLICIT = 2; + public static final int CONTENT_HINT_IMPLICIT = 2; private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.UnidentifiedSenderMessageContent_Destroy(this.unsafeHandle); + Native.UnidentifiedSenderMessageContent_Destroy(this.unsafeHandle); } public UnidentifiedSenderMessageContent(long nativeHandle) { @@ -31,7 +35,8 @@ public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - public UnidentifiedSenderMessageContent(byte[] serialized) throws InvalidMetadataMessageException, InvalidCertificateException { + public UnidentifiedSenderMessageContent(byte[] serialized) + throws InvalidMetadataMessageException, InvalidCertificateException { try { this.unsafeHandle = Native.UnidentifiedSenderMessageContent_Deserialize(serialized); } catch (Exception e) { @@ -39,16 +44,15 @@ public UnidentifiedSenderMessageContent(byte[] serialized) throws InvalidMetadat } } - public UnidentifiedSenderMessageContent(CiphertextMessage message, - SenderCertificate senderCertificate, - int contentHint, - Optional groupId) { + public UnidentifiedSenderMessageContent( + CiphertextMessage message, + SenderCertificate senderCertificate, + int contentHint, + Optional groupId) { try (NativeHandleGuard certificateGuard = new NativeHandleGuard(senderCertificate)) { - this.unsafeHandle = Native.UnidentifiedSenderMessageContent_New( - message, - certificateGuard.nativeHandle(), - contentHint, - groupId.orElse(null)); + this.unsafeHandle = + Native.UnidentifiedSenderMessageContent_New( + message, certificateGuard.nativeHandle(), contentHint, groupId.orElse(null)); } } @@ -60,7 +64,8 @@ public int getType() { public SenderCertificate getSenderCertificate() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - return new SenderCertificate(Native.UnidentifiedSenderMessageContent_GetSenderCert(guard.nativeHandle())); + return new SenderCertificate( + Native.UnidentifiedSenderMessageContent_GetSenderCert(guard.nativeHandle())); } } @@ -84,7 +89,8 @@ public int getContentHint() { public Optional getGroupId() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - return Optional.ofNullable(Native.UnidentifiedSenderMessageContent_GetGroupId(guard.nativeHandle())); + return Optional.ofNullable( + Native.UnidentifiedSenderMessageContent_GetGroupId(guard.nativeHandle())); } } } diff --git a/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxClient.java b/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxClient.java index ed66e9abd2..c0fa60e15a 100644 --- a/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxClient.java +++ b/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxClient.java @@ -11,20 +11,21 @@ /** * SgxClient provides bindings to interact with a Signal SGX service. * - * Interaction with the service is done over a websocket, which is handled by the client. Once the websocket - * has been initiated, the client establishes a connection in the following manner: + *

Interaction with the service is done over a websocket, which is handled by the client. Once + * the websocket has been initiated, the client establishes a connection in the following manner: * *

* - * After a connection has been established, a client may send or receive messages. To send a message, they - * formulate the plaintext, then pass it to SgxClient.establishedSend() to get the ciphertext message - * to pass along. When a message is received (as ciphertext), it is passed to SgxClient.establishedRecv(), - * which decrypts and verifies it, passing the plaintext back to the client for processing. + * After a connection has been established, a client may send or receive messages. To send a + * message, they formulate the plaintext, then pass it to SgxClient.establishedSend() to get the + * ciphertext message to pass along. When a message is received (as ciphertext), it is passed to + * SgxClient.establishedRecv(), which decrypts and verifies it, passing the plaintext back to the + * client for processing. */ public class SgxClient implements NativeHandleGuard.Owner { private final long unsafeHandle; @@ -33,7 +34,8 @@ protected SgxClient(final long unsafeHandle) { this.unsafeHandle = unsafeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.SgxClientState_Destroy(this.unsafeHandle); } @@ -49,7 +51,10 @@ public byte[] initialRequest() { } } - /** Called by client upon receipt of first non-attestation message from service, to complete handshake. */ + /** + * Called by client upon receipt of first non-attestation message from service, to complete + * handshake. + */ public void completeHandshake(byte[] handshakeResponse) throws SgxCommunicationFailureException { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { Native.SgxClientState_CompleteHandshake(guard.nativeHandle(), handshakeResponse); diff --git a/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxCommunicationFailureException.java b/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxCommunicationFailureException.java index 59eac61f49..a9a0ad2750 100644 --- a/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxCommunicationFailureException.java +++ b/java/client/src/main/java/org/signal/libsignal/sgxsession/SgxCommunicationFailureException.java @@ -6,6 +6,11 @@ package org.signal.libsignal.sgxsession; public class SgxCommunicationFailureException extends Exception { - public SgxCommunicationFailureException(String msg) { super(msg); } - public SgxCommunicationFailureException(Throwable t) { super(t); } + public SgxCommunicationFailureException(String msg) { + super(msg); + } + + public SgxCommunicationFailureException(Throwable t) { + super(t); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/svr2/Pin.java b/java/client/src/main/java/org/signal/libsignal/svr2/Pin.java index 125df787b0..c445ed6a06 100644 --- a/java/client/src/main/java/org/signal/libsignal/svr2/Pin.java +++ b/java/client/src/main/java/org/signal/libsignal/svr2/Pin.java @@ -9,41 +9,40 @@ /** * Supports operations on pins for Secure Value Recovery - *

- * This class provides hashing pins for local verification and for use with the remote SVR service. In either case, all - * pins are UTF-8 encoded bytes that must be normalized *before* being provided to this class. Normalizing a string pin - * requires the following steps: * - *

  • The string should be trimmed for leading and trailing whitespace.
  • - *
  • If the whole string consists of digits, then non-arabic digits must be replaced with their - * arabic 0-9 equivalents.
  • - *
  • The string must then be NKFD normalized
  • + *

    This class provides hashing pins for local verification and for use with the remote SVR + * service. In either case, all pins are UTF-8 encoded bytes that must be normalized *before* being + * provided to this class. Normalizing a string pin requires the following steps: + *

  • The string should be trimmed for leading and trailing whitespace. + *
  • If the whole string consists of digits, then non-arabic digits must be replaced with their + * arabic 0-9 equivalents. + *
  • The string must then be NKFD + * normalized */ public class Pin { - private Pin() { - } + private Pin() {} - /** - * Create an encoded password hash string. - * - * This creates a hashed pin that should be used for local pin verification only. - * - * @param pin A normalized, UTF-8 encoded byte representation of the pin - * @return A hashed pin string that can be verified later - */ - public static String localHash(final byte[] pin) { - return Native.Pin_LocalHash(pin); - } + /** + * Create an encoded password hash string. + * + *

    This creates a hashed pin that should be used for local pin verification only. + * + * @param pin A normalized, UTF-8 encoded byte representation of the pin + * @return A hashed pin string that can be verified later + */ + public static String localHash(final byte[] pin) { + return Native.Pin_LocalHash(pin); + } - /** - * Verify an encoded password hash against a pin - * - * @param encodedHash An encoded string of the hash, as returned by {@link Pin#localHash} - * @param pin A normalized, UTF-8 encoded byte representation of the pin to verify - * @return true if the pin matches the hash, false otherwise - */ - public static boolean verifyLocalHash(final String encodedHash, final byte[] pin) { - return Native.Pin_VerifyLocalHash(encodedHash, pin); - } + /** + * Verify an encoded password hash against a pin + * + * @param encodedHash An encoded string of the hash, as returned by {@link Pin#localHash} + * @param pin A normalized, UTF-8 encoded byte representation of the pin to verify + * @return true if the pin matches the hash, false otherwise + */ + public static boolean verifyLocalHash(final String encodedHash, final byte[] pin) { + return Native.Pin_VerifyLocalHash(encodedHash, pin); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/svr2/PinHash.java b/java/client/src/main/java/org/signal/libsignal/svr2/PinHash.java index b3e98a1af6..45a7564dad 100644 --- a/java/client/src/main/java/org/signal/libsignal/svr2/PinHash.java +++ b/java/client/src/main/java/org/signal/libsignal/svr2/PinHash.java @@ -8,70 +8,68 @@ import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; -/** - * A hash of the pin that can be used to interact with a Secure Value Recovery service. - */ +/** A hash of the pin that can be used to interact with a Secure Value Recovery service. */ public class PinHash implements NativeHandleGuard.Owner { - private final long unsafeHandle; + private final long unsafeHandle; - private PinHash(final long unsafeHandle) { - this.unsafeHandle = unsafeHandle; - } + private PinHash(final long unsafeHandle) { + this.unsafeHandle = unsafeHandle; + } - @Override - @SuppressWarnings("deprecation") - protected void finalize() { - Native.PinHash_Destroy(this.unsafeHandle); - } + @Override + @SuppressWarnings("deprecation") + protected void finalize() { + Native.PinHash_Destroy(this.unsafeHandle); + } - public long unsafeNativeHandleWithoutGuard() { - return this.unsafeHandle; - } + public long unsafeNativeHandleWithoutGuard() { + return this.unsafeHandle; + } + /** + * Hash a pin for use with a remote SecureValueRecovery1 service. + * + *

    Note: This should be used with SVR1 only. For SVR1, the salt should be the backup id. For + * SVR2 clients, use {@link PinHash#svr2} which handles salt selection internally. + * + * @param normalizedPin A normalized, UTF-8 encoded byte representation of the pin + * @param salt A 32 byte salt + * @return A {@link PinHash} + */ + public static PinHash svr1(final byte[] normalizedPin, final byte[] salt) { + return new PinHash(Native.PinHash_FromSalt(normalizedPin, salt)); + } - /** - * Hash a pin for use with a remote SecureValueRecovery1 service. - * - * Note: This should be used with SVR1 only. For SVR1, the salt should be the backup id. - * For SVR2 clients, use {@link PinHash#svr2} which handles salt selection internally. - * - * @param pin A normalized, UTF-8 encoded byte representation of the pin - * @param salt A 32 byte salt - * @return A {@link PinHash} - */ - public static PinHash svr1(final byte[] normalizedPin, final byte[] salt) { - return new PinHash(Native.PinHash_FromSalt(normalizedPin, salt)); - } + /** + * Hash a pin for use with a remote SecureValueRecovery2 service. + * + *

    Note: This should be used with SVR2 only. For SVR1 clients, use {@link PinHash#svr1} + * + * @param normalizedPin A normalized, UTF-8 encoded byte representation of the pin + * @param username The Basic Auth username used to authenticate with SVR2 + * @param mrenclave The mrenclave where the hashed pin will be stored + * @return A {@link PinHash} + */ + public static PinHash svr2( + final byte[] normalizedPin, final String username, final byte[] mrenclave) { + return new PinHash(Native.PinHash_FromUsernameMrenclave(normalizedPin, username, mrenclave)); + } - /** - * Hash a pin for use with a remote SecureValueRecovery2 service. - * - * Note: This should be used with SVR2 only. For SVR1 clients, use {@link PinHash#svr1} - * - * @param pin A normalized, UTF-8 encoded byte representation of the pin - * @param username The Basic Auth username used to authenticate with SVR2 - * @param mrenclave The mrenclave where the hashed pin will be stored - * @return A {@link PinHash} - */ - public static PinHash svr2(final byte[] normalizedPin, final String username, final byte[] mrenclave) { - return new PinHash(Native.PinHash_FromUsernameMrenclave(normalizedPin, username, mrenclave)); - } + /** + * A key that can be used to encrypt or decrypt values before uploading them to a secure store. + * + * @return a 32 byte encryption key + */ + public byte[] encryptionKey() { + return Native.PinHash_EncryptionKey(unsafeHandle); + } - /** - * A key that can be used to encrypt or decrypt values before uploading them to a secure store. - * - * @return a 32 byte encryption key - */ - public byte[] encryptionKey() { - return Native.PinHash_EncryptionKey(unsafeHandle); - } - - /** - * A secret that can be used to access a value in a secure store. - * - * @return a 32 byte access key - */ - public byte[] accessKey() { - return Native.PinHash_AccessKey(unsafeHandle); - } + /** + * A secret that can be used to access a value in a secure store. + * + * @return a 32 byte access key + */ + public byte[] accessKey() { + return Native.PinHash_AccessKey(unsafeHandle); + } } diff --git a/java/client/src/main/java/org/signal/libsignal/svr2/Svr2Client.java b/java/client/src/main/java/org/signal/libsignal/svr2/Svr2Client.java index 9776a93020..e96e1e756e 100644 --- a/java/client/src/main/java/org/signal/libsignal/svr2/Svr2Client.java +++ b/java/client/src/main/java/org/signal/libsignal/svr2/Svr2Client.java @@ -5,19 +5,21 @@ package org.signal.libsignal.svr2; -import org.signal.libsignal.sgxsession.SgxClient; +import java.time.Instant; import org.signal.libsignal.attest.AttestationDataException; import org.signal.libsignal.internal.Native; - -import java.time.Instant; +import org.signal.libsignal.sgxsession.SgxClient; /** - * Svr2Client provides bindings to interact with Signal's v2 Secure Value Recovery service.

    + * Svr2Client provides bindings to interact with Signal's v2 Secure Value Recovery service. + * *

    - * {@inheritDoc} + * + *

    {@inheritDoc} */ public class Svr2Client extends SgxClient { - public Svr2Client(byte[] mrenclave, byte[] attestationMsg, Instant currentInstant) throws AttestationDataException { - super(Native.Svr2Client_New(mrenclave, attestationMsg, currentInstant.toEpochMilli())); - } + public Svr2Client(byte[] mrenclave, byte[] attestationMsg, Instant currentInstant) + throws AttestationDataException { + super(Native.Svr2Client_New(mrenclave, attestationMsg, currentInstant.toEpochMilli())); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/crypto/Aes256Ctr32Tests.java b/java/client/src/test/java/org/signal/libsignal/crypto/Aes256Ctr32Tests.java index 50bddabd98..af210f0277 100644 --- a/java/client/src/test/java/org/signal/libsignal/crypto/Aes256Ctr32Tests.java +++ b/java/client/src/test/java/org/signal/libsignal/crypto/Aes256Ctr32Tests.java @@ -14,19 +14,16 @@ public class Aes256Ctr32Tests extends TestCase { public void testAesCtr32Kats() throws Exception { testAesCtr32Kat( - "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", - "fd4c14729f5004ba49d832ad7be87c18f4fafb58962b9a43c3be41713ded93dbf854ac4ca26285b7f76e04b8f8d4e7d9f7548f9b465c8f713c106e9f63f54305331a4983a2f4b718de29fa794da12eee808642faeff8271a0ea28e3cc80eeb65a8eb61f69d8ba97f6bf9054453f55efb8f9422081f1620fe44acf99e81122f73d3f921d5e3391654e9947904984375b725fdfba895c5cde3d225d7be3a213c3965178a7dc1e3b552ec7b2ffd9c77ebcc243c4500dfdfbe3b7554aa427c01305bec48d71af27c5911d1e649c620d22cf5f3a5aeb9468651da796f369522faf91efabf0febd33fca41c9534606a4ea0199b904b243ba9cb8f37a792df02efab8f0e2e0cf1d579daba042cfe4c9430ad4eda786052fcf15e7acfa2736aab4590f73675fa1805fe23892c63e0cd01d006935a6e3f8e105a754803d00d9857e49636ab034164156856d58a244ead475300d93b31e44b5be3bbf6994edb895804b4f1bad43ecfe08b4e130148b669fe620e4f73034fc3e748237870bec3b1f517684654d1d6bc074ddf7b759a2405f78ed84d1006d25af9bbc12d6c632f5d543da0cbe9ea866b2c92126009c27ad59394b76337de246b50895317e2e345df3629a5f6227f64522866e7a39121ccc552e3dabc989dce066dea355f788c5d92ada099917a297cfefa867ce37656fac6a50798c10b394d5ba54f85cf0f7ef1eeddfca1e53e93f1349888cc745190c196f84ecf0721287cc592d406f0a6cc5a55294bf7aa3b35f6cefc61cab794b12444312b5e50ec0712e221cc95e9e26e9c3d000881e792afcb58641b1a94613d64ec72f3db9ab65ba07a4f05b7e9ee7b335d86a06fcbdb8cbd695aeef53964a965ffe4c6d7b4e580ab139f8422a702e09eacbea5d512c31a955b3d60310be2bbdd73484bae6612791a19da3c7b0fd1487e72131a8f9cb801790ce8a6e1e378662cedcd5ee82bd390576acfe5334ecd9d907273aefe67058916388210638e5e60f20ee92389b3533fd6affd33095b23d169f0913657f033b8d5c4ea517f167c1d53e031787bbe6d5b577245fff8151cd8fdcc5d6c32df70fb8043d42f896cd513b4c85cff292676cf13b6a1931e87727a561711a3105d9f3519b90c9429b5cd3edaae3ee334826a3fd74d6175b5589db392f956a67c5e67be59656f1cb37e52c636b2692a60c2044327472fa9af651afbcf55d8398a31d343074931a72d54833b29ef21fcb6ef419bb56313513e46c65d833677dbb0f2813e9ce5ef70676102ca0d3c14bbdd659a7498fa08cd359d428a803aefcc660e9fc704e9bacc5f1d27f2528d46b3fcaa2d47dfa28bf4c", - "f0c1dd48e5843eb03de5abb298697dc0f103a9d0c230620bcd86467758379daa01ae18087d96096a8814e98808ab9b9c943917273054201ca3cdf2d49f3ac7896d34db1cb1d7959b4dd503f7b25b3390e0dbcacb15bbe8978236d75ae24d7ca0c4d516846ec0cc0e05b505b3d9d1c6e50165918c26672ed1525265b29f6336138cedca58e7f447a81b9485f743b5e01fd5a543f18d9335c5e2d19cae8245a9224a2baabdf7670e47bd22cf465df8563621124a8091325c670e4f8fa028686505cee87d52d63d1965e65daf61f5e1b00ae33d4e5a42496950e8d75710cf8c47718f6071850d11b552e19ba0fabef5ccc7813ba4bd0b593694b317f04fbe9caf48aff14a4555f78ab056d4148747c7bd5a8b6e4bc85d42aae4e2634ad9028e5f32345a6813c291588362a7ecf6e0c3b3a3db9dbaa82d2754962f5d9b3e0fd166cb11b5254081417dac0e35c00b56ebebd12112ae202c094fe3b24252f0787fb09c6c51036ceac6ddde4ac59aada7c76bc79e950b66ffe6a015450e8770c8b2b491ccec7610bf9a7f523e5a579ff64c62700a7e8304139c68cfdab34f7ad18b8989a9802ed9dd393d889cf4d526c9b53fdb0b78dcfad47b88c23d6992e0e63c31f80d69b427ea7e71944a61013a0c70b2e9cfe233a61cb4939d2fdde75e6ff8fee6b45d481ad0ad0110469edffc01b1bf2e4f1414f925d86ad198a27a0388637edc7dd547b8aeca86eccb3ad5c0615af8428096c8142d75235c465995e5eff6225e94913457551c1c185e1d7bfa2437ab56da49954834628ac480d7bada35ecbc34dc6efeb26009c82a0cc3f477757a91dc6d652ce7edd82cb891ba3b49bfeb74bd2a35b3f5bce74a34359dc00db8e0961cb9758cd99ef25cf718974d60ed5e7733f525c81edb0464c7930add3e9336d8715aeb37bb624844246a19d433c0ed615c221e5e89745d2467743773560639894b1abd0f6e5289b5826cee5fca76bdd6d0d4dd69fb4a50d7d814a48c7e35920abb8f0c1e60ba92d612f4f4bf5695a089de639bfbc6f317f4fd895d3257efbe1d49e944b82badd4b21164d4bae7a872f183a3c8385f54fdd8f471672132dd44e51ccdcfe183c0ce00032a048866af6dfea9e15b58a1709320e8fca16defeab233027a9ea3118a521c94be5c48a72de9c6fabf2196e123fc1356dea223712599758a2f6ffe91921c1acee3ec6c7b7a29a1d3c5f88ae6fb50b42e36c0773731e28ca3c93a18627d287ed5f538691421dffd36e3bb871854bc585f367edbe70b029f81f3605982eafa4135e54b78d0c6cdf18afe22ff7308da7011f15d3524906f10fb6b780fa9cc4b", - "a6aad9eced14bf1c61910dba", - 35); + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "fd4c14729f5004ba49d832ad7be87c18f4fafb58962b9a43c3be41713ded93dbf854ac4ca26285b7f76e04b8f8d4e7d9f7548f9b465c8f713c106e9f63f54305331a4983a2f4b718de29fa794da12eee808642faeff8271a0ea28e3cc80eeb65a8eb61f69d8ba97f6bf9054453f55efb8f9422081f1620fe44acf99e81122f73d3f921d5e3391654e9947904984375b725fdfba895c5cde3d225d7be3a213c3965178a7dc1e3b552ec7b2ffd9c77ebcc243c4500dfdfbe3b7554aa427c01305bec48d71af27c5911d1e649c620d22cf5f3a5aeb9468651da796f369522faf91efabf0febd33fca41c9534606a4ea0199b904b243ba9cb8f37a792df02efab8f0e2e0cf1d579daba042cfe4c9430ad4eda786052fcf15e7acfa2736aab4590f73675fa1805fe23892c63e0cd01d006935a6e3f8e105a754803d00d9857e49636ab034164156856d58a244ead475300d93b31e44b5be3bbf6994edb895804b4f1bad43ecfe08b4e130148b669fe620e4f73034fc3e748237870bec3b1f517684654d1d6bc074ddf7b759a2405f78ed84d1006d25af9bbc12d6c632f5d543da0cbe9ea866b2c92126009c27ad59394b76337de246b50895317e2e345df3629a5f6227f64522866e7a39121ccc552e3dabc989dce066dea355f788c5d92ada099917a297cfefa867ce37656fac6a50798c10b394d5ba54f85cf0f7ef1eeddfca1e53e93f1349888cc745190c196f84ecf0721287cc592d406f0a6cc5a55294bf7aa3b35f6cefc61cab794b12444312b5e50ec0712e221cc95e9e26e9c3d000881e792afcb58641b1a94613d64ec72f3db9ab65ba07a4f05b7e9ee7b335d86a06fcbdb8cbd695aeef53964a965ffe4c6d7b4e580ab139f8422a702e09eacbea5d512c31a955b3d60310be2bbdd73484bae6612791a19da3c7b0fd1487e72131a8f9cb801790ce8a6e1e378662cedcd5ee82bd390576acfe5334ecd9d907273aefe67058916388210638e5e60f20ee92389b3533fd6affd33095b23d169f0913657f033b8d5c4ea517f167c1d53e031787bbe6d5b577245fff8151cd8fdcc5d6c32df70fb8043d42f896cd513b4c85cff292676cf13b6a1931e87727a561711a3105d9f3519b90c9429b5cd3edaae3ee334826a3fd74d6175b5589db392f956a67c5e67be59656f1cb37e52c636b2692a60c2044327472fa9af651afbcf55d8398a31d343074931a72d54833b29ef21fcb6ef419bb56313513e46c65d833677dbb0f2813e9ce5ef70676102ca0d3c14bbdd659a7498fa08cd359d428a803aefcc660e9fc704e9bacc5f1d27f2528d46b3fcaa2d47dfa28bf4c", + "f0c1dd48e5843eb03de5abb298697dc0f103a9d0c230620bcd86467758379daa01ae18087d96096a8814e98808ab9b9c943917273054201ca3cdf2d49f3ac7896d34db1cb1d7959b4dd503f7b25b3390e0dbcacb15bbe8978236d75ae24d7ca0c4d516846ec0cc0e05b505b3d9d1c6e50165918c26672ed1525265b29f6336138cedca58e7f447a81b9485f743b5e01fd5a543f18d9335c5e2d19cae8245a9224a2baabdf7670e47bd22cf465df8563621124a8091325c670e4f8fa028686505cee87d52d63d1965e65daf61f5e1b00ae33d4e5a42496950e8d75710cf8c47718f6071850d11b552e19ba0fabef5ccc7813ba4bd0b593694b317f04fbe9caf48aff14a4555f78ab056d4148747c7bd5a8b6e4bc85d42aae4e2634ad9028e5f32345a6813c291588362a7ecf6e0c3b3a3db9dbaa82d2754962f5d9b3e0fd166cb11b5254081417dac0e35c00b56ebebd12112ae202c094fe3b24252f0787fb09c6c51036ceac6ddde4ac59aada7c76bc79e950b66ffe6a015450e8770c8b2b491ccec7610bf9a7f523e5a579ff64c62700a7e8304139c68cfdab34f7ad18b8989a9802ed9dd393d889cf4d526c9b53fdb0b78dcfad47b88c23d6992e0e63c31f80d69b427ea7e71944a61013a0c70b2e9cfe233a61cb4939d2fdde75e6ff8fee6b45d481ad0ad0110469edffc01b1bf2e4f1414f925d86ad198a27a0388637edc7dd547b8aeca86eccb3ad5c0615af8428096c8142d75235c465995e5eff6225e94913457551c1c185e1d7bfa2437ab56da49954834628ac480d7bada35ecbc34dc6efeb26009c82a0cc3f477757a91dc6d652ce7edd82cb891ba3b49bfeb74bd2a35b3f5bce74a34359dc00db8e0961cb9758cd99ef25cf718974d60ed5e7733f525c81edb0464c7930add3e9336d8715aeb37bb624844246a19d433c0ed615c221e5e89745d2467743773560639894b1abd0f6e5289b5826cee5fca76bdd6d0d4dd69fb4a50d7d814a48c7e35920abb8f0c1e60ba92d612f4f4bf5695a089de639bfbc6f317f4fd895d3257efbe1d49e944b82badd4b21164d4bae7a872f183a3c8385f54fdd8f471672132dd44e51ccdcfe183c0ce00032a048866af6dfea9e15b58a1709320e8fca16defeab233027a9ea3118a521c94be5c48a72de9c6fabf2196e123fc1356dea223712599758a2f6ffe91921c1acee3ec6c7b7a29a1d3c5f88ae6fb50b42e36c0773731e28ca3c93a18627d287ed5f538691421dffd36e3bb871854bc585f367edbe70b029f81f3605982eafa4135e54b78d0c6cdf18afe22ff7308da7011f15d3524906f10fb6b780fa9cc4b", + "a6aad9eced14bf1c61910dba", + 35); } private static void testAesCtr32Kat( - String hex_key, - String hex_plaintext, - String hex_ciphertext, - String hex_nonce, - int initialCtr) throws IOException, InvalidKeyException { + String hex_key, String hex_plaintext, String hex_ciphertext, String hex_nonce, int initialCtr) + throws IOException, InvalidKeyException { byte[] key = Hex.fromStringCondensed(hex_key); byte[] plaintext = Hex.fromStringCondensed(hex_plaintext); diff --git a/java/client/src/test/java/org/signal/libsignal/crypto/Aes256GcmEncryptionTests.java b/java/client/src/test/java/org/signal/libsignal/crypto/Aes256GcmEncryptionTests.java index 3ef05189d7..7c1bf31d4f 100644 --- a/java/client/src/test/java/org/signal/libsignal/crypto/Aes256GcmEncryptionTests.java +++ b/java/client/src/test/java/org/signal/libsignal/crypto/Aes256GcmEncryptionTests.java @@ -8,19 +8,18 @@ import java.io.IOException; import junit.framework.TestCase; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.util.Hex; public class Aes256GcmEncryptionTests extends TestCase { public void testAesGcmKats() throws Exception { testAesGcmKat( - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662", - "76fc6ece0f4e1768cddf8853bb2d551b", - "cafebabefacedbaddecaf888", - "feedfacedeadbeeffeedfacedeadbeefabaddad2"); + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662", + "76fc6ece0f4e1768cddf8853bb2d551b", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2"); } private static void testAesGcmKat( @@ -29,39 +28,40 @@ private static void testAesGcmKat( String hex_ciphertext, String hex_tag, String hex_nonce, - String hex_associated_data) throws IOException, InvalidKeyException { + String hex_associated_data) + throws IOException, InvalidKeyException { - byte[] key = Hex.fromStringCondensed(hex_key); - byte[] plaintext = Hex.fromStringCondensed(hex_plaintext); - byte[] nonce = Hex.fromStringCondensed(hex_nonce); - byte[] ad = Hex.fromStringCondensed(hex_associated_data); + byte[] key = Hex.fromStringCondensed(hex_key); + byte[] plaintext = Hex.fromStringCondensed(hex_plaintext); + byte[] nonce = Hex.fromStringCondensed(hex_nonce); + byte[] ad = Hex.fromStringCondensed(hex_associated_data); - Aes256GcmEncryption gcmEnc = new Aes256GcmEncryption(key, nonce, ad); - byte[] ciphertext = plaintext.clone(); - gcmEnc.encrypt(ciphertext); - byte[] tag = gcmEnc.computeTag(); - assertEquals(Hex.toStringCondensed(ciphertext), hex_ciphertext); - assertEquals(Hex.toStringCondensed(tag), hex_tag); + Aes256GcmEncryption gcmEnc = new Aes256GcmEncryption(key, nonce, ad); + byte[] ciphertext = plaintext.clone(); + gcmEnc.encrypt(ciphertext); + byte[] tag = gcmEnc.computeTag(); + assertEquals(Hex.toStringCondensed(ciphertext), hex_ciphertext); + assertEquals(Hex.toStringCondensed(tag), hex_tag); - Aes256GcmDecryption gcmDec = new Aes256GcmDecryption(key, nonce, ad); - byte[] decrypted = ciphertext.clone(); - gcmDec.decrypt(decrypted); - assertEquals(Hex.toStringCondensed(decrypted), hex_plaintext); - assertEquals(gcmDec.verifyTag(tag), true); + Aes256GcmDecryption gcmDec = new Aes256GcmDecryption(key, nonce, ad); + byte[] decrypted = ciphertext.clone(); + gcmDec.decrypt(decrypted); + assertEquals(Hex.toStringCondensed(decrypted), hex_plaintext); + assertEquals(gcmDec.verifyTag(tag), true); - Aes256GcmEncryption gcmEnc2 = new Aes256GcmEncryption(key, nonce, ad); - byte[] ciphertextSplit = plaintext.clone(); - gcmEnc2.encrypt(ciphertextSplit, 0, 1); - gcmEnc2.encrypt(ciphertextSplit, 1, plaintext.length - 1); - byte[] tag2 = gcmEnc2.computeTag(); - assertEquals(Hex.toStringCondensed(ciphertextSplit), hex_ciphertext); - assertEquals(Hex.toStringCondensed(tag2), hex_tag); + Aes256GcmEncryption gcmEnc2 = new Aes256GcmEncryption(key, nonce, ad); + byte[] ciphertextSplit = plaintext.clone(); + gcmEnc2.encrypt(ciphertextSplit, 0, 1); + gcmEnc2.encrypt(ciphertextSplit, 1, plaintext.length - 1); + byte[] tag2 = gcmEnc2.computeTag(); + assertEquals(Hex.toStringCondensed(ciphertextSplit), hex_ciphertext); + assertEquals(Hex.toStringCondensed(tag2), hex_tag); - Aes256GcmDecryption gcmDec2 = new Aes256GcmDecryption(key, nonce, ad); - byte[] decryptedSplit = ciphertext.clone(); - gcmDec2.decrypt(decryptedSplit, 0, 1); - gcmDec2.decrypt(decryptedSplit, 1, ciphertext.length - 1); - assertEquals(Hex.toStringCondensed(decryptedSplit), hex_plaintext); - assertEquals(gcmDec2.verifyTag(tag), true); + Aes256GcmDecryption gcmDec2 = new Aes256GcmDecryption(key, nonce, ad); + byte[] decryptedSplit = ciphertext.clone(); + gcmDec2.decrypt(decryptedSplit, 0, 1); + gcmDec2.decrypt(decryptedSplit, 1, ciphertext.length - 1); + assertEquals(Hex.toStringCondensed(decryptedSplit), hex_plaintext); + assertEquals(gcmDec2.verifyTag(tag), true); } } diff --git a/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicHashTests.java b/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicHashTests.java index 89151ed667..a13d5298ab 100644 --- a/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicHashTests.java +++ b/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicHashTests.java @@ -4,54 +4,62 @@ // package org.signal.libsignal.crypto; + import junit.framework.TestCase; -import org.signal.libsignal.crypto.CryptographicHash; import org.signal.libsignal.protocol.util.Hex; public class CryptographicHashTests extends TestCase { - void hashKat(String algo, String hexInput, String hexExpectedOutput) throws Exception { - CryptographicHash hash = new CryptographicHash(algo); - - byte[] input = Hex.fromStringCondensed(hexInput); - - hash.update(input); - byte[] digestAllInOne = hash.finish(); - - assertEquals(Hex.toStringCondensed(digestAllInOne), hexExpectedOutput); - - if(input.length > 1) { - hash.update(input, 0, 1); - hash.update(input, 1, input.length - 1); - byte[] digestSplit = hash.finish(); - assertEquals(Hex.toStringCondensed(digestSplit), hexExpectedOutput); - - hash.update(input, 0, input.length - 1); - hash.update(input, input.length - 1, 1); - byte[] digestSplit2 = hash.finish(); - assertEquals(Hex.toStringCondensed(digestSplit2), hexExpectedOutput); - } - } - - public void testSha1() throws Exception { - hashKat("Sha1", "", "da39a3ee5e6b4b0d3255bfef95601890afd80709"); - hashKat("Sha1", "616263", "a9993e364706816aba3e25717850c26c9cd0d89d"); - hashKat("Sha1", "f1ea1c9b787bad", "b234020692659c3dee19f7e75390984dd7e7ebbb"); - } - - public void testSha256() throws Exception { - hashKat("Sha256", "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - hashKat("Sha256", "616263", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); - hashKat("Sha256", "6d65737361676520646967657374", - "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"); - } - - public void testSha512() throws Exception { - hashKat("Sha512", "", - "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); - hashKat("Sha512", "616263", - "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); - hashKat("Sha512", "6d65737361676520646967657374", - "107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c"); - } + void hashKat(String algo, String hexInput, String hexExpectedOutput) throws Exception { + CryptographicHash hash = new CryptographicHash(algo); + + byte[] input = Hex.fromStringCondensed(hexInput); + + hash.update(input); + byte[] digestAllInOne = hash.finish(); + + assertEquals(Hex.toStringCondensed(digestAllInOne), hexExpectedOutput); + + if (input.length > 1) { + hash.update(input, 0, 1); + hash.update(input, 1, input.length - 1); + byte[] digestSplit = hash.finish(); + assertEquals(Hex.toStringCondensed(digestSplit), hexExpectedOutput); + + hash.update(input, 0, input.length - 1); + hash.update(input, input.length - 1, 1); + byte[] digestSplit2 = hash.finish(); + assertEquals(Hex.toStringCondensed(digestSplit2), hexExpectedOutput); + } + } + + public void testSha1() throws Exception { + hashKat("Sha1", "", "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + hashKat("Sha1", "616263", "a9993e364706816aba3e25717850c26c9cd0d89d"); + hashKat("Sha1", "f1ea1c9b787bad", "b234020692659c3dee19f7e75390984dd7e7ebbb"); + } + + public void testSha256() throws Exception { + hashKat("Sha256", "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + hashKat("Sha256", "616263", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + hashKat( + "Sha256", + "6d65737361676520646967657374", + "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"); + } + + public void testSha512() throws Exception { + hashKat( + "Sha512", + "", + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); + hashKat( + "Sha512", + "616263", + "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); + hashKat( + "Sha512", + "6d65737361676520646967657374", + "107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c"); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicMacTests.java b/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicMacTests.java index e8b1da70e2..36522cb94a 100644 --- a/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicMacTests.java +++ b/java/client/src/test/java/org/signal/libsignal/crypto/CryptographicMacTests.java @@ -4,58 +4,62 @@ // package org.signal.libsignal.crypto; + import junit.framework.TestCase; -import org.signal.libsignal.crypto.CryptographicMac; import org.signal.libsignal.protocol.util.Hex; public class CryptographicMacTests extends TestCase { - void hmacKat(String algo, String hexKey, String hexInput, String hexExpectedOutput) throws Exception { - CryptographicMac hmac = new CryptographicMac(algo, Hex.fromStringCondensed(hexKey)); - - byte[] input = Hex.fromStringCondensed(hexInput); - - hmac.update(input); - byte[] macAllInOne = hmac.finish(); - assertEquals(Hex.toStringCondensed(macAllInOne), hexExpectedOutput); - - if(input.length > 1) { - hmac.update(input, 0, 1); - hmac.update(input, 1, input.length - 1); - byte[] macSplit = hmac.finish(); - assertEquals(Hex.toStringCondensed(macSplit), hexExpectedOutput); - - hmac.update(input, 0, input.length - 1); - hmac.update(input, input.length - 1, 1); - byte[] macSplit2 = hmac.finish(); - assertEquals(Hex.toStringCondensed(macSplit2), hexExpectedOutput); - } - } - - public void testHmacSha1() throws Exception { - // RFC 2202 - hmacKat("HmacSha1", - "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "4869205468657265", - "b617318655057264e28bc0b6fb378c8ef146be00"); - - hmacKat("HmacSha1", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", - "125d7342b9ac11cd91a39af48aa17b4f63f175d3"); - } - - public void testHmacSha256() throws Exception { - // RFC 4231 - hmacKat("HmacSha256", - "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "4869205468657265", - "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"); - - hmacKat("HmacSha256", - "4a656665", - "7768617420646f2079612077616e7420666f72206e6f7468696e673f", - "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"); - } + void hmacKat(String algo, String hexKey, String hexInput, String hexExpectedOutput) + throws Exception { + CryptographicMac hmac = new CryptographicMac(algo, Hex.fromStringCondensed(hexKey)); + + byte[] input = Hex.fromStringCondensed(hexInput); + + hmac.update(input); + byte[] macAllInOne = hmac.finish(); + assertEquals(Hex.toStringCondensed(macAllInOne), hexExpectedOutput); + + if (input.length > 1) { + hmac.update(input, 0, 1); + hmac.update(input, 1, input.length - 1); + byte[] macSplit = hmac.finish(); + assertEquals(Hex.toStringCondensed(macSplit), hexExpectedOutput); + + hmac.update(input, 0, input.length - 1); + hmac.update(input, input.length - 1, 1); + byte[] macSplit2 = hmac.finish(); + assertEquals(Hex.toStringCondensed(macSplit2), hexExpectedOutput); + } + } + + public void testHmacSha1() throws Exception { + // RFC 2202 + hmacKat( + "HmacSha1", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "4869205468657265", + "b617318655057264e28bc0b6fb378c8ef146be00"); + + hmacKat( + "HmacSha1", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "125d7342b9ac11cd91a39af48aa17b4f63f175d3"); + } + + public void testHmacSha256() throws Exception { + // RFC 4231 + hmacKat( + "HmacSha256", + "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "4869205468657265", + "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"); + hmacKat( + "HmacSha256", + "4a656665", + "7768617420646f2079612077616e7420666f72206e6f7468696e673f", + "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/devicetransfer/DeviceTransferKeyTest.java b/java/client/src/test/java/org/signal/libsignal/devicetransfer/DeviceTransferKeyTest.java index cb6fe9d91f..6a54486582 100644 --- a/java/client/src/test/java/org/signal/libsignal/devicetransfer/DeviceTransferKeyTest.java +++ b/java/client/src/test/java/org/signal/libsignal/devicetransfer/DeviceTransferKeyTest.java @@ -5,10 +5,9 @@ package org.signal.libsignal.devicetransfer; -import junit.framework.TestCase; - import java.io.ByteArrayInputStream; import java.security.cert.CertificateFactory; +import junit.framework.TestCase; public class DeviceTransferKeyTest extends TestCase { public void testDeviceTransferKey() throws Exception { diff --git a/java/client/src/test/java/org/signal/libsignal/hsmenclave/HsmEnclaveClientTest.java b/java/client/src/test/java/org/signal/libsignal/hsmenclave/HsmEnclaveClientTest.java index 5ba76ddaa6..490e85d1fd 100644 --- a/java/client/src/test/java/org/signal/libsignal/hsmenclave/HsmEnclaveClientTest.java +++ b/java/client/src/test/java/org/signal/libsignal/hsmenclave/HsmEnclaveClientTest.java @@ -5,17 +5,24 @@ package org.signal.libsignal.hsmenclave; -import junit.framework.TestCase; - import java.util.ArrayList; import java.util.List; +import junit.framework.TestCase; public class HsmEnclaveClientTest extends TestCase { public void testCreateClient() throws Exception { byte[] validKey = new byte[32]; List hashes = new ArrayList<>(); - hashes.add(new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); - hashes.add(new byte[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}); + hashes.add( + new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + }); + hashes.add( + new byte[] { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1 + }); HsmEnclaveClient hsmEnclaveClient = new HsmEnclaveClient(validKey, hashes); byte[] initialMessage = hsmEnclaveClient.initialRequest(); assertEquals(112, initialMessage.length); @@ -24,8 +31,16 @@ public void testCreateClient() throws Exception { public void testCreateClientFailsWithInvalidPublicKey() { byte[] invalidKey = new byte[31]; List hashes = new ArrayList<>(); - hashes.add(new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); - hashes.add(new byte[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}); + hashes.add( + new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + }); + hashes.add( + new byte[] { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1 + }); try { new HsmEnclaveClient(invalidKey, hashes); } catch (IllegalArgumentException e) { @@ -37,8 +52,15 @@ public void testCreateClientFailsWithInvalidPublicKey() { public void testCreateClientFailsWithInvalidHash() { byte[] validKey = new byte[32]; List hashes = new ArrayList<>(); - hashes.add(new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); - hashes.add(new byte[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0}); + hashes.add( + new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }); + hashes.add( + new byte[] { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0 + }); try { new HsmEnclaveClient(validKey, hashes); } catch (IllegalArgumentException e) { @@ -61,10 +83,14 @@ public void testCreateClientFailsWithNoHashes() { public void testEstablishedSendFailsPriorToEstablishment() { byte[] validKey = new byte[32]; List hashes = new ArrayList<>(); - hashes.add(new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); + hashes.add( + new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + }); HsmEnclaveClient hsmEnclaveClient = new HsmEnclaveClient(validKey, hashes); try { - hsmEnclaveClient.establishedSend(new byte[]{1, 2, 3}); + hsmEnclaveClient.establishedSend(new byte[] {1, 2, 3}); } catch (IllegalStateException e) { return; } @@ -74,10 +100,14 @@ public void testEstablishedSendFailsPriorToEstablishment() { public void testEstablishedRecvFailsPriorToEstablishment() { byte[] validKey = new byte[32]; List hashes = new ArrayList<>(); - hashes.add(new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); + hashes.add( + new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + }); HsmEnclaveClient hsmEnclaveClient = new HsmEnclaveClient(validKey, hashes); try { - hsmEnclaveClient.establishedRecv(new byte[]{1, 2, 3}); + hsmEnclaveClient.establishedRecv(new byte[] {1, 2, 3}); } catch (IllegalStateException e) { return; } diff --git a/java/client/src/test/java/org/signal/libsignal/media/Mp4SanitizerTest.java b/java/client/src/test/java/org/signal/libsignal/media/Mp4SanitizerTest.java index 90ce1ccb57..da2f0a8aba 100644 --- a/java/client/src/test/java/org/signal/libsignal/media/Mp4SanitizerTest.java +++ b/java/client/src/test/java/org/signal/libsignal/media/Mp4SanitizerTest.java @@ -5,148 +5,153 @@ package org.signal.libsignal.media; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; -import org.signal.libsignal.protocol.util.ByteUtil; -import org.signal.libsignal.internal.Native; +import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; - -import static org.junit.Assert.fail; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.signal.libsignal.internal.Native; +import org.signal.libsignal.protocol.util.ByteUtil; public class Mp4SanitizerTest { - @Before - public void checkLibsignalMediaAvailable() { - try { - Native.SignalMedia_CheckAvailable(); - } catch (UnsatisfiedLinkError e) { - Assume.assumeNoException(e); - } + @Before + public void checkLibsignalMediaAvailable() { + try { + Native.SignalMedia_CheckAvailable(); + } catch (UnsatisfiedLinkError e) { + Assume.assumeNoException(e); } - - @Test - public void testEmptyMp4() throws Exception { - byte[] data = new byte[] {}; - try { - Mp4Sanitizer.sanitize(new ByteArrayInputStream(data), data.length); - fail("empty mp4 accepted"); - } catch (ParseException e) { - // good - } + } + + @Test + public void testEmptyMp4() throws Exception { + byte[] data = new byte[] {}; + try { + Mp4Sanitizer.sanitize(new ByteArrayInputStream(data), data.length); + fail("empty mp4 accepted"); + } catch (ParseException e) { + // good } - - @Test - public void testTruncatedMp4() throws Exception { - byte[] data = new byte[] { 0, 0, 0, 0 }; - try { - Mp4Sanitizer.sanitize(new ByteArrayInputStream(data), data.length); - fail("truncated mp4 accepted"); - } catch (ParseException e) { - // good - } + } + + @Test + public void testTruncatedMp4() throws Exception { + byte[] data = new byte[] {0, 0, 0, 0}; + try { + Mp4Sanitizer.sanitize(new ByteArrayInputStream(data), data.length); + fail("truncated mp4 accepted"); + } catch (ParseException e) { + // good } - - @Test - public void testNoopMinimalMp4() throws Exception { - byte[] metadata = ByteUtil.combine(ftyp(), moov()); - byte[] mp4Data = ByteUtil.combine(metadata, mdat()); - - SanitizedMetadata sanitized = Mp4Sanitizer.sanitize(new ByteArrayInputStream(mp4Data), mp4Data.length); - - assertSanitizedMetadataEquals(sanitized, metadata.length, mp4Data.length - metadata.length, null); - } - - @Test - public void testMinimalMp4() throws Exception { - byte[] metadata = ByteUtil.combine(ftyp(), moov()); - byte[] mp4Data = ByteUtil.combine(ftyp(), mdat(), moov()); - - SanitizedMetadata sanitized = Mp4Sanitizer.sanitize(new ByteArrayInputStream(mp4Data), mp4Data.length); - - assertSanitizedMetadataEquals(sanitized, ftyp().length, mp4Data.length - metadata.length, metadata); - } - - @Test - public void testMp4IoError() throws Exception { - InputStream ioErrorStream = new InputStream() { - @Override - public int read() throws IOException { - throw new IOException("test io error"); - } + } + + @Test + public void testNoopMinimalMp4() throws Exception { + byte[] metadata = ByteUtil.combine(ftyp(), moov()); + byte[] mp4Data = ByteUtil.combine(metadata, mdat()); + + SanitizedMetadata sanitized = + Mp4Sanitizer.sanitize(new ByteArrayInputStream(mp4Data), mp4Data.length); + + assertSanitizedMetadataEquals( + sanitized, metadata.length, mp4Data.length - metadata.length, null); + } + + @Test + public void testMinimalMp4() throws Exception { + byte[] metadata = ByteUtil.combine(ftyp(), moov()); + byte[] mp4Data = ByteUtil.combine(ftyp(), mdat(), moov()); + + SanitizedMetadata sanitized = + Mp4Sanitizer.sanitize(new ByteArrayInputStream(mp4Data), mp4Data.length); + + assertSanitizedMetadataEquals( + sanitized, ftyp().length, mp4Data.length - metadata.length, metadata); + } + + @Test + public void testMp4IoError() throws Exception { + InputStream ioErrorStream = + new InputStream() { + @Override + public int read() throws IOException { + throw new IOException("test io error"); + } }; - try { - Mp4Sanitizer.sanitize(ioErrorStream, 1); - fail("InputStream exception not propagated"); - } catch (IOException e) { - // good - } - } - - private static byte[] ftyp() throws IOException { - ByteArrayOutputStream ftypOutputStream = new ByteArrayOutputStream(); - DataOutputStream ftypDataOutputStream = new DataOutputStream(ftypOutputStream); - - ftypDataOutputStream.writeInt(20); // box size - ftypDataOutputStream.write("ftyp".getBytes()); // box type - ftypDataOutputStream.write("isom".getBytes()); // major_brand - ftypDataOutputStream.writeInt(0); // minor_version - ftypDataOutputStream.write("isom".getBytes()); // compatible_brands - - return ftypOutputStream.toByteArray(); - } - - private static byte[] moov() throws IOException { - ByteArrayOutputStream moovOutputStream = new ByteArrayOutputStream(); - DataOutputStream moovDataOutputStream = new DataOutputStream(moovOutputStream); - - // moov box header - moovDataOutputStream.writeInt(56); // box size - moovDataOutputStream.write("moov".getBytes()); // box type - - // trak box (inside moov box) - moovDataOutputStream.writeInt(48); // box size - moovDataOutputStream.write("trak".getBytes()); // box type - - // mdia box (inside trak box) - moovDataOutputStream.writeInt(40); // box size - moovDataOutputStream.write("mdia".getBytes()); // box type - - // minf box (inside mdia box) - moovDataOutputStream.writeInt(32); // box size - moovDataOutputStream.write("minf".getBytes()); // box type - - // stbl box (inside minf box) - moovDataOutputStream.writeInt(24); // box size - moovDataOutputStream.write("stbl".getBytes()); // box type - - // stco box (inside stbl box) - moovDataOutputStream.writeInt(16); // box size - moovDataOutputStream.write("stco".getBytes()); // box type - moovDataOutputStream.writeInt(0); // box version & flags - moovDataOutputStream.writeInt(0); // entry count - - return moovOutputStream.toByteArray(); - } - - private static byte[] mdat() throws IOException { - ByteArrayOutputStream mdatOutputStream = new ByteArrayOutputStream(); - DataOutputStream mdatDataOutputStream = new DataOutputStream(mdatOutputStream); - - mdatDataOutputStream.writeInt(8); // box size - mdatDataOutputStream.write("mdat".getBytes()); // box type - - return mdatOutputStream.toByteArray(); - } - - private static void assertSanitizedMetadataEquals(SanitizedMetadata sanitized, long dataOffset, long dataLength, byte[] metadata) { - Assert.assertArrayEquals(sanitized.getSanitizedMetadata(), metadata); - Assert.assertEquals(sanitized.getDataOffset(), dataOffset); - Assert.assertEquals(sanitized.getDataLength(), dataLength); + try { + Mp4Sanitizer.sanitize(ioErrorStream, 1); + fail("InputStream exception not propagated"); + } catch (IOException e) { + // good } + } + + private static byte[] ftyp() throws IOException { + ByteArrayOutputStream ftypOutputStream = new ByteArrayOutputStream(); + DataOutputStream ftypDataOutputStream = new DataOutputStream(ftypOutputStream); + + ftypDataOutputStream.writeInt(20); // box size + ftypDataOutputStream.write("ftyp".getBytes()); // box type + ftypDataOutputStream.write("isom".getBytes()); // major_brand + ftypDataOutputStream.writeInt(0); // minor_version + ftypDataOutputStream.write("isom".getBytes()); // compatible_brands + + return ftypOutputStream.toByteArray(); + } + + private static byte[] moov() throws IOException { + ByteArrayOutputStream moovOutputStream = new ByteArrayOutputStream(); + DataOutputStream moovDataOutputStream = new DataOutputStream(moovOutputStream); + + // moov box header + moovDataOutputStream.writeInt(56); // box size + moovDataOutputStream.write("moov".getBytes()); // box type + + // trak box (inside moov box) + moovDataOutputStream.writeInt(48); // box size + moovDataOutputStream.write("trak".getBytes()); // box type + + // mdia box (inside trak box) + moovDataOutputStream.writeInt(40); // box size + moovDataOutputStream.write("mdia".getBytes()); // box type + + // minf box (inside mdia box) + moovDataOutputStream.writeInt(32); // box size + moovDataOutputStream.write("minf".getBytes()); // box type + + // stbl box (inside minf box) + moovDataOutputStream.writeInt(24); // box size + moovDataOutputStream.write("stbl".getBytes()); // box type + + // stco box (inside stbl box) + moovDataOutputStream.writeInt(16); // box size + moovDataOutputStream.write("stco".getBytes()); // box type + moovDataOutputStream.writeInt(0); // box version & flags + moovDataOutputStream.writeInt(0); // entry count + + return moovOutputStream.toByteArray(); + } + + private static byte[] mdat() throws IOException { + ByteArrayOutputStream mdatOutputStream = new ByteArrayOutputStream(); + DataOutputStream mdatDataOutputStream = new DataOutputStream(mdatOutputStream); + + mdatDataOutputStream.writeInt(8); // box size + mdatDataOutputStream.write("mdat".getBytes()); // box type + + return mdatOutputStream.toByteArray(); + } + + private static void assertSanitizedMetadataEquals( + SanitizedMetadata sanitized, long dataOffset, long dataLength, byte[] metadata) { + Assert.assertArrayEquals(sanitized.getSanitizedMetadata(), metadata); + Assert.assertEquals(sanitized.getDataOffset(), dataOffset); + Assert.assertEquals(sanitized.getDataLength(), dataLength); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java b/java/client/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java index c834dedaf4..094ecbe062 100644 --- a/java/client/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java +++ b/java/client/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java @@ -1,13 +1,23 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.UUID; import junit.framework.TestCase; - +import org.signal.libsignal.internal.Native; +import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.metadata.SealedSessionCipher.DecryptionResult; -import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.metadata.certificate.CertificateValidator; import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.metadata.certificate.ServerCertificate; +import org.signal.libsignal.metadata.protocol.UnidentifiedSenderMessageContent; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidMessageException; @@ -30,81 +40,115 @@ import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.signal.libsignal.protocol.message.PlaintextContent; import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; +import org.signal.libsignal.protocol.state.KyberPreKeyRecord; import org.signal.libsignal.protocol.state.PreKeyBundle; import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.SessionRecord; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.signal.libsignal.protocol.state.KyberPreKeyRecord; - -import org.signal.libsignal.internal.Native; -import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.protocol.util.Hex; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Optional; -import java.util.UUID; - public class SealedSessionCipherTest extends TestCase { - private static SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair, int signedPreKeyId) - throws InvalidKeyException - { - ECKeyPair keyPair = Curve.generateKeyPair(); - byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); + private static SignedPreKeyRecord generateSignedPreKey( + IdentityKeyPair identityKeyPair, int signedPreKeyId) throws InvalidKeyException { + ECKeyPair keyPair = Curve.generateKeyPair(); + byte[] signature = + Curve.calculateSignature( + identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature); } - private static KyberPreKeyRecord generateKyberPreKey(IdentityKeyPair identityKeyPair, int kyberPreKeyId) - throws InvalidKeyException { - KEMKeyPair keyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024); - byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); + private static KyberPreKeyRecord generateKyberPreKey( + IdentityKeyPair identityKeyPair, int kyberPreKeyId) throws InvalidKeyException { + KEMKeyPair keyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024); + byte[] signature = + Curve.calculateSignature( + identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); return new KyberPreKeyRecord(kyberPreKeyId, System.currentTimeMillis(), keyPair, signature); } - public void testEncryptDecrypt() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidMetadataMessageException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { + public void testEncryptDecrypt() + throws UntrustedIdentityException, + InvalidKeyException, + InvalidCertificateException, + InvalidMetadataMessageException, + ProtocolDuplicateMessageException, + ProtocolUntrustedIdentityException, + ProtocolLegacyMessageException, + ProtocolInvalidKeyException, + InvalidMetadataVersionException, + ProtocolInvalidVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyIdException, + ProtocolNoSessionException, + SelfSendException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - byte[] ciphertext = aliceCipher.encrypt(bobAddress, senderCertificate, "smert za smert".getBytes()); - - - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); - - DecryptionResult plaintext = bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + byte[] ciphertext = + aliceCipher.encrypt(bobAddress, senderCertificate, "smert za smert".getBytes()); + + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + + DecryptionResult plaintext = + bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); assertEquals(new String(plaintext.getPaddedMessage()), "smert za smert"); assertEquals(plaintext.getSenderUuid(), "9d0652a3-dcc3-4d11-975f-74d61598733f"); - assertEquals(plaintext.getSenderAci().toServiceIdString(), "9d0652a3-dcc3-4d11-975f-74d61598733f"); + assertEquals( + plaintext.getSenderAci().toServiceIdString(), "9d0652a3-dcc3-4d11-975f-74d61598733f"); assertEquals(plaintext.getSenderE164().get(), "+14151111111"); assertEquals(plaintext.getDeviceId(), 1); } public void testEncryptDecryptUntrusted() throws Exception { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); - ECKeyPair falseTrustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(falseTrustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - byte[] ciphertext = aliceCipher.encrypt(bobAddress, senderCertificate, "\u0438 \u0432\u043E\u0442 \u044F".getBytes()); - - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + ECKeyPair falseTrustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + falseTrustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + byte[] ciphertext = + aliceCipher.encrypt( + bobAddress, senderCertificate, "\u0438 \u0432\u043E\u0442 \u044F".getBytes()); + + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); try { bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); @@ -116,18 +160,31 @@ public void testEncryptDecryptUntrusted() throws Exception { public void testEncryptDecryptExpired() throws Exception { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - byte[] ciphertext = aliceCipher.encrypt(bobAddress, senderCertificate, "\u0438 \u0432\u043E\u0442 \u044F".getBytes()); - - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + byte[] ciphertext = + aliceCipher.encrypt( + bobAddress, senderCertificate, "\u0438 \u0432\u043E\u0442 \u044F".getBytes()); + + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); try { bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31338); @@ -139,20 +196,31 @@ public void testEncryptDecryptExpired() throws Exception { public void testEncryptFromWrongIdentity() throws Exception { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); - ECKeyPair randomKeyPair = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, randomKeyPair.getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - byte[] ciphertext = aliceCipher.encrypt(bobAddress, senderCertificate, "smert za smert".getBytes()); - - - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + ECKeyPair randomKeyPair = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + randomKeyPair.getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + byte[] ciphertext = + aliceCipher.encrypt(bobAddress, senderCertificate, "smert za smert".getBytes()); + + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); try { bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); @@ -161,125 +229,276 @@ public void testEncryptFromWrongIdentity() throws Exception { } } - public void testEncryptDecryptGroup() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidMessageException, InvalidVersionException, InvalidMetadataMessageException, InvalidRegistrationIdException, LegacyMessageException, NoSessionException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { + public void testEncryptDecryptGroup() + throws UntrustedIdentityException, + InvalidKeyException, + InvalidCertificateException, + InvalidMessageException, + InvalidVersionException, + InvalidMetadataMessageException, + InvalidRegistrationIdException, + LegacyMessageException, + NoSessionException, + ProtocolDuplicateMessageException, + ProtocolUntrustedIdentityException, + ProtocolLegacyMessageException, + ProtocolInvalidKeyException, + InvalidMetadataVersionException, + ProtocolInvalidVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyIdException, + ProtocolNoSessionException, + SelfSendException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = + new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - SignalProtocolAddress senderAddress = new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + SignalProtocolAddress senderAddress = + new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); UUID distributionId = UUID.fromString("d1d1d1d1-7000-11eb-b32a-33b8a8a487a6"); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); GroupSessionBuilder aliceSessionBuilder = new GroupSessionBuilder(aliceStore); - GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); + GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, senderAddress); - GroupCipher bobGroupCipher = new GroupCipher(bobStore, senderAddress); + GroupCipher bobGroupCipher = new GroupCipher(bobStore, senderAddress); - SenderKeyDistributionMessage sentAliceDistributionMessage = aliceSessionBuilder.create(senderAddress, distributionId); - SenderKeyDistributionMessage receivedAliceDistributionMessage = new SenderKeyDistributionMessage(sentAliceDistributionMessage.serialize()); + SenderKeyDistributionMessage sentAliceDistributionMessage = + aliceSessionBuilder.create(senderAddress, distributionId); + SenderKeyDistributionMessage receivedAliceDistributionMessage = + new SenderKeyDistributionMessage(sentAliceDistributionMessage.serialize()); bobSessionBuilder.process(senderAddress, receivedAliceDistributionMessage); - CiphertextMessage ciphertextFromAlice = aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); + CiphertextMessage ciphertextFromAlice = + aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); - UnidentifiedSenderMessageContent usmcFromAlice = new UnidentifiedSenderMessageContent(ciphertextFromAlice, senderCertificate, UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, Optional.of(new byte[]{42, 43})); + UnidentifiedSenderMessageContent usmcFromAlice = + new UnidentifiedSenderMessageContent( + ciphertextFromAlice, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, + Optional.of(new byte[] {42, 43})); - byte[] aliceMessage = aliceCipher.multiRecipientEncrypt(Arrays.asList(bobAddress), usmcFromAlice); + byte[] aliceMessage = + aliceCipher.multiRecipientEncrypt(Arrays.asList(bobAddress), usmcFromAlice); byte[] bobMessage = SealedSessionCipher.multiRecipientMessageForSingleRecipient(aliceMessage); - DecryptionResult plaintext = bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), bobMessage, 31335); + DecryptionResult plaintext = + bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), bobMessage, 31335); assertEquals(new String(plaintext.getPaddedMessage()), "smert ze smert"); assertEquals(plaintext.getSenderUuid(), "9d0652a3-dcc3-4d11-975f-74d61598733f"); assertEquals(plaintext.getSenderE164().get(), "+14151111111"); assertEquals(plaintext.getDeviceId(), 1); - assertTrue(Arrays.equals(plaintext.getGroupId().get(), new byte[]{42, 43})); + assertTrue(Arrays.equals(plaintext.getGroupId().get(), new byte[] {42, 43})); } - - public void testEncryptGroupWithBadRegistrationId() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidMessageException, InvalidMetadataMessageException, InvalidRegistrationIdException, LegacyMessageException, NoSessionException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { + public void testEncryptGroupWithBadRegistrationId() + throws UntrustedIdentityException, + InvalidKeyException, + InvalidCertificateException, + InvalidMessageException, + InvalidMetadataMessageException, + InvalidRegistrationIdException, + LegacyMessageException, + NoSessionException, + ProtocolDuplicateMessageException, + ProtocolUntrustedIdentityException, + ProtocolLegacyMessageException, + ProtocolInvalidKeyException, + InvalidMetadataVersionException, + ProtocolInvalidVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyIdException, + ProtocolNoSessionException, + SelfSendException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = + new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); - ECKeyPair bobPreKey = Curve.generateKeyPair(); - IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); + ECKeyPair bobPreKey = Curve.generateKeyPair(); + IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); SignedPreKeyRecord bobSignedPreKey = generateSignedPreKey(bobIdentityKey, 2); KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); - PreKeyBundle bobBundle = new PreKeyBundle(0x4000, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey(), 12, bobKyberPreKey.getKeyPair().getPublicKey(), bobKyberPreKey.getSignature()); + PreKeyBundle bobBundle = + new PreKeyBundle( + 0x4000, + 1, + 1, + bobPreKey.getPublicKey(), + 2, + bobSignedPreKey.getKeyPair().getPublicKey(), + bobSignedPreKey.getSignature(), + bobIdentityKey.getPublicKey(), + 12, + bobKyberPreKey.getKeyPair().getPublicKey(), + bobKyberPreKey.getSignature()); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, bobAddress); aliceSessionBuilder.process(bobBundle); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - SignalProtocolAddress senderAddress = new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + SignalProtocolAddress senderAddress = + new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); UUID distributionId = UUID.fromString("d1d1d1d1-7000-11eb-b32a-33b8a8a487a6"); GroupSessionBuilder aliceGroupSessionBuilder = new GroupSessionBuilder(aliceStore); - SenderKeyDistributionMessage sentAliceDistributionMessage = aliceGroupSessionBuilder.create(senderAddress, distributionId); + SenderKeyDistributionMessage sentAliceDistributionMessage = + aliceGroupSessionBuilder.create(senderAddress, distributionId); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, senderAddress); - CiphertextMessage ciphertextFromAlice = aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); + CiphertextMessage ciphertextFromAlice = + aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); - UnidentifiedSenderMessageContent usmcFromAlice = new UnidentifiedSenderMessageContent(ciphertextFromAlice, senderCertificate, UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, Optional.of(new byte[]{42, 43})); + UnidentifiedSenderMessageContent usmcFromAlice = + new UnidentifiedSenderMessageContent( + ciphertextFromAlice, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, + Optional.of(new byte[] {42, 43})); try { - byte[] aliceMessage = aliceCipher.multiRecipientEncrypt(Arrays.asList(bobAddress), usmcFromAlice); + byte[] aliceMessage = + aliceCipher.multiRecipientEncrypt(Arrays.asList(bobAddress), usmcFromAlice); fail("should have thrown"); } catch (InvalidRegistrationIdException e) { assertEquals(e.getAddress(), bobAddress); } } - public void testEncryptGroupWithManyRecipients() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidMessageException, InvalidMetadataMessageException, InvalidRegistrationIdException, LegacyMessageException, NoSessionException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { + public void testEncryptGroupWithManyRecipients() + throws UntrustedIdentityException, + InvalidKeyException, + InvalidCertificateException, + InvalidMessageException, + InvalidMetadataMessageException, + InvalidRegistrationIdException, + LegacyMessageException, + NoSessionException, + ProtocolDuplicateMessageException, + ProtocolUntrustedIdentityException, + ProtocolLegacyMessageException, + ProtocolInvalidKeyException, + InvalidMetadataVersionException, + ProtocolInvalidVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyIdException, + ProtocolNoSessionException, + SelfSendException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); TestInMemorySignalProtocolStore carolStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); - SignalProtocolAddress carolAddress = new SignalProtocolAddress("38381c3b-2606-4ca7-9310-7cb927f2ab4a", 1); + SignalProtocolAddress bobAddress = + new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); + SignalProtocolAddress carolAddress = + new SignalProtocolAddress("38381c3b-2606-4ca7-9310-7cb927f2ab4a", 1); - ECKeyPair bobPreKey = Curve.generateKeyPair(); - IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); + ECKeyPair bobPreKey = Curve.generateKeyPair(); + IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); SignedPreKeyRecord bobSignedPreKey = generateSignedPreKey(bobIdentityKey, 2); - KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); + KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); - PreKeyBundle bobBundle = new PreKeyBundle(0x1234, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey(), 12, bobKyberPreKey.getKeyPair().getPublicKey(), bobKyberPreKey.getSignature()); + PreKeyBundle bobBundle = + new PreKeyBundle( + 0x1234, + 1, + 1, + bobPreKey.getPublicKey(), + 2, + bobSignedPreKey.getKeyPair().getPublicKey(), + bobSignedPreKey.getSignature(), + bobIdentityKey.getPublicKey(), + 12, + bobKyberPreKey.getKeyPair().getPublicKey(), + bobKyberPreKey.getSignature()); SessionBuilder aliceSessionBuilderForBob = new SessionBuilder(aliceStore, bobAddress); aliceSessionBuilderForBob.process(bobBundle); - ECKeyPair carolPreKey = Curve.generateKeyPair(); - IdentityKeyPair carolIdentityKey = carolStore.getIdentityKeyPair(); + ECKeyPair carolPreKey = Curve.generateKeyPair(); + IdentityKeyPair carolIdentityKey = carolStore.getIdentityKeyPair(); SignedPreKeyRecord carolSignedPreKey = generateSignedPreKey(carolIdentityKey, 2); - KyberPreKeyRecord carolKyberPreKey = generateKyberPreKey(carolIdentityKey, 12); - - PreKeyBundle carolBundle = new PreKeyBundle(0x1111, 1, 1, carolPreKey.getPublicKey(), 2, carolSignedPreKey.getKeyPair().getPublicKey(), carolSignedPreKey.getSignature(), carolIdentityKey.getPublicKey(), 12, carolKyberPreKey.getKeyPair().getPublicKey(), carolKyberPreKey.getSignature()); + KyberPreKeyRecord carolKyberPreKey = generateKyberPreKey(carolIdentityKey, 12); + + PreKeyBundle carolBundle = + new PreKeyBundle( + 0x1111, + 1, + 1, + carolPreKey.getPublicKey(), + 2, + carolSignedPreKey.getKeyPair().getPublicKey(), + carolSignedPreKey.getSignature(), + carolIdentityKey.getPublicKey(), + 12, + carolKyberPreKey.getKeyPair().getPublicKey(), + carolKyberPreKey.getSignature()); SessionBuilder aliceSessionBuilderForCarol = new SessionBuilder(aliceStore, carolAddress); aliceSessionBuilderForCarol.process(carolBundle); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - SignalProtocolAddress senderAddress = new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + SignalProtocolAddress senderAddress = + new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); UUID distributionId = UUID.fromString("d1d1d1d1-7000-11eb-b32a-33b8a8a487a6"); GroupSessionBuilder aliceGroupSessionBuilder = new GroupSessionBuilder(aliceStore); - SenderKeyDistributionMessage sentAliceDistributionMessage = aliceGroupSessionBuilder.create(senderAddress, distributionId); + SenderKeyDistributionMessage sentAliceDistributionMessage = + aliceGroupSessionBuilder.create(senderAddress, distributionId); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, senderAddress); - CiphertextMessage ciphertextFromAlice = aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); + CiphertextMessage ciphertextFromAlice = + aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); - UnidentifiedSenderMessageContent usmcFromAlice = new UnidentifiedSenderMessageContent(ciphertextFromAlice, senderCertificate, UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, Optional.of(new byte[]{42, 43})); + UnidentifiedSenderMessageContent usmcFromAlice = + new UnidentifiedSenderMessageContent( + ciphertextFromAlice, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, + Optional.of(new byte[] {42, 43})); ArrayList addresses = new ArrayList<>(); for (int i = 0; i < 1000; ++i) { @@ -292,35 +511,84 @@ public void testEncryptGroupWithManyRecipients() throws UntrustedIdentityExcepti byte[] aliceMessage = aliceCipher.multiRecipientEncrypt(addresses, usmcFromAlice); } - public void testEncryptGroupWithMissingSession() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidMessageException, InvalidMetadataMessageException, InvalidRegistrationIdException, LegacyMessageException, NoSessionException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { + public void testEncryptGroupWithMissingSession() + throws UntrustedIdentityException, + InvalidKeyException, + InvalidCertificateException, + InvalidMessageException, + InvalidMetadataMessageException, + InvalidRegistrationIdException, + LegacyMessageException, + NoSessionException, + ProtocolDuplicateMessageException, + ProtocolUntrustedIdentityException, + ProtocolLegacyMessageException, + ProtocolInvalidKeyException, + InvalidMetadataVersionException, + ProtocolInvalidVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyIdException, + ProtocolNoSessionException, + SelfSendException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); - SignalProtocolAddress carolAddress = new SignalProtocolAddress("38381c3b-2606-4ca7-9310-7cb927f2ab4a", 1); - - ECKeyPair bobPreKey = Curve.generateKeyPair(); - IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = + new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); + SignalProtocolAddress carolAddress = + new SignalProtocolAddress("38381c3b-2606-4ca7-9310-7cb927f2ab4a", 1); + + ECKeyPair bobPreKey = Curve.generateKeyPair(); + IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); SignedPreKeyRecord bobSignedPreKey = generateSignedPreKey(bobIdentityKey, 2); - KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); + KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); - PreKeyBundle bobBundle = new PreKeyBundle(0x1234, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey(), 12, bobKyberPreKey.getKeyPair().getPublicKey(), bobKyberPreKey.getSignature()); + PreKeyBundle bobBundle = + new PreKeyBundle( + 0x1234, + 1, + 1, + bobPreKey.getPublicKey(), + 2, + bobSignedPreKey.getKeyPair().getPublicKey(), + bobSignedPreKey.getSignature(), + bobIdentityKey.getPublicKey(), + 12, + bobKyberPreKey.getKeyPair().getPublicKey(), + bobKyberPreKey.getSignature()); SessionBuilder aliceSessionBuilderForBob = new SessionBuilder(aliceStore, bobAddress); aliceSessionBuilderForBob.process(bobBundle); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - SignalProtocolAddress senderAddress = new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + SignalProtocolAddress senderAddress = + new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); UUID distributionId = UUID.fromString("d1d1d1d1-7000-11eb-b32a-33b8a8a487a6"); GroupSessionBuilder aliceGroupSessionBuilder = new GroupSessionBuilder(aliceStore); - SenderKeyDistributionMessage sentAliceDistributionMessage = aliceGroupSessionBuilder.create(senderAddress, distributionId); + SenderKeyDistributionMessage sentAliceDistributionMessage = + aliceGroupSessionBuilder.create(senderAddress, distributionId); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, senderAddress); - CiphertextMessage ciphertextFromAlice = aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); + CiphertextMessage ciphertextFromAlice = + aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); - UnidentifiedSenderMessageContent usmcFromAlice = new UnidentifiedSenderMessageContent(ciphertextFromAlice, senderCertificate, UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, Optional.of(new byte[]{42, 43})); + UnidentifiedSenderMessageContent usmcFromAlice = + new UnidentifiedSenderMessageContent( + ciphertextFromAlice, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, + Optional.of(new byte[] {42, 43})); ArrayList addresses = new ArrayList<>(); for (int i = 0; i < 1000; ++i) { @@ -337,35 +605,73 @@ public void testEncryptGroupWithMissingSession() throws UntrustedIdentityExcepti } } - public void testProtocolException() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidMessageException, InvalidMetadataMessageException, InvalidRegistrationIdException, LegacyMessageException, NoSessionException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { + public void testProtocolException() + throws UntrustedIdentityException, + InvalidKeyException, + InvalidCertificateException, + InvalidMessageException, + InvalidMetadataMessageException, + InvalidRegistrationIdException, + LegacyMessageException, + NoSessionException, + ProtocolDuplicateMessageException, + ProtocolUntrustedIdentityException, + ProtocolLegacyMessageException, + ProtocolInvalidKeyException, + InvalidMetadataVersionException, + ProtocolInvalidVersionException, + ProtocolInvalidMessageException, + ProtocolInvalidKeyIdException, + ProtocolNoSessionException, + SelfSendException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = + new SignalProtocolAddress("e80f7bbe-5b94-471e-bd8c-2173654ea3d1", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); - - SignalProtocolAddress senderAddress = new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); + ECKeyPair trustRoot = Curve.generateKeyPair(); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + + SignalProtocolAddress senderAddress = + new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); UUID distributionId = UUID.fromString("d1d1d1d1-7000-11eb-b32a-33b8a8a487a6"); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); GroupSessionBuilder aliceSessionBuilder = new GroupSessionBuilder(aliceStore); - GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); + GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, senderAddress); - GroupCipher bobGroupCipher = new GroupCipher(bobStore, senderAddress); + GroupCipher bobGroupCipher = new GroupCipher(bobStore, senderAddress); // Send a group message without sending the distribution ID first. aliceSessionBuilder.create(senderAddress, distributionId); - CiphertextMessage ciphertextFromAlice = aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); - - UnidentifiedSenderMessageContent usmcFromAlice = new UnidentifiedSenderMessageContent(ciphertextFromAlice, senderCertificate, UnidentifiedSenderMessageContent.CONTENT_HINT_RESENDABLE, Optional.of(new byte[]{42, 1})); - - byte[] aliceMessage = aliceCipher.multiRecipientEncrypt(Arrays.asList(bobAddress), usmcFromAlice); + CiphertextMessage ciphertextFromAlice = + aliceGroupCipher.encrypt(distributionId, "smert ze smert".getBytes()); + + UnidentifiedSenderMessageContent usmcFromAlice = + new UnidentifiedSenderMessageContent( + ciphertextFromAlice, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_RESENDABLE, + Optional.of(new byte[] {42, 1})); + + byte[] aliceMessage = + aliceCipher.multiRecipientEncrypt(Arrays.asList(bobAddress), usmcFromAlice); byte[] bobMessage = SealedSessionCipher.multiRecipientMessageForSingleRecipient(aliceMessage); try { @@ -376,77 +682,145 @@ public void testProtocolException() throws UntrustedIdentityException, InvalidKe assertEquals(e.getSenderAci().toServiceIdString(), "9d0652a3-dcc3-4d11-975f-74d61598733f"); assertEquals(e.getSenderDevice(), 1); assertEquals(e.getContentHint(), UnidentifiedSenderMessageContent.CONTENT_HINT_RESENDABLE); - assertEquals(Hex.toStringCondensed(e.getGroupId().get()), Hex.toStringCondensed(new byte[]{42, 1})); + assertEquals( + Hex.toStringCondensed(e.getGroupId().get()), Hex.toStringCondensed(new byte[] {42, 1})); } } - public void testDecryptionErrorMessage() throws InvalidCertificateException, InvalidKeyException, InvalidMessageException, InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolDuplicateMessageException, ProtocolInvalidKeyException, ProtocolInvalidKeyIdException, ProtocolInvalidMessageException, ProtocolInvalidVersionException, ProtocolLegacyMessageException, ProtocolNoSessionException, ProtocolUntrustedIdentityException, SelfSendException, UntrustedIdentityException { + public void testDecryptionErrorMessage() + throws InvalidCertificateException, + InvalidKeyException, + InvalidMessageException, + InvalidMetadataMessageException, + InvalidMetadataVersionException, + NoSessionException, + ProtocolDuplicateMessageException, + ProtocolInvalidKeyException, + ProtocolInvalidKeyIdException, + ProtocolInvalidMessageException, + ProtocolInvalidVersionException, + ProtocolLegacyMessageException, + ProtocolNoSessionException, + ProtocolUntrustedIdentityException, + SelfSendException, + UntrustedIdentityException { TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); + TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolAddress bobAddress = new SignalProtocolAddress("+14152222222", 1); initializeSessions(aliceStore, bobStore, bobAddress); - ECKeyPair trustRoot = Curve.generateKeyPair(); + ECKeyPair trustRoot = Curve.generateKeyPair(); CertificateValidator certificateValidator = new CertificateValidator(trustRoot.getPublicKey()); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 1, + aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), + 31337); + SealedSessionCipher aliceCipher = + new SealedSessionCipher( + aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); // Send a message from Alice to Bob to set up the session. - byte[] ciphertext = aliceCipher.encrypt(bobAddress, senderCertificate, "smert za smert".getBytes()); + byte[] ciphertext = + aliceCipher.encrypt(bobAddress, senderCertificate, "smert za smert".getBytes()); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); + SealedSessionCipher bobCipher = + new SealedSessionCipher( + bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); bobCipher.decrypt(certificateValidator, ciphertext, 31335); // Pretend Bob's reply fails to decrypt. - SignalProtocolAddress aliceAddress = new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); + SignalProtocolAddress aliceAddress = + new SignalProtocolAddress("9d0652a3-dcc3-4d11-975f-74d61598733f", 1); SessionCipher bobUnsealedCipher = new SessionCipher(bobStore, aliceAddress); CiphertextMessage bobMessage = bobUnsealedCipher.encrypt("reply".getBytes()); - DecryptionErrorMessage errorMessage = DecryptionErrorMessage.forOriginalMessage(bobMessage.serialize(), bobMessage.getType(), 408, bobAddress.getDeviceId()); + DecryptionErrorMessage errorMessage = + DecryptionErrorMessage.forOriginalMessage( + bobMessage.serialize(), bobMessage.getType(), 408, bobAddress.getDeviceId()); PlaintextContent errorMessageContent = new PlaintextContent(errorMessage); - UnidentifiedSenderMessageContent errorMessageUsmc = new UnidentifiedSenderMessageContent(errorMessageContent, senderCertificate, UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, Optional.empty()); + UnidentifiedSenderMessageContent errorMessageUsmc = + new UnidentifiedSenderMessageContent( + errorMessageContent, + senderCertificate, + UnidentifiedSenderMessageContent.CONTENT_HINT_IMPLICIT, + Optional.empty()); byte[] errorMessageCiphertext = aliceCipher.encrypt(bobAddress, errorMessageUsmc); - DecryptionResult result = bobCipher.decrypt(certificateValidator, errorMessageCiphertext, 31335); - DecryptionErrorMessage bobErrorMessage = DecryptionErrorMessage.extractFromSerializedContent(result.getPaddedMessage()); + DecryptionResult result = + bobCipher.decrypt(certificateValidator, errorMessageCiphertext, 31335); + DecryptionErrorMessage bobErrorMessage = + DecryptionErrorMessage.extractFromSerializedContent(result.getPaddedMessage()); assertEquals(bobErrorMessage.getTimestamp(), 408); assertEquals(bobErrorMessage.getDeviceId(), bobAddress.getDeviceId()); SessionRecord bobSessionWithAlice = bobStore.loadSession(aliceAddress); - assert(bobSessionWithAlice.currentRatchetKeyMatches(bobErrorMessage.getRatchetKey().get())); + assert (bobSessionWithAlice.currentRatchetKeyMatches(bobErrorMessage.getRatchetKey().get())); } - private SenderCertificate createCertificateFor(ECKeyPair trustRoot, UUID uuid, String e164, int deviceId, ECPublicKey identityKey, long expires) + private SenderCertificate createCertificateFor( + ECKeyPair trustRoot, + UUID uuid, + String e164, + int deviceId, + ECPublicKey identityKey, + long expires) throws InvalidKeyException, InvalidCertificateException { ECKeyPair serverKey = Curve.generateKeyPair(); - try ( - NativeHandleGuard serverPublicGuard = new NativeHandleGuard(serverKey.getPublicKey()); - NativeHandleGuard trustRootPrivateGuard = new NativeHandleGuard(trustRoot.getPrivateKey()); - ) { - ServerCertificate serverCertificate = new ServerCertificate(Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); - - try ( - NativeHandleGuard identityGuard = new NativeHandleGuard(identityKey); - NativeHandleGuard serverCertificateGuard = new NativeHandleGuard(serverCertificate); - NativeHandleGuard serverPrivateGuard = new NativeHandleGuard(serverKey.getPrivateKey()); - ) { - return new SenderCertificate(Native.SenderCertificate_New(uuid.toString(), e164, deviceId, identityGuard.nativeHandle(), expires, serverCertificateGuard.nativeHandle(), serverPrivateGuard.nativeHandle())); + try (NativeHandleGuard serverPublicGuard = new NativeHandleGuard(serverKey.getPublicKey()); + NativeHandleGuard trustRootPrivateGuard = + new NativeHandleGuard(trustRoot.getPrivateKey()); ) { + ServerCertificate serverCertificate = + new ServerCertificate( + Native.ServerCertificate_New( + 1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); + + try (NativeHandleGuard identityGuard = new NativeHandleGuard(identityKey); + NativeHandleGuard serverCertificateGuard = new NativeHandleGuard(serverCertificate); + NativeHandleGuard serverPrivateGuard = + new NativeHandleGuard(serverKey.getPrivateKey()); ) { + return new SenderCertificate( + Native.SenderCertificate_New( + uuid.toString(), + e164, + deviceId, + identityGuard.nativeHandle(), + expires, + serverCertificateGuard.nativeHandle(), + serverPrivateGuard.nativeHandle())); } } } - private void initializeSessions(TestInMemorySignalProtocolStore aliceStore, TestInMemorySignalProtocolStore bobStore, SignalProtocolAddress bobAddress) - throws InvalidKeyException, UntrustedIdentityException - { - ECKeyPair bobPreKey = Curve.generateKeyPair(); - IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); + private void initializeSessions( + TestInMemorySignalProtocolStore aliceStore, + TestInMemorySignalProtocolStore bobStore, + SignalProtocolAddress bobAddress) + throws InvalidKeyException, UntrustedIdentityException { + ECKeyPair bobPreKey = Curve.generateKeyPair(); + IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair(); SignedPreKeyRecord bobSignedPreKey = generateSignedPreKey(bobIdentityKey, 2); - KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); + KyberPreKeyRecord bobKyberPreKey = generateKyberPreKey(bobIdentityKey, 12); - PreKeyBundle bobBundle = new PreKeyBundle(1, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey(), 12, bobKyberPreKey.getKeyPair().getPublicKey(), bobKyberPreKey.getSignature()); + PreKeyBundle bobBundle = + new PreKeyBundle( + 1, + 1, + 1, + bobPreKey.getPublicKey(), + 2, + bobSignedPreKey.getKeyPair().getPublicKey(), + bobSignedPreKey.getSignature(), + bobIdentityKey.getPublicKey(), + 12, + bobKyberPreKey.getKeyPair().getPublicKey(), + bobKyberPreKey.getSignature()); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, bobAddress); aliceSessionBuilder.process(bobBundle); diff --git a/java/client/src/test/java/org/signal/libsignal/metadata/TestInMemorySignalProtocolStore.java b/java/client/src/test/java/org/signal/libsignal/metadata/TestInMemorySignalProtocolStore.java index 0860b9eaa9..2306e22429 100644 --- a/java/client/src/test/java/org/signal/libsignal/metadata/TestInMemorySignalProtocolStore.java +++ b/java/client/src/test/java/org/signal/libsignal/metadata/TestInMemorySignalProtocolStore.java @@ -1,11 +1,14 @@ -package org.signal.libsignal.metadata; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +package org.signal.libsignal.metadata; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.state.impl.InMemorySignalProtocolStore; import org.signal.libsignal.protocol.util.KeyHelper; @@ -17,11 +20,11 @@ public TestInMemorySignalProtocolStore() { private static IdentityKeyPair generateIdentityKeyPair() { ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); + return new IdentityKeyPair( + new IdentityKey(identityKeyPairKeys.getPublicKey()), identityKeyPairKeys.getPrivateKey()); } private static int generateRegistrationId() { return KeyHelper.generateRegistrationId(false); } -} \ No newline at end of file +} diff --git a/java/client/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java b/java/client/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java index 145e13f114..13c319c26f 100644 --- a/java/client/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java +++ b/java/client/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java @@ -1,19 +1,20 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata.certificate; +import java.util.Optional; +import java.util.UUID; import junit.framework.TestCase; - +import org.signal.libsignal.internal.Native; +import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.InvalidKeyException; +import org.signal.libsignal.protocol.ServiceId; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECPublicKey; -import org.signal.libsignal.protocol.ecc.ECPrivateKey; -import org.signal.libsignal.protocol.ServiceId; - -import org.signal.libsignal.internal.Native; -import org.signal.libsignal.internal.NativeHandleGuard; - -import java.util.Optional; -import java.util.UUID; public class SenderCertificateTest extends TestCase { @@ -21,15 +22,29 @@ public class SenderCertificateTest extends TestCase { public void testSignature() throws InvalidCertificateException, InvalidKeyException { ECKeyPair key = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 31337, key.getPublicKey(), 31337); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 31337, + key.getPublicKey(), + 31337); new CertificateValidator(trustRoot.getPublicKey()).validate(senderCertificate, 31336); } public void testExpiredSignature() throws InvalidCertificateException, InvalidKeyException { - ECKeyPair key = Curve.generateKeyPair(); + ECKeyPair key = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 31338, key.getPublicKey(), 31337); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 31338, + key.getPublicKey(), + 31337); try { new CertificateValidator(trustRoot.getPublicKey()).validate(senderCertificate, 31338); throw new AssertionError(); @@ -39,9 +54,16 @@ public void testExpiredSignature() throws InvalidCertificateException, InvalidKe } public void testBadSignature() throws InvalidCertificateException, InvalidKeyException { - ECKeyPair key = Curve.generateKeyPair(); + ECKeyPair key = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 31338, key.getPublicKey(), 31337); + SenderCertificate senderCertificate = + createCertificateFor( + trustRoot, + UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), + "+14151111111", + 31338, + key.getPublicKey(), + 31337); byte[] badSignature = senderCertificate.getSerialized(); @@ -53,34 +75,51 @@ public void testBadSignature() throws InvalidCertificateException, InvalidKeyExc new CertificateValidator(trustRoot.getPublicKey()).validate(badCert, 31336); throw new AssertionError(); } catch (InvalidCertificateException e) { - // good + // good } } - public void testGetSenderAci() throws InvalidCertificateException, InvalidKeyException, ServiceId.InvalidServiceIdException { + public void testGetSenderAci() + throws InvalidCertificateException, InvalidKeyException, ServiceId.InvalidServiceIdException { ECKeyPair key = Curve.generateKeyPair(); UUID uuid = UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, uuid, null, 31338, key.getPublicKey(), 31337); + SenderCertificate senderCertificate = + createCertificateFor(trustRoot, uuid, null, 31338, key.getPublicKey(), 31337); assertEquals(Optional.empty(), senderCertificate.getSenderE164()); assertEquals(uuid, senderCertificate.getSenderAci().getRawUUID()); } - private SenderCertificate createCertificateFor(ECKeyPair trustRoot, UUID uuid, String e164, int deviceId, ECPublicKey identityKey, long expires) + private SenderCertificate createCertificateFor( + ECKeyPair trustRoot, + UUID uuid, + String e164, + int deviceId, + ECPublicKey identityKey, + long expires) throws InvalidKeyException, InvalidCertificateException { ECKeyPair serverKey = Curve.generateKeyPair(); - try ( - NativeHandleGuard serverPublicGuard = new NativeHandleGuard(serverKey.getPublicKey()); - NativeHandleGuard trustRootPrivateGuard = new NativeHandleGuard(trustRoot.getPrivateKey()); - ) { - ServerCertificate serverCertificate = new ServerCertificate(Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); - - try ( - NativeHandleGuard identityGuard = new NativeHandleGuard(identityKey); - NativeHandleGuard serverCertificateGuard = new NativeHandleGuard(serverCertificate); - NativeHandleGuard serverPrivateGuard = new NativeHandleGuard(serverKey.getPrivateKey()); - ) { - return new SenderCertificate(Native.SenderCertificate_New(uuid.toString(), e164, deviceId, identityGuard.nativeHandle(), expires, serverCertificateGuard.nativeHandle(), serverPrivateGuard.nativeHandle())); + try (NativeHandleGuard serverPublicGuard = new NativeHandleGuard(serverKey.getPublicKey()); + NativeHandleGuard trustRootPrivateGuard = + new NativeHandleGuard(trustRoot.getPrivateKey()); ) { + ServerCertificate serverCertificate = + new ServerCertificate( + Native.ServerCertificate_New( + 1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); + + try (NativeHandleGuard identityGuard = new NativeHandleGuard(identityKey); + NativeHandleGuard serverCertificateGuard = new NativeHandleGuard(serverCertificate); + NativeHandleGuard serverPrivateGuard = + new NativeHandleGuard(serverKey.getPrivateKey()); ) { + return new SenderCertificate( + Native.SenderCertificate_New( + uuid.toString(), + e164, + deviceId, + identityGuard.nativeHandle(), + expires, + serverCertificateGuard.nativeHandle(), + serverPrivateGuard.nativeHandle())); } } } diff --git a/java/client/src/test/java/org/signal/libsignal/metadata/certificate/ServerCertificateTest.java b/java/client/src/test/java/org/signal/libsignal/metadata/certificate/ServerCertificateTest.java index e8eac43929..7b48b35c3d 100644 --- a/java/client/src/test/java/org/signal/libsignal/metadata/certificate/ServerCertificateTest.java +++ b/java/client/src/test/java/org/signal/libsignal/metadata/certificate/ServerCertificateTest.java @@ -1,44 +1,50 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.metadata.certificate; import junit.framework.TestCase; - +import org.signal.libsignal.internal.Native; +import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; -import org.signal.libsignal.internal.Native; -import org.signal.libsignal.internal.NativeHandleGuard; - public class ServerCertificateTest extends TestCase { public void testSignature() throws InvalidKeyException, InvalidCertificateException { ECKeyPair trustRoot = Curve.generateKeyPair(); - ECKeyPair keyPair = Curve.generateKeyPair(); - - try ( - NativeHandleGuard serverPublicGuard = new NativeHandleGuard(keyPair.getPublicKey()); - NativeHandleGuard trustRootPrivateGuard = new NativeHandleGuard(trustRoot.getPrivateKey()); - ) { - ServerCertificate certificate = new ServerCertificate( - Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); - + ECKeyPair keyPair = Curve.generateKeyPair(); + + try (NativeHandleGuard serverPublicGuard = new NativeHandleGuard(keyPair.getPublicKey()); + NativeHandleGuard trustRootPrivateGuard = + new NativeHandleGuard(trustRoot.getPrivateKey()); ) { + ServerCertificate certificate = + new ServerCertificate( + Native.ServerCertificate_New( + 1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); + new CertificateValidator(trustRoot.getPublicKey()).validate(certificate); - + byte[] serialized = certificate.getSerialized(); - new CertificateValidator(trustRoot.getPublicKey()).validate(new ServerCertificate(serialized)); + new CertificateValidator(trustRoot.getPublicKey()) + .validate(new ServerCertificate(serialized)); } } public void testBadSignature() throws Exception { ECKeyPair trustRoot = Curve.generateKeyPair(); - ECKeyPair keyPair = Curve.generateKeyPair(); + ECKeyPair keyPair = Curve.generateKeyPair(); - try ( - NativeHandleGuard serverPublicGuard = new NativeHandleGuard(keyPair.getPublicKey()); - NativeHandleGuard trustRootPrivateGuard = new NativeHandleGuard(trustRoot.getPrivateKey()); - ) { - ServerCertificate certificate = new ServerCertificate( - Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); + try (NativeHandleGuard serverPublicGuard = new NativeHandleGuard(keyPair.getPublicKey()); + NativeHandleGuard trustRootPrivateGuard = + new NativeHandleGuard(trustRoot.getPrivateKey()); ) { + ServerCertificate certificate = + new ServerCertificate( + Native.ServerCertificate_New( + 1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())); byte[] badSignature = certificate.getSerialized(); @@ -47,12 +53,12 @@ public void testBadSignature() throws Exception { ServerCertificate badCert = new ServerCertificate(badSignature); try { - new CertificateValidator(trustRoot.getPublicKey()).validate(new ServerCertificate(badSignature)); - fail(); + new CertificateValidator(trustRoot.getPublicKey()) + .validate(new ServerCertificate(badSignature)); + fail(); } catch (InvalidCertificateException e) { - // good + // good } } } - } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/BundleFactory.java b/java/client/src/test/java/org/signal/libsignal/protocol/BundleFactory.java index b494f6ef07..0996fe9380 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/BundleFactory.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/BundleFactory.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import org.signal.libsignal.protocol.state.PreKeyBundle; diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/CurveTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/CurveTest.java index 7a5db7d5d8..63f3b7d4c6 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/CurveTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/CurveTest.java @@ -1,16 +1,20 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import junit.framework.TestCase; - import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; public class CurveTest extends TestCase { public void testLargeSignatures() throws InvalidKeyException { - ECKeyPair keys = Curve.generateKeyPair(); - byte[] message = new byte[1024 * 1024]; - byte[] signature = Curve.calculateSignature(keys.getPrivateKey(), message); + ECKeyPair keys = Curve.generateKeyPair(); + byte[] message = new byte[1024 * 1024]; + byte[] signature = Curve.calculateSignature(keys.getPrivateKey(), message); assertTrue(Curve.verifySignature(keys.getPublicKey(), message, signature)); @@ -18,5 +22,4 @@ public void testLargeSignatures() throws InvalidKeyException { assertFalse(Curve.verifySignature(keys.getPublicKey(), message, signature)); } - } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/IdentityKeyTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/IdentityKeyTest.java index 6869b6f1fe..dce6589e53 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/IdentityKeyTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/IdentityKeyTest.java @@ -6,8 +6,6 @@ package org.signal.libsignal.protocol; import junit.framework.TestCase; -import org.signal.libsignal.protocol.IdentityKey; -import org.signal.libsignal.protocol.IdentityKeyPair; public class IdentityKeyTest extends TestCase { public void testSignAlternateKey() { diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/PQXDHBundleFactory.java b/java/client/src/test/java/org/signal/libsignal/protocol/PQXDHBundleFactory.java index daa3a9b245..08643cd77b 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/PQXDHBundleFactory.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/PQXDHBundleFactory.java @@ -1,5 +1,11 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; +import java.util.Random; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.kem.KEMKeyPair; @@ -11,32 +17,45 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.util.Medium; -import java.util.Random; - public final class PQXDHBundleFactory implements BundleFactory { @Override public PreKeyBundle createBundle(SignalProtocolStore store) throws InvalidKeyException { - ECKeyPair preKeyPair = Curve.generateKeyPair(); - ECKeyPair signedPreKeyPair = Curve.generateKeyPair(); - byte[] signedPreKeySignature = Curve.calculateSignature(store.getIdentityKeyPair().getPrivateKey(), - signedPreKeyPair.getPublicKey().serialize()); - KEMKeyPair kyberPreKeyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024); - byte[] kyberPreKeySignature = Curve.calculateSignature(store.getIdentityKeyPair().getPrivateKey(), - kyberPreKeyPair.getPublicKey().serialize()); + ECKeyPair preKeyPair = Curve.generateKeyPair(); + ECKeyPair signedPreKeyPair = Curve.generateKeyPair(); + byte[] signedPreKeySignature = + Curve.calculateSignature( + store.getIdentityKeyPair().getPrivateKey(), + signedPreKeyPair.getPublicKey().serialize()); + KEMKeyPair kyberPreKeyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024); + byte[] kyberPreKeySignature = + Curve.calculateSignature( + store.getIdentityKeyPair().getPrivateKey(), kyberPreKeyPair.getPublicKey().serialize()); Random random = new Random(); int preKeyId = random.nextInt(Medium.MAX_VALUE); int signedPreKeyId = random.nextInt(Medium.MAX_VALUE); int kyberPreKeyId = random.nextInt(Medium.MAX_VALUE); store.storePreKey(preKeyId, new PreKeyRecord(preKeyId, preKeyPair)); - store.storeSignedPreKey(signedPreKeyId, new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), signedPreKeyPair, signedPreKeySignature)); - store.storeKyberPreKey(kyberPreKeyId, new KyberPreKeyRecord(kyberPreKeyId, System.currentTimeMillis(), kyberPreKeyPair, kyberPreKeySignature)); + store.storeSignedPreKey( + signedPreKeyId, + new SignedPreKeyRecord( + signedPreKeyId, System.currentTimeMillis(), signedPreKeyPair, signedPreKeySignature)); + store.storeKyberPreKey( + kyberPreKeyId, + new KyberPreKeyRecord( + kyberPreKeyId, System.currentTimeMillis(), kyberPreKeyPair, kyberPreKeySignature)); - return new PreKeyBundle(store.getLocalRegistrationId(), 1, - preKeyId, preKeyPair.getPublicKey(), - signedPreKeyId, signedPreKeyPair.getPublicKey(), signedPreKeySignature, + return new PreKeyBundle( + store.getLocalRegistrationId(), + 1, + preKeyId, + preKeyPair.getPublicKey(), + signedPreKeyId, + signedPreKeyPair.getPublicKey(), + signedPreKeySignature, store.getIdentityKeyPair().getPublicKey(), - kyberPreKeyId, kyberPreKeyPair.getPublicKey(), kyberPreKeySignature); + kyberPreKeyId, + kyberPreKeyPair.getPublicKey(), + kyberPreKeySignature); } } - diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/PlaintextContentTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/PlaintextContentTest.java index cc089c0ad2..e6e35226dc 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/PlaintextContentTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/PlaintextContentTest.java @@ -5,46 +5,53 @@ package org.signal.libsignal.protocol; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; + +import java.util.Arrays; import org.junit.Test; import org.signal.libsignal.protocol.message.CiphertextMessage; import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.signal.libsignal.protocol.message.PlaintextContent; -import org.signal.libsignal.protocol.util.Hex; - -import java.util.Arrays; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.fail; public class PlaintextContentTest { @Test public void testRoundTripSerialization() throws Exception { // We don't have the whole Content proto exposed at the Java level in this project, // so just check the literal bytes that we expect. - byte[] expectedPlaintextBody = new byte[] { - // DecryptionErrorMessage field, 9 bytes long - (byte)0x42, (byte)0x09, - // The serialized DecryptionErrorMessage - (byte)0x10, (byte)0x80, (byte)0xb8, (byte)0xbe, - (byte)0x97, (byte)0xe1, (byte)0x2f, (byte)0x18, - (byte)0x08, - // Tail padding marker - (byte)0x80, - }; + byte[] expectedPlaintextBody = + new byte[] { + // DecryptionErrorMessage field, 9 bytes long + (byte) 0x42, + (byte) 0x09, + // The serialized DecryptionErrorMessage + (byte) 0x10, + (byte) 0x80, + (byte) 0xb8, + (byte) 0xbe, + (byte) 0x97, + (byte) 0xe1, + (byte) 0x2f, + (byte) 0x18, + (byte) 0x08, + // Tail padding marker + (byte) 0x80, + }; long timestamp = 1640995200000L; int originalDeviceId = 8; // DEMs don't extract any information from the original message for a SenderKey message, // so we use that here to avoid having to construct a valid original message. - DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage( - new byte[] {}, CiphertextMessage.SENDERKEY_TYPE, timestamp, originalDeviceId); + DecryptionErrorMessage decryptionErrorMessage = + DecryptionErrorMessage.forOriginalMessage( + new byte[] {}, CiphertextMessage.SENDERKEY_TYPE, timestamp, originalDeviceId); byte[] serializedDEM = decryptionErrorMessage.serialize(); assertArrayEquals( - "expectedBody needs updating", - serializedDEM, - Arrays.copyOfRange(expectedPlaintextBody, 2, 2 + serializedDEM.length)); + "expectedBody needs updating", + serializedDEM, + Arrays.copyOfRange(expectedPlaintextBody, 2, 2 + serializedDEM.length)); byte[] serializedPlaintextContent = new PlaintextContent(decryptionErrorMessage).serialize(); byte[] deserializedPlaintextBody = new PlaintextContent(serializedPlaintextContent).getBody(); @@ -56,11 +63,13 @@ public void testDeserializationRejectsGarbage() throws Exception { try { new PlaintextContent(new byte[] {}); fail(); - } catch (InvalidMessageException e) {} + } catch (InvalidMessageException e) { + } try { - new PlaintextContent(new byte[] {(byte)0}); + new PlaintextContent(new byte[] {(byte) 0}); fail(); - } catch (InvalidVersionException e) {} + } catch (InvalidVersionException e) { + } } } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/ProtocolAddressTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/ProtocolAddressTest.java index 91d934a2ed..149c3a38e8 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/ProtocolAddressTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/ProtocolAddressTest.java @@ -5,14 +5,12 @@ package org.signal.libsignal.protocol; -import java.util.UUID; -import org.junit.Test; -import org.signal.libsignal.protocol.SignalProtocolAddress; -import org.signal.libsignal.protocol.ServiceId; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import java.util.UUID; +import org.junit.Test; + public class ProtocolAddressTest { @Test public void testRoundTripServiceId() { diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/ServiceIdTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/ServiceIdTest.java index cd56ab9362..1587442dde 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/ServiceIdTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/ServiceIdTest.java @@ -7,100 +7,106 @@ import java.util.UUID; import junit.framework.TestCase; - import org.signal.libsignal.protocol.ServiceId.InvalidServiceIdException; - import org.signal.libsignal.protocol.util.Hex; public class ServiceIdTest extends TestCase { - private static final String TEST_UUID_STRING = "c04d643e-1c2d-43b6-bcb7-d7f41f7f0990"; - private static final UUID TEST_UUID = UUID.fromString(TEST_UUID_STRING); - private static final String TEST_UUID_HEX = "c04d643e1c2d43b6bcb7d7f41f7f0990"; - - public void testFromUUIDAndBack() throws Exception { - UUID original = UUID.randomUUID(); - ServiceId.Aci aci = new ServiceId.Aci(original); - assertEquals(original, aci.getRawUUID()); - ServiceId.Pni pni = new ServiceId.Pni(original); - assertEquals(original, pni.getRawUUID()); + private static final String TEST_UUID_STRING = "c04d643e-1c2d-43b6-bcb7-d7f41f7f0990"; + private static final UUID TEST_UUID = UUID.fromString(TEST_UUID_STRING); + private static final String TEST_UUID_HEX = "c04d643e1c2d43b6bcb7d7f41f7f0990"; + + public void testFromUUIDAndBack() throws Exception { + UUID original = UUID.randomUUID(); + ServiceId.Aci aci = new ServiceId.Aci(original); + assertEquals(original, aci.getRawUUID()); + ServiceId.Pni pni = new ServiceId.Pni(original); + assertEquals(original, pni.getRawUUID()); + } + + public void testAciRepresentations() throws Exception { + ServiceId.Aci aci = new ServiceId.Aci(TEST_UUID); + assertEquals(TEST_UUID_STRING, aci.toServiceIdString()); + assertEquals(TEST_UUID_HEX, Hex.toStringCondensed(aci.toServiceIdBinary())); + assertEquals(String.format("", TEST_UUID_STRING), aci.toLogString()); + assertEquals(String.format("", TEST_UUID_STRING), aci.toString()); + } + + public void testPniRepresentations() throws Exception { + ServiceId.Pni pni = new ServiceId.Pni(TEST_UUID); + assertEquals(String.format("PNI:%s", TEST_UUID_STRING), pni.toServiceIdString()); + assertEquals( + String.format("01%s", TEST_UUID_HEX), Hex.toStringCondensed(pni.toServiceIdBinary())); + assertEquals(String.format("", TEST_UUID_STRING), pni.toLogString()); + assertEquals(String.format("", TEST_UUID_STRING), pni.toString()); + } + + public void testParseFromString() throws Exception { + assert (ServiceId.parseFromString(TEST_UUID_STRING) instanceof ServiceId.Aci); + ServiceId.Aci.parseFromString(TEST_UUID_STRING); + + assert (ServiceId.parseFromString(String.format("PNI:%s", TEST_UUID_STRING)) + instanceof ServiceId.Pni); + ServiceId.Pni.parseFromString(String.format("PNI:%s", TEST_UUID_STRING)); + + try { + ServiceId.parseFromString(String.format("ACI:%s", TEST_UUID_STRING)); + fail("Successfully parsed an invalid Service-Id-String"); + } catch (InvalidServiceIdException ex) { } - - public void testAciRepresentations() throws Exception { - ServiceId.Aci aci = new ServiceId.Aci(TEST_UUID); - assertEquals(TEST_UUID_STRING, aci.toServiceIdString()); - assertEquals(TEST_UUID_HEX, Hex.toStringCondensed(aci.toServiceIdBinary())); - assertEquals(String.format("", TEST_UUID_STRING), aci.toLogString()); - assertEquals(String.format("", TEST_UUID_STRING), aci.toString()); - } - - public void testPniRepresentations() throws Exception { - ServiceId.Pni pni = new ServiceId.Pni(TEST_UUID); - assertEquals(String.format("PNI:%s", TEST_UUID_STRING), pni.toServiceIdString()); - assertEquals(String.format("01%s", TEST_UUID_HEX), Hex.toStringCondensed(pni.toServiceIdBinary())); - assertEquals(String.format("", TEST_UUID_STRING), pni.toLogString()); - assertEquals(String.format("", TEST_UUID_STRING), pni.toString()); + } + + public void testParseFromBinary() throws Exception { + byte[] aciBytes = Hex.fromStringCondensedAssert(TEST_UUID_HEX); + assert (ServiceId.parseFromBinary(aciBytes) instanceof ServiceId.Aci); + ServiceId.Aci.parseFromBinary(aciBytes); + + byte[] pniBytes = Hex.fromStringCondensedAssert("01" + TEST_UUID_HEX); + assert (ServiceId.parseFromBinary(pniBytes) instanceof ServiceId.Pni); + ServiceId.Pni.parseFromBinary(pniBytes); + + byte[] invalidAciBytes = Hex.fromStringCondensedAssert("00" + TEST_UUID_HEX); + try { + ServiceId.parseFromBinary(invalidAciBytes); + fail("Successfully parsed in invalid Service-Id-Binary"); + } catch (InvalidServiceIdException ex) { } + } - public void testParseFromString() throws Exception { - assert(ServiceId.parseFromString(TEST_UUID_STRING) instanceof ServiceId.Aci); - ServiceId.Aci.parseFromString(TEST_UUID_STRING); - - assert(ServiceId.parseFromString(String.format("PNI:%s", TEST_UUID_STRING)) instanceof ServiceId.Pni); - ServiceId.Pni.parseFromString(String.format("PNI:%s", TEST_UUID_STRING)); - - try { - ServiceId.parseFromString(String.format("ACI:%s", TEST_UUID_STRING)); - fail("Successfully parsed an invalid Service-Id-String"); - } catch (InvalidServiceIdException ex) { - } + public void testNullInputs() throws Exception { + try { + new ServiceId.Aci((UUID) null); + fail("Should have failed"); + } catch (IllegalArgumentException ex) { } - - public void testParseFromBinary() throws Exception { - byte[] aciBytes = Hex.fromStringCondensedAssert(TEST_UUID_HEX); - assert(ServiceId.parseFromBinary(aciBytes) instanceof ServiceId.Aci); - ServiceId.Aci.parseFromBinary(aciBytes); - - byte[] pniBytes = Hex.fromStringCondensedAssert("01" + TEST_UUID_HEX); - assert(ServiceId.parseFromBinary(pniBytes) instanceof ServiceId.Pni); - ServiceId.Pni.parseFromBinary(pniBytes); - - byte[] invalidAciBytes = Hex.fromStringCondensedAssert("00" + TEST_UUID_HEX); - try { - ServiceId.parseFromBinary(invalidAciBytes); - fail("Successfully parsed in invalid Service-Id-Binary"); - } catch (InvalidServiceIdException ex) { - } + try { + new ServiceId.Pni((UUID) null); + fail("Should have failed"); + } catch (IllegalArgumentException ex) { } - - public void testNullInputs() throws Exception { - try { - new ServiceId.Aci((UUID)null); - fail("Should have failed"); - } catch (IllegalArgumentException ex){} - try { - new ServiceId.Pni((UUID)null); - fail("Should have failed"); - } catch (IllegalArgumentException ex){} - try { - ServiceId.parseFromString((String)null); - fail("Should have failed"); - } catch (InvalidServiceIdException ex){} - try { - ServiceId.parseFromBinary((byte[])null); - fail("Should have failed"); - } catch (InvalidServiceIdException ex){} + try { + ServiceId.parseFromString((String) null); + fail("Should have failed"); + } catch (InvalidServiceIdException ex) { } - - public void testInvalidServiceId() throws Exception { - try { - byte[] invalidServiceIdBytes = Hex.fromStringCondensedAssert("02" + TEST_UUID_HEX); - ServiceId.parseFromBinary(invalidServiceIdBytes); - fail("Should have failed"); - } catch(InvalidServiceIdException ex) {} - try { - String invalidServiceString = "SGL:" + TEST_UUID_STRING; - ServiceId.parseFromString(invalidServiceString); - fail("Should have failed"); - } catch(InvalidServiceIdException ex) {} + try { + ServiceId.parseFromBinary((byte[]) null); + fail("Should have failed"); + } catch (InvalidServiceIdException ex) { + } + } + + public void testInvalidServiceId() throws Exception { + try { + byte[] invalidServiceIdBytes = Hex.fromStringCondensedAssert("02" + TEST_UUID_HEX); + ServiceId.parseFromBinary(invalidServiceIdBytes); + fail("Should have failed"); + } catch (InvalidServiceIdException ex) { + } + try { + String invalidServiceString = "SGL:" + TEST_UUID_STRING; + ServiceId.parseFromString(invalidServiceString); + fail("Should have failed"); + } catch (InvalidServiceIdException ex) { } + } } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/SessionBuilderTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/SessionBuilderTest.java index ffe830fd6e..9b400c2488 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/SessionBuilderTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/SessionBuilderTest.java @@ -1,44 +1,46 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; -import org.signal.libsignal.protocol.ecc.Curve; -import org.signal.libsignal.protocol.ecc.ECKeyPair; -import org.signal.libsignal.protocol.kem.KEMKeyPair; -import org.signal.libsignal.protocol.kem.KEMKeyType; -import org.signal.libsignal.protocol.message.CiphertextMessage; -import org.signal.libsignal.protocol.message.PreKeySignalMessage; -import org.signal.libsignal.protocol.message.SignalMessage; -import org.signal.libsignal.protocol.state.IdentityKeyStore; -import org.signal.libsignal.protocol.state.KyberPreKeyRecord; -import org.signal.libsignal.protocol.state.PreKeyBundle; -import org.signal.libsignal.protocol.state.PreKeyRecord; -import org.signal.libsignal.protocol.state.SignalProtocolStore; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.signal.libsignal.protocol.util.Medium; -import org.signal.libsignal.protocol.util.Pair; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Random; import java.util.Set; - import org.junit.Test; +import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.junit.experimental.runners.Enclosed; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; +import org.signal.libsignal.protocol.message.CiphertextMessage; +import org.signal.libsignal.protocol.message.PreKeySignalMessage; +import org.signal.libsignal.protocol.message.SignalMessage; +import org.signal.libsignal.protocol.state.IdentityKeyStore; +import org.signal.libsignal.protocol.state.PreKeyBundle; +import org.signal.libsignal.protocol.state.SessionRecord; +import org.signal.libsignal.protocol.state.SignalProtocolStore; +import org.signal.libsignal.protocol.util.Medium; +import org.signal.libsignal.protocol.util.Pair; @RunWith(Enclosed.class) public class SessionBuilderTest { - private static final SignalProtocolAddress ALICE_ADDRESS = new SignalProtocolAddress("+14151111111", 1); - private static final SignalProtocolAddress BOB_ADDRESS = new SignalProtocolAddress("+14152222222", 1); + private static final SignalProtocolAddress ALICE_ADDRESS = + new SignalProtocolAddress("+14151111111", 1); + private static final SignalProtocolAddress BOB_ADDRESS = + new SignalProtocolAddress("+14152222222", 1); @RunWith(Parameterized.class) public static class Versioned { @@ -52,16 +54,24 @@ public Versioned(BundleFactory bundleFactory, int expectedVersion) { @Parameters(name = "v{1}") public static Collection data() throws Exception { - return Arrays.asList(new Object[][] { - {new X3DHBundleFactory(), 3}, - {new PQXDHBundleFactory(), 4} - }); + return Arrays.asList( + new Object[][] { + {new X3DHBundleFactory(), 3}, + {new PQXDHBundleFactory(), 4} + }); } @Test public void testBasicPreKey() - throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { - SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); + throws InvalidKeyException, + InvalidVersionException, + InvalidMessageException, + InvalidKeyIdException, + DuplicateMessageException, + LegacyMessageException, + UntrustedIdentityException, + NoSessionException { + SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); final SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); @@ -73,9 +83,9 @@ public void testBasicPreKey() assertTrue(aliceStore.containsSession(BOB_ADDRESS)); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionVersion() == expectedVersion); - String originalMessage = "Good, fast, cheap: pick two"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); - CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); + String originalMessage = "Good, fast, cheap: pick two"; + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); + CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); @@ -92,14 +102,15 @@ public void testBasicPreKey() CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(bobOutgoingMessage.getType() == CiphertextMessage.WHISPER_TYPE); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new SignalMessage(bobOutgoingMessage.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new SignalMessage(bobOutgoingMessage.serialize())); assertTrue(new String(alicePlaintext).equals(originalMessage)); runInteraction(aliceStore, bobStore); - aliceStore = new TestInMemorySignalProtocolStore(); + aliceStore = new TestInMemorySignalProtocolStore(); aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); + aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); PreKeyBundle anotherBundle = bundleFactory.createBundle(bobStore); aliceSessionBuilder.process(anotherBundle); @@ -110,17 +121,24 @@ public void testBasicPreKey() plaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(outgoingMessage.serialize())); fail("shouldn't be trusted!"); } catch (UntrustedIdentityException uie) { - bobStore.saveIdentity(ALICE_ADDRESS, new PreKeySignalMessage(outgoingMessage.serialize()).getIdentityKey()); + bobStore.saveIdentity( + ALICE_ADDRESS, new PreKeySignalMessage(outgoingMessage.serialize()).getIdentityKey()); } plaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(outgoingMessage.serialize())); assertTrue(new String(plaintext).equals(originalMessage)); Random random = new Random(); - PreKeyBundle badIdentityBundle = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, - random.nextInt(Medium.MAX_VALUE), Curve.generateKeyPair().getPublicKey(), - random.nextInt(Medium.MAX_VALUE), bobPreKey.getSignedPreKey(), bobPreKey.getSignedPreKeySignature(), - aliceStore.getIdentityKeyPair().getPublicKey()); + PreKeyBundle badIdentityBundle = + new PreKeyBundle( + bobStore.getLocalRegistrationId(), + 1, + random.nextInt(Medium.MAX_VALUE), + Curve.generateKeyPair().getPublicKey(), + random.nextInt(Medium.MAX_VALUE), + bobPreKey.getSignedPreKey(), + bobPreKey.getSignedPreKeySignature(), + aliceStore.getIdentityKeyPair().getPublicKey()); try { aliceSessionBuilder.process(badIdentityBundle); @@ -131,8 +149,16 @@ public void testBasicPreKey() } @Test - public void testRepeatBundleMessage() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException { - SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); + public void testRepeatBundleMessage() + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + InvalidKeyIdException, + DuplicateMessageException, + LegacyMessageException, + NoSessionException { + SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); @@ -140,8 +166,8 @@ public void testRepeatBundleMessage() throws InvalidKeyException, UntrustedIdent PreKeyBundle bobPreKey = bundleFactory.createBundle(bobStore); aliceSessionBuilder.process(bobPreKey); - String originalMessage = "Good, fast, cheap: pick two"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); + String originalMessage = "Good, fast, cheap: pick two"; + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes()); @@ -152,49 +178,60 @@ public void testRepeatBundleMessage() throws InvalidKeyException, UntrustedIdent SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); - byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); assertTrue(originalMessage.equals(new String(plaintext))); CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new SignalMessage(bobOutgoingMessage.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new SignalMessage(bobOutgoingMessage.serialize())); assertTrue(originalMessage.equals(new String(alicePlaintext))); // The test - PreKeySignalMessage incomingMessageTwo = new PreKeySignalMessage(outgoingMessageTwo.serialize()); + PreKeySignalMessage incomingMessageTwo = + new PreKeySignalMessage(outgoingMessageTwo.serialize()); plaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(incomingMessageTwo.serialize())); assertTrue(originalMessage.equals(new String(plaintext))); bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); - alicePlaintext = aliceSessionCipher.decrypt(new SignalMessage(bobOutgoingMessage.serialize())); + alicePlaintext = + aliceSessionCipher.decrypt(new SignalMessage(bobOutgoingMessage.serialize())); assertTrue(originalMessage.equals(new String(alicePlaintext))); } @Test public void testOptionalOneTimePreKey() throws Exception { - SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); - PreKeyBundle bobPreKey = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKey = bundleFactory.createBundle(bobStore); // Simply remove the pre-key information from a valid bundle - bobPreKey = new PreKeyBundle(bobPreKey.getRegistrationId(), 1, - -1, null, - bobPreKey.getSignedPreKeyId(), bobPreKey.getSignedPreKey(), bobPreKey.getSignedPreKeySignature(), - bobPreKey.getIdentityKey(), - bobPreKey.getKyberPreKeyId(), bobPreKey.getKyberPreKey(), bobPreKey.getKyberPreKeySignature()); + bobPreKey = + new PreKeyBundle( + bobPreKey.getRegistrationId(), + 1, + -1, + null, + bobPreKey.getSignedPreKeyId(), + bobPreKey.getSignedPreKey(), + bobPreKey.getSignedPreKeySignature(), + bobPreKey.getIdentityKey(), + bobPreKey.getKyberPreKeyId(), + bobPreKey.getKyberPreKey(), + bobPreKey.getKyberPreKeySignature()); aliceSessionBuilder.process(bobPreKey); assertTrue(aliceStore.containsSession(BOB_ADDRESS)); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionVersion() == expectedVersion); - String originalMessage = "Good, fast, cheap: pick two"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); - CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); + String originalMessage = "Good, fast, cheap: pick two"; + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); + CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); @@ -202,43 +239,95 @@ public void testOptionalOneTimePreKey() throws Exception { assertTrue(!incomingMessage.getPreKeyId().isPresent()); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); - byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); assertTrue(bobStore.containsSession(ALICE_ADDRESS)); assertEquals(bobStore.loadSession(ALICE_ADDRESS).getSessionVersion(), expectedVersion); assertNotNull(bobStore.loadSession(ALICE_ADDRESS).getAliceBaseKey()); assertEquals(originalMessage, new String(plaintext)); } - } + @Test + public void testExpiresUnacknowledgedSessions() + throws InvalidKeyException, + InvalidVersionException, + InvalidMessageException, + InvalidKeyIdException, + DuplicateMessageException, + LegacyMessageException, + UntrustedIdentityException, + NoSessionException { + SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); + + final SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + + PreKeyBundle bobPreKey = bundleFactory.createBundle(bobStore); + + aliceSessionBuilder.process(bobPreKey, Instant.EPOCH); + + SessionRecord initialSession = aliceStore.loadSession(BOB_ADDRESS); + assertTrue(initialSession.hasSenderChain(Instant.EPOCH)); + assertFalse(initialSession.hasSenderChain(Instant.EPOCH.plus(90, ChronoUnit.DAYS))); + + String originalMessage = "Good, fast, cheap: pick two"; + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); + CiphertextMessage outgoingMessage = + aliceSessionCipher.encrypt(originalMessage.getBytes(), Instant.EPOCH); + + assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); + + SessionRecord updatedSession = aliceStore.loadSession(BOB_ADDRESS); + assertTrue(updatedSession.hasSenderChain(Instant.EPOCH)); + assertFalse(updatedSession.hasSenderChain(Instant.EPOCH.plus(90, ChronoUnit.DAYS))); + + try { + aliceSessionCipher.encrypt( + originalMessage.getBytes(), Instant.EPOCH.plus(90, ChronoUnit.DAYS)); + fail("should have expired"); + } catch (NoSessionException e) { + // Expected + } + } + } public static class VersionAgnostic { @Test - public void testBadSignedPreKeySignature() throws InvalidKeyException, UntrustedIdentityException { - SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); + public void testBadSignedPreKeySignature() + throws InvalidKeyException, UntrustedIdentityException { + SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); IdentityKeyStore bobIdentityKeyStore = new TestInMemoryIdentityKeyStore(); - ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); - ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); - byte[] bobSignedPreKeySignature = Curve.calculateSignature( - bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), - bobSignedPreKeyPair.getPublicKey().serialize()); - + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = + Curve.calculateSignature( + bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); - for (int i=0;i> aliceOutOfOrderMessages = new HashSet<>(); - for (int i=0;i<10;i++) { - String loopingMessage = ("What do we mean by saying that existence precedes essence? " + - "We mean that man first of all exists, encounters himself, " + - "surges up in the world--and defines himself aftward. " + i); + for (int i = 0; i < 10; i++) { + String loopingMessage = + ("What do we mean by saying that existence precedes essence? " + + "We mean that man first of all exists, encounters himself, " + + "surges up in the world--and defines himself aftward. " + + i); CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes()); aliceOutOfOrderMessages.add(new Pair<>(loopingMessage, aliceLoopingMessage)); } - for (int i=0;i<10;i++) { - String loopingMessage = ("What do we mean by saying that existence precedes essence? " + - "We mean that man first of all exists, encounters himself, " + - "surges up in the world--and defines himself aftward. " + i); + for (int i = 0; i < 10; i++) { + String loopingMessage = + ("What do we mean by saying that existence precedes essence? " + + "We mean that man first of all exists, encounters himself, " + + "surges up in the world--and defines himself aftward. " + + i); CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes()); - byte[] loopingPlaintext = bobSessionCipher.decrypt(new SignalMessage(aliceLoopingMessage.serialize())); + byte[] loopingPlaintext = + bobSessionCipher.decrypt(new SignalMessage(aliceLoopingMessage.serialize())); assertTrue(new String(loopingPlaintext).equals(loopingMessage)); } - for (int i=0;i<10;i++) { + for (int i = 0; i < 10; i++) { String loopingMessage = ("You can only desire based on what you know: " + i); CiphertextMessage bobLoopingMessage = bobSessionCipher.encrypt(loopingMessage.getBytes()); - byte[] loopingPlaintext = aliceSessionCipher.decrypt(new SignalMessage(bobLoopingMessage.serialize())); + byte[] loopingPlaintext = + aliceSessionCipher.decrypt(new SignalMessage(bobLoopingMessage.serialize())); assertTrue(new String(loopingPlaintext).equals(loopingMessage)); } for (Pair aliceOutOfOrderMessage : aliceOutOfOrderMessages) { - byte[] outOfOrderPlaintext = bobSessionCipher.decrypt(new SignalMessage(aliceOutOfOrderMessage.second().serialize())); + byte[] outOfOrderPlaintext = + bobSessionCipher.decrypt(new SignalMessage(aliceOutOfOrderMessage.second().serialize())); assertTrue(new String(outOfOrderPlaintext).equals(aliceOutOfOrderMessage.first())); } } - } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/SessionCipherTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/SessionCipherTest.java index 83d843c3b2..00b7795504 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/SessionCipherTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/SessionCipherTest.java @@ -1,16 +1,9 @@ -package org.signal.libsignal.protocol; - -import junit.framework.TestCase; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.protocol.ecc.Curve; -import org.signal.libsignal.protocol.ecc.ECKeyPair; -import org.signal.libsignal.protocol.ecc.ECPublicKey; -import org.signal.libsignal.protocol.ecc.ECPrivateKey; -import org.signal.libsignal.protocol.message.CiphertextMessage; -import org.signal.libsignal.protocol.message.SignalMessage; -import org.signal.libsignal.protocol.state.SignalProtocolStore; -import org.signal.libsignal.protocol.state.SessionRecord; -import org.signal.libsignal.protocol.util.Pair; +package org.signal.libsignal.protocol; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -19,7 +12,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Random; - +import junit.framework.TestCase; +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; +import org.signal.libsignal.protocol.message.CiphertextMessage; +import org.signal.libsignal.protocol.message.SignalMessage; +import org.signal.libsignal.protocol.state.SessionRecord; +import org.signal.libsignal.protocol.state.SignalProtocolStore; public class SessionCipherTest extends TestCase { @@ -34,9 +33,14 @@ public PairOfSessions(SessionRecord a, SessionRecord b) { } public void testBasicSessionV3() - throws InvalidKeyException, DuplicateMessageException, - LegacyMessageException, InvalidMessageException, InvalidVersionException, NoSuchAlgorithmException, NoSessionException, UntrustedIdentityException - { + throws InvalidKeyException, + DuplicateMessageException, + LegacyMessageException, + InvalidMessageException, + InvalidVersionException, + NoSuchAlgorithmException, + NoSessionException, + UntrustedIdentityException { PairOfSessions sessions = initializeSessionsV3(); runInteraction(sessions.aliceSession, sessions.bobSession); } @@ -45,22 +49,25 @@ public void testMessageKeyLimits() throws Exception { PairOfSessions sessions = initializeSessionsV3(); SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); aliceStore.storeSession(new SignalProtocolAddress("+14159999999", 1), sessions.aliceSession); bobStore.storeSession(new SignalProtocolAddress("+14158888888", 1), sessions.bobSession); - SessionCipher aliceCipher = new SessionCipher(aliceStore, new SignalProtocolAddress("+14159999999", 1)); - SessionCipher bobCipher = new SessionCipher(bobStore, new SignalProtocolAddress("+14158888888", 1)); + SessionCipher aliceCipher = + new SessionCipher(aliceStore, new SignalProtocolAddress("+14159999999", 1)); + SessionCipher bobCipher = + new SessionCipher(bobStore, new SignalProtocolAddress("+14158888888", 1)); List inflight = new LinkedList<>(); - for (int i=0;i<2010;i++) { - inflight.add(aliceCipher.encrypt("you've never been so hungry, you've never been so cold".getBytes())); + for (int i = 0; i < 2010; i++) { + inflight.add( + aliceCipher.encrypt("you've never been so hungry, you've never been so cold".getBytes())); } bobCipher.decrypt(new SignalMessage(inflight.get(1000).serialize())); - bobCipher.decrypt(new SignalMessage(inflight.get(inflight.size()-1).serialize())); + bobCipher.decrypt(new SignalMessage(inflight.get(inflight.size() - 1).serialize())); try { bobCipher.decrypt(new SignalMessage(inflight.get(0).serialize())); @@ -74,7 +81,7 @@ public void testDecryptAfterReset() throws Exception { PairOfSessions sessions = initializeSessionsV3(); SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); SignalProtocolAddress aliceAddress = new SignalProtocolAddress("+14159999999", 1); SignalProtocolAddress bobAddress = new SignalProtocolAddress("+141588888888", 1); @@ -82,12 +89,12 @@ public void testDecryptAfterReset() throws Exception { aliceStore.storeSession(bobAddress, sessions.aliceSession); bobStore.storeSession(aliceAddress, sessions.bobSession); - SessionCipher aliceCipher = new SessionCipher(aliceStore, bobAddress); - SessionCipher bobCipher = new SessionCipher(bobStore, aliceAddress); + SessionCipher aliceCipher = new SessionCipher(aliceStore, bobAddress); + SessionCipher bobCipher = new SessionCipher(bobStore, aliceAddress); - byte[] alicePlaintext = "This is a plaintext message.".getBytes(); - CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); - byte[] bobPlaintext = bobCipher.decrypt(new SignalMessage(message.serialize())); + byte[] alicePlaintext = "This is a plaintext message.".getBytes(); + CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); + byte[] bobPlaintext = bobCipher.decrypt(new SignalMessage(message.serialize())); assertTrue(Arrays.equals(alicePlaintext, bobPlaintext)); @@ -107,7 +114,7 @@ public void testDecryptAfterDelete() throws Exception { PairOfSessions sessions = initializeSessionsV3(); SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); SignalProtocolAddress aliceAddress = new SignalProtocolAddress("+14159999999", 1); SignalProtocolAddress bobAddress = new SignalProtocolAddress("+141588888888", 1); @@ -115,12 +122,12 @@ public void testDecryptAfterDelete() throws Exception { aliceStore.storeSession(bobAddress, sessions.aliceSession); bobStore.storeSession(aliceAddress, sessions.bobSession); - SessionCipher aliceCipher = new SessionCipher(aliceStore, bobAddress); - SessionCipher bobCipher = new SessionCipher(bobStore, aliceAddress); + SessionCipher aliceCipher = new SessionCipher(aliceStore, bobAddress); + SessionCipher bobCipher = new SessionCipher(bobStore, aliceAddress); - byte[] alicePlaintext = "This is a plaintext message.".getBytes(); - CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); - byte[] bobPlaintext = bobCipher.decrypt(new SignalMessage(message.serialize())); + byte[] alicePlaintext = "This is a plaintext message.".getBytes(); + CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); + byte[] bobPlaintext = bobCipher.decrypt(new SignalMessage(message.serialize())); assertTrue(Arrays.equals(alicePlaintext, bobPlaintext)); @@ -135,32 +142,41 @@ public void testDecryptAfterDelete() throws Exception { } private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobSessionRecord) - throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, InvalidVersionException, InvalidKeyException, NoSuchAlgorithmException, NoSessionException, UntrustedIdentityException { + throws DuplicateMessageException, + LegacyMessageException, + InvalidMessageException, + InvalidVersionException, + InvalidKeyException, + NoSuchAlgorithmException, + NoSessionException, + UntrustedIdentityException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); aliceStore.storeSession(new SignalProtocolAddress("+14159999999", 1), aliceSessionRecord); bobStore.storeSession(new SignalProtocolAddress("+14158888888", 1), bobSessionRecord); - SessionCipher aliceCipher = new SessionCipher(aliceStore, new SignalProtocolAddress("+14159999999", 1)); - SessionCipher bobCipher = new SessionCipher(bobStore, new SignalProtocolAddress("+14158888888", 1)); + SessionCipher aliceCipher = + new SessionCipher(aliceStore, new SignalProtocolAddress("+14159999999", 1)); + SessionCipher bobCipher = + new SessionCipher(bobStore, new SignalProtocolAddress("+14158888888", 1)); - byte[] alicePlaintext = "This is a plaintext message.".getBytes(); - CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); - byte[] bobPlaintext = bobCipher.decrypt(new SignalMessage(message.serialize())); + byte[] alicePlaintext = "This is a plaintext message.".getBytes(); + CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); + byte[] bobPlaintext = bobCipher.decrypt(new SignalMessage(message.serialize())); assertTrue(Arrays.equals(alicePlaintext, bobPlaintext)); - byte[] bobReply = "This is a message from Bob.".getBytes(); - CiphertextMessage reply = bobCipher.encrypt(bobReply); - byte[] receivedReply = aliceCipher.decrypt(new SignalMessage(reply.serialize())); + byte[] bobReply = "This is a message from Bob.".getBytes(); + CiphertextMessage reply = bobCipher.encrypt(bobReply); + byte[] receivedReply = aliceCipher.decrypt(new SignalMessage(reply.serialize())); assertTrue(Arrays.equals(bobReply, receivedReply)); List aliceCiphertextMessages = new ArrayList<>(); - List alicePlaintextMessages = new ArrayList<>(); + List alicePlaintextMessages = new ArrayList<>(); - for (int i=0;i<50;i++) { + for (int i = 0; i < 50; i++) { alicePlaintextMessages.add(("alice message " + i).getBytes()); aliceCiphertextMessages.add(aliceCipher.encrypt(("alice message " + i).getBytes())); } @@ -170,15 +186,16 @@ private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobS Collections.shuffle(aliceCiphertextMessages, new Random(seed)); Collections.shuffle(alicePlaintextMessages, new Random(seed)); - for (int i=0;i bobCiphertextMessages = new ArrayList<>(); - List bobPlaintextMessages = new ArrayList<>(); + List bobPlaintextMessages = new ArrayList<>(); - for (int i=0;i<20;i++) { + for (int i = 0; i < 20; i++) { bobPlaintextMessages.add(("bob message " + i).getBytes()); bobCiphertextMessages.add(bobCipher.encrypt(("bob message " + i).getBytes())); } @@ -188,52 +205,61 @@ private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobS Collections.shuffle(bobCiphertextMessages, new Random(seed)); Collections.shuffle(bobPlaintextMessages, new Random(seed)); - for (int i=0;i data() throws Exception { - return Arrays.asList(new Object[][] { - {new X3DHBundleFactory(), 3}, - {new PQXDHBundleFactory(), 4} - }); + return Arrays.asList( + new Object[][] { + {new X3DHBundleFactory(), 3}, + {new PQXDHBundleFactory(), 4} + }); } @Test public void testBasicSimultaneousInitiate() - throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, - InvalidMessageException, DuplicateMessageException, LegacyMessageException, - InvalidKeyIdException, NoSessionException - { + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + DuplicateMessageException, + LegacyMessageException, + InvalidKeyIdException, + NoSessionException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); PreKeyBundle alicePreKeyBundle = bundleFactory.createBundle(aliceStore); - PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); @@ -69,7 +71,7 @@ public void testBasicSimultaneousInitiate() aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); - CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBob.getType(), CiphertextMessage.PREKEY_TYPE); @@ -78,8 +80,10 @@ public void testBasicSimultaneousInitiate() assertSessionIdNotEquals(aliceStore, bobStore); assertSessionIdNotEquals(aliceStore, bobStore); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); - byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); + byte[] bobPlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); @@ -93,7 +97,8 @@ public void testBasicSimultaneousInitiate() assertEquals(aliceResponse.getType(), CiphertextMessage.WHISPER_TYPE); - byte[] responsePlaintext = bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); + byte[] responsePlaintext = + bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertSessionIdEquals(aliceStore, bobStore); @@ -110,18 +115,22 @@ public void testBasicSimultaneousInitiate() @Test public void testLostSimultaneousInitiate() - throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, - InvalidMessageException, DuplicateMessageException, LegacyMessageException, - InvalidKeyIdException, NoSessionException - { + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + DuplicateMessageException, + LegacyMessageException, + InvalidKeyIdException, + NoSessionException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); PreKeyBundle alicePreKeyBundle = bundleFactory.createBundle(aliceStore); - PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); @@ -129,7 +138,7 @@ public void testLostSimultaneousInitiate() aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); - CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBob.getType(), CiphertextMessage.PREKEY_TYPE); @@ -137,7 +146,8 @@ public void testLostSimultaneousInitiate() assertSessionIdNotEquals(aliceStore, bobStore); - byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); + byte[] bobPlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); assertTrue(new String(bobPlaintext).equals("hey there")); assertEquals(bobStore.loadSession(ALICE_ADDRESS).getSessionVersion(), expectedVersion); @@ -146,7 +156,8 @@ public void testLostSimultaneousInitiate() assertEquals(aliceResponse.getType(), CiphertextMessage.PREKEY_TYPE); - byte[] responsePlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(aliceResponse.serialize())); + byte[] responsePlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertSessionIdEquals(aliceStore, bobStore); @@ -163,18 +174,22 @@ public void testLostSimultaneousInitiate() @Test public void testSimultaneousInitiateLostMessage() - throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, - InvalidMessageException, DuplicateMessageException, LegacyMessageException, - InvalidKeyIdException, NoSessionException - { + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + DuplicateMessageException, + LegacyMessageException, + InvalidKeyIdException, + NoSessionException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); PreKeyBundle alicePreKeyBundle = bundleFactory.createBundle(aliceStore); - PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); @@ -182,7 +197,7 @@ public void testSimultaneousInitiateLostMessage() aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); - CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBob.getType(), CiphertextMessage.PREKEY_TYPE); @@ -190,8 +205,10 @@ public void testSimultaneousInitiateLostMessage() assertSessionIdNotEquals(aliceStore, bobStore); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); - byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); + byte[] bobPlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); @@ -219,18 +236,22 @@ public void testSimultaneousInitiateLostMessage() @Test public void testSimultaneousInitiateRepeatedMessages() - throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, - InvalidMessageException, DuplicateMessageException, LegacyMessageException, - InvalidKeyIdException, NoSessionException - { + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + DuplicateMessageException, + LegacyMessageException, + InvalidKeyIdException, + NoSessionException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); PreKeyBundle alicePreKeyBundle = bundleFactory.createBundle(aliceStore); - PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); @@ -238,7 +259,7 @@ public void testSimultaneousInitiateRepeatedMessages() aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); - CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBob.getType(), CiphertextMessage.PREKEY_TYPE); @@ -246,8 +267,10 @@ public void testSimultaneousInitiateRepeatedMessages() assertSessionIdNotEquals(aliceStore, bobStore); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); - byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); + byte[] bobPlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); @@ -257,17 +280,20 @@ public void testSimultaneousInitiateRepeatedMessages() assertSessionIdNotEquals(aliceStore, bobStore); - for (int i=0;i<50;i++) { - CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); - CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); + for (int i = 0; i < 50; i++) { + CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAliceRepeat = + bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBobRepeat.getType(), CiphertextMessage.WHISPER_TYPE); assertEquals(messageForAliceRepeat.getType(), CiphertextMessage.WHISPER_TYPE); assertSessionIdNotEquals(aliceStore, bobStore); - byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new SignalMessage(messageForAliceRepeat.serialize())); - byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new SignalMessage(messageForBobRepeat.serialize())); + byte[] alicePlaintextRepeat = + aliceSessionCipher.decrypt(new SignalMessage(messageForAliceRepeat.serialize())); + byte[] bobPlaintextRepeat = + bobSessionCipher.decrypt(new SignalMessage(messageForBobRepeat.serialize())); assertTrue(new String(alicePlaintextRepeat).equals("sample message")); assertTrue(new String(bobPlaintextRepeat).equals("hey there")); @@ -279,7 +305,8 @@ public void testSimultaneousInitiateRepeatedMessages() assertEquals(aliceResponse.getType(), CiphertextMessage.WHISPER_TYPE); - byte[] responsePlaintext = bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); + byte[] responsePlaintext = + bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertSessionIdEquals(aliceStore, bobStore); @@ -296,22 +323,26 @@ public void testSimultaneousInitiateRepeatedMessages() @Test public void testRepeatedSimultaneousInitiateRepeatedMessages() - throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, - InvalidMessageException, DuplicateMessageException, LegacyMessageException, - InvalidKeyIdException, NoSessionException - { + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + DuplicateMessageException, + LegacyMessageException, + InvalidKeyIdException, + NoSessionException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); - for (int i=0;i<15;i++) { + for (int i = 0; i < 15; i++) { PreKeyBundle alicePreKeyBundle = bundleFactory.createBundle(aliceStore); - PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); @@ -324,8 +355,10 @@ public void testRepeatedSimultaneousInitiateRepeatedMessages() assertSessionIdNotEquals(aliceStore, bobStore); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); - byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); + byte[] bobPlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); @@ -336,17 +369,20 @@ public void testRepeatedSimultaneousInitiateRepeatedMessages() assertFalse(isSessionIdEqual(aliceStore, bobStore)); } - for (int i=0;i<50;i++) { - CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); - CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); + for (int i = 0; i < 50; i++) { + CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAliceRepeat = + bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBobRepeat.getType(), CiphertextMessage.WHISPER_TYPE); assertEquals(messageForAliceRepeat.getType(), CiphertextMessage.WHISPER_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); - byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new SignalMessage(messageForAliceRepeat.serialize())); - byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new SignalMessage(messageForBobRepeat.serialize())); + byte[] alicePlaintextRepeat = + aliceSessionCipher.decrypt(new SignalMessage(messageForAliceRepeat.serialize())); + byte[] bobPlaintextRepeat = + bobSessionCipher.decrypt(new SignalMessage(messageForBobRepeat.serialize())); assertTrue(new String(alicePlaintextRepeat).equals("sample message")); assertTrue(new String(bobPlaintextRepeat).equals("hey there")); @@ -358,7 +394,8 @@ public void testRepeatedSimultaneousInitiateRepeatedMessages() assertEquals(aliceResponse.getType(), CiphertextMessage.WHISPER_TYPE); - byte[] responsePlaintext = bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); + byte[] responsePlaintext = + bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); @@ -375,28 +412,32 @@ public void testRepeatedSimultaneousInitiateRepeatedMessages() @Test public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() - throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, - InvalidMessageException, DuplicateMessageException, LegacyMessageException, - InvalidKeyIdException, NoSessionException - { + throws InvalidKeyException, + UntrustedIdentityException, + InvalidVersionException, + InvalidMessageException, + DuplicateMessageException, + LegacyMessageException, + InvalidKeyIdException, + NoSessionException { SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore(); - SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); + SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); - PreKeyBundle bobLostPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobLostPreKeyBundle = bundleFactory.createBundle(bobStore); aliceSessionBuilder.process(bobLostPreKeyBundle); - CiphertextMessage lostMessageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage lostMessageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); - for (int i=0;i<15;i++) { + for (int i = 0; i < 15; i++) { PreKeyBundle alicePreKeyBundle = bundleFactory.createBundle(aliceStore); - PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); + PreKeyBundle bobPreKeyBundle = bundleFactory.createBundle(bobStore); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); @@ -409,8 +450,10 @@ public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() assertFalse(isSessionIdEqual(aliceStore, bobStore)); - byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); - byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); + byte[] alicePlaintext = + aliceSessionCipher.decrypt(new PreKeySignalMessage(messageForAlice.serialize())); + byte[] bobPlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); @@ -421,17 +464,20 @@ public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() assertFalse(isSessionIdEqual(aliceStore, bobStore)); } - for (int i=0;i<50;i++) { - CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); - CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); + for (int i = 0; i < 50; i++) { + CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAliceRepeat = + bobSessionCipher.encrypt("sample message".getBytes()); assertEquals(messageForBobRepeat.getType(), CiphertextMessage.WHISPER_TYPE); assertEquals(messageForAliceRepeat.getType(), CiphertextMessage.WHISPER_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); - byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new SignalMessage(messageForAliceRepeat.serialize())); - byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new SignalMessage(messageForBobRepeat.serialize())); + byte[] alicePlaintextRepeat = + aliceSessionCipher.decrypt(new SignalMessage(messageForAliceRepeat.serialize())); + byte[] bobPlaintextRepeat = + bobSessionCipher.decrypt(new SignalMessage(messageForBobRepeat.serialize())); assertTrue(new String(alicePlaintextRepeat).equals("sample message")); assertTrue(new String(bobPlaintextRepeat).equals("hey there")); @@ -443,7 +489,8 @@ public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() assertEquals(aliceResponse.getType(), CiphertextMessage.WHISPER_TYPE); - byte[] responsePlaintext = bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); + byte[] responsePlaintext = + bobSessionCipher.decrypt(new SignalMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); @@ -457,13 +504,15 @@ public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); - byte[] lostMessagePlaintext = bobSessionCipher.decrypt(new PreKeySignalMessage(lostMessageForBob.serialize())); + byte[] lostMessagePlaintext = + bobSessionCipher.decrypt(new PreKeySignalMessage(lostMessageForBob.serialize())); assertTrue(new String(lostMessagePlaintext).equals("hey there")); assertFalse(isSessionIdEqual(aliceStore, bobStore)); - CiphertextMessage blastFromThePast = bobSessionCipher.encrypt("unexpected!".getBytes()); - byte[] blastFromThePastPlaintext = aliceSessionCipher.decrypt(new SignalMessage(blastFromThePast.serialize())); + CiphertextMessage blastFromThePast = bobSessionCipher.encrypt("unexpected!".getBytes()); + byte[] blastFromThePastPlaintext = + aliceSessionCipher.decrypt(new SignalMessage(blastFromThePast.serialize())); assertTrue(new String(blastFromThePastPlaintext).equals("unexpected!")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); @@ -473,12 +522,14 @@ private void assertSessionIdEquals(SignalProtocolStore aliceStore, SignalProtoco assertTrue(isSessionIdEqual(aliceStore, bobStore)); } - private void assertSessionIdNotEquals(SignalProtocolStore aliceStore, SignalProtocolStore bobStore) { + private void assertSessionIdNotEquals( + SignalProtocolStore aliceStore, SignalProtocolStore bobStore) { assertFalse(isSessionIdEqual(aliceStore, bobStore)); } private boolean isSessionIdEqual(SignalProtocolStore aliceStore, SignalProtocolStore bobStore) { - return Arrays.equals(aliceStore.loadSession(BOB_ADDRESS).getAliceBaseKey(), - bobStore.loadSession(ALICE_ADDRESS).getAliceBaseKey()); + return Arrays.equals( + aliceStore.loadSession(BOB_ADDRESS).getAliceBaseKey(), + bobStore.loadSession(ALICE_ADDRESS).getAliceBaseKey()); } } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/TestBadSignedPreKeysStore.java b/java/client/src/test/java/org/signal/libsignal/protocol/TestBadSignedPreKeysStore.java index 704b9c7413..15a20b12be 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/TestBadSignedPreKeysStore.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/TestBadSignedPreKeysStore.java @@ -1,6 +1,10 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; -import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; public class TestBadSignedPreKeysStore extends TestInMemorySignalProtocolStore { diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemoryIdentityKeyStore.java b/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemoryIdentityKeyStore.java index 286d68054e..4e5a75551b 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemoryIdentityKeyStore.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemoryIdentityKeyStore.java @@ -1,10 +1,16 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.util.KeyHelper; -public class TestInMemoryIdentityKeyStore extends org.signal.libsignal.protocol.state.impl.InMemoryIdentityKeyStore { +public class TestInMemoryIdentityKeyStore + extends org.signal.libsignal.protocol.state.impl.InMemoryIdentityKeyStore { public TestInMemoryIdentityKeyStore() { super(generateIdentityKeyPair(), generateRegistrationId()); } @@ -12,12 +18,11 @@ public TestInMemoryIdentityKeyStore() { private static IdentityKeyPair generateIdentityKeyPair() { ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); + return new IdentityKeyPair( + new IdentityKey(identityKeyPairKeys.getPublicKey()), identityKeyPairKeys.getPrivateKey()); } private static int generateRegistrationId() { return KeyHelper.generateRegistrationId(false); } - } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemorySignalProtocolStore.java b/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemorySignalProtocolStore.java index 6b1ff60fff..9358893e58 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemorySignalProtocolStore.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/TestInMemorySignalProtocolStore.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import org.signal.libsignal.protocol.ecc.Curve; @@ -13,8 +18,8 @@ public TestInMemorySignalProtocolStore() { private static IdentityKeyPair generateIdentityKeyPair() { ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); + return new IdentityKeyPair( + new IdentityKey(identityKeyPairKeys.getPublicKey()), identityKeyPairKeys.getPrivateKey()); } private static int generateRegistrationId() { diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/TestNoSignedPreKeysStore.java b/java/client/src/test/java/org/signal/libsignal/protocol/TestNoSignedPreKeysStore.java index 843265904c..5a01bee994 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/TestNoSignedPreKeysStore.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/TestNoSignedPreKeysStore.java @@ -1,6 +1,10 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; -import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; public class TestNoSignedPreKeysStore extends TestInMemorySignalProtocolStore { diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/X3DHBundleFactory.java b/java/client/src/test/java/org/signal/libsignal/protocol/X3DHBundleFactory.java index 012dad679f..d7ad24a431 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/X3DHBundleFactory.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/X3DHBundleFactory.java @@ -1,33 +1,45 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; +import java.util.Random; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; -import org.signal.libsignal.protocol.kem.KEMKeyPair; -import org.signal.libsignal.protocol.kem.KEMKeyType; import org.signal.libsignal.protocol.state.PreKeyBundle; import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.util.Medium; -import java.util.Random; - public final class X3DHBundleFactory implements BundleFactory { @Override public PreKeyBundle createBundle(SignalProtocolStore store) throws InvalidKeyException { - ECKeyPair preKeyPair = Curve.generateKeyPair(); - ECKeyPair signedPreKeyPair = Curve.generateKeyPair(); - byte[] signedPreKeySignature = Curve.calculateSignature(store.getIdentityKeyPair().getPrivateKey(), - signedPreKeyPair.getPublicKey().serialize()); + ECKeyPair preKeyPair = Curve.generateKeyPair(); + ECKeyPair signedPreKeyPair = Curve.generateKeyPair(); + byte[] signedPreKeySignature = + Curve.calculateSignature( + store.getIdentityKeyPair().getPrivateKey(), + signedPreKeyPair.getPublicKey().serialize()); Random random = new Random(); int preKeyId = random.nextInt(Medium.MAX_VALUE); int signedPreKeyId = random.nextInt(Medium.MAX_VALUE); store.storePreKey(preKeyId, new PreKeyRecord(preKeyId, preKeyPair)); - store.storeSignedPreKey(signedPreKeyId, new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), signedPreKeyPair, signedPreKeySignature)); + store.storeSignedPreKey( + signedPreKeyId, + new SignedPreKeyRecord( + signedPreKeyId, System.currentTimeMillis(), signedPreKeyPair, signedPreKeySignature)); - return new PreKeyBundle(store.getLocalRegistrationId(), 1, - preKeyId, preKeyPair.getPublicKey(), - signedPreKeyId, signedPreKeyPair.getPublicKey(), signedPreKeySignature, + return new PreKeyBundle( + store.getLocalRegistrationId(), + 1, + preKeyId, + preKeyPair.getPublicKey(), + signedPreKeyId, + signedPreKeyPair.getPublicKey(), + signedPreKeySignature, store.getIdentityKeyPair().getPublicKey()); } } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/ecc/Curve25519Test.java b/java/client/src/test/java/org/signal/libsignal/protocol/ecc/Curve25519Test.java index cfc9a66a7a..dddf5ca482 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/ecc/Curve25519Test.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/ecc/Curve25519Test.java @@ -1,54 +1,66 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.ecc; +import java.util.Arrays; import junit.framework.TestCase; - import org.signal.libsignal.protocol.InvalidKeyException; -import java.util.Arrays; - - public class Curve25519Test extends TestCase { public void testAgreement() throws InvalidKeyException { - byte[] alicePublic = {(byte) 0x05, (byte) 0x1b, (byte) 0xb7, (byte) 0x59, (byte) 0x66, - (byte) 0xf2, (byte) 0xe9, (byte) 0x3a, (byte) 0x36, (byte) 0x91, - (byte) 0xdf, (byte) 0xff, (byte) 0x94, (byte) 0x2b, (byte) 0xb2, - (byte) 0xa4, (byte) 0x66, (byte) 0xa1, (byte) 0xc0, (byte) 0x8b, - (byte) 0x8d, (byte) 0x78, (byte) 0xca, (byte) 0x3f, (byte) 0x4d, - (byte) 0x6d, (byte) 0xf8, (byte) 0xb8, (byte) 0xbf, (byte) 0xa2, - (byte) 0xe4, (byte) 0xee, (byte) 0x28}; - - byte[] alicePrivate = {(byte) 0xc8, (byte) 0x06, (byte) 0x43, (byte) 0x9d, (byte) 0xc9, - (byte) 0xd2, (byte) 0xc4, (byte) 0x76, (byte) 0xff, (byte) 0xed, - (byte) 0x8f, (byte) 0x25, (byte) 0x80, (byte) 0xc0, (byte) 0x88, - (byte) 0x8d, (byte) 0x58, (byte) 0xab, (byte) 0x40, (byte) 0x6b, - (byte) 0xf7, (byte) 0xae, (byte) 0x36, (byte) 0x98, (byte) 0x87, - (byte) 0x90, (byte) 0x21, (byte) 0xb9, (byte) 0x6b, (byte) 0xb4, - (byte) 0xbf, (byte) 0x59}; - - byte[] bobPublic = {(byte) 0x05, (byte) 0x65, (byte) 0x36, (byte) 0x14, (byte) 0x99, - (byte) 0x3d, (byte) 0x2b, (byte) 0x15, (byte) 0xee, (byte) 0x9e, - (byte) 0x5f, (byte) 0xd3, (byte) 0xd8, (byte) 0x6c, (byte) 0xe7, - (byte) 0x19, (byte) 0xef, (byte) 0x4e, (byte) 0xc1, (byte) 0xda, - (byte) 0xae, (byte) 0x18, (byte) 0x86, (byte) 0xa8, (byte) 0x7b, - (byte) 0x3f, (byte) 0x5f, (byte) 0xa9, (byte) 0x56, (byte) 0x5a, - (byte) 0x27, (byte) 0xa2, (byte) 0x2f}; - - byte[] bobPrivate = {(byte) 0xb0, (byte) 0x3b, (byte) 0x34, (byte) 0xc3, (byte) 0x3a, - (byte) 0x1c, (byte) 0x44, (byte) 0xf2, (byte) 0x25, (byte) 0xb6, - (byte) 0x62, (byte) 0xd2, (byte) 0xbf, (byte) 0x48, (byte) 0x59, - (byte) 0xb8, (byte) 0x13, (byte) 0x54, (byte) 0x11, (byte) 0xfa, - (byte) 0x7b, (byte) 0x03, (byte) 0x86, (byte) 0xd4, (byte) 0x5f, - (byte) 0xb7, (byte) 0x5d, (byte) 0xc5, (byte) 0xb9, (byte) 0x1b, - (byte) 0x44, (byte) 0x66}; - - byte[] shared = {(byte) 0x32, (byte) 0x5f, (byte) 0x23, (byte) 0x93, (byte) 0x28, - (byte) 0x94, (byte) 0x1c, (byte) 0xed, (byte) 0x6e, (byte) 0x67, - (byte) 0x3b, (byte) 0x86, (byte) 0xba, (byte) 0x41, (byte) 0x01, - (byte) 0x74, (byte) 0x48, (byte) 0xe9, (byte) 0x9b, (byte) 0x64, - (byte) 0x9a, (byte) 0x9c, (byte) 0x38, (byte) 0x06, (byte) 0xc1, - (byte) 0xdd, (byte) 0x7c, (byte) 0xa4, (byte) 0xc4, (byte) 0x77, - (byte) 0xe6, (byte) 0x29}; + byte[] alicePublic = { + (byte) 0x05, (byte) 0x1b, (byte) 0xb7, (byte) 0x59, (byte) 0x66, + (byte) 0xf2, (byte) 0xe9, (byte) 0x3a, (byte) 0x36, (byte) 0x91, + (byte) 0xdf, (byte) 0xff, (byte) 0x94, (byte) 0x2b, (byte) 0xb2, + (byte) 0xa4, (byte) 0x66, (byte) 0xa1, (byte) 0xc0, (byte) 0x8b, + (byte) 0x8d, (byte) 0x78, (byte) 0xca, (byte) 0x3f, (byte) 0x4d, + (byte) 0x6d, (byte) 0xf8, (byte) 0xb8, (byte) 0xbf, (byte) 0xa2, + (byte) 0xe4, (byte) 0xee, (byte) 0x28 + }; + + byte[] alicePrivate = { + (byte) 0xc8, (byte) 0x06, (byte) 0x43, (byte) 0x9d, (byte) 0xc9, + (byte) 0xd2, (byte) 0xc4, (byte) 0x76, (byte) 0xff, (byte) 0xed, + (byte) 0x8f, (byte) 0x25, (byte) 0x80, (byte) 0xc0, (byte) 0x88, + (byte) 0x8d, (byte) 0x58, (byte) 0xab, (byte) 0x40, (byte) 0x6b, + (byte) 0xf7, (byte) 0xae, (byte) 0x36, (byte) 0x98, (byte) 0x87, + (byte) 0x90, (byte) 0x21, (byte) 0xb9, (byte) 0x6b, (byte) 0xb4, + (byte) 0xbf, (byte) 0x59 + }; + + byte[] bobPublic = { + (byte) 0x05, (byte) 0x65, (byte) 0x36, (byte) 0x14, (byte) 0x99, + (byte) 0x3d, (byte) 0x2b, (byte) 0x15, (byte) 0xee, (byte) 0x9e, + (byte) 0x5f, (byte) 0xd3, (byte) 0xd8, (byte) 0x6c, (byte) 0xe7, + (byte) 0x19, (byte) 0xef, (byte) 0x4e, (byte) 0xc1, (byte) 0xda, + (byte) 0xae, (byte) 0x18, (byte) 0x86, (byte) 0xa8, (byte) 0x7b, + (byte) 0x3f, (byte) 0x5f, (byte) 0xa9, (byte) 0x56, (byte) 0x5a, + (byte) 0x27, (byte) 0xa2, (byte) 0x2f + }; + + byte[] bobPrivate = { + (byte) 0xb0, (byte) 0x3b, (byte) 0x34, (byte) 0xc3, (byte) 0x3a, + (byte) 0x1c, (byte) 0x44, (byte) 0xf2, (byte) 0x25, (byte) 0xb6, + (byte) 0x62, (byte) 0xd2, (byte) 0xbf, (byte) 0x48, (byte) 0x59, + (byte) 0xb8, (byte) 0x13, (byte) 0x54, (byte) 0x11, (byte) 0xfa, + (byte) 0x7b, (byte) 0x03, (byte) 0x86, (byte) 0xd4, (byte) 0x5f, + (byte) 0xb7, (byte) 0x5d, (byte) 0xc5, (byte) 0xb9, (byte) 0x1b, + (byte) 0x44, (byte) 0x66 + }; + + byte[] shared = { + (byte) 0x32, (byte) 0x5f, (byte) 0x23, (byte) 0x93, (byte) 0x28, + (byte) 0x94, (byte) 0x1c, (byte) 0xed, (byte) 0x6e, (byte) 0x67, + (byte) 0x3b, (byte) 0x86, (byte) 0xba, (byte) 0x41, (byte) 0x01, + (byte) 0x74, (byte) 0x48, (byte) 0xe9, (byte) 0x9b, (byte) 0x64, + (byte) 0x9a, (byte) 0x9c, (byte) 0x38, (byte) 0x06, (byte) 0xc1, + (byte) 0xdd, (byte) 0x7c, (byte) 0xa4, (byte) 0xc4, (byte) 0x77, + (byte) 0xe6, (byte) 0x29 + }; ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0); ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate); @@ -64,59 +76,67 @@ public void testAgreement() throws InvalidKeyException { } public void testRandomAgreements() throws InvalidKeyException { - for (int i=0;i<50;i++) { - ECKeyPair alice = Curve.generateKeyPair(); - ECKeyPair bob = Curve.generateKeyPair(); + for (int i = 0; i < 50; i++) { + ECKeyPair alice = Curve.generateKeyPair(); + ECKeyPair bob = Curve.generateKeyPair(); - byte[] sharedAlice = Curve.calculateAgreement(bob.getPublicKey(), alice.getPrivateKey()); - byte[] sharedBob = Curve.calculateAgreement(alice.getPublicKey(), bob.getPrivateKey()); + byte[] sharedAlice = Curve.calculateAgreement(bob.getPublicKey(), alice.getPrivateKey()); + byte[] sharedBob = Curve.calculateAgreement(alice.getPublicKey(), bob.getPrivateKey()); assertTrue(Arrays.equals(sharedAlice, sharedBob)); } } public void testSignature() throws InvalidKeyException { - byte[] aliceIdentityPrivate = {(byte)0xc0, (byte)0x97, (byte)0x24, (byte)0x84, (byte)0x12, - (byte)0xe5, (byte)0x8b, (byte)0xf0, (byte)0x5d, (byte)0xf4, - (byte)0x87, (byte)0x96, (byte)0x82, (byte)0x05, (byte)0x13, - (byte)0x27, (byte)0x94, (byte)0x17, (byte)0x8e, (byte)0x36, - (byte)0x76, (byte)0x37, (byte)0xf5, (byte)0x81, (byte)0x8f, - (byte)0x81, (byte)0xe0, (byte)0xe6, (byte)0xce, (byte)0x73, - (byte)0xe8, (byte)0x65}; - - byte[] aliceIdentityPublic = {(byte)0x05, (byte)0xab, (byte)0x7e, (byte)0x71, (byte)0x7d, - (byte)0x4a, (byte)0x16, (byte)0x3b, (byte)0x7d, (byte)0x9a, - (byte)0x1d, (byte)0x80, (byte)0x71, (byte)0xdf, (byte)0xe9, - (byte)0xdc, (byte)0xf8, (byte)0xcd, (byte)0xcd, (byte)0x1c, - (byte)0xea, (byte)0x33, (byte)0x39, (byte)0xb6, (byte)0x35, - (byte)0x6b, (byte)0xe8, (byte)0x4d, (byte)0x88, (byte)0x7e, - (byte)0x32, (byte)0x2c, (byte)0x64}; - - byte[] aliceEphemeralPublic = {(byte)0x05, (byte)0xed, (byte)0xce, (byte)0x9d, (byte)0x9c, - (byte)0x41, (byte)0x5c, (byte)0xa7, (byte)0x8c, (byte)0xb7, - (byte)0x25, (byte)0x2e, (byte)0x72, (byte)0xc2, (byte)0xc4, - (byte)0xa5, (byte)0x54, (byte)0xd3, (byte)0xeb, (byte)0x29, - (byte)0x48, (byte)0x5a, (byte)0x0e, (byte)0x1d, (byte)0x50, - (byte)0x31, (byte)0x18, (byte)0xd1, (byte)0xa8, (byte)0x2d, - (byte)0x99, (byte)0xfb, (byte)0x4a}; - - byte[] aliceSignature = {(byte)0x5d, (byte)0xe8, (byte)0x8c, (byte)0xa9, (byte)0xa8, - (byte)0x9b, (byte)0x4a, (byte)0x11, (byte)0x5d, (byte)0xa7, - (byte)0x91, (byte)0x09, (byte)0xc6, (byte)0x7c, (byte)0x9c, - (byte)0x74, (byte)0x64, (byte)0xa3, (byte)0xe4, (byte)0x18, - (byte)0x02, (byte)0x74, (byte)0xf1, (byte)0xcb, (byte)0x8c, - (byte)0x63, (byte)0xc2, (byte)0x98, (byte)0x4e, (byte)0x28, - (byte)0x6d, (byte)0xfb, (byte)0xed, (byte)0xe8, (byte)0x2d, - (byte)0xeb, (byte)0x9d, (byte)0xcd, (byte)0x9f, (byte)0xae, - (byte)0x0b, (byte)0xfb, (byte)0xb8, (byte)0x21, (byte)0x56, - (byte)0x9b, (byte)0x3d, (byte)0x90, (byte)0x01, (byte)0xbd, - (byte)0x81, (byte)0x30, (byte)0xcd, (byte)0x11, (byte)0xd4, - (byte)0x86, (byte)0xce, (byte)0xf0, (byte)0x47, (byte)0xbd, - (byte)0x60, (byte)0xb8, (byte)0x6e, (byte)0x88}; + byte[] aliceIdentityPrivate = { + (byte) 0xc0, (byte) 0x97, (byte) 0x24, (byte) 0x84, (byte) 0x12, + (byte) 0xe5, (byte) 0x8b, (byte) 0xf0, (byte) 0x5d, (byte) 0xf4, + (byte) 0x87, (byte) 0x96, (byte) 0x82, (byte) 0x05, (byte) 0x13, + (byte) 0x27, (byte) 0x94, (byte) 0x17, (byte) 0x8e, (byte) 0x36, + (byte) 0x76, (byte) 0x37, (byte) 0xf5, (byte) 0x81, (byte) 0x8f, + (byte) 0x81, (byte) 0xe0, (byte) 0xe6, (byte) 0xce, (byte) 0x73, + (byte) 0xe8, (byte) 0x65 + }; + + byte[] aliceIdentityPublic = { + (byte) 0x05, (byte) 0xab, (byte) 0x7e, (byte) 0x71, (byte) 0x7d, + (byte) 0x4a, (byte) 0x16, (byte) 0x3b, (byte) 0x7d, (byte) 0x9a, + (byte) 0x1d, (byte) 0x80, (byte) 0x71, (byte) 0xdf, (byte) 0xe9, + (byte) 0xdc, (byte) 0xf8, (byte) 0xcd, (byte) 0xcd, (byte) 0x1c, + (byte) 0xea, (byte) 0x33, (byte) 0x39, (byte) 0xb6, (byte) 0x35, + (byte) 0x6b, (byte) 0xe8, (byte) 0x4d, (byte) 0x88, (byte) 0x7e, + (byte) 0x32, (byte) 0x2c, (byte) 0x64 + }; + + byte[] aliceEphemeralPublic = { + (byte) 0x05, (byte) 0xed, (byte) 0xce, (byte) 0x9d, (byte) 0x9c, + (byte) 0x41, (byte) 0x5c, (byte) 0xa7, (byte) 0x8c, (byte) 0xb7, + (byte) 0x25, (byte) 0x2e, (byte) 0x72, (byte) 0xc2, (byte) 0xc4, + (byte) 0xa5, (byte) 0x54, (byte) 0xd3, (byte) 0xeb, (byte) 0x29, + (byte) 0x48, (byte) 0x5a, (byte) 0x0e, (byte) 0x1d, (byte) 0x50, + (byte) 0x31, (byte) 0x18, (byte) 0xd1, (byte) 0xa8, (byte) 0x2d, + (byte) 0x99, (byte) 0xfb, (byte) 0x4a + }; + + byte[] aliceSignature = { + (byte) 0x5d, (byte) 0xe8, (byte) 0x8c, (byte) 0xa9, (byte) 0xa8, + (byte) 0x9b, (byte) 0x4a, (byte) 0x11, (byte) 0x5d, (byte) 0xa7, + (byte) 0x91, (byte) 0x09, (byte) 0xc6, (byte) 0x7c, (byte) 0x9c, + (byte) 0x74, (byte) 0x64, (byte) 0xa3, (byte) 0xe4, (byte) 0x18, + (byte) 0x02, (byte) 0x74, (byte) 0xf1, (byte) 0xcb, (byte) 0x8c, + (byte) 0x63, (byte) 0xc2, (byte) 0x98, (byte) 0x4e, (byte) 0x28, + (byte) 0x6d, (byte) 0xfb, (byte) 0xed, (byte) 0xe8, (byte) 0x2d, + (byte) 0xeb, (byte) 0x9d, (byte) 0xcd, (byte) 0x9f, (byte) 0xae, + (byte) 0x0b, (byte) 0xfb, (byte) 0xb8, (byte) 0x21, (byte) 0x56, + (byte) 0x9b, (byte) 0x3d, (byte) 0x90, (byte) 0x01, (byte) 0xbd, + (byte) 0x81, (byte) 0x30, (byte) 0xcd, (byte) 0x11, (byte) 0xd4, + (byte) 0x86, (byte) 0xce, (byte) 0xf0, (byte) 0x47, (byte) 0xbd, + (byte) 0x60, (byte) 0xb8, (byte) 0x6e, (byte) 0x88 + }; ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(aliceIdentityPrivate); - ECPublicKey alicePublicKey = Curve.decodePoint(aliceIdentityPublic, 0); - ECPublicKey aliceEphemeral = Curve.decodePoint(aliceEphemeralPublic, 0); + ECPublicKey alicePublicKey = Curve.decodePoint(aliceIdentityPublic, 0); + ECPublicKey aliceEphemeral = Curve.decodePoint(aliceEphemeralPublic, 0); if (!Curve.verifySignature(alicePublicKey, aliceEphemeral.serialize(), aliceSignature)) { throw new AssertionError("Sig verification failed!"); @@ -125,7 +145,7 @@ public void testSignature() throws InvalidKeyException { byte[] aliceKey = aliceEphemeral.getPublicKeyBytes(); assertTrue(aliceKey.length == 32); - for (int i=0;i ciphertexts = new ArrayList<>(100); - for (int i=0;i<100;i++) { - ciphertexts.add(aliceGroupCipher.encrypt(DISTRIBUTION_ID, "up the punks".getBytes()).serialize()); + for (int i = 0; i < 100; i++) { + ciphertexts.add( + aliceGroupCipher.encrypt(DISTRIBUTION_ID, "up the punks".getBytes()).serialize()); } while (ciphertexts.size() > 0) { - int index = randomInt() % ciphertexts.size(); + int index = randomInt() % ciphertexts.size(); byte[] ciphertext = ciphertexts.remove(index); - byte[] plaintext = bobGroupCipher.decrypt(ciphertext); + byte[] plaintext = bobGroupCipher.decrypt(ciphertext); assertTrue(new String(plaintext).equals("up the punks")); } @@ -200,7 +250,8 @@ public void testOutOfOrder() public void testEncryptNoSession() { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); - GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, new SignalProtocolAddress("+10002223333", 1)); + GroupCipher aliceGroupCipher = + new GroupCipher(aliceStore, new SignalProtocolAddress("+10002223333", 1)); try { aliceGroupCipher.encrypt(DISTRIBUTION_ID, "up the punks".getBytes()); fail("Should have failed!"); @@ -209,26 +260,31 @@ public void testEncryptNoSession() { } } - - public void testTooFarInFuture() throws DuplicateMessageException, InvalidMessageException, LegacyMessageException, NoSessionException { + public void testTooFarInFuture() + throws DuplicateMessageException, + InvalidMessageException, + LegacyMessageException, + NoSessionException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); - InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); + InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); GroupSessionBuilder aliceSessionBuilder = new GroupSessionBuilder(aliceStore); - GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); + GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, SENDER_ADDRESS); - GroupCipher bobGroupCipher = new GroupCipher(bobStore, SENDER_ADDRESS); + GroupCipher bobGroupCipher = new GroupCipher(bobStore, SENDER_ADDRESS); - SenderKeyDistributionMessage aliceDistributionMessage = aliceSessionBuilder.create(SENDER_ADDRESS, DISTRIBUTION_ID); + SenderKeyDistributionMessage aliceDistributionMessage = + aliceSessionBuilder.create(SENDER_ADDRESS, DISTRIBUTION_ID); bobSessionBuilder.process(SENDER_ADDRESS, aliceDistributionMessage); - for (int i=0;i<25001;i++) { + for (int i = 0; i < 25001; i++) { aliceGroupCipher.encrypt(DISTRIBUTION_ID, "up the punks".getBytes()); } - byte[] tooFarCiphertext = aliceGroupCipher.encrypt(DISTRIBUTION_ID, "notta gonna worka".getBytes()).serialize(); + byte[] tooFarCiphertext = + aliceGroupCipher.encrypt(DISTRIBUTION_ID, "notta gonna worka".getBytes()).serialize(); try { bobGroupCipher.decrypt(tooFarCiphertext); fail("Should have failed!"); @@ -239,26 +295,28 @@ public void testTooFarInFuture() throws DuplicateMessageException, InvalidMessag public void testMessageKeyLimit() throws Exception { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); - InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); + InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); GroupSessionBuilder aliceSessionBuilder = new GroupSessionBuilder(aliceStore); - GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); + GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, SENDER_ADDRESS); - GroupCipher bobGroupCipher = new GroupCipher(bobStore, SENDER_ADDRESS); + GroupCipher bobGroupCipher = new GroupCipher(bobStore, SENDER_ADDRESS); - SenderKeyDistributionMessage aliceDistributionMessage = aliceSessionBuilder.create(SENDER_ADDRESS, DISTRIBUTION_ID); + SenderKeyDistributionMessage aliceDistributionMessage = + aliceSessionBuilder.create(SENDER_ADDRESS, DISTRIBUTION_ID); bobSessionBuilder.process(SENDER_ADDRESS, aliceDistributionMessage); List inflight = new LinkedList<>(); - for (int i=0;i<2010;i++) { - inflight.add(aliceGroupCipher.encrypt(DISTRIBUTION_ID, "up the punks".getBytes()).serialize()); + for (int i = 0; i < 2010; i++) { + inflight.add( + aliceGroupCipher.encrypt(DISTRIBUTION_ID, "up the punks".getBytes()).serialize()); } bobGroupCipher.decrypt(inflight.get(1000)); - bobGroupCipher.decrypt(inflight.get(inflight.size()-1)); + bobGroupCipher.decrypt(inflight.get(inflight.size() - 1)); try { bobGroupCipher.decrypt(inflight.get(0)); @@ -268,7 +326,6 @@ public void testMessageKeyLimit() throws Exception { } } - private int randomInt() { try { return SecureRandom.getInstance("SHA1PRNG").nextInt(Integer.MAX_VALUE); diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/incrementalmac/IncrementalStreamsTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/incrementalmac/IncrementalStreamsTest.java index 4657a33ae6..5e0774c832 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/incrementalmac/IncrementalStreamsTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/incrementalmac/IncrementalStreamsTest.java @@ -5,127 +5,175 @@ package org.signal.libsignal.protocol.incrementalmac; -import junit.framework.TestCase; -import org.signal.libsignal.protocol.util.Hex; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import junit.framework.TestCase; +import org.signal.libsignal.protocol.util.Hex; public class IncrementalStreamsTest extends TestCase { - private static final byte[] TEST_HMAC_KEY = Hex.fromStringCondensedAssert("a83481457efecc69ad1342e21d9c0297f71debbf5c9304b4c1b2e433c1a78f98"); - private static final String TEST_EXPECTED_DIGEST = "84892f70600e549fb72879667a9d96a273f144b698ff9ef5a76062a56061a909884f6d9f42918a9e476ed518c4ac8f714bd33f045152ae049877fd3d1b0db25a"; - private static final ChunkSizeChoice SIZE_CHOICE = ChunkSizeChoice.everyNthByte(32); - private static final String[] TEST_INPUT_PARTS = {"this is a test", " input to the incremental ", "mac stream"}; - - public void testIncrementalDigestCreation() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] actualDigest = fullIncrementalDigest(out, TEST_INPUT_PARTS); - assertEquals(String.join("", TEST_INPUT_PARTS), out.toString()); - assertEquals(TEST_EXPECTED_DIGEST, Hex.toStringCondensed(actualDigest)); + private static final byte[] TEST_HMAC_KEY = + Hex.fromStringCondensedAssert( + "a83481457efecc69ad1342e21d9c0297f71debbf5c9304b4c1b2e433c1a78f98"); + private static final String TEST_EXPECTED_DIGEST = + "84892f70600e549fb72879667a9d96a273f144b698ff9ef5a76062a56061a909884f6d9f42918a9e476ed518c4ac8f714bd33f045152ae049877fd3d1b0db25a"; + private static final int CHUNK_SIZE = 32; + private static final ChunkSizeChoice SIZE_CHOICE = ChunkSizeChoice.everyNthByte(CHUNK_SIZE); + private static final String[] TEST_INPUT_PARTS = { + "this is a test", " input to the incremental ", "mac stream" + }; + + public void testIncrementalDigestCreation() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] actualDigest = fullIncrementalDigest(out, TEST_INPUT_PARTS); + assertEquals(String.join("", TEST_INPUT_PARTS), out.toString()); + assertEquals(TEST_EXPECTED_DIGEST, Hex.toStringCondensed(actualDigest)); + } + + public void testIncrementalValidationSuccess() throws IOException { + byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); + ByteArrayInputStream in = + new ByteArrayInputStream(String.join("", TEST_INPUT_PARTS).getBytes()); + + try (IncrementalMacInputStream incrementalIn = + new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { + byte[] buffer = new byte[CHUNK_SIZE / 3]; // intentionally small + int totalBytesRead = 0; + while (incrementalIn.read(buffer) != -1) {} } + } - public void testIncrementalValidationSuccess() throws IOException { - byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); - ByteArrayInputStream in = new ByteArrayInputStream(String.join("", TEST_INPUT_PARTS).getBytes()); + public void testIncrementalValidationSuccessaByteByByte() throws IOException { + byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); + ByteArrayInputStream in = + new ByteArrayInputStream(String.join("", TEST_INPUT_PARTS).getBytes()); - try (IncrementalMacInputStream incrementalIn = new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { - byte[] buffer = new byte[10]; // intentionally small - while (incrementalIn.read(buffer) != -1) { - } - } + try (IncrementalMacInputStream incrementalIn = + new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { + while (incrementalIn.read() != -1) {} } - - public void testIncrementalValidationFailure() throws IOException { - byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); - byte[] corruptInput = String.join("", TEST_INPUT_PARTS).getBytes(); - corruptInput[42] ^= 0xff; - int EXPECTED_SUCCESSFUL_READS = 2; - ByteArrayInputStream in = new ByteArrayInputStream(corruptInput); - try (IncrementalMacInputStream incrementalIn = new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { - byte[] buffer = new byte[SIZE_CHOICE.getSizeInBytes()]; - for (int i = 0; i < EXPECTED_SUCCESSFUL_READS; i++) { - incrementalIn.read(buffer); - } - try { - incrementalIn.read(buffer); - fail("The read should have failed"); - } catch (InvalidMacException _ex) { - - } - } + } + + public void testIncrementalValidationOneShotRead() throws IOException { + byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); + ByteArrayInputStream in = + new ByteArrayInputStream(String.join("", TEST_INPUT_PARTS).getBytes()); + byte[] largeBuffer = new byte[1024]; + + try (IncrementalMacInputStream incrementalIn = + new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { + // Even though the buffer allows it, we get one chunk at a time. + assertEquals(CHUNK_SIZE, incrementalIn.read(largeBuffer)); + assertEquals(18, incrementalIn.read(largeBuffer)); } - - public void testSingleByteRead() throws IOException { - byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); - ByteArrayInputStream in = new ByteArrayInputStream(new byte[]{}); - try (IncrementalMacInputStream incrementalIn = new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { - // The first read with an empty input should call finalize on incremental mac, and throw an exception - try { - incrementalIn.read(); - fail("Validation should have failed"); - } catch (IOException ex) { - } - } - + } + + public void testIncrementalValidationFailure() throws IOException { + byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); + byte[] corruptInput = String.join("", TEST_INPUT_PARTS).getBytes(); + corruptInput[42] ^= 0xff; + ByteArrayInputStream in = new ByteArrayInputStream(corruptInput); + try (IncrementalMacInputStream incrementalIn = + new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { + byte[] buffer = new byte[CHUNK_SIZE / 2]; + assertEquals(16, incrementalIn.read(buffer)); + assertEquals(16, incrementalIn.read(buffer)); + // by now we have read the first chunk, and the next read will fail + try { + incrementalIn.read(buffer); + fail("The read should have failed"); + } catch (InvalidMacException _ex) { + } } - - public void testMultipleFlushesWhileWriting() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayOutputStream digestStream = new ByteArrayOutputStream(); - try (IncrementalMacOutputStream incrementalOut = new IncrementalMacOutputStream(out, TEST_HMAC_KEY, SIZE_CHOICE, digestStream)) { - for (String part : TEST_INPUT_PARTS) { - incrementalOut.write(part.getBytes()); - incrementalOut.flush(); - } - } - byte[] actualDigest = digestStream.toByteArray(); - assertEquals(TEST_EXPECTED_DIGEST, Hex.toStringCondensed(actualDigest)); + } + + public void testNoDataIsReadWithoutValidation() throws IOException { + byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); + byte[] corruptInput = String.join("", TEST_INPUT_PARTS).getBytes(); + corruptInput[1] ^= 0xff; + ByteArrayInputStream in = new ByteArrayInputStream(corruptInput); + try (IncrementalMacInputStream incrementalIn = + new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { + try { + // even though the corruption is in the second byte + incrementalIn.read(); + fail("The read should have failed"); + } catch (InvalidMacException _ex) { + } } - - public void testOutputStreamCloseIsIdempotent() throws IOException { - ByteArrayOutputStream digestStream = new ByteArrayOutputStream(); - IncrementalMacOutputStream incrementalOut = new IncrementalMacOutputStream(new ByteArrayOutputStream(), TEST_HMAC_KEY, SIZE_CHOICE, digestStream); - for (String part : TEST_INPUT_PARTS) { - incrementalOut.write(part.getBytes()); - } - incrementalOut.close(); - incrementalOut.close(); - - assertEquals(TEST_EXPECTED_DIGEST, Hex.toStringCondensed(digestStream.toByteArray())); + } + + public void testSingleByteRead() throws IOException { + byte[] digest = fullIncrementalDigest(new ByteArrayOutputStream(), TEST_INPUT_PARTS); + ByteArrayInputStream in = new ByteArrayInputStream(new byte[] {}); + try (IncrementalMacInputStream incrementalIn = + new IncrementalMacInputStream(in, TEST_HMAC_KEY, SIZE_CHOICE, digest)) { + int read = incrementalIn.read(); + assertEquals(-1, read); } - - public void testEmptyInput() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayOutputStream digest = new ByteArrayOutputStream(); - try (IncrementalMacOutputStream incrementalOut = new IncrementalMacOutputStream(out, TEST_HMAC_KEY, ChunkSizeChoice.inferChunkSize(0), digest)) { - incrementalOut.write(new byte[0]); - incrementalOut.flush(); - } + } + + public void testMultipleFlushesWhileWriting() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream digestStream = new ByteArrayOutputStream(); + try (IncrementalMacOutputStream incrementalOut = + new IncrementalMacOutputStream(out, TEST_HMAC_KEY, SIZE_CHOICE, digestStream)) { + for (String part : TEST_INPUT_PARTS) { + incrementalOut.write(part.getBytes()); + incrementalOut.flush(); + } } - - public void testInvalidChunkSize() throws IOException { - try { - new IncrementalMacOutputStream( - new ByteArrayOutputStream(), - TEST_HMAC_KEY, - ChunkSizeChoice.everyNthByte(0), - new ByteArrayOutputStream()); - } - catch(AssertionError ex) { - assertTrue(ex.getMessage().contains("chunk size must be positive")); - } + byte[] actualDigest = digestStream.toByteArray(); + assertEquals(TEST_EXPECTED_DIGEST, Hex.toStringCondensed(actualDigest)); + } + + public void testOutputStreamCloseIsIdempotent() throws IOException { + ByteArrayOutputStream digestStream = new ByteArrayOutputStream(); + IncrementalMacOutputStream incrementalOut = + new IncrementalMacOutputStream( + new ByteArrayOutputStream(), TEST_HMAC_KEY, SIZE_CHOICE, digestStream); + for (String part : TEST_INPUT_PARTS) { + incrementalOut.write(part.getBytes()); } - - private byte[] fullIncrementalDigest(OutputStream innerOut, String[] input) throws IOException { - ByteArrayOutputStream digestStream = new ByteArrayOutputStream(); - try (IncrementalMacOutputStream incrementalOut = new IncrementalMacOutputStream(innerOut, TEST_HMAC_KEY, SIZE_CHOICE, digestStream)) { - for (String part : input) { - incrementalOut.write(part.getBytes()); - } - incrementalOut.flush(); - } - return digestStream.toByteArray(); + incrementalOut.close(); + incrementalOut.close(); + + assertEquals(TEST_EXPECTED_DIGEST, Hex.toStringCondensed(digestStream.toByteArray())); + } + + public void testEmptyInput() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream digest = new ByteArrayOutputStream(); + try (IncrementalMacOutputStream incrementalOut = + new IncrementalMacOutputStream( + out, TEST_HMAC_KEY, ChunkSizeChoice.inferChunkSize(0), digest)) { + incrementalOut.write(new byte[0]); + incrementalOut.flush(); + } + } + + public void testInvalidChunkSize() throws IOException { + try { + new IncrementalMacOutputStream( + new ByteArrayOutputStream(), + TEST_HMAC_KEY, + ChunkSizeChoice.everyNthByte(0), + new ByteArrayOutputStream()); + } catch (AssertionError ex) { + assertTrue(ex.getMessage().contains("chunk size must be positive")); + } + } + + private byte[] fullIncrementalDigest(OutputStream innerOut, String[] input) throws IOException { + ByteArrayOutputStream digestStream = new ByteArrayOutputStream(); + try (IncrementalMacOutputStream incrementalOut = + new IncrementalMacOutputStream(innerOut, TEST_HMAC_KEY, SIZE_CHOICE, digestStream)) { + for (String part : input) { + incrementalOut.write(part.getBytes()); + } + incrementalOut.flush(); } + return digestStream.toByteArray(); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/kdf/HKDFTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/kdf/HKDFTest.java index 21762a519c..69ab11d1da 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/kdf/HKDFTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/kdf/HKDFTest.java @@ -1,31 +1,26 @@ -package org.signal.libsignal.protocol.kdf; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import junit.framework.TestCase; +package org.signal.libsignal.protocol.kdf; import java.util.Arrays; +import junit.framework.TestCase; +import org.signal.libsignal.protocol.util.Hex; public class HKDFTest extends TestCase { public void testVectorV3() { - byte[] ikm = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b}; - - byte[] salt = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, - 0x0a, 0x0b, 0x0c}; - - byte[] info = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4, - (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9}; - - byte[] okm = {(byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa, - (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43, - (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f, - (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90, - (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d, - (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4, - (byte) 0xc5, (byte) 0xbf, (byte) 0x34, (byte) 0x00, (byte) 0x72, - (byte) 0x08, (byte) 0xd5, (byte) 0xb8, (byte) 0x87, (byte) 0x18, - (byte) 0x58, (byte) 0x65}; + byte[] ikm = Hex.fromStringCondensedAssert("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); + + byte[] salt = Hex.fromStringCondensedAssert("000102030405060708090a0b0c"); + + byte[] info = Hex.fromStringCondensedAssert("f0f1f2f3f4f5f6f7f8f9"); + + byte[] okm = + Hex.fromStringCondensedAssert( + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"); byte[] actualOutput = HKDF.deriveSecrets(ikm, salt, info, 42); @@ -33,96 +28,34 @@ public void testVectorV3() { } public void testVectorLongV3() { - byte[] ikm = {(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, - (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, - (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, - (byte) 0x0f, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, - (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, - (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c, (byte) 0x1d, - (byte) 0x1e, (byte) 0x1f, (byte) 0x20, (byte) 0x21, (byte) 0x22, - (byte) 0x23, (byte) 0x24, (byte) 0x25, (byte) 0x26, (byte) 0x27, - (byte) 0x28, (byte) 0x29, (byte) 0x2a, (byte) 0x2b, (byte) 0x2c, - (byte) 0x2d, (byte) 0x2e, (byte) 0x2f, (byte) 0x30, (byte) 0x31, - (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x35, (byte) 0x36, - (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0x3a, (byte) 0x3b, - (byte) 0x3c, (byte) 0x3d, (byte) 0x3e, (byte) 0x3f, (byte) 0x40, - (byte) 0x41, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, - (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x4a, - (byte) 0x4b, (byte) 0x4c, (byte) 0x4d, (byte) 0x4e, (byte) 0x4f}; - - byte[] salt = {(byte) 0x60, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, - (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, - (byte) 0x6a, (byte) 0x6b, (byte) 0x6c, (byte) 0x6d, (byte) 0x6e, - (byte) 0x6f, (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, - (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, - (byte) 0x79, (byte) 0x7a, (byte) 0x7b, (byte) 0x7c, (byte) 0x7d, - (byte) 0x7e, (byte) 0x7f, (byte) 0x80, (byte) 0x81, (byte) 0x82, - (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, - (byte) 0x88, (byte) 0x89, (byte) 0x8a, (byte) 0x8b, (byte) 0x8c, - (byte) 0x8d, (byte) 0x8e, (byte) 0x8f, (byte) 0x90, (byte) 0x91, - (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, - (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9a, (byte) 0x9b, - (byte) 0x9c, (byte) 0x9d, (byte) 0x9e, (byte) 0x9f, (byte) 0xa0, - (byte) 0xa1, (byte) 0xa2, (byte) 0xa3, (byte) 0xa4, (byte) 0xa5, - (byte) 0xa6, (byte) 0xa7, (byte) 0xa8, (byte) 0xa9, (byte) 0xaa, - (byte) 0xab, (byte) 0xac, (byte) 0xad, (byte) 0xae, (byte) 0xaf}; - - byte[] info = {(byte) 0xb0, (byte) 0xb1, (byte) 0xb2, (byte) 0xb3, (byte) 0xb4, - (byte) 0xb5, (byte) 0xb6, (byte) 0xb7, (byte) 0xb8, (byte) 0xb9, - (byte) 0xba, (byte) 0xbb, (byte) 0xbc, (byte) 0xbd, (byte) 0xbe, - (byte) 0xbf, (byte) 0xc0, (byte) 0xc1, (byte) 0xc2, (byte) 0xc3, - (byte) 0xc4, (byte) 0xc5, (byte) 0xc6, (byte) 0xc7, (byte) 0xc8, - (byte) 0xc9, (byte) 0xca, (byte) 0xcb, (byte) 0xcc, (byte) 0xcd, - (byte) 0xce, (byte) 0xcf, (byte) 0xd0, (byte) 0xd1, (byte) 0xd2, - (byte) 0xd3, (byte) 0xd4, (byte) 0xd5, (byte) 0xd6, (byte) 0xd7, - (byte) 0xd8, (byte) 0xd9, (byte) 0xda, (byte) 0xdb, (byte) 0xdc, - (byte) 0xdd, (byte) 0xde, (byte) 0xdf, (byte) 0xe0, (byte) 0xe1, - (byte) 0xe2, (byte) 0xe3, (byte) 0xe4, (byte) 0xe5, (byte) 0xe6, - (byte) 0xe7, (byte) 0xe8, (byte) 0xe9, (byte) 0xea, (byte) 0xeb, - (byte) 0xec, (byte) 0xed, (byte) 0xee, (byte) 0xef, (byte) 0xf0, - (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4, (byte) 0xf5, - (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9, (byte) 0xfa, - (byte) 0xfb, (byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff}; - - byte[] okm = {(byte) 0xb1, (byte) 0x1e, (byte) 0x39, (byte) 0x8d, (byte) 0xc8, - (byte) 0x03, (byte) 0x27, (byte) 0xa1, (byte) 0xc8, (byte) 0xe7, - (byte) 0xf7, (byte) 0x8c, (byte) 0x59, (byte) 0x6a, (byte) 0x49, - (byte) 0x34, (byte) 0x4f, (byte) 0x01, (byte) 0x2e, (byte) 0xda, - (byte) 0x2d, (byte) 0x4e, (byte) 0xfa, (byte) 0xd8, (byte) 0xa0, - (byte) 0x50, (byte) 0xcc, (byte) 0x4c, (byte) 0x19, (byte) 0xaf, - (byte) 0xa9, (byte) 0x7c, (byte) 0x59, (byte) 0x04, (byte) 0x5a, - (byte) 0x99, (byte) 0xca, (byte) 0xc7, (byte) 0x82, (byte) 0x72, - (byte) 0x71, (byte) 0xcb, (byte) 0x41, (byte) 0xc6, (byte) 0x5e, - (byte) 0x59, (byte) 0x0e, (byte) 0x09, (byte) 0xda, (byte) 0x32, - (byte) 0x75, (byte) 0x60, (byte) 0x0c, (byte) 0x2f, (byte) 0x09, - (byte) 0xb8, (byte) 0x36, (byte) 0x77, (byte) 0x93, (byte) 0xa9, - (byte) 0xac, (byte) 0xa3, (byte) 0xdb, (byte) 0x71, (byte) 0xcc, - (byte) 0x30, (byte) 0xc5, (byte) 0x81, (byte) 0x79, (byte) 0xec, - (byte) 0x3e, (byte) 0x87, (byte) 0xc1, (byte) 0x4c, (byte) 0x01, - (byte) 0xd5, (byte) 0xc1, (byte) 0xf3, (byte) 0x43, (byte) 0x4f, - (byte) 0x1d, (byte) 0x87}; + byte[] ikm = + Hex.fromStringCondensedAssert( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"); + + byte[] salt = + Hex.fromStringCondensedAssert( + "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"); + + byte[] info = + Hex.fromStringCondensedAssert( + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); + + byte[] okm = + Hex.fromStringCondensedAssert( + "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87"); byte[] actualOutput = HKDF.deriveSecrets(ikm, salt, info, 82); assertTrue(Arrays.equals(okm, actualOutput)); } public void testNullInfo() { - byte[] ikm = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b}; - - byte[] salt = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, - 0x0a, 0x0b, 0x0c}; - - byte[] okm = {(byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa, - (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43, - (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f, - (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90, - (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d, - (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4, - (byte) 0xc5, (byte) 0xbf, (byte) 0x34, (byte) 0x00, (byte) 0x72, - (byte) 0x08, (byte) 0xd5, (byte) 0xb8, (byte) 0x87, (byte) 0x18, - (byte) 0x58, (byte) 0x65}; + byte[] ikm = Hex.fromStringCondensedAssert("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); + + byte[] salt = Hex.fromStringCondensedAssert("000102030405060708090a0b0c"); + + byte[] okm = + Hex.fromStringCondensedAssert( + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"); byte[] outputWithNull = HKDF.deriveSecrets(ikm, salt, null, 42); byte[] outputWithEmpty = HKDF.deriveSecrets(ikm, salt, new byte[] {}, 42); diff --git a/java/client/src/test/java/org/signal/libsignal/protocol/ratchet/RatchetingSessionTest.java b/java/client/src/test/java/org/signal/libsignal/protocol/ratchet/RatchetingSessionTest.java index 717ed0e8d7..569ca7da0f 100644 --- a/java/client/src/test/java/org/signal/libsignal/protocol/ratchet/RatchetingSessionTest.java +++ b/java/client/src/test/java/org/signal/libsignal/protocol/ratchet/RatchetingSessionTest.java @@ -1,7 +1,12 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.ratchet; +import java.util.Arrays; import junit.framework.TestCase; - import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyException; @@ -11,199 +16,241 @@ import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.state.SessionRecord; -import java.util.Arrays; - public class RatchetingSessionTest extends TestCase { public void testRatchetingSessionAsBob() throws InvalidKeyException { - byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, - (byte) 0x76, (byte) 0xb8, (byte) 0x77, (byte) 0x02, - (byte) 0x05, (byte) 0x74, (byte) 0x5a, (byte) 0x3a, - (byte) 0x6e, (byte) 0x24, (byte) 0xf5, (byte) 0x79, - (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a, - (byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, - (byte) 0x92, (byte) 0x8e, (byte) 0xbb, (byte) 0xad, - (byte) 0xc9, (byte) 0xc0, (byte) 0x5a, (byte) 0xd4, - (byte) 0x58}; - - byte[] bobPrivate = {(byte) 0xa1, (byte) 0xca, (byte) 0xb4, (byte) 0x8f, - (byte) 0x7c, (byte) 0x89, (byte) 0x3f, (byte) 0xaf, - (byte) 0xa9, (byte) 0x88, (byte) 0x0a, (byte) 0x28, - (byte) 0xc3, (byte) 0xb4, (byte) 0x99, (byte) 0x9d, - (byte) 0x28, (byte) 0xd6, (byte) 0x32, (byte) 0x95, - (byte) 0x62, (byte) 0xd2, (byte) 0x7a, (byte) 0x4e, - (byte) 0xa4, (byte) 0xe2, (byte) 0x2e, (byte) 0x9f, - (byte) 0xf1, (byte) 0xbd, (byte) 0xd6, (byte) 0x5a}; - - byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, - (byte) 0x74, (byte) 0xf6, (byte) 0x96, (byte) 0x69, - (byte) 0x56, (byte) 0xc2, (byte) 0xdd, (byte) 0x47, - (byte) 0x3f, (byte) 0x8f, (byte) 0xa1, (byte) 0x5a, - (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c, - (byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, - (byte) 0x16, (byte) 0x92, (byte) 0x32, (byte) 0x4c, - (byte) 0xef, (byte) 0xb1, (byte) 0xc5, (byte) 0xe6, - (byte) 0x26}; - - byte[] bobIdentityPrivate = {(byte) 0x48, (byte) 0x75, (byte) 0xcc, (byte) 0x69, - (byte) 0xdd, (byte) 0xf8, (byte) 0xea, (byte) 0x07, - (byte) 0x19, (byte) 0xec, (byte) 0x94, (byte) 0x7d, - (byte) 0x61, (byte) 0x08, (byte) 0x11, (byte) 0x35, - (byte) 0x86, (byte) 0x8d, (byte) 0x5f, (byte) 0xd8, - (byte) 0x01, (byte) 0xf0, (byte) 0x2c, (byte) 0x02, - (byte) 0x25, (byte) 0xe5, (byte) 0x16, (byte) 0xdf, - (byte) 0x21, (byte) 0x56, (byte) 0x60, (byte) 0x5e}; - - byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, - (byte) 0xb1, (byte) 0xa9, (byte) 0x86, (byte) 0x2c, - (byte) 0x3a, (byte) 0xf6, (byte) 0xbe, (byte) 0xac, - (byte) 0xa8, (byte) 0x92, (byte) 0x02, (byte) 0x77, - (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a, - (byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, - (byte) 0xc9, (byte) 0x06, (byte) 0xae, (byte) 0xb3, - (byte) 0x5e, (byte) 0x03, (byte) 0xcf, (byte) 0x89, - (byte) 0x50}; - - byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, - (byte) 0x1f, (byte) 0x52, (byte) 0x02, (byte) 0x83, - (byte) 0xef, (byte) 0xcc, (byte) 0x55, (byte) 0xfc, - (byte) 0xa5, (byte) 0xe6, (byte) 0x70, (byte) 0x75, - (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f, - (byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, - (byte) 0xaf, (byte) 0x76, (byte) 0xdf, (byte) 0x18, - (byte) 0xc5, (byte) 0x1d, (byte) 0x29, (byte) 0xd3, - (byte) 0x4b}; - - byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, - (byte) 0x56, (byte) 0x60, (byte) 0xad, (byte) 0xa6, - (byte) 0x5b, (byte) 0x40, (byte) 0x10, (byte) 0x07, - (byte) 0xf6, (byte) 0x15, (byte) 0xe6, (byte) 0x54, - (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43, - (byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, - (byte) 0x87, (byte) 0x51, (byte) 0x49, (byte) 0xbc, - (byte) 0xee, (byte) 0xfc, (byte) 0xb4, (byte) 0x2b, - (byte) 0x4a}; - - byte[] bobSignedPreKeyPublic = {(byte)0x05, (byte)0xac, (byte)0x24, (byte)0x8a, (byte)0x8f, - (byte)0x26, (byte)0x3b, (byte)0xe6, (byte)0x86, (byte)0x35, - (byte)0x76, (byte)0xeb, (byte)0x03, (byte)0x62, (byte)0xe2, - (byte)0x8c, (byte)0x82, (byte)0x8f, (byte)0x01, (byte)0x07, - (byte)0xa3, (byte)0x37, (byte)0x9d, (byte)0x34, (byte)0xba, - (byte)0xb1, (byte)0x58, (byte)0x6b, (byte)0xf8, (byte)0xc7, - (byte)0x70, (byte)0xcd, (byte)0x67}; - - byte[] bobSignedPreKeyPrivate = {(byte)0x58, (byte)0x39, (byte)0x00, (byte)0x13, (byte)0x1f, - (byte)0xb7, (byte)0x27, (byte)0x99, (byte)0x8b, (byte)0x78, - (byte)0x03, (byte)0xfe, (byte)0x6a, (byte)0xc2, (byte)0x2c, - (byte)0xc5, (byte)0x91, (byte)0xf3, (byte)0x42, (byte)0xe4, - (byte)0xe4, (byte)0x2a, (byte)0x8c, (byte)0x8d, (byte)0x5d, - (byte)0x78, (byte)0x19, (byte)0x42, (byte)0x09, (byte)0xb8, - (byte)0xd2, (byte)0x53}; - - byte[] senderChain = {(byte)0x97, (byte)0x97, (byte)0xca, (byte)0xca, (byte)0x53, - (byte)0xc9, (byte)0x89, (byte)0xbb, (byte)0xe2, (byte)0x29, - (byte)0xa4, (byte)0x0c, (byte)0xa7, (byte)0x72, (byte)0x70, - (byte)0x10, (byte)0xeb, (byte)0x26, (byte)0x04, (byte)0xfc, - (byte)0x14, (byte)0x94, (byte)0x5d, (byte)0x77, (byte)0x95, - (byte)0x8a, (byte)0x0a, (byte)0xed, (byte)0xa0, (byte)0x88, - (byte)0xb4, (byte)0x4d}; - - IdentityKey bobIdentityKeyPublic = new IdentityKey(bobIdentityPublic, 0); - ECPrivateKey bobIdentityKeyPrivate = Curve.decodePrivatePoint(bobIdentityPrivate); - IdentityKeyPair bobIdentityKey = new IdentityKeyPair(bobIdentityKeyPublic, bobIdentityKeyPrivate); - ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0); - ECPrivateKey bobEphemeralPrivateKey = Curve.decodePrivatePoint(bobPrivate); - ECKeyPair bobEphemeralKey = new ECKeyPair(bobEphemeralPublicKey, bobEphemeralPrivateKey); - ECKeyPair bobBaseKey = bobEphemeralKey; - ECKeyPair bobSignedPreKey = new ECKeyPair(Curve.decodePoint(bobSignedPreKeyPublic, 0), Curve.decodePrivatePoint(bobSignedPreKeyPrivate)); - - ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0); - ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0); - IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0); - - SessionRecord session = SessionRecord.initializeBobSession(bobIdentityKey, - bobSignedPreKey, - bobEphemeralKey, - aliceIdentityPublicKey, - aliceBasePublicKey); + byte[] bobPublic = { + (byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, + (byte) 0x76, (byte) 0xb8, (byte) 0x77, (byte) 0x02, + (byte) 0x05, (byte) 0x74, (byte) 0x5a, (byte) 0x3a, + (byte) 0x6e, (byte) 0x24, (byte) 0xf5, (byte) 0x79, + (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a, + (byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, + (byte) 0x92, (byte) 0x8e, (byte) 0xbb, (byte) 0xad, + (byte) 0xc9, (byte) 0xc0, (byte) 0x5a, (byte) 0xd4, + (byte) 0x58 + }; + + byte[] bobPrivate = { + (byte) 0xa1, (byte) 0xca, (byte) 0xb4, (byte) 0x8f, + (byte) 0x7c, (byte) 0x89, (byte) 0x3f, (byte) 0xaf, + (byte) 0xa9, (byte) 0x88, (byte) 0x0a, (byte) 0x28, + (byte) 0xc3, (byte) 0xb4, (byte) 0x99, (byte) 0x9d, + (byte) 0x28, (byte) 0xd6, (byte) 0x32, (byte) 0x95, + (byte) 0x62, (byte) 0xd2, (byte) 0x7a, (byte) 0x4e, + (byte) 0xa4, (byte) 0xe2, (byte) 0x2e, (byte) 0x9f, + (byte) 0xf1, (byte) 0xbd, (byte) 0xd6, (byte) 0x5a + }; + + byte[] bobIdentityPublic = { + (byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, + (byte) 0x74, (byte) 0xf6, (byte) 0x96, (byte) 0x69, + (byte) 0x56, (byte) 0xc2, (byte) 0xdd, (byte) 0x47, + (byte) 0x3f, (byte) 0x8f, (byte) 0xa1, (byte) 0x5a, + (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c, + (byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, + (byte) 0x16, (byte) 0x92, (byte) 0x32, (byte) 0x4c, + (byte) 0xef, (byte) 0xb1, (byte) 0xc5, (byte) 0xe6, + (byte) 0x26 + }; + + byte[] bobIdentityPrivate = { + (byte) 0x48, (byte) 0x75, (byte) 0xcc, (byte) 0x69, + (byte) 0xdd, (byte) 0xf8, (byte) 0xea, (byte) 0x07, + (byte) 0x19, (byte) 0xec, (byte) 0x94, (byte) 0x7d, + (byte) 0x61, (byte) 0x08, (byte) 0x11, (byte) 0x35, + (byte) 0x86, (byte) 0x8d, (byte) 0x5f, (byte) 0xd8, + (byte) 0x01, (byte) 0xf0, (byte) 0x2c, (byte) 0x02, + (byte) 0x25, (byte) 0xe5, (byte) 0x16, (byte) 0xdf, + (byte) 0x21, (byte) 0x56, (byte) 0x60, (byte) 0x5e + }; + + byte[] aliceBasePublic = { + (byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, + (byte) 0xb1, (byte) 0xa9, (byte) 0x86, (byte) 0x2c, + (byte) 0x3a, (byte) 0xf6, (byte) 0xbe, (byte) 0xac, + (byte) 0xa8, (byte) 0x92, (byte) 0x02, (byte) 0x77, + (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a, + (byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, + (byte) 0xc9, (byte) 0x06, (byte) 0xae, (byte) 0xb3, + (byte) 0x5e, (byte) 0x03, (byte) 0xcf, (byte) 0x89, + (byte) 0x50 + }; + + byte[] aliceEphemeralPublic = { + (byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, + (byte) 0x1f, (byte) 0x52, (byte) 0x02, (byte) 0x83, + (byte) 0xef, (byte) 0xcc, (byte) 0x55, (byte) 0xfc, + (byte) 0xa5, (byte) 0xe6, (byte) 0x70, (byte) 0x75, + (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f, + (byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, + (byte) 0xaf, (byte) 0x76, (byte) 0xdf, (byte) 0x18, + (byte) 0xc5, (byte) 0x1d, (byte) 0x29, (byte) 0xd3, + (byte) 0x4b + }; + + byte[] aliceIdentityPublic = { + (byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, + (byte) 0x56, (byte) 0x60, (byte) 0xad, (byte) 0xa6, + (byte) 0x5b, (byte) 0x40, (byte) 0x10, (byte) 0x07, + (byte) 0xf6, (byte) 0x15, (byte) 0xe6, (byte) 0x54, + (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43, + (byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, + (byte) 0x87, (byte) 0x51, (byte) 0x49, (byte) 0xbc, + (byte) 0xee, (byte) 0xfc, (byte) 0xb4, (byte) 0x2b, + (byte) 0x4a + }; + + byte[] bobSignedPreKeyPublic = { + (byte) 0x05, (byte) 0xac, (byte) 0x24, (byte) 0x8a, (byte) 0x8f, + (byte) 0x26, (byte) 0x3b, (byte) 0xe6, (byte) 0x86, (byte) 0x35, + (byte) 0x76, (byte) 0xeb, (byte) 0x03, (byte) 0x62, (byte) 0xe2, + (byte) 0x8c, (byte) 0x82, (byte) 0x8f, (byte) 0x01, (byte) 0x07, + (byte) 0xa3, (byte) 0x37, (byte) 0x9d, (byte) 0x34, (byte) 0xba, + (byte) 0xb1, (byte) 0x58, (byte) 0x6b, (byte) 0xf8, (byte) 0xc7, + (byte) 0x70, (byte) 0xcd, (byte) 0x67 + }; + + byte[] bobSignedPreKeyPrivate = { + (byte) 0x58, (byte) 0x39, (byte) 0x00, (byte) 0x13, (byte) 0x1f, + (byte) 0xb7, (byte) 0x27, (byte) 0x99, (byte) 0x8b, (byte) 0x78, + (byte) 0x03, (byte) 0xfe, (byte) 0x6a, (byte) 0xc2, (byte) 0x2c, + (byte) 0xc5, (byte) 0x91, (byte) 0xf3, (byte) 0x42, (byte) 0xe4, + (byte) 0xe4, (byte) 0x2a, (byte) 0x8c, (byte) 0x8d, (byte) 0x5d, + (byte) 0x78, (byte) 0x19, (byte) 0x42, (byte) 0x09, (byte) 0xb8, + (byte) 0xd2, (byte) 0x53 + }; + + byte[] senderChain = { + (byte) 0x97, (byte) 0x97, (byte) 0xca, (byte) 0xca, (byte) 0x53, + (byte) 0xc9, (byte) 0x89, (byte) 0xbb, (byte) 0xe2, (byte) 0x29, + (byte) 0xa4, (byte) 0x0c, (byte) 0xa7, (byte) 0x72, (byte) 0x70, + (byte) 0x10, (byte) 0xeb, (byte) 0x26, (byte) 0x04, (byte) 0xfc, + (byte) 0x14, (byte) 0x94, (byte) 0x5d, (byte) 0x77, (byte) 0x95, + (byte) 0x8a, (byte) 0x0a, (byte) 0xed, (byte) 0xa0, (byte) 0x88, + (byte) 0xb4, (byte) 0x4d + }; + + IdentityKey bobIdentityKeyPublic = new IdentityKey(bobIdentityPublic, 0); + ECPrivateKey bobIdentityKeyPrivate = Curve.decodePrivatePoint(bobIdentityPrivate); + IdentityKeyPair bobIdentityKey = + new IdentityKeyPair(bobIdentityKeyPublic, bobIdentityKeyPrivate); + ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0); + ECPrivateKey bobEphemeralPrivateKey = Curve.decodePrivatePoint(bobPrivate); + ECKeyPair bobEphemeralKey = new ECKeyPair(bobEphemeralPublicKey, bobEphemeralPrivateKey); + ECKeyPair bobBaseKey = bobEphemeralKey; + ECKeyPair bobSignedPreKey = + new ECKeyPair( + Curve.decodePoint(bobSignedPreKeyPublic, 0), + Curve.decodePrivatePoint(bobSignedPreKeyPrivate)); + + ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0); + ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0); + IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0); + + SessionRecord session = + SessionRecord.initializeBobSession( + bobIdentityKey, + bobSignedPreKey, + bobEphemeralKey, + aliceIdentityPublicKey, + aliceBasePublicKey); assertTrue(session.getLocalIdentityKey().equals(bobIdentityKey.getPublicKey())); assertTrue(session.getRemoteIdentityKey().equals(aliceIdentityPublicKey)); assertTrue(Arrays.equals(session.getSenderChainKeyValue(), senderChain)); } -public void testRatchetingSessionAsAlice() throws InvalidKeyException { - byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, (byte) 0x76, - (byte) 0xb8, (byte) 0x77, (byte) 0x02, (byte) 0x05, (byte) 0x74, - (byte) 0x5a, (byte) 0x3a, (byte) 0x6e, (byte) 0x24, (byte) 0xf5, - (byte) 0x79, (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a, - (byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, (byte) 0x92, - (byte) 0x8e, (byte) 0xbb, (byte) 0xad, (byte) 0xc9, (byte) 0xc0, - (byte) 0x5a, (byte) 0xd4, (byte) 0x58}; - - byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, (byte) 0x74, - (byte) 0xf6, (byte) 0x96, (byte) 0x69, (byte) 0x56, (byte) 0xc2, - (byte) 0xdd, (byte) 0x47, (byte) 0x3f, (byte) 0x8f, (byte) 0xa1, - (byte) 0x5a, (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c, - (byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, (byte) 0x16, - (byte) 0x92, (byte) 0x32, (byte) 0x4c, (byte) 0xef, (byte) 0xb1, - (byte) 0xc5, (byte) 0xe6, (byte) 0x26}; - - byte[] bobSignedPreKeyPublic = {(byte)0x05, (byte)0xac, (byte)0x24, (byte)0x8a, (byte)0x8f, - (byte)0x26, (byte)0x3b, (byte)0xe6, (byte)0x86, (byte)0x35, - (byte)0x76, (byte)0xeb, (byte)0x03, (byte)0x62, (byte)0xe2, - (byte)0x8c, (byte)0x82, (byte)0x8f, (byte)0x01, (byte)0x07, - (byte)0xa3, (byte)0x37, (byte)0x9d, (byte)0x34, (byte)0xba, - (byte)0xb1, (byte)0x58, (byte)0x6b, (byte)0xf8, (byte)0xc7, - (byte)0x70, (byte)0xcd, (byte)0x67}; - - byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, (byte) 0xb1, - (byte) 0xa9, (byte) 0x86, (byte) 0x2c, (byte) 0x3a, (byte) 0xf6, - (byte) 0xbe, (byte) 0xac, (byte) 0xa8, (byte) 0x92, (byte) 0x02, - (byte) 0x77, (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a, - (byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, (byte) 0xc9, - (byte) 0x06, (byte) 0xae, (byte) 0xb3, (byte) 0x5e, (byte) 0x03, - (byte) 0xcf, (byte) 0x89, (byte) 0x50}; - - byte[] aliceBasePrivate = {(byte) 0x11, (byte) 0xae, (byte) 0x7c, (byte) 0x64, (byte) 0xd1, - (byte) 0xe6, (byte) 0x1c, (byte) 0xd5, (byte) 0x96, (byte) 0xb7, - (byte) 0x6a, (byte) 0x0d, (byte) 0xb5, (byte) 0x01, (byte) 0x26, - (byte) 0x73, (byte) 0x39, (byte) 0x1c, (byte) 0xae, (byte) 0x66, - (byte) 0xed, (byte) 0xbf, (byte) 0xcf, (byte) 0x07, (byte) 0x3b, - (byte) 0x4d, (byte) 0xa8, (byte) 0x05, (byte) 0x16, (byte) 0xa4, - (byte) 0x74, (byte) 0x49}; - - byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, (byte) 0x1f, - (byte) 0x52, (byte) 0x02, (byte) 0x83, (byte) 0xef, (byte) 0xcc, - (byte) 0x55, (byte) 0xfc, (byte) 0xa5, (byte) 0xe6, (byte) 0x70, - (byte) 0x75, (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f, - (byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, (byte) 0xaf, - (byte) 0x76, (byte) 0xdf, (byte) 0x18, (byte) 0xc5, (byte) 0x1d, - (byte) 0x29, (byte) 0xd3, (byte) 0x4b}; - - byte[] aliceEphemeralPrivate = {(byte) 0xd1, (byte) 0xba, (byte) 0x38, (byte) 0xce, (byte) 0xa9, - (byte) 0x17, (byte) 0x43, (byte) 0xd3, (byte) 0x39, (byte) 0x39, - (byte) 0xc3, (byte) 0x3c, (byte) 0x84, (byte) 0x98, (byte) 0x65, - (byte) 0x09, (byte) 0x28, (byte) 0x01, (byte) 0x61, (byte) 0xb8, - (byte) 0xb6, (byte) 0x0f, (byte) 0xc7, (byte) 0x87, (byte) 0x0c, - (byte) 0x59, (byte) 0x9c, (byte) 0x1d, (byte) 0x46, (byte) 0x20, - (byte) 0x12, (byte) 0x48}; - - byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, (byte) 0x56, - (byte) 0x60, (byte) 0xad, (byte) 0xa6, (byte) 0x5b, (byte) 0x40, - (byte) 0x10, (byte) 0x07, (byte) 0xf6, (byte) 0x15, (byte) 0xe6, - (byte) 0x54, (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43, - (byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, (byte) 0x87, - (byte) 0x51, (byte) 0x49, (byte) 0xbc, (byte) 0xee, (byte) 0xfc, - (byte) 0xb4, (byte) 0x2b, (byte) 0x4a}; - - byte[] aliceIdentityPrivate = {(byte) 0x90, (byte) 0x40, (byte) 0xf0, (byte) 0xd4, (byte) 0xe0, - (byte) 0x9c, (byte) 0xf3, (byte) 0x8f, (byte) 0x6d, (byte) 0xc7, - (byte) 0xc1, (byte) 0x37, (byte) 0x79, (byte) 0xc9, (byte) 0x08, - (byte) 0xc0, (byte) 0x15, (byte) 0xa1, (byte) 0xda, (byte) 0x4f, - (byte) 0xa7, (byte) 0x87, (byte) 0x37, (byte) 0xa0, (byte) 0x80, - (byte) 0xeb, (byte) 0x0a, (byte) 0x6f, (byte) 0x4f, (byte) 0x5f, - (byte) 0x8f, (byte) 0x58}; + public void testRatchetingSessionAsAlice() throws InvalidKeyException { + byte[] bobPublic = { + (byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, (byte) 0x76, + (byte) 0xb8, (byte) 0x77, (byte) 0x02, (byte) 0x05, (byte) 0x74, + (byte) 0x5a, (byte) 0x3a, (byte) 0x6e, (byte) 0x24, (byte) 0xf5, + (byte) 0x79, (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a, + (byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, (byte) 0x92, + (byte) 0x8e, (byte) 0xbb, (byte) 0xad, (byte) 0xc9, (byte) 0xc0, + (byte) 0x5a, (byte) 0xd4, (byte) 0x58 + }; + + byte[] bobIdentityPublic = { + (byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, (byte) 0x74, + (byte) 0xf6, (byte) 0x96, (byte) 0x69, (byte) 0x56, (byte) 0xc2, + (byte) 0xdd, (byte) 0x47, (byte) 0x3f, (byte) 0x8f, (byte) 0xa1, + (byte) 0x5a, (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c, + (byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, (byte) 0x16, + (byte) 0x92, (byte) 0x32, (byte) 0x4c, (byte) 0xef, (byte) 0xb1, + (byte) 0xc5, (byte) 0xe6, (byte) 0x26 + }; + + byte[] bobSignedPreKeyPublic = { + (byte) 0x05, (byte) 0xac, (byte) 0x24, (byte) 0x8a, (byte) 0x8f, + (byte) 0x26, (byte) 0x3b, (byte) 0xe6, (byte) 0x86, (byte) 0x35, + (byte) 0x76, (byte) 0xeb, (byte) 0x03, (byte) 0x62, (byte) 0xe2, + (byte) 0x8c, (byte) 0x82, (byte) 0x8f, (byte) 0x01, (byte) 0x07, + (byte) 0xa3, (byte) 0x37, (byte) 0x9d, (byte) 0x34, (byte) 0xba, + (byte) 0xb1, (byte) 0x58, (byte) 0x6b, (byte) 0xf8, (byte) 0xc7, + (byte) 0x70, (byte) 0xcd, (byte) 0x67 + }; + + byte[] aliceBasePublic = { + (byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, (byte) 0xb1, + (byte) 0xa9, (byte) 0x86, (byte) 0x2c, (byte) 0x3a, (byte) 0xf6, + (byte) 0xbe, (byte) 0xac, (byte) 0xa8, (byte) 0x92, (byte) 0x02, + (byte) 0x77, (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a, + (byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, (byte) 0xc9, + (byte) 0x06, (byte) 0xae, (byte) 0xb3, (byte) 0x5e, (byte) 0x03, + (byte) 0xcf, (byte) 0x89, (byte) 0x50 + }; + + byte[] aliceBasePrivate = { + (byte) 0x11, (byte) 0xae, (byte) 0x7c, (byte) 0x64, (byte) 0xd1, + (byte) 0xe6, (byte) 0x1c, (byte) 0xd5, (byte) 0x96, (byte) 0xb7, + (byte) 0x6a, (byte) 0x0d, (byte) 0xb5, (byte) 0x01, (byte) 0x26, + (byte) 0x73, (byte) 0x39, (byte) 0x1c, (byte) 0xae, (byte) 0x66, + (byte) 0xed, (byte) 0xbf, (byte) 0xcf, (byte) 0x07, (byte) 0x3b, + (byte) 0x4d, (byte) 0xa8, (byte) 0x05, (byte) 0x16, (byte) 0xa4, + (byte) 0x74, (byte) 0x49 + }; + + byte[] aliceEphemeralPublic = { + (byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, (byte) 0x1f, + (byte) 0x52, (byte) 0x02, (byte) 0x83, (byte) 0xef, (byte) 0xcc, + (byte) 0x55, (byte) 0xfc, (byte) 0xa5, (byte) 0xe6, (byte) 0x70, + (byte) 0x75, (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f, + (byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, (byte) 0xaf, + (byte) 0x76, (byte) 0xdf, (byte) 0x18, (byte) 0xc5, (byte) 0x1d, + (byte) 0x29, (byte) 0xd3, (byte) 0x4b + }; + + byte[] aliceEphemeralPrivate = { + (byte) 0xd1, (byte) 0xba, (byte) 0x38, (byte) 0xce, (byte) 0xa9, + (byte) 0x17, (byte) 0x43, (byte) 0xd3, (byte) 0x39, (byte) 0x39, + (byte) 0xc3, (byte) 0x3c, (byte) 0x84, (byte) 0x98, (byte) 0x65, + (byte) 0x09, (byte) 0x28, (byte) 0x01, (byte) 0x61, (byte) 0xb8, + (byte) 0xb6, (byte) 0x0f, (byte) 0xc7, (byte) 0x87, (byte) 0x0c, + (byte) 0x59, (byte) 0x9c, (byte) 0x1d, (byte) 0x46, (byte) 0x20, + (byte) 0x12, (byte) 0x48 + }; + + byte[] aliceIdentityPublic = { + (byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, (byte) 0x56, + (byte) 0x60, (byte) 0xad, (byte) 0xa6, (byte) 0x5b, (byte) 0x40, + (byte) 0x10, (byte) 0x07, (byte) 0xf6, (byte) 0x15, (byte) 0xe6, + (byte) 0x54, (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43, + (byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, (byte) 0x87, + (byte) 0x51, (byte) 0x49, (byte) 0xbc, (byte) 0xee, (byte) 0xfc, + (byte) 0xb4, (byte) 0x2b, (byte) 0x4a + }; + + byte[] aliceIdentityPrivate = { + (byte) 0x90, (byte) 0x40, (byte) 0xf0, (byte) 0xd4, (byte) 0xe0, + (byte) 0x9c, (byte) 0xf3, (byte) 0x8f, (byte) 0x6d, (byte) 0xc7, + (byte) 0xc1, (byte) 0x37, (byte) 0x79, (byte) 0xc9, (byte) 0x08, + (byte) 0xc0, (byte) 0x15, (byte) 0xa1, (byte) 0xda, (byte) 0x4f, + (byte) 0xa7, (byte) 0x87, (byte) 0x37, (byte) 0xa0, (byte) 0x80, + (byte) 0xeb, (byte) 0x0a, (byte) 0x6f, (byte) 0x4f, (byte) 0x5f, + (byte) 0x8f, (byte) 0x58 + }; /* byte[] receiverChain = {(byte)0x97, (byte)0x97, (byte)0xca, (byte)0xca, (byte)0x53, @@ -214,37 +261,37 @@ public void testRatchetingSessionAsAlice() throws InvalidKeyException { (byte)0x8a, (byte)0x0a, (byte)0xed, (byte)0xa0, (byte)0x88, (byte)0xb4, (byte)0x4d}; */ - byte[] receiverChain = {(byte)0xab, (byte)0x9b, (byte)0xe5, (byte)0x0e, (byte)0x5c, - (byte)0xb2, (byte)0x2a, (byte)0x92, (byte)0x54, (byte)0x46, - (byte)0xab, (byte)0x90, (byte)0xee, (byte)0x56, (byte)0x70, - (byte)0x54, (byte)0x5f, (byte)0x4f, (byte)0xd3, (byte)0x29, - (byte)0x02, (byte)0x45, (byte)0x9e, (byte)0xc2, (byte)0x74, - (byte)0xb6, (byte)0xad, (byte)0x0a, (byte)0xe5, (byte)0xd6, - (byte)0x03, (byte)0x1a}; - - IdentityKey bobIdentityKey = new IdentityKey(bobIdentityPublic, 0); - ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0); - ECPublicKey bobSignedPreKey = Curve.decodePoint(bobSignedPreKeyPublic, 0); - ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0); - ECPrivateKey aliceBasePrivateKey = Curve.decodePrivatePoint(aliceBasePrivate); - ECKeyPair aliceBaseKey = new ECKeyPair(aliceBasePublicKey, aliceBasePrivateKey); - ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0); - ECPrivateKey aliceEphemeralPrivateKey = Curve.decodePrivatePoint(aliceEphemeralPrivate); - ECKeyPair aliceEphemeralKey = new ECKeyPair(aliceEphemeralPublicKey, aliceEphemeralPrivateKey); - IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0); - ECPrivateKey aliceIdentityPrivateKey = Curve.decodePrivatePoint(aliceIdentityPrivate); - IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(aliceIdentityPublicKey, aliceIdentityPrivateKey); - - SessionRecord session = SessionRecord.initializeAliceSession(aliceIdentityKey, - aliceBaseKey, - bobIdentityKey, - bobSignedPreKey, - bobEphemeralPublicKey); + byte[] receiverChain = { + (byte) 0xab, (byte) 0x9b, (byte) 0xe5, (byte) 0x0e, (byte) 0x5c, + (byte) 0xb2, (byte) 0x2a, (byte) 0x92, (byte) 0x54, (byte) 0x46, + (byte) 0xab, (byte) 0x90, (byte) 0xee, (byte) 0x56, (byte) 0x70, + (byte) 0x54, (byte) 0x5f, (byte) 0x4f, (byte) 0xd3, (byte) 0x29, + (byte) 0x02, (byte) 0x45, (byte) 0x9e, (byte) 0xc2, (byte) 0x74, + (byte) 0xb6, (byte) 0xad, (byte) 0x0a, (byte) 0xe5, (byte) 0xd6, + (byte) 0x03, (byte) 0x1a + }; + + IdentityKey bobIdentityKey = new IdentityKey(bobIdentityPublic, 0); + ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0); + ECPublicKey bobSignedPreKey = Curve.decodePoint(bobSignedPreKeyPublic, 0); + ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0); + ECPrivateKey aliceBasePrivateKey = Curve.decodePrivatePoint(aliceBasePrivate); + ECKeyPair aliceBaseKey = new ECKeyPair(aliceBasePublicKey, aliceBasePrivateKey); + ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0); + ECPrivateKey aliceEphemeralPrivateKey = Curve.decodePrivatePoint(aliceEphemeralPrivate); + ECKeyPair aliceEphemeralKey = new ECKeyPair(aliceEphemeralPublicKey, aliceEphemeralPrivateKey); + IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0); + ECPrivateKey aliceIdentityPrivateKey = Curve.decodePrivatePoint(aliceIdentityPrivate); + IdentityKeyPair aliceIdentityKey = + new IdentityKeyPair(aliceIdentityPublicKey, aliceIdentityPrivateKey); + + SessionRecord session = + SessionRecord.initializeAliceSession( + aliceIdentityKey, aliceBaseKey, bobIdentityKey, bobSignedPreKey, bobEphemeralPublicKey); assertTrue(session.getLocalIdentityKey().equals(aliceIdentityKey.getPublicKey())); assertTrue(session.getRemoteIdentityKey().equals(bobIdentityKey)); - assertTrue(Arrays.equals(session.getReceiverChainKeyValue(bobEphemeralPublicKey), - receiverChain)); - + assertTrue( + Arrays.equals(session.getReceiverChainKeyValue(bobEphemeralPublicKey), receiverChain)); } } diff --git a/java/client/src/test/java/org/signal/libsignal/sgxsession/SgxClientTest.java b/java/client/src/test/java/org/signal/libsignal/sgxsession/SgxClientTest.java index d86ee5920d..dad11cb08a 100644 --- a/java/client/src/test/java/org/signal/libsignal/sgxsession/SgxClientTest.java +++ b/java/client/src/test/java/org/signal/libsignal/sgxsession/SgxClientTest.java @@ -5,6 +5,11 @@ package org.signal.libsignal.sgxsession; +import static org.junit.Assert.assertEquals; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -15,98 +20,102 @@ import org.signal.libsignal.svr2.Svr2Client; import org.signal.libsignal.util.ResourceReader; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; - @RunWith(Parameterized.class) public class SgxClientTest { - static enum ServiceType { - SVR2, - CDS2 - } - - private byte[] mrenclave; - private byte[] attestationMsg; - private Instant validInstant; - private ServiceType serviceType; - - public SgxClientTest(byte[] mrenclave, byte[] attestationMsg, Instant earliestValidInstant, ServiceType serviceType) { - this.mrenclave = mrenclave; - this.attestationMsg = attestationMsg; - this.validInstant = earliestValidInstant; - this.serviceType = serviceType; - } - - @Parameters(name = "{3}") - public static Collection data() throws Exception { - byte[] cds2Handshake = ResourceReader.readAll(SgxClientTest.class.getResourceAsStream("cds2handshakestart.data")); - byte[] svr2Handshake = ResourceReader.readAll(SgxClientTest.class.getResourceAsStream("svr2handshakestart.data")); - return Arrays.asList(new Object[][] { - { - Hex.fromStringCondensed("39d78f17f8aa9a8e9cdaf16595947a057bac21f014d1abfd6a99b2dfd4e18d1d"), - cds2Handshake, - Instant.ofEpochMilli(1655857680000L), - ServiceType.CDS2 - }, - { - Hex.fromStringCondensed("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"), - svr2Handshake, - Instant.ofEpochSecond(1683836600), - ServiceType.SVR2 - } + static enum ServiceType { + SVR2, + CDS2 + } + + private byte[] mrenclave; + private byte[] attestationMsg; + private Instant validInstant; + private ServiceType serviceType; + + public SgxClientTest( + byte[] mrenclave, + byte[] attestationMsg, + Instant earliestValidInstant, + ServiceType serviceType) { + this.mrenclave = mrenclave; + this.attestationMsg = attestationMsg; + this.validInstant = earliestValidInstant; + this.serviceType = serviceType; + } + + @Parameters(name = "{3}") + public static Collection data() throws Exception { + byte[] cds2Handshake = + ResourceReader.readAll(SgxClientTest.class.getResourceAsStream("cds2handshakestart.data")); + byte[] svr2Handshake = + ResourceReader.readAll(SgxClientTest.class.getResourceAsStream("svr2handshakestart.data")); + return Arrays.asList( + new Object[][] { + { + Hex.fromStringCondensed( + "39d78f17f8aa9a8e9cdaf16595947a057bac21f014d1abfd6a99b2dfd4e18d1d"), + cds2Handshake, + Instant.ofEpochMilli(1655857680000L), + ServiceType.CDS2 + }, + { + Hex.fromStringCondensed( + "a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"), + svr2Handshake, + Instant.ofEpochSecond(1683836600), + ServiceType.SVR2 + } }); + } + + private SgxClient getClient(byte[] mrenclave, byte[] attestationMsg, Instant currentTime) + throws AttestationDataException { + switch (serviceType) { + case SVR2: + return new Svr2Client(mrenclave, attestationMsg, currentTime); + case CDS2: + return new Cds2Client(mrenclave, attestationMsg, currentTime); } - - - private SgxClient getClient(byte[] mrenclave, byte[] attestationMsg, Instant currentTime) throws AttestationDataException { - switch (serviceType) { - case SVR2: - return new Svr2Client(mrenclave, attestationMsg, currentTime); - case CDS2: - return new Cds2Client(mrenclave, attestationMsg, currentTime); - } - throw new IllegalStateException(); - } - - @Test - public void testCreateClient() throws AttestationDataException { - SgxClient client = getClient(mrenclave, attestationMsg, validInstant); - byte[] initialMessage = client.initialRequest(); - assertEquals(48, initialMessage.length); - } - - - @Test(expected = AttestationDataException.class) - public void testCreateClientFailsWithInvalidMrenclave() throws AttestationDataException { - byte[] invalidMrenclave = new byte[]{}; - getClient(invalidMrenclave, attestationMsg, validInstant); - } - - @Test(expected = AttestationDataException.class) - public void testCreateClientFailsWithInvalidMessage() throws AttestationDataException { - byte[] invalidMessage = new byte[0]; - getClient(mrenclave, invalidMessage, validInstant); - } - - @Test(expected = AttestationDataException.class) - public void testCreateClientFailsWithInvalidNonEmptyMessage() throws AttestationDataException { - byte[] invalidMessage = new byte[]{ 1 }; - getClient(mrenclave, invalidMessage, validInstant); - } - - @Test(expected = IllegalStateException.class) - public void testEstablishedSendFailsPriorToEstablishment() throws AttestationDataException, SgxCommunicationFailureException { - SgxClient client = getClient(mrenclave, attestationMsg, validInstant); - client.establishedSend(new byte[]{1, 2, 3}); - } - - @Test(expected = IllegalStateException.class) - public void testEstablishedRecvFailsPriorToEstablishment() throws AttestationDataException, SgxCommunicationFailureException { - SgxClient client = getClient(mrenclave, attestationMsg, validInstant); - client.establishedRecv(new byte[]{1, 2, 3}); - } + throw new IllegalStateException(); + } + + @Test + public void testCreateClient() throws AttestationDataException { + SgxClient client = getClient(mrenclave, attestationMsg, validInstant); + byte[] initialMessage = client.initialRequest(); + assertEquals(48, initialMessage.length); + } + + @Test(expected = AttestationDataException.class) + public void testCreateClientFailsWithInvalidMrenclave() throws AttestationDataException { + byte[] invalidMrenclave = new byte[] {}; + getClient(invalidMrenclave, attestationMsg, validInstant); + } + + @Test(expected = AttestationDataException.class) + public void testCreateClientFailsWithInvalidMessage() throws AttestationDataException { + byte[] invalidMessage = new byte[0]; + getClient(mrenclave, invalidMessage, validInstant); + } + + @Test(expected = AttestationDataException.class) + public void testCreateClientFailsWithInvalidNonEmptyMessage() throws AttestationDataException { + byte[] invalidMessage = new byte[] {1}; + getClient(mrenclave, invalidMessage, validInstant); + } + + @Test(expected = IllegalStateException.class) + public void testEstablishedSendFailsPriorToEstablishment() + throws AttestationDataException, SgxCommunicationFailureException { + SgxClient client = getClient(mrenclave, attestationMsg, validInstant); + client.establishedSend(new byte[] {1, 2, 3}); + } + + @Test(expected = IllegalStateException.class) + public void testEstablishedRecvFailsPriorToEstablishment() + throws AttestationDataException, SgxCommunicationFailureException { + SgxClient client = getClient(mrenclave, attestationMsg, validInstant); + client.establishedRecv(new byte[] {1, 2, 3}); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/svr2/PinTest.java b/java/client/src/test/java/org/signal/libsignal/svr2/PinTest.java index dde92ac729..e535aca67a 100644 --- a/java/client/src/test/java/org/signal/libsignal/svr2/PinTest.java +++ b/java/client/src/test/java/org/signal/libsignal/svr2/PinTest.java @@ -2,95 +2,102 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.svr2; -import org.junit.Test; -import org.signal.libsignal.attest.AttestationDataException; -import org.signal.libsignal.protocol.kdf.HKDF; -import org.signal.libsignal.protocol.util.Hex; +import static org.junit.Assert.*; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.*; +import org.junit.Test; +import org.signal.libsignal.attest.AttestationDataException; +import org.signal.libsignal.protocol.kdf.HKDF; +import org.signal.libsignal.protocol.util.Hex; public class PinTest { - @Test(expected = IllegalArgumentException.class) - public void badSaltLength() { - PinHash.svr1("password".getBytes(StandardCharsets.UTF_8), new byte[]{(byte) 0xFF}); - } - - @Test(expected = IllegalArgumentException.class) - public void badEncodedHash() { - Pin.verifyLocalHash("not-a-hash", "password".getBytes(StandardCharsets.UTF_8)); - } - - @Test - public void verify() { - byte[] pin = "password".getBytes(StandardCharsets.UTF_8); - String pwhash = Pin.localHash(pin); - assertTrue(Pin.verifyLocalHash(pwhash, pin)); - assertFalse(Pin.verifyLocalHash(pwhash, "badpassword".getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void known() throws IOException { - final byte[] salt = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); - final byte[] pin = "password".getBytes(StandardCharsets.UTF_8); - - final PinHash pinHash = PinHash.svr1(pin, salt); - assertArrayEquals( - pinHash.accessKey(), - Hex.fromStringCondensed("ab7e8499d21f80a6600b3b9ee349ac6d72c07e3359fe885a934ba7aa844429f8")); - - assertArrayEquals( - pinHash.encryptionKey(), - Hex.fromStringCondensed("44652df80490fc66bb864a9e638b2f7dc9e20649671dd66bbb9c37bee2bfecf1") - ); - } - - @Test - public void known2() throws IOException { - final byte[] salt = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"); - final byte[] pin = "anotherpassword".getBytes(StandardCharsets.UTF_8); - - final PinHash pinHash = PinHash.svr1(pin, salt); - assertArrayEquals( - pinHash.accessKey(), - Hex.fromStringCondensed("301d9dd1e96f20ce51083f67d3298fd37b97525de8324d5e12ed2d407d3d927b")); - - assertArrayEquals( - pinHash.encryptionKey(), - Hex.fromStringCondensed("b6f16aa0591732e339b7e99cdd5fd6586a1c285c9d66876947fd82f66ed99757") - ); - } - - private static byte[] bebytes(final long l) { - final ByteBuffer bb = ByteBuffer.allocate(8); - bb.order(ByteOrder.BIG_ENDIAN); - bb.putLong(l); - return bb.array(); - } - - @Test - public void testSaltWithGroupId() throws IOException, AttestationDataException { - final byte[] mrenclave = Hex.fromStringCondensed("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"); - final byte[] pin = "password".getBytes(StandardCharsets.UTF_8); - final String username = "username"; - final long groupId = Long.parseUnsignedLong("15525669046665930652"); - final PinHash actual = PinHash.svr2(pin, username, mrenclave); - - // svr2 hash should use salt derrived froup groupId - final byte[] expectedSalt = HKDF.deriveSecrets(username.getBytes(StandardCharsets.UTF_8), bebytes(groupId), new byte[]{}, 32); - final byte[] knownSalt = Hex.fromStringCondensed("260d1f6d233c9326e8ba744e778b7b127147c7211d9bc3219ab3b7394766c508"); - assertArrayEquals(knownSalt, expectedSalt); - - final PinHash expected = PinHash.svr1(pin, expectedSalt); - assertArrayEquals(actual.accessKey(), expected.accessKey()); - assertArrayEquals(actual.encryptionKey(), expected.encryptionKey()); - - } + @Test(expected = IllegalArgumentException.class) + public void badSaltLength() { + PinHash.svr1("password".getBytes(StandardCharsets.UTF_8), new byte[] {(byte) 0xFF}); + } + + @Test(expected = IllegalArgumentException.class) + public void badEncodedHash() { + Pin.verifyLocalHash("not-a-hash", "password".getBytes(StandardCharsets.UTF_8)); + } + + @Test + public void verify() { + byte[] pin = "password".getBytes(StandardCharsets.UTF_8); + String pwhash = Pin.localHash(pin); + assertTrue(Pin.verifyLocalHash(pwhash, pin)); + assertFalse(Pin.verifyLocalHash(pwhash, "badpassword".getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void known() throws IOException { + final byte[] salt = + Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + final byte[] pin = "password".getBytes(StandardCharsets.UTF_8); + + final PinHash pinHash = PinHash.svr1(pin, salt); + assertArrayEquals( + pinHash.accessKey(), + Hex.fromStringCondensed( + "ab7e8499d21f80a6600b3b9ee349ac6d72c07e3359fe885a934ba7aa844429f8")); + + assertArrayEquals( + pinHash.encryptionKey(), + Hex.fromStringCondensed( + "44652df80490fc66bb864a9e638b2f7dc9e20649671dd66bbb9c37bee2bfecf1")); + } + + @Test + public void known2() throws IOException { + final byte[] salt = + Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"); + final byte[] pin = "anotherpassword".getBytes(StandardCharsets.UTF_8); + + final PinHash pinHash = PinHash.svr1(pin, salt); + assertArrayEquals( + pinHash.accessKey(), + Hex.fromStringCondensed( + "301d9dd1e96f20ce51083f67d3298fd37b97525de8324d5e12ed2d407d3d927b")); + + assertArrayEquals( + pinHash.encryptionKey(), + Hex.fromStringCondensed( + "b6f16aa0591732e339b7e99cdd5fd6586a1c285c9d66876947fd82f66ed99757")); + } + + private static byte[] bebytes(final long l) { + final ByteBuffer bb = ByteBuffer.allocate(8); + bb.order(ByteOrder.BIG_ENDIAN); + bb.putLong(l); + return bb.array(); + } + + @Test + public void testSaltWithGroupId() throws IOException, AttestationDataException { + final byte[] mrenclave = + Hex.fromStringCondensed("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"); + final byte[] pin = "password".getBytes(StandardCharsets.UTF_8); + final String username = "username"; + final long groupId = Long.parseUnsignedLong("15525669046665930652"); + final PinHash actual = PinHash.svr2(pin, username, mrenclave); + + // svr2 hash should use salt derrived froup groupId + final byte[] expectedSalt = + HKDF.deriveSecrets( + username.getBytes(StandardCharsets.UTF_8), bebytes(groupId), new byte[] {}, 32); + final byte[] knownSalt = + Hex.fromStringCondensed("260d1f6d233c9326e8ba744e778b7b127147c7211d9bc3219ab3b7394766c508"); + assertArrayEquals(knownSalt, expectedSalt); + + final PinHash expected = PinHash.svr1(pin, expectedSalt); + assertArrayEquals(actual.accessKey(), expected.accessKey()); + assertArrayEquals(actual.encryptionKey(), expected.encryptionKey()); + } } diff --git a/java/client/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java b/java/client/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java index 9fa680b291..bb952a07d7 100644 --- a/java/client/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java +++ b/java/client/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java @@ -5,136 +5,142 @@ package org.signal.libsignal.usernames; +import java.security.SecureRandom; +import java.util.List; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import junit.framework.TestCase; import org.signal.libsignal.protocol.util.Hex; -import java.security.SecureRandom; -import java.util.List; - public class UsernamesTest extends TestCase { - public void testUsernameGeneration() throws BaseUsernameException { - String nickname = "SiGNAl"; - List usernames = Username.candidatesFrom(nickname, 3, 32); - assertFalse("Non-zero number of usernames expected", usernames.size() == 0); - for (Username name : usernames) { - assertTrue(String.format("%s does not start with %s", name, nickname), name.getUsername().startsWith(nickname)); - } - } - - public void testInvalidNicknameValidation() throws BaseUsernameException { - List invalidNicknames = List.of("hi", "way_too_long_to_be_a_reasonable_nickname", "I⍰Unicode", "s p a c e s", "0zerostart"); - for (String nickname : invalidNicknames) { - try { - Username.candidatesFrom(nickname, 3, 32); - fail(String.format("'%s' should not be considered valid", nickname)); - } catch (BaseUsernameException ex) { - // this is fine - } - - } - } - - public void testValidUsernameHashing() throws BaseUsernameException { - String username = "he110.42"; - byte[] hash = new Username(username).getHash(); - assertEquals(32, hash.length); - assertEquals("f63f0521eb3adfe1d936f4b626b89558483507fbdb838fc554af059111cf322e", Hex.toStringCondensed(hash)); + public void testUsernameGeneration() throws BaseUsernameException { + String nickname = "SiGNAl"; + List usernames = Username.candidatesFrom(nickname, 3, 32); + assertFalse("Non-zero number of usernames expected", usernames.size() == 0); + for (Username name : usernames) { + assertTrue( + String.format("%s does not start with %s", name, nickname), + name.getUsername().startsWith(nickname)); } - - public void testToTheProofAndBack() throws BaseUsernameException { - Username username = new Username("hello_signal.42"); - assertNotNull(username.getHash()); - byte[] proof = username.generateProof(); - assertNotNull(proof); - assertEquals(128, proof.length); - Username.verifyProof(proof, username.getHash()); + } + + public void testInvalidNicknameValidation() throws BaseUsernameException { + List invalidNicknames = + List.of( + "hi", + "way_too_long_to_be_a_reasonable_nickname", + "I⍰Unicode", + "s p a c e s", + "0zerostart"); + for (String nickname : invalidNicknames) { + try { + Username.candidatesFrom(nickname, 3, 32); + fail(String.format("'%s' should not be considered valid", nickname)); + } catch (BaseUsernameException ex) { + // this is fine + } } - - public void testInvalidHash() throws BaseUsernameException { - Username username = new Username("hello_signal.42"); - byte[] proof = username.generateProof(); - - SecureRandom r = new SecureRandom(); - byte[] badHash = new byte[32]; - r.nextBytes(badHash); - - try { - Username.verifyProof(proof, badHash); - } catch (BaseUsernameException ex) { - assertTrue(ex.getMessage().contains("Username could not be verified")); - } + } + + public void testValidUsernameHashing() throws BaseUsernameException { + String username = "he110.42"; + byte[] hash = new Username(username).getHash(); + assertEquals(32, hash.length); + assertEquals( + "f63f0521eb3adfe1d936f4b626b89558483507fbdb838fc554af059111cf322e", + Hex.toStringCondensed(hash)); + } + + public void testToTheProofAndBack() throws BaseUsernameException { + Username username = new Username("hello_signal.42"); + assertNotNull(username.getHash()); + byte[] proof = username.generateProof(); + assertNotNull(proof); + assertEquals(128, proof.length); + Username.verifyProof(proof, username.getHash()); + } + + public void testInvalidHash() throws BaseUsernameException { + Username username = new Username("hello_signal.42"); + byte[] proof = username.generateProof(); + + SecureRandom r = new SecureRandom(); + byte[] badHash = new byte[32]; + r.nextBytes(badHash); + + try { + Username.verifyProof(proof, badHash); + } catch (BaseUsernameException ex) { + assertTrue(ex.getMessage().contains("Username could not be verified")); } + } - public void testInvalidRandomness() throws BaseUsernameException { - try { - new Username("valid_name.01").generateProofWithRandomness(new byte[31]); - } catch (Error err) { - assertTrue(err.getMessage().contains("Failed to create proof")); - } + public void testInvalidRandomness() throws BaseUsernameException { + try { + new Username("valid_name.01").generateProofWithRandomness(new byte[31]); + } catch (Error err) { + assertTrue(err.getMessage().contains("Failed to create proof")); } - - public void testInvalidUsernames() throws BaseUsernameException { - List usernames = List.of("0zerostart.01", "zero.00", "short_zero.0", "short_one.1"); - for (String name : usernames) { - try { - new Username(name); - fail(String.format("'%s' should not be valid", name)); - } catch (BaseUsernameException ex) { - // this is fine - } - } - for (String name : usernames) { - try { - new Username(name).generateProof(); - fail(String.format("'%s' should not be valid", name)); - } catch (BaseUsernameException ex) { - // this is fine - } - } + } + + public void testInvalidUsernames() throws BaseUsernameException { + List usernames = List.of("0zerostart.01", "zero.00", "short_zero.0", "short_one.1"); + for (String name : usernames) { + try { + new Username(name); + fail(String.format("'%s' should not be valid", name)); + } catch (BaseUsernameException ex) { + // this is fine + } } - - public void testUsernameLinkHappyCase() throws BaseUsernameException { - final Username expectedUsername = new Username("hello_signal.42"); - final Username.UsernameLink link = expectedUsername.generateLink(); - final Username actualUsername = Username.fromLink(link); - assertEquals(expectedUsername.getUsername(), actualUsername.getUsername()); + for (String name : usernames) { + try { + new Username(name).generateProof(); + fail(String.format("'%s' should not be valid", name)); + } catch (BaseUsernameException ex) { + // this is fine + } } - - public void testCreateLinkFailsForLongUsername() throws BaseUsernameException { - final String longUsername = Stream.generate(() -> "a") - .limit(128) - .collect(Collectors.joining()); - try { - new Username(longUsername).generateLink(); - fail("Expected to fail creating a link for a long username"); - } catch (BaseUsernameException ex) { - // this is fine - } + } + + public void testUsernameLinkHappyCase() throws BaseUsernameException { + final Username expectedUsername = new Username("hello_signal.42"); + final Username.UsernameLink link = expectedUsername.generateLink(); + final Username actualUsername = Username.fromLink(link); + assertEquals(expectedUsername.getUsername(), actualUsername.getUsername()); + } + + public void testCreateLinkFailsForLongUsername() throws BaseUsernameException { + final String longUsername = Stream.generate(() -> "a").limit(128).collect(Collectors.joining()); + try { + new Username(longUsername).generateLink(); + fail("Expected to fail creating a link for a long username"); + } catch (BaseUsernameException ex) { + // this is fine } - - public void testDecryptUsernameFromLinkFailsForInvalidEntropySize() throws BaseUsernameException { - final byte[] entropy = new byte[16]; - final byte[] encryptedUsername = new byte[32]; - try { - Username.fromLink(new Username.UsernameLink(entropy, encryptedUsername)); - fail("Expected to fail decrypting username link with an invalid entropy size"); - } catch (BaseUsernameException ex) { - // this is fine - } + } + + public void testDecryptUsernameFromLinkFailsForInvalidEntropySize() throws BaseUsernameException { + final byte[] entropy = new byte[16]; + final byte[] encryptedUsername = new byte[32]; + try { + Username.fromLink(new Username.UsernameLink(entropy, encryptedUsername)); + fail("Expected to fail decrypting username link with an invalid entropy size"); + } catch (BaseUsernameException ex) { + // this is fine } - - public void testDecryptUsernameFromLinkFailsForInvalidEncryptedUsername() throws BaseUsernameException { - final byte[] entropy = new byte[32]; - final byte[] encryptedUsername = new byte[32]; - try { - Username.fromLink(new Username.UsernameLink(entropy, encryptedUsername)); - fail("Expected to fail decrypting username link with an invalid link data"); - } catch (BaseUsernameException ex) { - // this is fine - } + } + + public void testDecryptUsernameFromLinkFailsForInvalidEncryptedUsername() + throws BaseUsernameException { + final byte[] entropy = new byte[32]; + final byte[] encryptedUsername = new byte[32]; + try { + Username.fromLink(new Username.UsernameLink(entropy, encryptedUsername)); + fail("Expected to fail decrypting username link with an invalid link data"); + } catch (BaseUsernameException ex) { + // this is fine } + } } diff --git a/java/client/src/test/java/org/signal/libsignal/util/ResourceReader.java b/java/client/src/test/java/org/signal/libsignal/util/ResourceReader.java index dc244a32b8..dbc180f332 100644 --- a/java/client/src/test/java/org/signal/libsignal/util/ResourceReader.java +++ b/java/client/src/test/java/org/signal/libsignal/util/ResourceReader.java @@ -2,6 +2,7 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.util; import java.io.ByteArrayOutputStream; @@ -9,17 +10,17 @@ import java.io.InputStream; public class ResourceReader { - public static byte[] readAll(final InputStream inputStream) throws IOException { - try { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final byte[] chunk = new byte[4096]; - int read; - while ((read = inputStream.read(chunk, 0, chunk.length)) != -1) { - baos.write(chunk, 0, read); - } - return baos.toByteArray(); - } finally { - inputStream.close(); - } + public static byte[] readAll(final InputStream inputStream) throws IOException { + try { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final byte[] chunk = new byte[4096]; + int read; + while ((read = inputStream.read(chunk, 0, chunk.length)) != -1) { + baos.write(chunk, 0, read); + } + return baos.toByteArray(); + } finally { + inputStream.close(); } + } } diff --git a/java/client/src/test/java/org/signal/libsignal/zkgroup/NativeErrorsTest.java b/java/client/src/test/java/org/signal/libsignal/zkgroup/NativeErrorsTest.java index 59ce733ce6..d35afe27cc 100644 --- a/java/client/src/test/java/org/signal/libsignal/zkgroup/NativeErrorsTest.java +++ b/java/client/src/test/java/org/signal/libsignal/zkgroup/NativeErrorsTest.java @@ -5,10 +5,9 @@ package org.signal.libsignal.zkgroup; -import org.junit.Test; import junit.framework.TestCase; +import org.junit.Test; import org.signal.libsignal.internal.Native; -import org.signal.libsignal.protocol.util.Hex; import org.signal.libsignal.zkgroup.internal.*; public final class NativeErrorsTest extends TestCase { @@ -19,18 +18,20 @@ public void testBadNativeCalls() { byte[] uuidCiphertext = new byte[65]; // valid size boolean failed = false; try { - Native.GroupSecretParams_DecryptServiceId(params, uuidCiphertext); - failed = true; - } catch (AssertionError e) {} + Native.GroupSecretParams_DecryptServiceId(params, uuidCiphertext); + failed = true; + } catch (AssertionError e) { + } if (failed) { - throw new AssertionError("Deserialization failure should Assert if CheckValidContents should have caught this"); + throw new AssertionError( + "Deserialization failure should Assert if CheckValidContents should have caught this"); } byte[] temp = new byte[1]; // wrong length try { - Native.ServerSecretParams_GenerateDeterministic(temp); - throw new AssertionError("Failed to catch wrong byte array length"); - } catch (IllegalArgumentException e) {} + Native.ServerSecretParams_GenerateDeterministic(temp); + throw new AssertionError("Failed to catch wrong byte array length"); + } catch (IllegalArgumentException e) { + } } - } diff --git a/java/client/src/test/java/org/signal/libsignal/zkgroup/RandomnessTest.java b/java/client/src/test/java/org/signal/libsignal/zkgroup/RandomnessTest.java index d001fb54c2..43e53ebbf5 100644 --- a/java/client/src/test/java/org/signal/libsignal/zkgroup/RandomnessTest.java +++ b/java/client/src/test/java/org/signal/libsignal/zkgroup/RandomnessTest.java @@ -5,22 +5,22 @@ package org.signal.libsignal.zkgroup; -import org.junit.Test; -import org.signal.libsignal.zkgroup.internal.*; -import org.signal.libsignal.protocol.util.Hex; +import static org.junit.Assert.assertArrayEquals; import java.io.IOException; import java.security.SecureRandom; - -import static org.junit.Assert.assertArrayEquals; +import org.junit.Test; +import org.signal.libsignal.protocol.util.Hex; +import org.signal.libsignal.zkgroup.internal.*; public final class RandomnessTest extends SecureRandomTest { @Test public void generate_usesSecureRandom() throws IOException { - byte[] array = Hex.fromStringCondensed("e18de7dfe7195f0b9320e309cd3ed3765dcf54a09be57813ee69f5ea35867689"); + byte[] array = + Hex.fromStringCondensed("e18de7dfe7195f0b9320e309cd3ed3765dcf54a09be57813ee69f5ea35867689"); SecureRandom secureRandom = createSecureRandom(array); - byte[] random = new byte[array.length]; + byte[] random = new byte[array.length]; secureRandom.nextBytes(random); assertArrayEquals(array, random); @@ -28,7 +28,8 @@ public void generate_usesSecureRandom() throws IOException { @Test public void generate_usesSecureRandom_alternativeValues() throws IOException { - byte[] array = Hex.fromStringCondensed("ba8a89a05eaf51cac3ce35256199b38a18e0e1fa16f1443db8e34b0489739b80"); + byte[] array = + Hex.fromStringCondensed("ba8a89a05eaf51cac3ce35256199b38a18e0e1fa16f1443db8e34b0489739b80"); SecureRandom secureRandom = createSecureRandom(array); byte[] random = new byte[array.length]; diff --git a/java/client/src/test/java/org/signal/libsignal/zkgroup/SecureRandomTest.java b/java/client/src/test/java/org/signal/libsignal/zkgroup/SecureRandomTest.java index 5d9d49faf6..a127c0021d 100644 --- a/java/client/src/test/java/org/signal/libsignal/zkgroup/SecureRandomTest.java +++ b/java/client/src/test/java/org/signal/libsignal/zkgroup/SecureRandomTest.java @@ -5,13 +5,12 @@ package org.signal.libsignal.zkgroup; -import java.security.SecureRandom; -import java.security.SecureRandomSpi; -import java.util.Arrays; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.security.SecureRandom; +import java.security.SecureRandomSpi; + public abstract class SecureRandomTest { private static class MockRandomSpi extends SecureRandomSpi { @@ -27,7 +26,8 @@ protected byte[] engineGenerateSeed(int numBytes) { protected void engineNextBytes(byte[] outBytes) { assertNotNull("Bytes have been used", bytes); - assertEquals("createSecureRandom was setup with wrong number of bytes", bytes.length, outBytes.length); + assertEquals( + "createSecureRandom was setup with wrong number of bytes", bytes.length, outBytes.length); System.arraycopy(bytes, 0, outBytes, 0, bytes.length); bytes = null; } diff --git a/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/CallLinksTest.java b/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/CallLinksTest.java index 2ed403e3c1..b836a7e3eb 100644 --- a/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/CallLinksTest.java +++ b/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/CallLinksTest.java @@ -5,186 +5,225 @@ package org.signal.libsignal.zkgroup.integrationtests; -import java.io.UnsupportedEncodingException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; import org.junit.Test; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.protocol.util.Hex; -import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.GenericServerPublicParams; import org.signal.libsignal.zkgroup.GenericServerSecretParams; +import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.SecureRandomTest; import org.signal.libsignal.zkgroup.VerificationFailedException; -import org.signal.libsignal.zkgroup.calllinks.CallLinkPublicParams; -import org.signal.libsignal.zkgroup.calllinks.CallLinkSecretParams; import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredential; import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialPresentation; import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialResponse; +import org.signal.libsignal.zkgroup.calllinks.CallLinkPublicParams; +import org.signal.libsignal.zkgroup.calllinks.CallLinkSecretParams; import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredential; import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialPresentation; import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialRequest; import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialRequestContext; import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialResponse; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.UUID; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - public final class CallLinksTest extends SecureRandomTest { - private static final Aci TEST_USER_ID = new Aci(UUID.fromString("00010203-0405-0607-0809-0a0b0c0d0e0f")); + private static final Aci TEST_USER_ID = + new Aci(UUID.fromString("00010203-0405-0607-0809-0a0b0c0d0e0f")); - private static final byte[] TEST_ARRAY_32 = Hex.fromStringCondensedAssert("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + private static final byte[] TEST_ARRAY_32 = + Hex.fromStringCondensedAssert( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); - private static final byte[] TEST_ARRAY_32_1 = Hex.fromStringCondensedAssert("6465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80818283"); + private static final byte[] TEST_ARRAY_32_1 = + Hex.fromStringCondensedAssert( + "6465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80818283"); - private static final byte[] TEST_ARRAY_32_2 = Hex.fromStringCondensedAssert("c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7"); + private static final byte[] TEST_ARRAY_32_2 = + Hex.fromStringCondensedAssert( + "c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7"); - private static final byte[] TEST_ARRAY_32_3 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32 }; + private static final byte[] TEST_ARRAY_32_3 = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32 + }; - private static final byte[] TEST_ARRAY_32_4 = { + private static final byte[] TEST_ARRAY_32_4 = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33}; + 28, 29, 30, 31, 32, 33 + }; - private static final byte[] TEST_ARRAY_32_5 = Hex.fromStringCondensedAssert("030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"); + private static final byte[] TEST_ARRAY_32_5 = + Hex.fromStringCondensedAssert( + "030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"); // 32 bytes of 0xFF is an invalid Ristretto point, so this should invalidate all the // the serialized zkgroup structures, since almost everything contains a Ristretto point private static final byte[] makeBadArray(byte[] a) { - byte[] temp = a.clone(); - for (int count=0; count < temp.length; count++) { - temp[count] = (byte)0xFF; - } - return temp; + byte[] temp = a.clone(); + for (int count = 0; count < temp.length; count++) { + temp[count] = (byte) 0xFF; + } + return temp; } @Test public void testGenericServerParams() throws InvalidInputException { // SERVER - GenericServerSecretParams serverSecretParams = GenericServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + GenericServerSecretParams serverSecretParams = + GenericServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); GenericServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); // SERVER - deserialize test new GenericServerSecretParams(serverSecretParams.serialize()); new GenericServerPublicParams(serverPublicParams.serialize()); try { - byte[] temp = new byte[32]; // wrong length + byte[] temp = new byte[32]; // wrong length new GenericServerSecretParams(temp); fail("Failed to catch invalid GenericServerSecretParams deserialize 1"); } catch (InvalidInputException e) { - // expected + // expected } try { new GenericServerSecretParams(makeBadArray(serverSecretParams.serialize())); fail("Failed to catch invalid GenericServerSecretParams deserialize 2"); } catch (InvalidInputException e) { - // expected + // expected } try { - byte[] temp = new byte[32]; // wrong length - new GenericServerPublicParams(temp); - fail("Failed to catch invalid GenericServerPublicParams deserialize 1"); + byte[] temp = new byte[32]; // wrong length + new GenericServerPublicParams(temp); + fail("Failed to catch invalid GenericServerPublicParams deserialize 1"); } catch (InvalidInputException e) { - // expected + // expected } try { - new GenericServerPublicParams(makeBadArray(serverPublicParams.serialize())); - fail("Failed to catch invalid GenericServerPublicParams deserialize 2"); + new GenericServerPublicParams(makeBadArray(serverPublicParams.serialize())); + fail("Failed to catch invalid GenericServerPublicParams deserialize 2"); } catch (InvalidInputException e) { - // expected + // expected } } @Test - public void testCreateCallLinkIntegration() throws InvalidInputException, VerificationFailedException { + public void testCreateCallLinkIntegration() + throws InvalidInputException, VerificationFailedException { // SERVER // Generate keys - GenericServerSecretParams serverSecretParams = GenericServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + GenericServerSecretParams serverSecretParams = + GenericServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); GenericServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); // CLIENT // Generate keys - CallLinkSecretParams clientSecretParams = CallLinkSecretParams.deriveFromRootKey(TEST_ARRAY_32_1); + CallLinkSecretParams clientSecretParams = + CallLinkSecretParams.deriveFromRootKey(TEST_ARRAY_32_1); CallLinkPublicParams clientPublicParams = clientSecretParams.getPublicParams(); // Create context and request byte[] roomId = TEST_ARRAY_32_2; - CreateCallLinkCredentialRequestContext context = CreateCallLinkCredentialRequestContext.forRoom(roomId, createSecureRandom(TEST_ARRAY_32_3)); - CreateCallLinkCredentialRequest request = context.getRequest(); + CreateCallLinkCredentialRequestContext context = + CreateCallLinkCredentialRequestContext.forRoom(roomId, createSecureRandom(TEST_ARRAY_32_3)); + CreateCallLinkCredentialRequest request = context.getRequest(); // SERVER // Issue credential Instant timestamp = Instant.now().truncatedTo(ChronoUnit.DAYS); - CreateCallLinkCredentialResponse response = request.issueCredential(TEST_USER_ID, timestamp, serverSecretParams, createSecureRandom(TEST_ARRAY_32_4)); + CreateCallLinkCredentialResponse response = + request.issueCredential( + TEST_USER_ID, timestamp, serverSecretParams, createSecureRandom(TEST_ARRAY_32_4)); // CLIENT // Gets stored credential - CreateCallLinkCredential credential = context.receiveResponse(response, TEST_USER_ID, serverPublicParams); - CreateCallLinkCredentialPresentation presentation = credential.present(roomId, TEST_USER_ID, serverPublicParams, clientSecretParams, createSecureRandom(TEST_ARRAY_32_5)); + CreateCallLinkCredential credential = + context.receiveResponse(response, TEST_USER_ID, serverPublicParams); + CreateCallLinkCredentialPresentation presentation = + credential.present( + roomId, + TEST_USER_ID, + serverPublicParams, + clientSecretParams, + createSecureRandom(TEST_ARRAY_32_5)); // SERVER // Verify presentation presentation.verify(roomId, serverSecretParams, clientPublicParams); - presentation.verify(roomId, timestamp.plus(1, ChronoUnit.DAYS), serverSecretParams, clientPublicParams); + presentation.verify( + roomId, timestamp.plus(1, ChronoUnit.DAYS), serverSecretParams, clientPublicParams); try { - presentation.verify(roomId, timestamp.plus(30, ChronoUnit.HOURS), serverSecretParams, clientPublicParams); - fail("credential expired 1"); + presentation.verify( + roomId, timestamp.plus(30, ChronoUnit.HOURS), serverSecretParams, clientPublicParams); + fail("credential expired 1"); } catch (VerificationFailedException e) { - // expected + // expected } try { - presentation.verify(roomId, timestamp.plus(30, ChronoUnit.HOURS).plusSeconds(1), serverSecretParams, clientPublicParams); - fail("credential expired 2"); + presentation.verify( + roomId, + timestamp.plus(30, ChronoUnit.HOURS).plusSeconds(1), + serverSecretParams, + clientPublicParams); + fail("credential expired 2"); } catch (VerificationFailedException e) { - // expected + // expected } } @Test - public void testCallLinkAuthIntegration() throws InvalidInputException, VerificationFailedException { + public void testCallLinkAuthIntegration() + throws InvalidInputException, VerificationFailedException { // SERVER // Generate keys - GenericServerSecretParams serverSecretParams = GenericServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + GenericServerSecretParams serverSecretParams = + GenericServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); GenericServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); // CLIENT // Generate keys - CallLinkSecretParams clientSecretParams = CallLinkSecretParams.deriveFromRootKey(TEST_ARRAY_32_1); + CallLinkSecretParams clientSecretParams = + CallLinkSecretParams.deriveFromRootKey(TEST_ARRAY_32_1); CallLinkPublicParams clientPublicParams = clientSecretParams.getPublicParams(); // SERVER // Issue credential Instant redemptionTime = Instant.now().truncatedTo(ChronoUnit.DAYS); - CallLinkAuthCredentialResponse response = CallLinkAuthCredentialResponse.issueCredential(TEST_USER_ID, redemptionTime, serverSecretParams, createSecureRandom(TEST_ARRAY_32_4)); + CallLinkAuthCredentialResponse response = + CallLinkAuthCredentialResponse.issueCredential( + TEST_USER_ID, redemptionTime, serverSecretParams, createSecureRandom(TEST_ARRAY_32_4)); // CLIENT // Gets stored credential - CallLinkAuthCredential credential = response.receive(TEST_USER_ID, redemptionTime, serverPublicParams); - CallLinkAuthCredentialPresentation presentation = credential.present(TEST_USER_ID, redemptionTime, serverPublicParams, clientSecretParams, createSecureRandom(TEST_ARRAY_32_5)); + CallLinkAuthCredential credential = + response.receive(TEST_USER_ID, redemptionTime, serverPublicParams); + CallLinkAuthCredentialPresentation presentation = + credential.present( + TEST_USER_ID, + redemptionTime, + serverPublicParams, + clientSecretParams, + createSecureRandom(TEST_ARRAY_32_5)); // SERVER // Verify presentation presentation.verify(serverSecretParams, clientPublicParams); - presentation.verify(redemptionTime.plus(1, ChronoUnit.DAYS), serverSecretParams, clientPublicParams); + presentation.verify( + redemptionTime.plus(1, ChronoUnit.DAYS), serverSecretParams, clientPublicParams); try { - presentation.verify(redemptionTime.plus(3, ChronoUnit.DAYS), serverSecretParams, clientPublicParams); - fail("credential expired 1"); + presentation.verify( + redemptionTime.plus(3, ChronoUnit.DAYS), serverSecretParams, clientPublicParams); + fail("credential expired 1"); } catch (VerificationFailedException e) { - // expected + // expected } // CLIENT assertEquals(TEST_USER_ID, clientSecretParams.decryptUserId(presentation.getUserId())); } } - diff --git a/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/ZkGroupTest.java b/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/ZkGroupTest.java index d4c6dcb114..6288fcb86f 100644 --- a/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/ZkGroupTest.java +++ b/java/client/src/test/java/org/signal/libsignal/zkgroup/integrationtests/ZkGroupTest.java @@ -5,7 +5,15 @@ package org.signal.libsignal.zkgroup.integrationtests; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import java.io.UnsupportedEncodingException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.UUID; import org.junit.Test; import org.signal.libsignal.protocol.ServiceId; import org.signal.libsignal.protocol.ServiceId.Aci; @@ -40,99 +48,102 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext; import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.UUID; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - public final class ZkGroupTest extends SecureRandomTest { - private static final UUID TEST_UUID = UUID.fromString("00010203-0405-0607-0809-0a0b0c0d0e0f"); + private static final UUID TEST_UUID = UUID.fromString("00010203-0405-0607-0809-0a0b0c0d0e0f"); - private static final UUID TEST_UUID_1 = UUID.fromString("64656667-6869-6A6B-6C6D-6E6F70717273"); + private static final UUID TEST_UUID_1 = UUID.fromString("64656667-6869-6A6B-6C6D-6E6F70717273"); - private static final byte[] TEST_ARRAY_32 = Hex.fromStringCondensedAssert("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + private static final byte[] TEST_ARRAY_32 = + Hex.fromStringCondensedAssert( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); - private static final byte[] TEST_ARRAY_32_1 = Hex.fromStringCondensedAssert("6465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80818283"); + private static final byte[] TEST_ARRAY_32_1 = + Hex.fromStringCondensedAssert( + "6465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80818283"); - private static final byte[] TEST_ARRAY_32_2 = Hex.fromStringCondensedAssert("c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7"); + private static final byte[] TEST_ARRAY_32_2 = + Hex.fromStringCondensedAssert( + "c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7"); - private static final byte[] TEST_ARRAY_32_3 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32 }; + private static final byte[] TEST_ARRAY_32_3 = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32 + }; - private static final byte[] TEST_ARRAY_32_4 = { + private static final byte[] TEST_ARRAY_32_4 = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33}; + 28, 29, 30, 31, 32, 33 + }; + private static final byte[] TEST_ARRAY_32_5 = + Hex.fromStringCondensedAssert( + "030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"); - private static final byte[] TEST_ARRAY_32_5 = Hex.fromStringCondensedAssert("030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"); - - private static final byte[] authPresentationResultV2 = Hex. fromStringCondensedAssert("01322f9100de0734550a81dc81724a81dbd3b1b43dbc1d552d53455911c2772f34a6356ca17c6d34d858391456af55d0ef841fbe1fa8c4ee810f21e0bb9f4ace4c5c48c72ebbeb2ccda5f7aa49aee6bc0051cdde166e0f8c5f1febd53a4437c570ee1aa223f5eb937db98f34e3653d85ec163f39847222a2dec4235ea41c47bb62028aae30945857ee77663079bcc4923d14a43ad4f6bc33715046f7bde52715375ca9f89be0e630d4bdaa211156d0306723f543b06f5e998447b962c8e9729b4cc00000000000000074d0eae8e4311a6ae3d2970ef198c398110462be47dd2f26e6559209ef6cc20001a05a0b319a172dbeb2293cc1e0e191cefb23e24cf0d6b4b5373a30044be10cb033674d631e17dfce09398f234e9d62e118a6077caea0ef8bf67d7d723db70fecf2098fa041317b7be9fdbb68b0f25f5c479d68bd917fc6f187c5bf7a58910231921fc43565232466325c039212362b6d1203ccaedf831dc7f9060dcaaffa02624042171f5f0e780b9f74cfa88a147f3f1c082f9ca8638af1788e7899cbae0c765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547440e20100"); + private static final byte[] authPresentationResultV2 = + Hex.fromStringCondensedAssert( + "01322f9100de0734550a81dc81724a81dbd3b1b43dbc1d552d53455911c2772f34a6356ca17c6d34d858391456af55d0ef841fbe1fa8c4ee810f21e0bb9f4ace4c5c48c72ebbeb2ccda5f7aa49aee6bc0051cdde166e0f8c5f1febd53a4437c570ee1aa223f5eb937db98f34e3653d85ec163f39847222a2dec4235ea41c47bb62028aae30945857ee77663079bcc4923d14a43ad4f6bc33715046f7bde52715375ca9f89be0e630d4bdaa211156d0306723f543b06f5e998447b962c8e9729b4cc00000000000000074d0eae8e4311a6ae3d2970ef198c398110462be47dd2f26e6559209ef6cc20001a05a0b319a172dbeb2293cc1e0e191cefb23e24cf0d6b4b5373a30044be10cb033674d631e17dfce09398f234e9d62e118a6077caea0ef8bf67d7d723db70fecf2098fa041317b7be9fdbb68b0f25f5c479d68bd917fc6f187c5bf7a58910231921fc43565232466325c039212362b6d1203ccaedf831dc7f9060dcaaffa02624042171f5f0e780b9f74cfa88a147f3f1c082f9ca8638af1788e7899cbae0c765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547440e20100"); // 32 bytes of 0xFF is an invalid Ristretto point, so this should invalidate all the // the serialized zkgroup structures, since almost everything contains a Ristretto point private static final byte[] makeBadArray(byte[] a) { - byte[] temp = a.clone(); - for (int count=0; count < temp.length; count++) { - temp[count] = (byte)0xFF; - } - return temp; + byte[] temp = a.clone(); + for (int count = 0; count < temp.length; count++) { + temp[count] = (byte) 0xFF; + } + return temp; } @Test public void testAuthIntegration() throws VerificationFailedException, InvalidInputException { - Aci aci = new Aci(TEST_UUID); + Aci aci = new Aci(TEST_UUID); int redemptionTime = 123456; // Generate keys (client's are per-group, server's are not) // --- // SERVER - ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + ServerSecretParams serverSecretParams = + ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); // SERVER - deserialize test { - new ServerSecretParams(serverSecretParams.serialize()); - new ServerPublicParams(serverPublicParams.serialize()); - try { - byte[] temp = new byte[32]; // wrong length - new ServerSecretParams(temp); - throw new AssertionError("Failed to catch invalid ServerSecretParams deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new ServerSecretParams(makeBadArray(serverSecretParams.serialize())); - throw new AssertionError("Failed to catch invalid ServerSecretParams deserialize 2"); - } catch (InvalidInputException e) { - // expected - } - try { - byte[] temp = new byte[32]; // wrong length - new ServerPublicParams(temp); - throw new AssertionError("Failed to catch invalid ServerPublicParams deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new ServerPublicParams(makeBadArray(serverPublicParams.serialize())); - throw new AssertionError("Failed to catch invalid ServerPublicParams deserialize 2"); - } catch (InvalidInputException e) { - // expected - } - } - - ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); + new ServerSecretParams(serverSecretParams.serialize()); + new ServerPublicParams(serverPublicParams.serialize()); + try { + byte[] temp = new byte[32]; // wrong length + new ServerSecretParams(temp); + throw new AssertionError("Failed to catch invalid ServerSecretParams deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new ServerSecretParams(makeBadArray(serverSecretParams.serialize())); + throw new AssertionError("Failed to catch invalid ServerSecretParams deserialize 2"); + } catch (InvalidInputException e) { + // expected + } + try { + byte[] temp = new byte[32]; // wrong length + new ServerPublicParams(temp); + throw new AssertionError("Failed to catch invalid ServerPublicParams deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new ServerPublicParams(makeBadArray(serverPublicParams.serialize())); + throw new AssertionError("Failed to catch invalid ServerPublicParams deserialize 2"); + } catch (InvalidInputException e) { + // expected + } + } + + ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); // CLIENT - GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); + GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey); assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize()); @@ -141,150 +152,159 @@ public void testAuthIntegration() throws VerificationFailedException, InvalidInp // CLIENT - deserialize test { - new GroupSecretParams(groupSecretParams.serialize()); - new GroupPublicParams(groupPublicParams.serialize()); - try { - byte[] temp = new byte[10]; // wrong length - new GroupMasterKey(temp); - throw new AssertionError("Failed to catch invalid GroupMasterKey deserialize"); - } catch (InvalidInputException e) { - // expected - } - try { - byte[] temp = new byte[10]; // wrong length - new GroupSecretParams(temp); - throw new AssertionError("Failed to catch invalid GroupSecretParams deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new GroupSecretParams(makeBadArray(groupSecretParams.serialize())); - throw new AssertionError("Failed to catch invalid GroupSecretParams deserialize 2"); - } catch (InvalidInputException e) { - // expected - } - try { - byte[] temp = new byte[10]; // wrong length - new GroupPublicParams(temp); - throw new AssertionError("Failed to catch invalid GroupPublicParams deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new GroupPublicParams(makeBadArray(groupPublicParams.serialize())); - throw new AssertionError("Failed to catch invalid GroupPublicParams deserialize 2"); - } catch (InvalidInputException e) { - // expected - } + new GroupSecretParams(groupSecretParams.serialize()); + new GroupPublicParams(groupPublicParams.serialize()); + try { + byte[] temp = new byte[10]; // wrong length + new GroupMasterKey(temp); + throw new AssertionError("Failed to catch invalid GroupMasterKey deserialize"); + } catch (InvalidInputException e) { + // expected + } + try { + byte[] temp = new byte[10]; // wrong length + new GroupSecretParams(temp); + throw new AssertionError("Failed to catch invalid GroupSecretParams deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new GroupSecretParams(makeBadArray(groupSecretParams.serialize())); + throw new AssertionError("Failed to catch invalid GroupSecretParams deserialize 2"); + } catch (InvalidInputException e) { + // expected + } + try { + byte[] temp = new byte[10]; // wrong length + new GroupPublicParams(temp); + throw new AssertionError("Failed to catch invalid GroupPublicParams deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new GroupPublicParams(makeBadArray(groupPublicParams.serialize())); + throw new AssertionError("Failed to catch invalid GroupPublicParams deserialize 2"); + } catch (InvalidInputException e) { + // expected + } } // SERVER // Issue credential - AuthCredentialResponse authCredentialResponse = serverZkAuth.issueAuthCredential(createSecureRandom(TEST_ARRAY_32_2), aci, redemptionTime); + AuthCredentialResponse authCredentialResponse = + serverZkAuth.issueAuthCredential(createSecureRandom(TEST_ARRAY_32_2), aci, redemptionTime); // CLIENT // Receive credential - ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); - ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher (groupSecretParams ); - AuthCredential authCredential = clientZkAuthCipher.receiveAuthCredential(aci, redemptionTime, authCredentialResponse); + ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); + ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); + AuthCredential authCredential = + clientZkAuthCipher.receiveAuthCredential(aci, redemptionTime, authCredentialResponse); // CLIENT - deserialize test { - new AuthCredentialResponse(authCredentialResponse.serialize()); - try { - byte[] temp = new byte[10]; - new AuthCredentialResponse(temp); - throw new AssertionError("Failed to catch invalid AuthCredentialResponse deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new AuthCredentialResponse(makeBadArray(authCredentialResponse.serialize())); - throw new AssertionError("Failed to catch invalid AuthCredentialResponse deserialize 2"); - } catch (InvalidInputException e) { - // expected - } + new AuthCredentialResponse(authCredentialResponse.serialize()); + try { + byte[] temp = new byte[10]; + new AuthCredentialResponse(temp); + throw new AssertionError("Failed to catch invalid AuthCredentialResponse deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new AuthCredentialResponse(makeBadArray(authCredentialResponse.serialize())); + throw new AssertionError("Failed to catch invalid AuthCredentialResponse deserialize 2"); + } catch (InvalidInputException e) { + // expected + } } // CLIENT - verify test { - UUID badUuid = TEST_UUID_1; - try { - clientZkAuthCipher.receiveAuthCredential(new Aci(badUuid), redemptionTime, authCredentialResponse); - throw new AssertionError("Failed to catch invalid AuthCredential 1"); - } catch (VerificationFailedException e) { - // expected - } - - byte[] temp = authCredentialResponse.serialize(); - temp[1]++; - AuthCredentialResponse badResponse = new AuthCredentialResponse(temp); - try { - clientZkAuthCipher.receiveAuthCredential(aci, redemptionTime, badResponse); - throw new AssertionError("Failed to catch invalid AuthCredential 2"); - } catch (VerificationFailedException e) { - // expected - } + UUID badUuid = TEST_UUID_1; + try { + clientZkAuthCipher.receiveAuthCredential( + new Aci(badUuid), redemptionTime, authCredentialResponse); + throw new AssertionError("Failed to catch invalid AuthCredential 1"); + } catch (VerificationFailedException e) { + // expected + } + + byte[] temp = authCredentialResponse.serialize(); + temp[1]++; + AuthCredentialResponse badResponse = new AuthCredentialResponse(temp); + try { + clientZkAuthCipher.receiveAuthCredential(aci, redemptionTime, badResponse); + throw new AssertionError("Failed to catch invalid AuthCredential 2"); + } catch (VerificationFailedException e) { + // expected + } } // Create and decrypt user entry UuidCiphertext uuidCiphertext = clientZkGroupCipher.encrypt(aci); - ServiceId plaintext = clientZkGroupCipher.decrypt(uuidCiphertext); + ServiceId plaintext = clientZkGroupCipher.decrypt(uuidCiphertext); assertEquals(aci, plaintext); // CLIENT - deserialize test { - new UuidCiphertext(uuidCiphertext.serialize()); - try { - byte[] temp = new byte[10]; - new UuidCiphertext(temp); - throw new AssertionError("Failed to catch invalid UuidCiphertext deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - - try { - new UuidCiphertext(makeBadArray(uuidCiphertext.serialize())); - throw new AssertionError("Failed to catch invalid UuidCiphertext deserialize 2"); - } catch (InvalidInputException e) { - // expected - } + new UuidCiphertext(uuidCiphertext.serialize()); + try { + byte[] temp = new byte[10]; + new UuidCiphertext(temp); + throw new AssertionError("Failed to catch invalid UuidCiphertext deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + + try { + new UuidCiphertext(makeBadArray(uuidCiphertext.serialize())); + throw new AssertionError("Failed to catch invalid UuidCiphertext deserialize 2"); + } catch (InvalidInputException e) { + // expected + } } // CLIENT - verify test { - byte[] temp = uuidCiphertext.serialize(); - temp[3]++; // We need a bad ciphertext that passes deserialization, this seems to work - try { - clientZkGroupCipher.decrypt(new UuidCiphertext(temp)); - throw new AssertionError("Failed to catch invalid UuidCiphertext decrypt"); - } catch (VerificationFailedException e) { - // expected - } + byte[] temp = uuidCiphertext.serialize(); + temp[3]++; // We need a bad ciphertext that passes deserialization, this seems to work + try { + clientZkGroupCipher.decrypt(new UuidCiphertext(temp)); + throw new AssertionError("Failed to catch invalid UuidCiphertext decrypt"); + } catch (VerificationFailedException e) { + // expected + } } // CLIENT - Create presentation - AuthCredentialPresentation presentation = clientZkAuthCipher.createAuthCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); - assertEquals(presentation.serialize()[0], 1); // Check V2 (versions start from 1 but are encoded starting from 0) + AuthCredentialPresentation presentation = + clientZkAuthCipher.createAuthCredentialPresentation( + createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); + assertEquals( + presentation.serialize()[0], + 1); // Check V2 (versions start from 1 but are encoded starting from 0) assertEquals(presentation.getVersion(), AuthCredentialPresentation.Version.V2); assertArrayEquals(presentation.serialize(), authPresentationResultV2); // CLIENT - deserialize test { - new AuthCredentialPresentation(presentation.serialize()); - byte[] temp = new byte[10]; - try { - new AuthCredentialPresentation(temp); - throw new AssertionError("Failed to catch invalid AuthCredentialPresentation deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new AuthCredentialPresentation(makeBadArray(presentation.serialize())); - throw new AssertionError("Failed to catch invalid AuthCredentialPresentation deserialize 2"); - } catch (InvalidInputException e) { - // expected - } + new AuthCredentialPresentation(presentation.serialize()); + byte[] temp = new byte[10]; + try { + new AuthCredentialPresentation(temp); + throw new AssertionError( + "Failed to catch invalid AuthCredentialPresentation deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new AuthCredentialPresentation(makeBadArray(presentation.serialize())); + throw new AssertionError( + "Failed to catch invalid AuthCredentialPresentation deserialize 2"); + } catch (InvalidInputException e) { + // expected + } } // SERVER - Verify presentation, using times at the edge of the acceptable window @@ -294,75 +314,87 @@ public void testAuthIntegration() throws VerificationFailedException, InvalidInp assertNull(presentation.getPniCiphertext()); assertEquals(presentation.getRedemptionTime(), redemptionInstant); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.minus(1, ChronoUnit.DAYS)); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.plus(2, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionInstant.minus(1, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionInstant.plus(2, ChronoUnit.DAYS)); try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionInstant.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); } catch (VerificationFailedException e) { // good } try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionInstant.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); } catch (VerificationFailedException e) { // good } try { - byte[] temp = presentation.serialize(); - temp[3]++; // We need a bad presentation that passes deserialization, this seems to work - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionInstant); - throw new AssertionError("verifyAuthCredentialPresentation should fail #3!"); + byte[] temp = presentation.serialize(); + temp[3]++; // We need a bad presentation that passes deserialization, this seems to work + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionInstant); + throw new AssertionError("verifyAuthCredentialPresentation should fail #3!"); } catch (VerificationFailedException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[0] = 0; // This interprets a V2 as V1, so should fail - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionInstant); - throw new AssertionError("verifyAuthCredentialPresentation should fail #4"); + byte[] temp = presentation.serialize(); + temp[0] = 0; // This interprets a V2 as V1, so should fail + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionInstant); + throw new AssertionError("verifyAuthCredentialPresentation should fail #4"); } catch (InvalidInputException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[0] = 40; // This interprets a V2 as a non-existent version, so should fail - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionInstant); - throw new AssertionError("verifyAuthCredentialPresentation should fail #5"); + byte[] temp = presentation.serialize(); + temp[0] = 40; // This interprets a V2 as a non-existent version, so should fail + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionInstant); + throw new AssertionError("verifyAuthCredentialPresentation should fail #5"); } catch (InvalidInputException e) { - // expected + // expected } - } - @Test - public void testAuthIntegrationCurrentTime() throws VerificationFailedException, InvalidInputException { + public void testAuthIntegrationCurrentTime() + throws VerificationFailedException, InvalidInputException { // This test is mostly the same as testAuthIntegration() except instead of using a hardcoded // redemption date to compare against test vectors, it uses the current time - Aci aci = new Aci(TEST_UUID); - int redemptionTime = (int)(Instant.now().truncatedTo(ChronoUnit.DAYS).getEpochSecond() / 86400); + Aci aci = new Aci(TEST_UUID); + int redemptionTime = + (int) (Instant.now().truncatedTo(ChronoUnit.DAYS).getEpochSecond() / 86400); // Generate keys (client's are per-group, server's are not) // --- // SERVER - ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + ServerSecretParams serverSecretParams = + ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); - ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); + ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); // CLIENT - GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); + GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey); assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize()); @@ -371,21 +403,25 @@ public void testAuthIntegrationCurrentTime() throws VerificationFailedException, // SERVER // Issue credential - AuthCredentialResponse authCredentialResponse = serverZkAuth.issueAuthCredential(createSecureRandom(TEST_ARRAY_32_2), aci, redemptionTime); + AuthCredentialResponse authCredentialResponse = + serverZkAuth.issueAuthCredential(createSecureRandom(TEST_ARRAY_32_2), aci, redemptionTime); // CLIENT // Receive credential - ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); - ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher (groupSecretParams ); - AuthCredential authCredential = clientZkAuthCipher.receiveAuthCredential(aci, redemptionTime, authCredentialResponse); + ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); + ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); + AuthCredential authCredential = + clientZkAuthCipher.receiveAuthCredential(aci, redemptionTime, authCredentialResponse); // Create and decrypt user entry UuidCiphertext uuidCiphertext = clientZkGroupCipher.encrypt(aci); - ServiceId plaintext = clientZkGroupCipher.decrypt(uuidCiphertext); + ServiceId plaintext = clientZkGroupCipher.decrypt(uuidCiphertext); assertEquals(aci, plaintext); // Create presentation - AuthCredentialPresentation presentation = clientZkAuthCipher.createAuthCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); + AuthCredentialPresentation presentation = + clientZkAuthCipher.createAuthCredentialPresentation( + createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); // Verify presentation, using times at the edge of the acceptable window Instant redemptionInstant = Instant.ofEpochSecond(86400L * redemptionTime); @@ -396,42 +432,52 @@ public void testAuthIntegrationCurrentTime() throws VerificationFailedException, // By default the library uses the current time serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.minus(1, ChronoUnit.DAYS)); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.plus(2, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionInstant.minus(1, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionInstant.plus(2, ChronoUnit.DAYS)); try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionInstant.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); } catch (VerificationFailedException e) { // good } try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionInstant.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionInstant.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); } catch (VerificationFailedException e) { // good } } @Test - public void testAuthWithPniIntegration() throws VerificationFailedException, InvalidInputException { + public void testAuthWithPniIntegration() + throws VerificationFailedException, InvalidInputException { - Aci aci = new Aci(TEST_UUID); - Pni pni = new Pni(TEST_UUID_1); + Aci aci = new Aci(TEST_UUID); + Pni pni = new Pni(TEST_UUID_1); Instant redemptionTime = Instant.now().truncatedTo(ChronoUnit.DAYS); // Generate keys (client's are per-group, server's are not) // --- // SERVER - ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + ServerSecretParams serverSecretParams = + ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); - ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); + ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); // CLIENT - GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); + GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey); assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize()); @@ -440,90 +486,108 @@ public void testAuthWithPniIntegration() throws VerificationFailedException, Inv // SERVER // Issue credential - AuthCredentialWithPniResponse authCredentialResponse = serverZkAuth.issueAuthCredentialWithPniAsServiceId(createSecureRandom(TEST_ARRAY_32_2), aci, pni, redemptionTime); + AuthCredentialWithPniResponse authCredentialResponse = + serverZkAuth.issueAuthCredentialWithPniAsServiceId( + createSecureRandom(TEST_ARRAY_32_2), aci, pni, redemptionTime); // CLIENT // Receive credential - ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); - ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher (groupSecretParams ); - AuthCredentialWithPni authCredential = clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId(aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); + ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); + ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); + AuthCredentialWithPni authCredential = + clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId( + aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); // CLIENT - deserialize test { - new AuthCredentialWithPniResponse(authCredentialResponse.serialize()); - try { - byte[] temp = new byte[10]; - new AuthCredentialWithPniResponse(temp); - throw new AssertionError("Failed to catch invalid AuthCredentialWithPniResponse deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new AuthCredentialWithPniResponse(makeBadArray(authCredentialResponse.serialize())); - throw new AssertionError("Failed to catch invalid AuthCredentialWithPniResponse deserialize 2"); - } catch (InvalidInputException e) { - // expected - } - } - + new AuthCredentialWithPniResponse(authCredentialResponse.serialize()); + try { + byte[] temp = new byte[10]; + new AuthCredentialWithPniResponse(temp); + throw new AssertionError( + "Failed to catch invalid AuthCredentialWithPniResponse deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new AuthCredentialWithPniResponse(makeBadArray(authCredentialResponse.serialize())); + throw new AssertionError( + "Failed to catch invalid AuthCredentialWithPniResponse deserialize 2"); + } catch (InvalidInputException e) { + // expected + } + } + // CLIENT - verify test { - try { - // Switch ACI and PNI - clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId(new Aci(pni.getRawUUID()), new Pni(aci.getRawUUID()), redemptionTime.getEpochSecond(), authCredentialResponse); - throw new AssertionError("Failed to catch invalid AuthCredentialWithPni 1"); - } catch (VerificationFailedException e) { - // expected - } - - byte[] temp = authCredentialResponse.serialize(); - temp[1]++; - AuthCredentialWithPniResponse badResponse = new AuthCredentialWithPniResponse(temp); - try { - clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId(aci, pni, redemptionTime.getEpochSecond(), badResponse); - throw new AssertionError("Failed to catch invalid AuthCredentialWithPni 2"); - } catch (VerificationFailedException e) { - // expected - } - - try { - // Use wrong kind of AuthCredentialWithPni - clientZkAuthCipher.receiveAuthCredentialWithPniAsAci(aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); - throw new AssertionError("Failed to catch AuthCredentialWithServiceId treated as Aci"); - } catch (VerificationFailedException e) { - // expected - } + try { + // Switch ACI and PNI + clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId( + new Aci(pni.getRawUUID()), + new Pni(aci.getRawUUID()), + redemptionTime.getEpochSecond(), + authCredentialResponse); + throw new AssertionError("Failed to catch invalid AuthCredentialWithPni 1"); + } catch (VerificationFailedException e) { + // expected + } + + byte[] temp = authCredentialResponse.serialize(); + temp[1]++; + AuthCredentialWithPniResponse badResponse = new AuthCredentialWithPniResponse(temp); + try { + clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId( + aci, pni, redemptionTime.getEpochSecond(), badResponse); + throw new AssertionError("Failed to catch invalid AuthCredentialWithPni 2"); + } catch (VerificationFailedException e) { + // expected + } + + try { + // Use wrong kind of AuthCredentialWithPni + clientZkAuthCipher.receiveAuthCredentialWithPniAsAci( + aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); + throw new AssertionError("Failed to catch AuthCredentialWithServiceId treated as Aci"); + } catch (VerificationFailedException e) { + // expected + } } // Create and decrypt user entry UuidCiphertext aciCiphertext = clientZkGroupCipher.encrypt(aci); - ServiceId aciPlaintext = clientZkGroupCipher.decrypt(aciCiphertext); + ServiceId aciPlaintext = clientZkGroupCipher.decrypt(aciCiphertext); assertEquals(aci, aciPlaintext); UuidCiphertext pniCiphertext = clientZkGroupCipher.encrypt(pni); - ServiceId pniPlaintext = clientZkGroupCipher.decrypt(pniCiphertext); + ServiceId pniPlaintext = clientZkGroupCipher.decrypt(pniCiphertext); assertEquals(pni, pniPlaintext); // CLIENT - Create presentation - AuthCredentialPresentation presentation = clientZkAuthCipher.createAuthCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); - assertEquals(presentation.serialize()[0], 2); // Check V3 (versions start from 1 but are encoded starting from 0) + AuthCredentialPresentation presentation = + clientZkAuthCipher.createAuthCredentialPresentation( + createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); + assertEquals( + presentation.serialize()[0], + 2); // Check V3 (versions start from 1 but are encoded starting from 0) assertEquals(presentation.getVersion(), AuthCredentialPresentation.Version.V3); // CLIENT - deserialize test { - new AuthCredentialPresentation(presentation.serialize()); - byte[] temp = new byte[10]; - try { - new AuthCredentialPresentation(temp); - throw new AssertionError("Failed to catch invalid AuthCredentialPresentation deserialize 1"); - } catch (InvalidInputException e) { - // expected - } - try { - new AuthCredentialPresentation(makeBadArray(presentation.serialize())); - throw new AssertionError("Failed to catch invalid AuthCredentialPresentation deserialize 2"); - } catch (InvalidInputException e) { - // expected - } + new AuthCredentialPresentation(presentation.serialize()); + byte[] temp = new byte[10]; + try { + new AuthCredentialPresentation(temp); + throw new AssertionError( + "Failed to catch invalid AuthCredentialPresentation deserialize 1"); + } catch (InvalidInputException e) { + // expected + } + try { + new AuthCredentialPresentation(makeBadArray(presentation.serialize())); + throw new AssertionError( + "Failed to catch invalid AuthCredentialPresentation deserialize 2"); + } catch (InvalidInputException e) { + // expected + } } // SERVER - Verify presentation, using times at the edge of the acceptable window @@ -531,73 +595,85 @@ public void testAuthWithPniIntegration() throws VerificationFailedException, Inv assertArrayEquals(pniCiphertext.serialize(), presentation.getPniCiphertext().serialize()); assertEquals(presentation.getRedemptionTime(), redemptionTime); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.minus(1, ChronoUnit.DAYS)); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.plus(2, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionTime.minus(1, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionTime.plus(2, ChronoUnit.DAYS)); try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionTime.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); } catch (VerificationFailedException e) { // good } try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionTime.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); } catch (VerificationFailedException e) { // good } try { - byte[] temp = presentation.serialize(); - temp[3] += 5; // We need a bad presentation that passes deserialization, this seems to work - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionTime); - throw new AssertionError("verifyAuthCredentialPresentation should fail #3!"); + byte[] temp = presentation.serialize(); + temp[3] += 5; // We need a bad presentation that passes deserialization, this seems to work + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionTime); + throw new AssertionError("verifyAuthCredentialPresentation should fail #3!"); } catch (VerificationFailedException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[0] = 0; // This interprets a V3 as V1, so should fail - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionTime); - throw new AssertionError("verifyAuthCredentialPresentation should fail #4"); + byte[] temp = presentation.serialize(); + temp[0] = 0; // This interprets a V3 as V1, so should fail + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionTime); + throw new AssertionError("verifyAuthCredentialPresentation should fail #4"); } catch (InvalidInputException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[0] = 40; // This interprets a V3 as a non-existent version, so should fail - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionTime); - throw new AssertionError("verifyAuthCredentialPresentation should fail #5"); + byte[] temp = presentation.serialize(); + temp[0] = 40; // This interprets a V3 as a non-existent version, so should fail + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionTime); + throw new AssertionError("verifyAuthCredentialPresentation should fail #5"); } catch (InvalidInputException e) { - // expected + // expected } - } @Test - public void testAuthWithPniAsAciIntegration() throws VerificationFailedException, InvalidInputException { + public void testAuthWithPniAsAciIntegration() + throws VerificationFailedException, InvalidInputException { - Aci aci = new Aci(TEST_UUID); - Pni pni = new Pni(TEST_UUID_1); + Aci aci = new Aci(TEST_UUID); + Pni pni = new Pni(TEST_UUID_1); Instant redemptionTime = Instant.now().truncatedTo(ChronoUnit.DAYS); // Generate keys (client's are per-group, server's are not) // --- // SERVER - ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + ServerSecretParams serverSecretParams = + ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); - ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); + ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams); // CLIENT - GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); + GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey); assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize()); @@ -606,37 +682,46 @@ public void testAuthWithPniAsAciIntegration() throws VerificationFailedException // SERVER // Issue credential - AuthCredentialWithPniResponse authCredentialResponse = serverZkAuth.issueAuthCredentialWithPniAsAci(createSecureRandom(TEST_ARRAY_32_2), aci, pni, redemptionTime); + AuthCredentialWithPniResponse authCredentialResponse = + serverZkAuth.issueAuthCredentialWithPniAsAci( + createSecureRandom(TEST_ARRAY_32_2), aci, pni, redemptionTime); // CLIENT // Receive credential - ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); - ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher (groupSecretParams ); - AuthCredentialWithPni authCredential = clientZkAuthCipher.receiveAuthCredentialWithPniAsAci(aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); + ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams); + ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); + AuthCredentialWithPni authCredential = + clientZkAuthCipher.receiveAuthCredentialWithPniAsAci( + aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); // CLIENT - verify test { - try { - // Use wrong kind of AuthCredentialWithPni - clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId(aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); - throw new AssertionError("Failed to catch AuthCredentialWithPniAsAci treated as ServiceId"); - } catch (VerificationFailedException e) { - // expected - } + try { + // Use wrong kind of AuthCredentialWithPni + clientZkAuthCipher.receiveAuthCredentialWithPniAsServiceId( + aci, pni, redemptionTime.getEpochSecond(), authCredentialResponse); + throw new AssertionError("Failed to catch AuthCredentialWithPniAsAci treated as ServiceId"); + } catch (VerificationFailedException e) { + // expected + } } // Create and decrypt user entry UuidCiphertext aciCiphertext = clientZkGroupCipher.encrypt(aci); - ServiceId aciPlaintext = clientZkGroupCipher.decrypt(aciCiphertext); + ServiceId aciPlaintext = clientZkGroupCipher.decrypt(aciCiphertext); assertEquals(aci, aciPlaintext); - Aci pniAsAci = new Aci(pni.getRawUUID()); + Aci pniAsAci = new Aci(pni.getRawUUID()); UuidCiphertext pniCiphertext = clientZkGroupCipher.encrypt(pniAsAci); - ServiceId pniPlaintext = clientZkGroupCipher.decrypt(pniCiphertext); + ServiceId pniPlaintext = clientZkGroupCipher.decrypt(pniCiphertext); assertEquals(pniAsAci, pniPlaintext); // CLIENT - Create presentation - AuthCredentialPresentation presentation = clientZkAuthCipher.createAuthCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); - assertEquals(presentation.serialize()[0], 2); // Check V3 (versions start from 1 but are encoded starting from 0) + AuthCredentialPresentation presentation = + clientZkAuthCipher.createAuthCredentialPresentation( + createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential); + assertEquals( + presentation.serialize()[0], + 2); // Check V3 (versions start from 1 but are encoded starting from 0) assertEquals(presentation.getVersion(), AuthCredentialPresentation.Version.V3); // SERVER - Verify presentation, using times at the edge of the acceptable window @@ -644,179 +729,218 @@ public void testAuthWithPniAsAciIntegration() throws VerificationFailedException assertArrayEquals(pniCiphertext.serialize(), presentation.getPniCiphertext().serialize()); assertEquals(presentation.getRedemptionTime(), redemptionTime); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.minus(1, ChronoUnit.DAYS)); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.plus(2, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionTime.minus(1, ChronoUnit.DAYS)); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentation, redemptionTime.plus(2, ChronoUnit.DAYS)); try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionTime.minus(1, ChronoUnit.DAYS).minus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #1!"); } catch (VerificationFailedException e) { // good } try { - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, redemptionTime.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); - throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, + presentation, + redemptionTime.plus(2, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS)); + throw new AssertionError("verifyAuthCredentialPresentation should fail #2!"); } catch (VerificationFailedException e) { // good } try { - byte[] temp = presentation.serialize(); - temp[3] += 5; // We need a bad presentation that passes deserialization, this seems to work - AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); - serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentationTemp, redemptionTime); - throw new AssertionError("verifyAuthCredentialPresentation should fail #3!"); + byte[] temp = presentation.serialize(); + temp[3] += 5; // We need a bad presentation that passes deserialization, this seems to work + AuthCredentialPresentation presentationTemp = new AuthCredentialPresentation(temp); + serverZkAuth.verifyAuthCredentialPresentation( + groupPublicParams, presentationTemp, redemptionTime); + throw new AssertionError("verifyAuthCredentialPresentation should fail #3!"); } catch (VerificationFailedException e) { - // expected + // expected } } @Test - public void testExpiringProfileKeyIntegration() throws VerificationFailedException, InvalidInputException, UnsupportedEncodingException { + public void testExpiringProfileKeyIntegration() + throws VerificationFailedException, InvalidInputException, UnsupportedEncodingException { - Aci userId = new Aci(TEST_UUID); + Aci userId = new Aci(TEST_UUID); // Generate keys (client's are per-group, server's are not) // --- // SERVER - ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + ServerSecretParams serverSecretParams = + ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); - ServerZkProfileOperations serverZkProfile = new ServerZkProfileOperations(serverSecretParams); + ServerZkProfileOperations serverZkProfile = new ServerZkProfileOperations(serverSecretParams); // CLIENT - GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); + GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey); assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize()); - GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams(); - ClientZkProfileOperations clientZkProfileCipher = new ClientZkProfileOperations(serverPublicParams); + GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams(); + ClientZkProfileOperations clientZkProfileCipher = + new ClientZkProfileOperations(serverPublicParams); - ProfileKey profileKey = new ProfileKey(TEST_ARRAY_32_1); + ProfileKey profileKey = new ProfileKey(TEST_ARRAY_32_1); ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(userId); // Create context and request - ProfileKeyCredentialRequestContext context = clientZkProfileCipher.createProfileKeyCredentialRequestContext(createSecureRandom(TEST_ARRAY_32_3), userId, profileKey); - ProfileKeyCredentialRequest request = context.getRequest(); + ProfileKeyCredentialRequestContext context = + clientZkProfileCipher.createProfileKeyCredentialRequestContext( + createSecureRandom(TEST_ARRAY_32_3), userId, profileKey); + ProfileKeyCredentialRequest request = context.getRequest(); - // SERVER + // SERVER Instant expiration = Instant.now().truncatedTo(ChronoUnit.DAYS).plus(5, ChronoUnit.DAYS); - ExpiringProfileKeyCredentialResponse response = serverZkProfile.issueExpiringProfileKeyCredential(createSecureRandom(TEST_ARRAY_32_4), request, userId, profileKeyCommitment, expiration); + ExpiringProfileKeyCredentialResponse response = + serverZkProfile.issueExpiringProfileKeyCredential( + createSecureRandom(TEST_ARRAY_32_4), request, userId, profileKeyCommitment, expiration); // SERVER - verification test { - byte[] temp = request.serialize(); - temp[4]++; // We need a bad presentation that passes deserialization, this seems to work - ProfileKeyCredentialRequest badRequest = new ProfileKeyCredentialRequest(temp); - try { - serverZkProfile.issueExpiringProfileKeyCredential(createSecureRandom(TEST_ARRAY_32_4), badRequest, userId, profileKeyCommitment, expiration); - throw new AssertionError("Failed to catch invalid ProfileKeyCredentialRequest"); - } catch (VerificationFailedException e) { - // expected - } - } - + byte[] temp = request.serialize(); + temp[4]++; // We need a bad presentation that passes deserialization, this seems to work + ProfileKeyCredentialRequest badRequest = new ProfileKeyCredentialRequest(temp); + try { + serverZkProfile.issueExpiringProfileKeyCredential( + createSecureRandom(TEST_ARRAY_32_4), + badRequest, + userId, + profileKeyCommitment, + expiration); + throw new AssertionError("Failed to catch invalid ProfileKeyCredentialRequest"); + } catch (VerificationFailedException e) { + // expected + } + } + // CLIENT // Gets stored profile credential - ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); - ExpiringProfileKeyCredential profileKeyCredential = clientZkProfileCipher.receiveExpiringProfileKeyCredential(context, response); + ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); + ExpiringProfileKeyCredential profileKeyCredential = + clientZkProfileCipher.receiveExpiringProfileKeyCredential(context, response); // Create encrypted UID and profile key UuidCiphertext uuidCiphertext = clientZkGroupCipher.encrypt(userId); - ServiceId plaintext = clientZkGroupCipher.decrypt(uuidCiphertext); + ServiceId plaintext = clientZkGroupCipher.decrypt(uuidCiphertext); assertEquals(plaintext, userId); - ProfileKeyCiphertext profileKeyCiphertext = clientZkGroupCipher.encryptProfileKey(profileKey, userId); - ProfileKey decryptedProfileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCiphertext, userId); + ProfileKeyCiphertext profileKeyCiphertext = + clientZkGroupCipher.encryptProfileKey(profileKey, userId); + ProfileKey decryptedProfileKey = + clientZkGroupCipher.decryptProfileKey(profileKeyCiphertext, userId); assertArrayEquals(profileKey.serialize(), decryptedProfileKey.serialize()); assertEquals(expiration, profileKeyCredential.getExpirationTime()); - ProfileKeyCredentialPresentation presentation = clientZkProfileCipher.createProfileKeyCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, profileKeyCredential); - assertEquals(presentation.serialize()[0], 2); // Check V3 (versions start from 1 but are encoded starting from 0) + ProfileKeyCredentialPresentation presentation = + clientZkProfileCipher.createProfileKeyCredentialPresentation( + createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, profileKeyCredential); + assertEquals( + presentation.serialize()[0], + 2); // Check V3 (versions start from 1 but are encoded starting from 0) assertEquals(presentation.getVersion(), ProfileKeyCredentialPresentation.Version.V3); // Verify presentation serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentation); - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentation, expiration.minusSeconds(5)); + serverZkProfile.verifyProfileKeyCredentialPresentation( + groupPublicParams, presentation, expiration.minusSeconds(5)); UuidCiphertext uuidCiphertextRecv = presentation.getUuidCiphertext(); assertArrayEquals(uuidCiphertext.serialize(), uuidCiphertextRecv.serialize()); try { - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentation, expiration); - throw new AssertionError("credential expired 1"); + serverZkProfile.verifyProfileKeyCredentialPresentation( + groupPublicParams, presentation, expiration); + throw new AssertionError("credential expired 1"); } catch (VerificationFailedException e) { - // expected + // expected } try { - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentation, expiration.plusSeconds(5)); - throw new AssertionError("credential expired 2"); + serverZkProfile.verifyProfileKeyCredentialPresentation( + groupPublicParams, presentation, expiration.plusSeconds(5)); + throw new AssertionError("credential expired 2"); } catch (VerificationFailedException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[2] += 8; // We need a bad presentation that passes deserializaton, this seems to work - ProfileKeyCredentialPresentation presentationTemp = new ProfileKeyCredentialPresentation(temp); - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentationTemp); - throw new AssertionError("verifyProfileKeyCredentialPresentation should fail 1"); + byte[] temp = presentation.serialize(); + temp[2] += 8; // We need a bad presentation that passes deserializaton, this seems to work + ProfileKeyCredentialPresentation presentationTemp = + new ProfileKeyCredentialPresentation(temp); + serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentationTemp); + throw new AssertionError("verifyProfileKeyCredentialPresentation should fail 1"); } catch (VerificationFailedException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[0] = 0; // This interprets a V3 as V1, so should fail - ProfileKeyCredentialPresentation presentationTemp = new ProfileKeyCredentialPresentation(temp); - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentationTemp); - throw new AssertionError("verifyProfileKeyCredentialPresentation should fail 2"); + byte[] temp = presentation.serialize(); + temp[0] = 0; // This interprets a V3 as V1, so should fail + ProfileKeyCredentialPresentation presentationTemp = + new ProfileKeyCredentialPresentation(temp); + serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentationTemp); + throw new AssertionError("verifyProfileKeyCredentialPresentation should fail 2"); } catch (VerificationFailedException e) { - // expected + // expected } try { - byte[] temp = presentation.serialize(); - temp[0] = 40; // This interprets a V3 as a non-existent version, so should fail - ProfileKeyCredentialPresentation presentationTemp = new ProfileKeyCredentialPresentation(temp); - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentationTemp); - throw new AssertionError("verifyProfileKeyCredentialPresentation should fail 3"); + byte[] temp = presentation.serialize(); + temp[0] = 40; // This interprets a V3 as a non-existent version, so should fail + ProfileKeyCredentialPresentation presentationTemp = + new ProfileKeyCredentialPresentation(temp); + serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentationTemp); + throw new AssertionError("verifyProfileKeyCredentialPresentation should fail 3"); } catch (InvalidInputException e) { - // expected + // expected } // Test that we can encode as a V1 presentation, even though it won't verify. - ProfileKeyCredentialPresentation v1Presentation = new ProfileKeyCredentialPresentation(presentation.getStructurallyValidV1PresentationBytes()); + ProfileKeyCredentialPresentation v1Presentation = + new ProfileKeyCredentialPresentation( + presentation.getStructurallyValidV1PresentationBytes()); assertEquals(v1Presentation.getUuidCiphertext(), presentation.getUuidCiphertext()); assertEquals(v1Presentation.getProfileKeyCiphertext(), presentation.getProfileKeyCiphertext()); try { - serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, v1Presentation); + serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, v1Presentation); } catch (VerificationFailedException e) { - // expected + // expected } } @Test public void testServerSignatures() throws VerificationFailedException { - ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + ServerSecretParams serverSecretParams = + ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams(); byte[] message = TEST_ARRAY_32_1; - NotarySignature signature = serverSecretParams.sign(createSecureRandom(TEST_ARRAY_32_2), message); + NotarySignature signature = + serverSecretParams.sign(createSecureRandom(TEST_ARRAY_32_2), message); serverPublicParams.verifySignature(message, signature); assertByteArray( -"87d354564d35ef91edba851e0815612e864c227a0471d50c270698604406d003a55473f576cf241fc6b41c6b16e5e63b333c02fe4a33858022fdd7a4ab367b06", signature.serialize()); + "87d354564d35ef91edba851e0815612e864c227a0471d50c270698604406d003a55473f576cf241fc6b41c6b16e5e63b333c02fe4a33858022fdd7a4ab367b06", + signature.serialize()); byte[] alteredMessage = message.clone(); alteredMessage[0] ^= 1; try { - serverPublicParams.verifySignature(alteredMessage, signature); - throw new AssertionError("signature validation should have failed!"); + serverPublicParams.verifySignature(alteredMessage, signature); + throw new AssertionError("signature validation should have failed!"); } catch (VerificationFailedException e) { // good } @@ -824,9 +948,11 @@ public void testServerSignatures() throws VerificationFailedException { @Test public void testGroupIdentifier() throws VerificationFailedException { - GroupSecretParams groupSecretParams = GroupSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); + GroupSecretParams groupSecretParams = + GroupSecretParams.generate(createSecureRandom(TEST_ARRAY_32)); GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams(); - //assertByteArray("31f2c60f86f4c5996e9e2568355591d9", groupPublicParams.getGroupIdentifier().serialize()); + // assertByteArray("31f2c60f86f4c5996e9e2568355591d9", + // groupPublicParams.getGroupIdentifier().serialize()); } @Test(expected = InvalidInputException.class) @@ -868,16 +994,21 @@ public void testWrongSizeSerializedInfallible() throws InvalidInputException { @Test public void testBlobEncryption() throws InvalidInputException, VerificationFailedException { - GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); + GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey); ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams); byte[] plaintext = Hex.fromStringCondensedAssert("0102030405060708111213141516171819"); - byte[] ciphertext = Hex.fromStringCondensedAssert("dd4d032ca9bb75a4a78541b90cb4e95743f3b0dabfc7e11101b098e34f6cf6513940a04c1f20a302692afdc7087f10196000"); + byte[] ciphertext = + Hex.fromStringCondensedAssert( + "dd4d032ca9bb75a4a78541b90cb4e95743f3b0dabfc7e11101b098e34f6cf6513940a04c1f20a302692afdc7087f10196000"); - byte[] ciphertextPaddedWith257 = Hex.fromStringCondensedAssert("5cb5b7bff06e85d929f3511fd194e638cf32a47663868bc8e64d98fb1bbe435ebd21c763ce2d42e85a1b2c169f12f9818ddadcf4b491398b7c5d46a224e1582749f5e2a4a2294caaaaab843a1b7cf6426fd543d09ff32a4ba5f319ca4442b4da34b3e2b5b4f8a52fdc4b484ea86b33db3ebb758dbd9614178f0e4e1f9b2b914f1e786936b62ed2b58b7ae3cb3e7ae0835b9516959837406662b85eac740cef83b60b5aaeaaab95643c2bef8ce87358fabff9d690052beb9e52d0c947e7c986b2f3ce3b7161cec72c08e2c4ade3debe3792d736c0457bc352afb8b6caa48a5b92c1ec05ba808ba8f94c6572ebbf29818912344987573de419dbcc7f1ea0e4b2dd4077b76b381819747ac332e46fa23abfc3338e2f4b081a8a53cba0988eef116764d944f1ce3f20a302692afdc7087f10196000"); + byte[] ciphertextPaddedWith257 = + Hex.fromStringCondensedAssert( + "5cb5b7bff06e85d929f3511fd194e638cf32a47663868bc8e64d98fb1bbe435ebd21c763ce2d42e85a1b2c169f12f9818ddadcf4b491398b7c5d46a224e1582749f5e2a4a2294caaaaab843a1b7cf6426fd543d09ff32a4ba5f319ca4442b4da34b3e2b5b4f8a52fdc4b484ea86b33db3ebb758dbd9614178f0e4e1f9b2b914f1e786936b62ed2b58b7ae3cb3e7ae0835b9516959837406662b85eac740cef83b60b5aaeaaab95643c2bef8ce87358fabff9d690052beb9e52d0c947e7c986b2f3ce3b7161cec72c08e2c4ade3debe3792d736c0457bc352afb8b6caa48a5b92c1ec05ba808ba8f94c6572ebbf29818912344987573de419dbcc7f1ea0e4b2dd4077b76b381819747ac332e46fa23abfc3338e2f4b081a8a53cba0988eef116764d944f1ce3f20a302692afdc7087f10196000"); - byte[] ciphertext2 = clientZkGroupCipher.encryptBlob(createSecureRandom(TEST_ARRAY_32_2), plaintext); + byte[] ciphertext2 = + clientZkGroupCipher.encryptBlob(createSecureRandom(TEST_ARRAY_32_2), plaintext); byte[] plaintext2 = clientZkGroupCipher.decryptBlob(ciphertext2); assertArrayEquals(plaintext, plaintext2); @@ -891,7 +1022,7 @@ public void testBlobEncryption() throws InvalidInputException, VerificationFaile public void testDeriveAccessKey() throws Exception { byte[] expectedAccessKey = Hex.fromStringCondensedAssert("5a723acee52c5ea02b92a3a360c09595"); byte[] profileKey = new byte[32]; - Arrays.fill(profileKey, (byte)0x02); + Arrays.fill(profileKey, (byte) 0x02); byte[] result = new ProfileKey(profileKey).deriveAccessKey(); assertArrayEquals(result, expectedAccessKey); @@ -902,6 +1033,4 @@ private void assertByteArray(String expectedAsHex, byte[] actual) { assertArrayEquals(expectedBytes, actual); } - } - diff --git a/java/code_size.json b/java/code_size.json index 60b436a6d3..5fd4b70e2f 100644 --- a/java/code_size.json +++ b/java/code_size.json @@ -59,5 +59,7 @@ { "version": "0.29.0", "size": 2993872 }, { "version": "0.30.0", "size": 2996056 }, { "version": "0.30.2", "size": 2993944 }, - { "version": "0.31.0", "size": 2984472 } + { "version": "0.31.0", "size": 2984472 }, + { "version": "0.32.0", "size": 2989712 }, + { "version": "0.32.1", "size": 3058720 } ] diff --git a/java/license_header.txt b/java/license_header.txt new file mode 100644 index 0000000000..b57f82c081 --- /dev/null +++ b/java/license_header.txt @@ -0,0 +1,5 @@ +// +// Copyright $YEAR Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + diff --git a/java/server/build.gradle b/java/server/build.gradle index b92ed4b2ea..b97d6efc9d 100644 --- a/java/server/build.gradle +++ b/java/server/build.gradle @@ -7,8 +7,6 @@ plugins { sourceCompatibility = 1.8 archivesBaseName = "libsignal-server" -version = version_number -group = group_info repositories { mavenCentral() @@ -60,12 +58,6 @@ java { processResources { // TODO: Build a different variant of the JNI library for server. dependsOn ':makeJniLibrariesDesktop' - dependsOn ':downloadNonLinuxLibraries' - gradle.taskGraph.whenReady { graph -> - if (graph.hasTask(":server:publish")) { - tasks.getByPath(":downloadNonLinuxLibraries").enabled = true - } - } } // MARK: Publishing @@ -102,17 +94,9 @@ publishing { } } } - repositories { - maven { - url = getReleaseRepositoryUrl() - credentials { - username = getRepositoryUsername() - password = getRepositoryPassword() - } - } - } } +setUpSigningKey(signing) signing { required { isReleaseBuild() && gradle.taskGraph.hasTask(":server:publish") } sign publishing.publications.mavenJava diff --git a/java/server/src/main/java/org/signal/libsignal/cds2/Cds2Metrics.java b/java/server/src/main/java/org/signal/libsignal/cds2/Cds2Metrics.java index d30c8b592c..3bd36adf86 100644 --- a/java/server/src/main/java/org/signal/libsignal/cds2/Cds2Metrics.java +++ b/java/server/src/main/java/org/signal/libsignal/cds2/Cds2Metrics.java @@ -5,32 +5,25 @@ package org.signal.libsignal.cds2; +import java.util.Map; import org.signal.libsignal.attest.AttestationDataException; import org.signal.libsignal.internal.Native; -import org.signal.libsignal.internal.NativeHandleGuard; - -import java.util.Optional; -import java.util.HashMap; -import java.util.Map; -import java.time.Instant; public final class Cds2Metrics { - private Cds2Metrics() {} + private Cds2Metrics() {} - /** - * Parse a cds2 attestation response (ClientHandshakeStart) and return - * supplemental information extracted from the response's evidence and - * endorsements. - * - * @param attestationMessage A ClientHandshakeStart message - * - * @throws AttestationDataException if the attestationMessage cannot be parsed - */ - public static Map extract(final byte[] attestationMessage) throws AttestationDataException { - @SuppressWarnings("unchecked") - Map result = Native.Cds2Metrics_extract(attestationMessage); - return result; - } + /** + * Parse a cds2 attestation response (ClientHandshakeStart) and return supplemental information + * extracted from the response's evidence and endorsements. + * + * @param attestationMessage A ClientHandshakeStart message + * @throws AttestationDataException if the attestationMessage cannot be parsed + */ + public static Map extract(final byte[] attestationMessage) + throws AttestationDataException { + @SuppressWarnings("unchecked") + Map result = Native.Cds2Metrics_extract(attestationMessage); + return result; + } } - diff --git a/java/server/src/test/java/org/signal/libsignal/cds2/Cds2MetricsTest.java b/java/server/src/test/java/org/signal/libsignal/cds2/Cds2MetricsTest.java index 89a1e81828..99c09aef2a 100644 --- a/java/server/src/test/java/org/signal/libsignal/cds2/Cds2MetricsTest.java +++ b/java/server/src/test/java/org/signal/libsignal/cds2/Cds2MetricsTest.java @@ -5,38 +5,37 @@ package org.signal.libsignal.cds2; -import org.signal.libsignal.attest.AttestationDataException; - -import junit.framework.TestCase; import java.io.InputStream; -import java.util.Map; import java.util.Arrays; - +import java.util.Map; +import junit.framework.TestCase; +import org.signal.libsignal.attest.AttestationDataException; public class Cds2MetricsTest extends TestCase { - private byte[] attestationMsg; - protected void setUp() throws Exception { - super.setUp(); + private byte[] attestationMsg; - // Test data should be ~14k - attestationMsg = new byte[15_000]; + protected void setUp() throws Exception { + super.setUp(); - try (InputStream stream = getClass().getResourceAsStream("clienthandshakestart.data")) { - assert stream != null; - int read = stream.read(attestationMsg); - // should be empty - assert(stream.read() == -1); - attestationMsg = Arrays.copyOf(attestationMsg, read); - } - } + // Test data should be ~14k + attestationMsg = new byte[15_000]; - public void testValidMetrics() throws AttestationDataException { - Map metrics = Cds2Metrics.extract(attestationMsg); - // 2022-08-14 02:31:29 UTC - assertEquals(metrics.get("tcb_info_expiration_ts").longValue(), 1658440468); - // May 21 10:50:10 2018 GMT - assertEquals(metrics.get("tcb_signer_not_before_ts").longValue(), 1526899810); - // May 21 10:50:10 2025 GMT - assertEquals(metrics.get("tcb_signer_not_after_ts").longValue(), 1747824610); + try (InputStream stream = getClass().getResourceAsStream("clienthandshakestart.data")) { + assert stream != null; + int read = stream.read(attestationMsg); + // should be empty + assert (stream.read() == -1); + attestationMsg = Arrays.copyOf(attestationMsg, read); } + } + + public void testValidMetrics() throws AttestationDataException { + Map metrics = Cds2Metrics.extract(attestationMsg); + // 2022-08-14 02:31:29 UTC + assertEquals(metrics.get("tcb_info_expiration_ts").longValue(), 1658440468); + // May 21 10:50:10 2018 GMT + assertEquals(metrics.get("tcb_signer_not_before_ts").longValue(), 1526899810); + // May 21 10:50:10 2025 GMT + assertEquals(metrics.get("tcb_signer_not_after_ts").longValue(), 1747824610); + } } diff --git a/java/settings.gradle b/java/settings.gradle index fad5fb463e..6d717998ea 100644 --- a/java/settings.gradle +++ b/java/settings.gradle @@ -6,7 +6,9 @@ pluginManagement { } } -include ':client', ':server' +rootProject.name = 'libsignal' + +include ':client', ':server', ':shared' if (hasProperty('skipAndroid')) { // Do nothing diff --git a/java/shared/build.gradle b/java/shared/build.gradle new file mode 100644 index 0000000000..fafefb035e --- /dev/null +++ b/java/shared/build.gradle @@ -0,0 +1,9 @@ +// Source files from 'shared' folder is included in both server and client's +// 'srcSets' and does not need to be its own Gradle subproject. However, +// 'spotless' plugin does not allow formatting files outside the 'projectDir'. +// For this reason we turn 'shared' into a Gradle subproject, but _do not_ set +// it up as 'java'. +repositories { + mavenCentral() + mavenLocal() +} diff --git a/java/shared/java/org/signal/libsignal/attest/AttestationDataException.java b/java/shared/java/org/signal/libsignal/attest/AttestationDataException.java index 2a9780e084..0b5280eba3 100644 --- a/java/shared/java/org/signal/libsignal/attest/AttestationDataException.java +++ b/java/shared/java/org/signal/libsignal/attest/AttestationDataException.java @@ -1,6 +1,16 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.attest; public class AttestationDataException extends Exception { - public AttestationDataException(String msg) { super(msg); } - public AttestationDataException(Throwable t) { super(t); } + public AttestationDataException(String msg) { + super(msg); + } + + public AttestationDataException(Throwable t) { + super(t); + } } diff --git a/java/shared/java/org/signal/libsignal/attest/DcapException.java b/java/shared/java/org/signal/libsignal/attest/DcapException.java index 9673b3f62b..ca050b8c2a 100644 --- a/java/shared/java/org/signal/libsignal/attest/DcapException.java +++ b/java/shared/java/org/signal/libsignal/attest/DcapException.java @@ -1,6 +1,16 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.attest; public class DcapException extends Exception { - public DcapException(String msg) { super(msg); } - public DcapException(Throwable t) { super(t); } + public DcapException(String msg) { + super(msg); + } + + public DcapException(Throwable t) { + super(t); + } } diff --git a/java/shared/java/org/signal/libsignal/grpc/GrpcClient.java b/java/shared/java/org/signal/libsignal/grpc/GrpcClient.java index 04af989314..3c79779b38 100644 --- a/java/shared/java/org/signal/libsignal/grpc/GrpcClient.java +++ b/java/shared/java/org/signal/libsignal/grpc/GrpcClient.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Map; - import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; @@ -24,7 +23,8 @@ public GrpcClient(String target) { this.unsafeHandle = Native.GrpcClient_New(target); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.GrpcClient_Destroy(this.unsafeHandle); } @@ -33,15 +33,19 @@ public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - public byte[] sendDirectMessage(String method, String urlFragment, byte[] body, Map> headers) { - return Native.GrpcClient_SendDirectMessage(this.unsafeHandle, method, urlFragment, body, headers); + public byte[] sendDirectMessage( + String method, String urlFragment, byte[] body, Map> headers) { + return Native.GrpcClient_SendDirectMessage( + this.unsafeHandle, method, urlFragment, body, headers); } - public void openStream(String uri, Map> headers, GrpcReplyListener replyListener) { + public void openStream( + String uri, Map> headers, GrpcReplyListener replyListener) { Native.GrpcClient_OpenStream(this.unsafeHandle, uri, headers, replyListener); } - public void sendMessageOnStream(String method, String urlFragment, byte[] body, Map> headers) { + public void sendMessageOnStream( + String method, String urlFragment, byte[] body, Map> headers) { Native.GrpcClient_SendMessageOnStream(this.unsafeHandle, method, urlFragment, body, headers); } } diff --git a/java/shared/java/org/signal/libsignal/grpc/GrpcReplyListener.java b/java/shared/java/org/signal/libsignal/grpc/GrpcReplyListener.java index e5daed5035..3391f5f91d 100644 --- a/java/shared/java/org/signal/libsignal/grpc/GrpcReplyListener.java +++ b/java/shared/java/org/signal/libsignal/grpc/GrpcReplyListener.java @@ -1,8 +1,13 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.grpc; public interface GrpcReplyListener { - void onReply(SignalRpcReply reply); + void onReply(SignalRpcReply reply); - void onError(String message); + void onError(String message); } diff --git a/java/shared/java/org/signal/libsignal/grpc/SignalRpcReply.java b/java/shared/java/org/signal/libsignal/grpc/SignalRpcReply.java index 284988df06..a26adc74cc 100644 --- a/java/shared/java/org/signal/libsignal/grpc/SignalRpcReply.java +++ b/java/shared/java/org/signal/libsignal/grpc/SignalRpcReply.java @@ -10,8 +10,7 @@ public class SignalRpcReply { private int statusCode; private byte[] message; - public SignalRpcReply() { - } + public SignalRpcReply() {} public SignalRpcReply(int statusCode, byte[] message) { this.statusCode = statusCode; diff --git a/java/shared/java/org/signal/libsignal/internal/Native.java b/java/shared/java/org/signal/libsignal/internal/Native.java index 567c21ac09..265b8ef73a 100644 --- a/java/shared/java/org/signal/libsignal/internal/Native.java +++ b/java/shared/java/org/signal/libsignal/internal/Native.java @@ -488,17 +488,16 @@ private Native() {} public static native String ServiceId_ServiceIdLog(byte[] value); public static native String ServiceId_ServiceIdString(byte[] value); - public static native void SessionBuilder_ProcessPreKeyBundle(long bundle, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore); + public static native void SessionBuilder_ProcessPreKeyBundle(long bundle, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, long now); public static native byte[] SessionCipher_DecryptPreKeySignalMessage(long message, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, PreKeyStore prekeyStore, SignedPreKeyStore signedPrekeyStore, KyberPreKeyStore kyberPrekeyStore); public static native byte[] SessionCipher_DecryptSignalMessage(long message, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore); - public static native CiphertextMessage SessionCipher_EncryptMessage(byte[] ptext, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore); + public static native CiphertextMessage SessionCipher_EncryptMessage(byte[] ptext, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, long now); public static native void SessionRecord_ArchiveCurrentState(long sessionRecord); public static native boolean SessionRecord_CurrentRatchetKeyMatches(long s, long key); public static native long SessionRecord_Deserialize(byte[] data); public static native void SessionRecord_Destroy(long handle); - public static native long SessionRecord_FromSingleSessionState(byte[] sessionState); public static native byte[] SessionRecord_GetAliceBaseKey(long obj); public static native byte[] SessionRecord_GetLocalIdentityKeyPublic(long obj); public static native int SessionRecord_GetLocalRegistrationId(long obj); @@ -507,7 +506,7 @@ private Native() {} public static native int SessionRecord_GetRemoteRegistrationId(long obj); public static native byte[] SessionRecord_GetSenderChainKeyValue(long obj); public static native int SessionRecord_GetSessionVersion(long s); - public static native boolean SessionRecord_HasSenderChain(long obj); + public static native boolean SessionRecord_HasUsableSenderChain(long s, long now); public static native long SessionRecord_InitializeAliceSession(long identityKeyPrivate, long identityKeyPublic, long basePrivate, long basePublic, long theirIdentityKey, long theirSignedPrekey, long theirRatchetKey); public static native long SessionRecord_InitializeBobSession(long identityKeyPrivate, long identityKeyPublic, long signedPrekeyPrivate, long signedPrekeyPublic, long ephPrivate, long ephPublic, long theirIdentityKey, long theirBaseKey); public static native long SessionRecord_NewFresh(); @@ -564,7 +563,7 @@ private Native() {} public static native void UuidCiphertext_CheckValidContents(byte[] buffer); public static native void ValidatingMac_Destroy(long handle); - public static native boolean ValidatingMac_Finalize(long mac); + public static native int ValidatingMac_Finalize(long mac); public static native long ValidatingMac_Initialize(byte[] key, int chunkSize, byte[] digests); - public static native boolean ValidatingMac_Update(long mac, byte[] bytes, int offset, int length); + public static native int ValidatingMac_Update(long mac, byte[] bytes, int offset, int length); } diff --git a/java/shared/java/org/signal/libsignal/internal/NativeHandleGuard.java b/java/shared/java/org/signal/libsignal/internal/NativeHandleGuard.java index 344a4074e7..f55bf30fbe 100644 --- a/java/shared/java/org/signal/libsignal/internal/NativeHandleGuard.java +++ b/java/shared/java/org/signal/libsignal/internal/NativeHandleGuard.java @@ -1,49 +1,47 @@ -/** - * Copyright (C) 2021 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2021 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// package org.signal.libsignal.internal; /** * Provides access to a Rust object handle while keeping the Java wrapper alive. * - * Intended for use with try-with-resources syntax. NativeHandleGuard prevents the Java wrapper from - * being finalized, which would destroy the Rust object, while the handle is in use. - * To use it, the Java wrapper type should conform to the {@link NativeHandleGuard.Owner} interface. - * - * Note that it is not necessary to use NativeHandleGuard in the implementation of {@code finalize} - * itself. The point of NativeHandleGuard is to delay finalization while the Rust object is being - * used; once finalization has begun, there can be no other uses of the Rust object from Java. + *

    Intended for use with try-with-resources syntax. NativeHandleGuard prevents the Java wrapper + * from being finalized, which would destroy the Rust object, while the handle is in use. To use it, + * the Java wrapper type should conform to the {@link NativeHandleGuard.Owner} interface. + * + *

    Note that it is not necessary to use NativeHandleGuard in the implementation of {@code + * finalize} itself. The point of NativeHandleGuard is to delay finalization while the Rust object + * is being used; once finalization has begun, there can be no other uses of the Rust object from + * Java. */ public class NativeHandleGuard implements AutoCloseable { - /** - * @see NativeHandleGuard - */ - public static interface Owner { - long unsafeNativeHandleWithoutGuard(); - } + /** + * @see NativeHandleGuard + */ + public static interface Owner { + long unsafeNativeHandleWithoutGuard(); + } - private final Owner owner; + private final Owner owner; - public NativeHandleGuard(Owner owner) { - this.owner = owner; - } + public NativeHandleGuard(Owner owner) { + this.owner = owner; + } - /** - * Returns the native handle owned by the Java object, or 0 if the owner is {@code null}. - */ - public long nativeHandle() { - if (owner == null) { - return 0; - } - return owner.unsafeNativeHandleWithoutGuard(); + /** Returns the native handle owned by the Java object, or 0 if the owner is {@code null}. */ + public long nativeHandle() { + if (owner == null) { + return 0; } + return owner.unsafeNativeHandleWithoutGuard(); + } - public void close() { - // Act as an optimization barrier, so the whole guard doesn't get inlined away. - // (In Java 9 we'd use Reference.reachabilityFence() for the same effect.) - Native.keepAlive(this.owner); - } -} \ No newline at end of file + public void close() { + // Act as an optimization barrier, so the whole guard doesn't get inlined away. + // (In Java 9 we'd use Reference.reachabilityFence() for the same effect.) + Native.keepAlive(this.owner); + } +} diff --git a/java/shared/java/org/signal/libsignal/protocol/DuplicateMessageException.java b/java/shared/java/org/signal/libsignal/protocol/DuplicateMessageException.java index 87ab889d04..07eab2bd6d 100644 --- a/java/shared/java/org/signal/libsignal/protocol/DuplicateMessageException.java +++ b/java/shared/java/org/signal/libsignal/protocol/DuplicateMessageException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class DuplicateMessageException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/IdentityKey.java b/java/shared/java/org/signal/libsignal/protocol/IdentityKey.java index 128dcbf540..fb61ad290f 100644 --- a/java/shared/java/org/signal/libsignal/protocol/IdentityKey.java +++ b/java/shared/java/org/signal/libsignal/protocol/IdentityKey.java @@ -1,23 +1,21 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.util.Hex; /** * A class for representing an identity key. - * + * * @author Moxie Marlinspike */ - public class IdentityKey { private final ECPublicKey publicKey; @@ -51,22 +49,21 @@ public String getFingerprint() { } public boolean verifyAlternateIdentity(IdentityKey other, byte[] signature) { - try ( - NativeHandleGuard guard = new NativeHandleGuard(this.publicKey); - NativeHandleGuard otherGuard = new NativeHandleGuard(other.publicKey); - ) { - return Native.IdentityKey_VerifyAlternateIdentity(guard.nativeHandle(), otherGuard.nativeHandle(), signature); + try (NativeHandleGuard guard = new NativeHandleGuard(this.publicKey); + NativeHandleGuard otherGuard = new NativeHandleGuard(other.publicKey); ) { + return Native.IdentityKey_VerifyAlternateIdentity( + guard.nativeHandle(), otherGuard.nativeHandle(), signature); } } @Override public boolean equals(Object other) { - if (other == null) return false; + if (other == null) return false; if (!(other instanceof IdentityKey)) return false; return publicKey.equals(((IdentityKey) other).getPublicKey()); } - + @Override public int hashCode() { return publicKey.hashCode(); diff --git a/java/shared/java/org/signal/libsignal/protocol/IdentityKeyPair.java b/java/shared/java/org/signal/libsignal/protocol/IdentityKeyPair.java index fdb5bf6be4..accf7abb9e 100644 --- a/java/shared/java/org/signal/libsignal/protocol/IdentityKeyPair.java +++ b/java/shared/java/org/signal/libsignal/protocol/IdentityKeyPair.java @@ -1,13 +1,12 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECPrivateKey; @@ -19,11 +18,11 @@ * @author Moxie Marlinspike */ public class IdentityKeyPair { - private final IdentityKey publicKey; + private final IdentityKey publicKey; private final ECPrivateKey privateKey; public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) { - this.publicKey = publicKey; + this.publicKey = publicKey; this.privateKey = privateKey; } @@ -52,21 +51,18 @@ public ECPrivateKey getPrivateKey() { } public byte[] serialize() { - try ( - NativeHandleGuard publicKey = new NativeHandleGuard(this.publicKey.getPublicKey()); - NativeHandleGuard privateKey = new NativeHandleGuard(this.privateKey); - ) { + try (NativeHandleGuard publicKey = new NativeHandleGuard(this.publicKey.getPublicKey()); + NativeHandleGuard privateKey = new NativeHandleGuard(this.privateKey); ) { return Native.IdentityKeyPair_Serialize(publicKey.nativeHandle(), privateKey.nativeHandle()); } } public byte[] signAlternateIdentity(IdentityKey other) { - try ( - NativeHandleGuard publicKey = new NativeHandleGuard(this.publicKey.getPublicKey()); - NativeHandleGuard privateKey = new NativeHandleGuard(this.privateKey); - NativeHandleGuard otherPublic = new NativeHandleGuard(other.getPublicKey()); - ) { - return Native.IdentityKeyPair_SignAlternateIdentity(publicKey.nativeHandle(), privateKey.nativeHandle(), otherPublic.nativeHandle()); + try (NativeHandleGuard publicKey = new NativeHandleGuard(this.publicKey.getPublicKey()); + NativeHandleGuard privateKey = new NativeHandleGuard(this.privateKey); + NativeHandleGuard otherPublic = new NativeHandleGuard(other.getPublicKey()); ) { + return Native.IdentityKeyPair_SignAlternateIdentity( + publicKey.nativeHandle(), privateKey.nativeHandle(), otherPublic.nativeHandle()); } } } diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidKeyException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidKeyException.java index ee8a55e68f..a7580f3bec 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidKeyException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidKeyException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class InvalidKeyException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidKeyIdException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidKeyIdException.java index 3c04efc6fa..db4186d92b 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidKeyIdException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidKeyIdException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class InvalidKeyIdException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidMacException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidMacException.java index 8f31953521..97dbe42d67 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidMacException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidMacException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class InvalidMacException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidMessageException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidMessageException.java index e1288b9b3e..d3e34897f0 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidMessageException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidMessageException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import java.util.List; diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidRegistrationIdException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidRegistrationIdException.java index b45d021a8d..cf706e1fe3 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidRegistrationIdException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidRegistrationIdException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2021 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2021 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class InvalidRegistrationIdException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidSessionException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidSessionException.java index 9f832558b6..ef9f704bd6 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidSessionException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidSessionException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class InvalidSessionException extends IllegalStateException { diff --git a/java/shared/java/org/signal/libsignal/protocol/InvalidVersionException.java b/java/shared/java/org/signal/libsignal/protocol/InvalidVersionException.java index fe4b59ffff..12f8ac7beb 100644 --- a/java/shared/java/org/signal/libsignal/protocol/InvalidVersionException.java +++ b/java/shared/java/org/signal/libsignal/protocol/InvalidVersionException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class InvalidVersionException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/LegacyMessageException.java b/java/shared/java/org/signal/libsignal/protocol/LegacyMessageException.java index 33ee1c210c..5f56267cae 100644 --- a/java/shared/java/org/signal/libsignal/protocol/LegacyMessageException.java +++ b/java/shared/java/org/signal/libsignal/protocol/LegacyMessageException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class LegacyMessageException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/NoSessionException.java b/java/shared/java/org/signal/libsignal/protocol/NoSessionException.java index ce90604943..9e7918e923 100644 --- a/java/shared/java/org/signal/libsignal/protocol/NoSessionException.java +++ b/java/shared/java/org/signal/libsignal/protocol/NoSessionException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class NoSessionException extends Exception { diff --git a/java/shared/java/org/signal/libsignal/protocol/ServiceId.java b/java/shared/java/org/signal/libsignal/protocol/ServiceId.java index e675301723..3865bdf586 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ServiceId.java +++ b/java/shared/java/org/signal/libsignal/protocol/ServiceId.java @@ -2,199 +2,203 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.protocol; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.UUID; - import org.signal.libsignal.internal.Native; public abstract class ServiceId { - private static final byte ACI_MARKER = 0x00; - private static final byte PNI_MARKER = 0x01; + private static final byte ACI_MARKER = 0x00; + private static final byte PNI_MARKER = 0x01; + + byte[] storage; + + ServiceId(byte[] storage) { + if (storage == null) { + throw new IllegalArgumentException("Service-Id-FixedWidthBinary cannot be null"); + } + this.storage = storage; + } + + ServiceId(byte marker, UUID uuid) { + if (uuid == null) { + throw new IllegalArgumentException("Source UUID must be specified"); + } + ByteBuffer bytes = ByteBuffer.wrap(new byte[17]); + long high = uuid.getMostSignificantBits(); + long low = uuid.getLeastSignificantBits(); + bytes.put(marker); + bytes.putLong(high); + bytes.putLong(low); + this.storage = bytes.array(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ServiceId) { + return Arrays.equals(this.storage, ((ServiceId) other).storage); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.storage); + } + + @Override + public String toString() { + return this.toLogString(); + } + + public String toLogString() { + return Native.ServiceId_ServiceIdLog(this.storage); + } + + public byte[] toServiceIdBinary() { + return Native.ServiceId_ServiceIdBinary(this.storage); + } + + public byte[] toServiceIdFixedWidthBinary() { + return this.storage.clone(); + } + + public String toServiceIdString() { + return Native.ServiceId_ServiceIdString(this.storage); + } + + public UUID getRawUUID() { + ByteBuffer buffer = ByteBuffer.wrap(this.storage); + byte unusedMarkerByte = buffer.get(); + return uuidFromBytes(buffer.slice()); + } + + public static ServiceId parseFromString(String serviceIdString) throws InvalidServiceIdException { + if (serviceIdString == null) { + throw new InvalidServiceIdException("Service-Id-String cannot be null"); + } + byte[] storage; + try { + storage = Native.ServiceId_ParseFromServiceIdString(serviceIdString); + } catch (IllegalArgumentException ex) { + throw new InvalidServiceIdException(); + } + return parseFromFixedWidthBinary(storage); + } + public static ServiceId parseFromBinary(byte[] serviceIdBinary) throws InvalidServiceIdException { + if (serviceIdBinary == null) { + throw new InvalidServiceIdException("Service-Id-Binary cannot be null"); + } byte[] storage; + try { + storage = Native.ServiceId_ParseFromServiceIdBinary(serviceIdBinary); + } catch (IllegalArgumentException ex) { + throw new InvalidServiceIdException(); + } + return parseFromFixedWidthBinary(storage); + } + + public static ServiceId parseFromFixedWidthBinary(byte[] storage) + throws InvalidServiceIdException { + if (storage == null) { + throw new InvalidServiceIdException(); + } + switch (storage[0]) { + case ACI_MARKER: + return new Aci(storage); + case PNI_MARKER: + return new Pni(storage); + default: + // This is already handled on the Rust side + throw new InvalidServiceIdException(); + } + } + + private static UUID uuidFromBytes(ByteBuffer buffer) { + long high = buffer.getLong(); + long low = buffer.getLong(); + return new UUID(high, low); + } + + public static class InvalidServiceIdException extends Exception { + public InvalidServiceIdException() { + super(); + } + + public InvalidServiceIdException(String message) { + super(message); + } + } + + public static final class Aci extends ServiceId { + public Aci(UUID uuid) { + super(ACI_MARKER, uuid); + } - ServiceId(byte[] storage) { - if (storage == null) { - throw new IllegalArgumentException("Service-Id-FixedWidthBinary cannot be null"); - } - this.storage = storage; + Aci(byte[] storage) { + super(storage); } - ServiceId(byte marker, UUID uuid) { - if (uuid == null) { - throw new IllegalArgumentException("Source UUID must be specified"); - } - ByteBuffer bytes = ByteBuffer.wrap(new byte[17]); - long high = uuid.getMostSignificantBits(); - long low = uuid.getLeastSignificantBits(); - bytes.put(marker); - bytes.putLong(high); - bytes.putLong(low); - this.storage = bytes.array(); + public static Aci parseFromString(String serviceIdString) throws InvalidServiceIdException { + ServiceId result = ServiceId.parseFromString(serviceIdString); + if (result instanceof Aci) { + return (Aci) result; + } + throw new InvalidServiceIdException(); } - @Override - public boolean equals(Object other) { - if(other instanceof ServiceId) { - return Arrays.equals(this.storage, ((ServiceId)other).storage); - } - return false; + public static Aci parseFromBinary(byte[] serviceIdBinary) throws InvalidServiceIdException { + ServiceId result = ServiceId.parseFromBinary(serviceIdBinary); + if (result instanceof Aci) { + return (Aci) result; + } + throw new InvalidServiceIdException(); } - @Override - public int hashCode() { - return Arrays.hashCode(this.storage); + public static Aci parseFromFixedWidthBinary(byte[] storage) throws InvalidServiceIdException { + ServiceId result = ServiceId.parseFromFixedWidthBinary(storage); + if (result instanceof Aci) { + return (Aci) result; + } + throw new InvalidServiceIdException(); } + } - @Override - public String toString() { - return this.toLogString(); + public static final class Pni extends ServiceId { + public Pni(UUID uuid) { + super(PNI_MARKER, uuid); } - public String toLogString() { - return Native.ServiceId_ServiceIdLog(this.storage); + Pni(byte[] storage) { + super(storage); } - public byte[] toServiceIdBinary() { - return Native.ServiceId_ServiceIdBinary(this.storage); - } - - public byte[] toServiceIdFixedWidthBinary() { - return this.storage.clone(); + public static Pni parseFromString(String serviceIdString) throws InvalidServiceIdException { + ServiceId result = ServiceId.parseFromString(serviceIdString); + if (result instanceof Pni) { + return (Pni) result; + } + throw new InvalidServiceIdException(); } - public String toServiceIdString() { - return Native.ServiceId_ServiceIdString(this.storage); + public static Pni parseFromBinary(byte[] serviceIdBinary) throws InvalidServiceIdException { + ServiceId result = ServiceId.parseFromBinary(serviceIdBinary); + if (result instanceof Pni) { + return (Pni) result; + } + throw new InvalidServiceIdException(); } - public UUID getRawUUID() { - ByteBuffer buffer = ByteBuffer.wrap(this.storage); - byte unusedMarkerByte = buffer.get(); - return uuidFromBytes(buffer.slice()); - } - - public static ServiceId parseFromString(String serviceIdString) throws InvalidServiceIdException { - if (serviceIdString == null) { - throw new InvalidServiceIdException("Service-Id-String cannot be null"); - } - byte[] storage; - try { - storage = Native.ServiceId_ParseFromServiceIdString(serviceIdString); - } - catch (IllegalArgumentException ex) { - throw new InvalidServiceIdException(); - } - return parseFromFixedWidthBinary(storage); - } - - public static ServiceId parseFromBinary(byte[] serviceIdBinary) throws InvalidServiceIdException { - if (serviceIdBinary == null) { - throw new InvalidServiceIdException("Service-Id-Binary cannot be null"); - } - byte[] storage; - try { - storage = Native.ServiceId_ParseFromServiceIdBinary(serviceIdBinary); - } - catch (IllegalArgumentException ex) { - throw new InvalidServiceIdException(); - } - return parseFromFixedWidthBinary(storage); - } - - public static ServiceId parseFromFixedWidthBinary(byte[] storage) throws InvalidServiceIdException { - if (storage == null) { - throw new InvalidServiceIdException(); - } - switch(storage[0]) { - case ACI_MARKER: - return new Aci(storage); - case PNI_MARKER: - return new Pni(storage); - default: - // This is already handled on the Rust side - throw new InvalidServiceIdException(); - } - } - - private static UUID uuidFromBytes(ByteBuffer buffer) { - long high = buffer.getLong(); - long low = buffer.getLong(); - return new UUID(high, low); - } - - public static class InvalidServiceIdException extends Exception { - public InvalidServiceIdException() { super(); } - public InvalidServiceIdException(String message) { super(message); } - } - - public final static class Aci extends ServiceId { - public Aci(UUID uuid) { - super(ACI_MARKER, uuid); - } - - Aci(byte[] storage) { - super(storage); - } - - public static Aci parseFromString(String serviceIdString) throws InvalidServiceIdException { - ServiceId result = ServiceId.parseFromString(serviceIdString); - if (result instanceof Aci) { - return (Aci)result; - } - throw new InvalidServiceIdException(); - } - - public static Aci parseFromBinary(byte[] serviceIdBinary) throws InvalidServiceIdException { - ServiceId result = ServiceId.parseFromBinary(serviceIdBinary); - if (result instanceof Aci) { - return (Aci)result; - } - throw new InvalidServiceIdException(); - } - - public static Aci parseFromFixedWidthBinary(byte[] storage) throws InvalidServiceIdException { - ServiceId result = ServiceId.parseFromFixedWidthBinary(storage); - if (result instanceof Aci) { - return (Aci)result; - } - throw new InvalidServiceIdException(); - } - } - - public final static class Pni extends ServiceId { - public Pni(UUID uuid) { - super(PNI_MARKER, uuid); - } - - Pni(byte[] storage) { - super(storage); - } - - public static Pni parseFromString(String serviceIdString) throws InvalidServiceIdException { - ServiceId result = ServiceId.parseFromString(serviceIdString); - if (result instanceof Pni) { - return (Pni)result; - } - throw new InvalidServiceIdException(); - } - - public static Pni parseFromBinary(byte[] serviceIdBinary) throws InvalidServiceIdException { - ServiceId result = ServiceId.parseFromBinary(serviceIdBinary); - if (result instanceof Pni) { - return (Pni)result; - } - throw new InvalidServiceIdException(); - } - - public static Pni parseFromFixedWidthBinary(byte[] storage) throws InvalidServiceIdException { - ServiceId result = ServiceId.parseFromFixedWidthBinary(storage); - if (result instanceof Pni) { - return (Pni)result; - } - throw new InvalidServiceIdException(); - } + public static Pni parseFromFixedWidthBinary(byte[] storage) throws InvalidServiceIdException { + ServiceId result = ServiceId.parseFromFixedWidthBinary(storage); + if (result instanceof Pni) { + return (Pni) result; + } + throw new InvalidServiceIdException(); } + } } diff --git a/java/shared/java/org/signal/libsignal/protocol/SessionBuilder.java b/java/shared/java/org/signal/libsignal/protocol/SessionBuilder.java index 8af7d3ca8a..2fb44d3239 100644 --- a/java/shared/java/org/signal/libsignal/protocol/SessionBuilder.java +++ b/java/shared/java/org/signal/libsignal/protocol/SessionBuilder.java @@ -1,72 +1,77 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; +import java.time.Instant; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - -import org.signal.libsignal.protocol.logging.Log; import org.signal.libsignal.protocol.state.IdentityKeyStore; import org.signal.libsignal.protocol.state.PreKeyBundle; import org.signal.libsignal.protocol.state.PreKeyStore; -import org.signal.libsignal.protocol.state.SessionRecord; import org.signal.libsignal.protocol.state.SessionStore; import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.signal.libsignal.protocol.state.SignedPreKeyStore; /** - * SessionBuilder is responsible for setting up encrypted sessions. - * Once a session has been established, {@link org.signal.libsignal.protocol.SessionCipher} - * can be used to encrypt/decrypt messages in that session. - *

    - * Sessions are built from one of three different possible vectors: + * SessionBuilder is responsible for setting up encrypted sessions. Once a session has been + * established, {@link org.signal.libsignal.protocol.SessionCipher} can be used to encrypt/decrypt + * messages in that session. + * + *

    Sessions are built from one of two different possible vectors: + * *

      - *
    1. A {@link org.signal.libsignal.protocol.state.PreKeyBundle} retrieved from a server.
    2. - *
    3. A {@link PreKeySignalMessage} received from a client.
    4. + *
    5. A {@link org.signal.libsignal.protocol.state.PreKeyBundle} retrieved from a server. + *
    6. A {@link org.signal.libsignal.protocol.message.PreKeySignalMessage} received from a client. *
    * - * Sessions are constructed per recipientId + deviceId tuple. Remote logical users are identified + * Only the first, however, is handled by SessionBuilder. + * + *

    Sessions are constructed per recipientId + deviceId tuple. Remote logical users are identified * by their recipientId, and each logical recipientId can have multiple physical devices. * - * This class is not thread-safe. + *

    This class is not thread-safe. * * @author Moxie Marlinspike */ public class SessionBuilder { private static final String TAG = SessionBuilder.class.getSimpleName(); - private final SessionStore sessionStore; - private final PreKeyStore preKeyStore; + private final SessionStore sessionStore; + private final PreKeyStore preKeyStore; private final SignedPreKeyStore signedPreKeyStore; - private final IdentityKeyStore identityKeyStore; + private final IdentityKeyStore identityKeyStore; private final SignalProtocolAddress remoteAddress; /** * Constructs a SessionBuilder. * - * @param sessionStore The {@link org.signal.libsignal.protocol.state.SessionStore} to store the constructed session in. - * @param preKeyStore The {@link org.signal.libsignal.protocol.state.PreKeyStore} where the client's local {@link org.signal.libsignal.protocol.state.PreKeyRecord}s are stored. - * @param identityKeyStore The {@link org.signal.libsignal.protocol.state.IdentityKeyStore} containing the client's identity key information. + * @param sessionStore The {@link org.signal.libsignal.protocol.state.SessionStore} to store the + * constructed session in. + * @param preKeyStore The {@link org.signal.libsignal.protocol.state.PreKeyStore} where the + * client's local {@link org.signal.libsignal.protocol.state.PreKeyRecord}s are stored. + * @param identityKeyStore The {@link org.signal.libsignal.protocol.state.IdentityKeyStore} + * containing the client's identity key information. * @param remoteAddress The address of the remote user to build a session with. */ - public SessionBuilder(SessionStore sessionStore, - PreKeyStore preKeyStore, - SignedPreKeyStore signedPreKeyStore, - IdentityKeyStore identityKeyStore, - SignalProtocolAddress remoteAddress) - { - this.sessionStore = sessionStore; - this.preKeyStore = preKeyStore; + public SessionBuilder( + SessionStore sessionStore, + PreKeyStore preKeyStore, + SignedPreKeyStore signedPreKeyStore, + IdentityKeyStore identityKeyStore, + SignalProtocolAddress remoteAddress) { + this.sessionStore = sessionStore; + this.preKeyStore = preKeyStore; this.signedPreKeyStore = signedPreKeyStore; - this.identityKeyStore = identityKeyStore; - this.remoteAddress = remoteAddress; + this.identityKeyStore = identityKeyStore; + this.remoteAddress = remoteAddress; } /** * Constructs a SessionBuilder + * * @param store The {@link SignalProtocolStore} to store all state information in. * @param remoteAddress The address of the remote user to build a session with. */ @@ -75,25 +80,42 @@ public SessionBuilder(SignalProtocolStore store, SignalProtocolAddress remoteAdd } /** - * Build a new session from a {@link org.signal.libsignal.protocol.state.PreKeyBundle} retrieved from - * a server. + * Build a new session from a {@link org.signal.libsignal.protocol.state.PreKeyBundle} retrieved + * from a server. * * @param preKey A PreKey for the destination recipient, retrieved from a server. - * @throws InvalidKeyException when the {@link org.signal.libsignal.protocol.state.PreKeyBundle} is - * badly formatted. - * @throws org.signal.libsignal.protocol.UntrustedIdentityException when the sender's - * {@link IdentityKey} is not - * trusted. + * @throws InvalidKeyException when the {@link org.signal.libsignal.protocol.state.PreKeyBundle} + * is badly formatted. + * @throws org.signal.libsignal.protocol.UntrustedIdentityException when the sender's {@link + * IdentityKey} is not trusted. */ public void process(PreKeyBundle preKey) throws InvalidKeyException, UntrustedIdentityException { - try ( - NativeHandleGuard preKeyGuard = new NativeHandleGuard(preKey); - NativeHandleGuard remoteAddressGuard = new NativeHandleGuard(this.remoteAddress); - ) { - Native.SessionBuilder_ProcessPreKeyBundle(preKeyGuard.nativeHandle(), - remoteAddressGuard.nativeHandle(), - sessionStore, - identityKeyStore); + process(preKey, Instant.now()); + } + + /** + * Build a new session from a {@link org.signal.libsignal.protocol.state.PreKeyBundle} retrieved + * from a server. + * + *

    You should only use this overload if you need to test session expiration explicitly. + * + * @param preKey A PreKey for the destination recipient, retrieved from a server. + * @param now The current time, used later to check if the session is stale. + * @throws InvalidKeyException when the {@link org.signal.libsignal.protocol.state.PreKeyBundle} + * is badly formatted. + * @throws org.signal.libsignal.protocol.UntrustedIdentityException when the sender's {@link + * IdentityKey} is not trusted. + */ + public void process(PreKeyBundle preKey, Instant now) + throws InvalidKeyException, UntrustedIdentityException { + try (NativeHandleGuard preKeyGuard = new NativeHandleGuard(preKey); + NativeHandleGuard remoteAddressGuard = new NativeHandleGuard(this.remoteAddress)) { + Native.SessionBuilder_ProcessPreKeyBundle( + preKeyGuard.nativeHandle(), + remoteAddressGuard.nativeHandle(), + sessionStore, + identityKeyStore, + now.toEpochMilli()); } } } diff --git a/java/shared/java/org/signal/libsignal/protocol/SessionCipher.java b/java/shared/java/org/signal/libsignal/protocol/SessionCipher.java index 6e37022397..aff7c808f8 100644 --- a/java/shared/java/org/signal/libsignal/protocol/SessionCipher.java +++ b/java/shared/java/org/signal/libsignal/protocol/SessionCipher.java @@ -1,69 +1,65 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; +import java.time.Instant; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - -import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.message.CiphertextMessage; import org.signal.libsignal.protocol.message.PreKeySignalMessage; import org.signal.libsignal.protocol.message.SignalMessage; -import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.signal.libsignal.protocol.state.IdentityKeyStore; +import org.signal.libsignal.protocol.state.KyberPreKeyStore; import org.signal.libsignal.protocol.state.PreKeyStore; import org.signal.libsignal.protocol.state.SessionRecord; import org.signal.libsignal.protocol.state.SessionStore; +import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.signal.libsignal.protocol.state.SignedPreKeyStore; -import org.signal.libsignal.protocol.state.KyberPreKeyStore; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; /** * The main entry point for Signal Protocol encrypt/decrypt operations. * - * Once a session has been established with {@link SessionBuilder}, - * this class can be used for all encrypt/decrypt operations within - * that session. + *

    Once a session has been established with {@link SessionBuilder}, this class can be used for + * all encrypt/decrypt operations within that session. * - * This class is not thread-safe. + *

    This class is not thread-safe. * * @author Moxie Marlinspike */ public class SessionCipher { - private final SessionStore sessionStore; - private final IdentityKeyStore identityKeyStore; - private final PreKeyStore preKeyStore; - private final SignedPreKeyStore signedPreKeyStore; - private final KyberPreKeyStore kyberPreKeyStore; + private final SessionStore sessionStore; + private final IdentityKeyStore identityKeyStore; + private final PreKeyStore preKeyStore; + private final SignedPreKeyStore signedPreKeyStore; + private final KyberPreKeyStore kyberPreKeyStore; private final SignalProtocolAddress remoteAddress; /** - * Construct a SessionCipher for encrypt/decrypt operations on a session. - * In order to use SessionCipher, a session must have already been created - * and stored using {@link SessionBuilder}. + * Construct a SessionCipher for encrypt/decrypt operations on a session. In order to use + * SessionCipher, a session must have already been created and stored using {@link + * SessionBuilder}. * - * @param sessionStore The {@link SessionStore} that contains a session for this recipient. - * @param remoteAddress The remote address that messages will be encrypted to or decrypted from. + * @param sessionStore The {@link SessionStore} that contains a session for this recipient. + * @param remoteAddress The remote address that messages will be encrypted to or decrypted from. */ - public SessionCipher(SessionStore sessionStore, - PreKeyStore preKeyStore, - SignedPreKeyStore signedPreKeyStore, - KyberPreKeyStore kyberPreKeyStore, - IdentityKeyStore identityKeyStore, - SignalProtocolAddress remoteAddress) - { - this.sessionStore = sessionStore; - this.preKeyStore = preKeyStore; - this.identityKeyStore = identityKeyStore; - this.remoteAddress = remoteAddress; + public SessionCipher( + SessionStore sessionStore, + PreKeyStore preKeyStore, + SignedPreKeyStore signedPreKeyStore, + KyberPreKeyStore kyberPreKeyStore, + IdentityKeyStore identityKeyStore, + SignalProtocolAddress remoteAddress) { + this.sessionStore = sessionStore; + this.preKeyStore = preKeyStore; + this.identityKeyStore = identityKeyStore; + this.remoteAddress = remoteAddress; this.signedPreKeyStore = signedPreKeyStore; - this.kyberPreKeyStore = kyberPreKeyStore;; + this.kyberPreKeyStore = kyberPreKeyStore; + ; } public SessionCipher(SignalProtocolStore store, SignalProtocolAddress remoteAddress) { @@ -73,53 +69,76 @@ public SessionCipher(SignalProtocolStore store, SignalProtocolAddress remoteAddr /** * Encrypt a message. * - * @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple. + * @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple. * @return A ciphertext message encrypted to the recipient+device tuple. + * @throws NoSessionException if there is no established session for this contact, or if an + * unacknowledged session has expired + * @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is out of date. */ - public CiphertextMessage encrypt(byte[] paddedMessage) throws UntrustedIdentityException { + public CiphertextMessage encrypt(byte[] paddedMessage) + throws NoSessionException, UntrustedIdentityException { + return encrypt(paddedMessage, Instant.now()); + } + + /** + * Encrypt a message. + * + *

    You should only use this overload if you need to test session expiration explicitly. + * + * @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple. + * @return A ciphertext message encrypted to the recipient+device tuple. + * @throws NoSessionException if there is no established session for this contact, or if an + * unacknowledged session has expired + * @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is out of date. + */ + public CiphertextMessage encrypt(byte[] paddedMessage, Instant now) + throws NoSessionException, UntrustedIdentityException { try (NativeHandleGuard remoteAddress = new NativeHandleGuard(this.remoteAddress)) { - return Native.SessionCipher_EncryptMessage(paddedMessage, - remoteAddress.nativeHandle(), - sessionStore, - identityKeyStore); + return Native.SessionCipher_EncryptMessage( + paddedMessage, + remoteAddress.nativeHandle(), + sessionStore, + identityKeyStore, + now.toEpochMilli()); } } /** * Decrypt a message. * - * @param ciphertext The {@link PreKeySignalMessage} to decrypt. - * + * @param ciphertext The {@link PreKeySignalMessage} to decrypt. * @return The plaintext. * @throws InvalidMessageException if the input is not valid ciphertext. * @throws DuplicateMessageException if the input is a message that has already been received. - * @throws InvalidKeyIdException when there is no local {@link org.signal.libsignal.protocol.state.PreKeyRecord} - * that corresponds to the PreKey ID in the message. + * @throws InvalidKeyIdException when there is no local {@link + * org.signal.libsignal.protocol.state.PreKeyRecord} that corresponds to the PreKey ID in the + * message. * @throws InvalidKeyException when the message is formatted incorrectly. * @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted. */ public byte[] decrypt(PreKeySignalMessage ciphertext) - throws DuplicateMessageException, InvalidMessageException, InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException - { - try ( - NativeHandleGuard ciphertextGuard = new NativeHandleGuard(ciphertext); - NativeHandleGuard remoteAddressGuard = new NativeHandleGuard(this.remoteAddress); - ) { - return Native.SessionCipher_DecryptPreKeySignalMessage(ciphertextGuard.nativeHandle(), - remoteAddressGuard.nativeHandle(), - sessionStore, - identityKeyStore, - preKeyStore, - signedPreKeyStore, - kyberPreKeyStore); + throws DuplicateMessageException, + InvalidMessageException, + InvalidKeyIdException, + InvalidKeyException, + UntrustedIdentityException { + try (NativeHandleGuard ciphertextGuard = new NativeHandleGuard(ciphertext); + NativeHandleGuard remoteAddressGuard = new NativeHandleGuard(this.remoteAddress); ) { + return Native.SessionCipher_DecryptPreKeySignalMessage( + ciphertextGuard.nativeHandle(), + remoteAddressGuard.nativeHandle(), + sessionStore, + identityKeyStore, + preKeyStore, + signedPreKeyStore, + kyberPreKeyStore); } } /** * Decrypt a message. * - * @param ciphertext The {@link SignalMessage} to decrypt. - * + * @param ciphertext The {@link SignalMessage} to decrypt. * @return The plaintext. * @throws InvalidMessageException if the input is not valid ciphertext. * @throws InvalidVersionException if the message version does not match the session version. @@ -127,16 +146,18 @@ public byte[] decrypt(PreKeySignalMessage ciphertext) * @throws NoSessionException if there is no established session for this contact. */ public byte[] decrypt(SignalMessage ciphertext) - throws InvalidMessageException, InvalidVersionException, DuplicateMessageException, NoSessionException, UntrustedIdentityException - { - try ( - NativeHandleGuard ciphertextGuard = new NativeHandleGuard(ciphertext); - NativeHandleGuard remoteAddressGuard = new NativeHandleGuard(this.remoteAddress); - ) { - return Native.SessionCipher_DecryptSignalMessage(ciphertextGuard.nativeHandle(), - remoteAddressGuard.nativeHandle(), - sessionStore, - identityKeyStore); + throws InvalidMessageException, + InvalidVersionException, + DuplicateMessageException, + NoSessionException, + UntrustedIdentityException { + try (NativeHandleGuard ciphertextGuard = new NativeHandleGuard(ciphertext); + NativeHandleGuard remoteAddressGuard = new NativeHandleGuard(this.remoteAddress); ) { + return Native.SessionCipher_DecryptSignalMessage( + ciphertextGuard.nativeHandle(), + remoteAddressGuard.nativeHandle(), + sessionStore, + identityKeyStore); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/SignalProtocolAddress.java b/java/shared/java/org/signal/libsignal/protocol/SignalProtocolAddress.java index 314159ead1..83722bf7b4 100644 --- a/java/shared/java/org/signal/libsignal/protocol/SignalProtocolAddress.java +++ b/java/shared/java/org/signal/libsignal/protocol/SignalProtocolAddress.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; import org.signal.libsignal.internal.Native; @@ -23,7 +23,8 @@ public SignalProtocolAddress(long unsafeHandle) { this.unsafeHandle = unsafeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.ProtocolAddress_Destroy(this.unsafeHandle); } @@ -37,7 +38,7 @@ public String getName() { /** * Returns a ServiceId if this address contains a valid ServiceId, {@code null} otherwise. * - * In a future release SignalProtocolAddresses will only support ServiceIds. + *

    In a future release SignalProtocolAddresses will only support ServiceIds. */ public ServiceId getServiceId() { try { @@ -60,10 +61,10 @@ public String toString() { @Override public boolean equals(Object other) { - if (other == null) return false; + if (other == null) return false; if (!(other instanceof SignalProtocolAddress)) return false; - SignalProtocolAddress that = (SignalProtocolAddress)other; + SignalProtocolAddress that = (SignalProtocolAddress) other; return this.getName().equals(that.getName()) && this.getDeviceId() == that.getDeviceId(); } diff --git a/java/shared/java/org/signal/libsignal/protocol/UntrustedIdentityException.java b/java/shared/java/org/signal/libsignal/protocol/UntrustedIdentityException.java index eb3fd78fd1..821e70b036 100644 --- a/java/shared/java/org/signal/libsignal/protocol/UntrustedIdentityException.java +++ b/java/shared/java/org/signal/libsignal/protocol/UntrustedIdentityException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol; public class UntrustedIdentityException extends Exception { @@ -12,12 +12,12 @@ public class UntrustedIdentityException extends Exception { public UntrustedIdentityException(String name, IdentityKey key) { this.name = name; - this.key = key; + this.key = key; } public UntrustedIdentityException(String name) { this.name = name; - this.key = null; + this.key = null; } public IdentityKey getUntrustedIdentity() { diff --git a/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java b/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java index 1a285509db..37862bf992 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java +++ b/java/shared/java/org/signal/libsignal/protocol/ecc/Curve.java @@ -1,13 +1,14 @@ -/** - * Copyright (C) 2013-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2013-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.ecc; + import org.signal.libsignal.protocol.InvalidKeyException; public class Curve { - public static final int DJB_TYPE = 0x05; + public static final int DJB_TYPE = 0x05; public static ECKeyPair generateKeyPair() { ECPrivateKey privateKey = ECPrivateKey.generate(); @@ -15,9 +16,7 @@ public static ECKeyPair generateKeyPair() { return new ECKeyPair(publicKey, privateKey); } - public static ECPublicKey decodePoint(byte[] bytes, int offset) - throws InvalidKeyException - { + public static ECPublicKey decodePoint(byte[] bytes, int offset) throws InvalidKeyException { if (bytes == null || bytes.length - offset < 1) { throw new InvalidKeyException("No key type identifier"); } @@ -30,8 +29,7 @@ public static ECPrivateKey decodePrivatePoint(byte[] bytes) throws InvalidKeyExc } public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) - throws InvalidKeyException - { + throws InvalidKeyException { if (publicKey == null) { throw new InvalidKeyException("public value is null"); } @@ -44,8 +42,7 @@ public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey priv } public static boolean verifySignature(ECPublicKey signingKey, byte[] message, byte[] signature) - throws InvalidKeyException - { + throws InvalidKeyException { if (signingKey == null || message == null || signature == null) { throw new InvalidKeyException("Values must not be null"); } @@ -54,13 +51,11 @@ public static boolean verifySignature(ECPublicKey signingKey, byte[] message, by } public static byte[] calculateSignature(ECPrivateKey signingKey, byte[] message) - throws InvalidKeyException - { + throws InvalidKeyException { if (signingKey == null || message == null) { throw new InvalidKeyException("Values must not be null"); } return signingKey.calculateSignature(message); } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/ecc/ECKeyPair.java b/java/shared/java/org/signal/libsignal/protocol/ecc/ECKeyPair.java index 1948fce273..bad1776ecf 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ecc/ECKeyPair.java +++ b/java/shared/java/org/signal/libsignal/protocol/ecc/ECKeyPair.java @@ -1,13 +1,13 @@ -/** - * Copyright (C) 2013-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2013-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.ecc; public class ECKeyPair { - private final ECPublicKey publicKey; + private final ECPublicKey publicKey; private final ECPrivateKey privateKey; public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { diff --git a/java/shared/java/org/signal/libsignal/protocol/ecc/ECPrivateKey.java b/java/shared/java/org/signal/libsignal/protocol/ecc/ECPrivateKey.java index ea6c652bfc..fd8b9bbc9a 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ecc/ECPrivateKey.java +++ b/java/shared/java/org/signal/libsignal/protocol/ecc/ECPrivateKey.java @@ -1,8 +1,7 @@ -/** - * Copyright (C) 2013-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2013-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// package org.signal.libsignal.protocol.ecc; @@ -22,15 +21,16 @@ static ECPrivateKey generate() { } public ECPrivateKey(long nativeHandle) { - if(nativeHandle == 0) { + if (nativeHandle == 0) { throw new NullPointerException(); } this.unsafeHandle = nativeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.ECPrivateKey_Destroy(this.unsafeHandle); + Native.ECPrivateKey_Destroy(this.unsafeHandle); } public byte[] serialize() { @@ -46,10 +46,8 @@ public byte[] calculateSignature(byte[] message) { } public byte[] calculateAgreement(ECPublicKey other) { - try ( - NativeHandleGuard privateKey = new NativeHandleGuard(this); - NativeHandleGuard publicKey = new NativeHandleGuard(other); - ) { + try (NativeHandleGuard privateKey = new NativeHandleGuard(this); + NativeHandleGuard publicKey = new NativeHandleGuard(other); ) { return Native.ECPrivateKey_Agree(privateKey.nativeHandle(), publicKey.nativeHandle()); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java b/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java index 3bd4fe0b09..8c4546b920 100644 --- a/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java +++ b/java/shared/java/org/signal/libsignal/protocol/ecc/ECPublicKey.java @@ -1,15 +1,14 @@ -/** - * Copyright (C) 2013-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2013-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// package org.signal.libsignal.protocol.ecc; +import java.util.Arrays; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.InvalidKeyException; -import java.util.Arrays; public class ECPublicKey implements Comparable, NativeHandleGuard.Owner { @@ -25,7 +24,7 @@ public ECPublicKey(byte[] serialized) throws InvalidKeyException { this.unsafeHandle = Native.ECPublicKey_Deserialize(serialized, 0); } - static public ECPublicKey fromPublicKeyBytes(byte[] key) { + public static ECPublicKey fromPublicKeyBytes(byte[] key) { byte[] with_type = new byte[33]; with_type[0] = 0x05; System.arraycopy(key, 0, with_type, 1, 32); @@ -39,9 +38,10 @@ public ECPublicKey(long nativeHandle) { this.unsafeHandle = nativeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.ECPublicKey_Destroy(this.unsafeHandle); + Native.ECPublicKey_Destroy(this.unsafeHandle); } public boolean verifySignature(byte[] message, byte[] signature) { @@ -73,12 +73,10 @@ public long unsafeNativeHandleWithoutGuard() { @Override public boolean equals(Object other) { - if (other == null) return false; + if (other == null) return false; if (!(other instanceof ECPublicKey)) return false; - try ( - NativeHandleGuard thisGuard = new NativeHandleGuard(this); - NativeHandleGuard thatGuard = new NativeHandleGuard((ECPublicKey)other); - ) { + try (NativeHandleGuard thisGuard = new NativeHandleGuard(this); + NativeHandleGuard thatGuard = new NativeHandleGuard((ECPublicKey) other); ) { return Native.ECPublicKey_Equals(thisGuard.nativeHandle(), thatGuard.nativeHandle()); } } @@ -90,10 +88,8 @@ public int hashCode() { @Override public int compareTo(ECPublicKey another) { - try ( - NativeHandleGuard guard = new NativeHandleGuard(this); - NativeHandleGuard otherGuard = new NativeHandleGuard(another); - ) { + try (NativeHandleGuard guard = new NativeHandleGuard(this); + NativeHandleGuard otherGuard = new NativeHandleGuard(another); ) { return Native.ECPublicKey_Compare(guard.nativeHandle(), otherGuard.nativeHandle()); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/DisplayableFingerprint.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/DisplayableFingerprint.java index 495571e341..3ba5711927 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/DisplayableFingerprint.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/DisplayableFingerprint.java @@ -1,11 +1,9 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ -package org.signal.libsignal.protocol.fingerprint; +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.internal.Native; +package org.signal.libsignal.protocol.fingerprint; public class DisplayableFingerprint { private String displayString; @@ -17,5 +15,4 @@ public class DisplayableFingerprint { public String getDisplayText() { return this.displayString; } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/Fingerprint.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/Fingerprint.java index e387764e66..dc32bc5a26 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/Fingerprint.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/Fingerprint.java @@ -1,20 +1,19 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.fingerprint; public class Fingerprint { private final DisplayableFingerprint displayableFingerprint; - private final ScannableFingerprint scannableFingerprint; + private final ScannableFingerprint scannableFingerprint; - public Fingerprint(DisplayableFingerprint displayableFingerprint, - ScannableFingerprint scannableFingerprint) - { + public Fingerprint( + DisplayableFingerprint displayableFingerprint, ScannableFingerprint scannableFingerprint) { this.displayableFingerprint = displayableFingerprint; - this.scannableFingerprint = scannableFingerprint; + this.scannableFingerprint = scannableFingerprint; } /** diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintGenerator.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintGenerator.java index 2b2df9e59b..3e0fa7a19a 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintGenerator.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintGenerator.java @@ -1,19 +1,17 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.fingerprint; import org.signal.libsignal.protocol.IdentityKey; -import java.util.List; - public interface FingerprintGenerator { - public Fingerprint createFor(int version, - byte[] localStableIdentifier, - IdentityKey localIdentityKey, - byte[] remoteStableIdentifier, - IdentityKey remoteIdentityKey); - + public Fingerprint createFor( + int version, + byte[] localStableIdentifier, + IdentityKey localIdentityKey, + byte[] remoteStableIdentifier, + IdentityKey remoteIdentityKey); } diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintParsingException.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintParsingException.java index e878a2b05d..cf1b3a0909 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintParsingException.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintParsingException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.fingerprint; public class FingerprintParsingException extends Exception { @@ -10,5 +10,4 @@ public class FingerprintParsingException extends Exception { public FingerprintParsingException(String message) { super(message); } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintVersionMismatchException.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintVersionMismatchException.java index 1057737fed..98956a8e5c 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintVersionMismatchException.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/FingerprintVersionMismatchException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.fingerprint; public class FingerprintVersionMismatchException extends Exception { @@ -13,7 +13,7 @@ public class FingerprintVersionMismatchException extends Exception { public FingerprintVersionMismatchException(int theirVersion, int ourVersion) { super(); this.theirVersion = theirVersion; - this.ourVersion = ourVersion; + this.ourVersion = ourVersion; } public int getTheirVersion() { diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/NumericFingerprintGenerator.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/NumericFingerprintGenerator.java index e706e65656..f69ac7769d 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/NumericFingerprintGenerator.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/NumericFingerprintGenerator.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.fingerprint; import org.signal.libsignal.internal.Native; @@ -14,15 +14,14 @@ public class NumericFingerprintGenerator implements FingerprintGenerator { /** * Construct a fingerprint generator for 60 digit numerics. * - * @param iterations The number of internal iterations to perform in the process of - * generating a fingerprint. This needs to be constant, and synchronized - * across all clients. - * - * The higher the iteration count, the higher the security level: - * - * - 1024 ~ 109.7 bits - * - 1400 > 110 bits - * - 5200 > 112 bits + * @param iterations The number of internal iterations to perform in the process of generating a + * fingerprint. This needs to be constant, and synchronized across all clients. + *

    The higher the iteration count, the higher the security level: + *

    */ public NumericFingerprintGenerator(int iterations) { this.iterations = iterations; @@ -39,25 +38,30 @@ public NumericFingerprintGenerator(int iterations) { * @return A unique fingerprint for this conversation. */ @Override - public Fingerprint createFor(int version, - byte[] localStableIdentifier, - final IdentityKey localIdentityKey, - byte[] remoteStableIdentifier, - final IdentityKey remoteIdentityKey) { + public Fingerprint createFor( + int version, + byte[] localStableIdentifier, + final IdentityKey localIdentityKey, + byte[] remoteStableIdentifier, + final IdentityKey remoteIdentityKey) { - long handle = Native.NumericFingerprintGenerator_New(this.iterations, version, - localStableIdentifier, - localIdentityKey.serialize(), - remoteStableIdentifier, - remoteIdentityKey.serialize()); + long handle = + Native.NumericFingerprintGenerator_New( + this.iterations, + version, + localStableIdentifier, + localIdentityKey.serialize(), + remoteStableIdentifier, + remoteIdentityKey.serialize()); - DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(Native.NumericFingerprintGenerator_GetDisplayString(handle)); + DisplayableFingerprint displayableFingerprint = + new DisplayableFingerprint(Native.NumericFingerprintGenerator_GetDisplayString(handle)); - ScannableFingerprint scannableFingerprint = new ScannableFingerprint(Native.NumericFingerprintGenerator_GetScannableEncoding(handle)); + ScannableFingerprint scannableFingerprint = + new ScannableFingerprint(Native.NumericFingerprintGenerator_GetScannableEncoding(handle)); Native.NumericFingerprintGenerator_Destroy(handle); return new Fingerprint(displayableFingerprint, scannableFingerprint); } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/fingerprint/ScannableFingerprint.java b/java/shared/java/org/signal/libsignal/protocol/fingerprint/ScannableFingerprint.java index b44023895c..174173d205 100644 --- a/java/shared/java/org/signal/libsignal/protocol/fingerprint/ScannableFingerprint.java +++ b/java/shared/java/org/signal/libsignal/protocol/fingerprint/ScannableFingerprint.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.fingerprint; import org.signal.libsignal.internal.Native; @@ -29,9 +29,7 @@ public byte[] getSerialized() { * @throws FingerprintVersionMismatchException if the scanned fingerprint is the wrong version. */ public boolean compareTo(byte[] scannedFingerprintData) - throws FingerprintVersionMismatchException, - FingerprintParsingException - { + throws FingerprintVersionMismatchException, FingerprintParsingException { return Native.ScannableFingerprint_Compare(this.encodedFingerprint, scannedFingerprintData); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/groups/GroupCipher.java b/java/shared/java/org/signal/libsignal/protocol/groups/GroupCipher.java index a40a0cabab..0aa8e43c43 100644 --- a/java/shared/java/org/signal/libsignal/protocol/groups/GroupCipher.java +++ b/java/shared/java/org/signal/libsignal/protocol/groups/GroupCipher.java @@ -1,14 +1,14 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.groups; +import java.util.UUID; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.DuplicateMessageException; -import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.LegacyMessageException; import org.signal.libsignal.protocol.NoSessionException; @@ -16,19 +16,16 @@ import org.signal.libsignal.protocol.groups.state.SenderKeyStore; import org.signal.libsignal.protocol.message.CiphertextMessage; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.util.UUID; - /** * The main entry point for Signal Protocol group encrypt/decrypt operations. * - * Once a session has been established with {@link org.signal.libsignal.protocol.groups.GroupSessionBuilder} - * and a {@link org.signal.libsignal.protocol.message.SenderKeyDistributionMessage} has been - * distributed to each member of the group, this class can be used for all subsequent encrypt/decrypt - * operations within that session (ie: until group membership changes). + *

    Once a session has been established with {@link + * org.signal.libsignal.protocol.groups.GroupSessionBuilder} and a {@link + * org.signal.libsignal.protocol.message.SenderKeyDistributionMessage} has been distributed to each + * member of the group, this class can be used for all subsequent encrypt/decrypt operations within + * that session (ie: until group membership changes). * - * This class is not thread-safe. + *

    This class is not thread-safe. * * @author Moxie Marlinspike */ @@ -39,7 +36,7 @@ public class GroupCipher { public GroupCipher(SenderKeyStore senderKeyStore, SignalProtocolAddress sender) { this.senderKeyStore = senderKeyStore; - this.sender = sender; + this.sender = sender; } /** @@ -49,9 +46,11 @@ public GroupCipher(SenderKeyStore senderKeyStore, SignalProtocolAddress sender) * @return Ciphertext. * @throws NoSessionException */ - public CiphertextMessage encrypt(UUID distributionId, byte[] paddedPlaintext) throws NoSessionException { + public CiphertextMessage encrypt(UUID distributionId, byte[] paddedPlaintext) + throws NoSessionException { try (NativeHandleGuard sender = new NativeHandleGuard(this.sender)) { - return Native.GroupCipher_EncryptMessage(sender.nativeHandle(), distributionId, paddedPlaintext, this.senderKeyStore); + return Native.GroupCipher_EncryptMessage( + sender.nativeHandle(), distributionId, paddedPlaintext, this.senderKeyStore); } } @@ -65,10 +64,13 @@ public CiphertextMessage encrypt(UUID distributionId, byte[] paddedPlaintext) th * @throws DuplicateMessageException */ public byte[] decrypt(byte[] senderKeyMessageBytes) - throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException - { + throws LegacyMessageException, + DuplicateMessageException, + InvalidMessageException, + NoSessionException { try (NativeHandleGuard sender = new NativeHandleGuard(this.sender)) { - return Native.GroupCipher_DecryptMessage(sender.nativeHandle(), senderKeyMessageBytes, this.senderKeyStore); + return Native.GroupCipher_DecryptMessage( + sender.nativeHandle(), senderKeyMessageBytes, this.senderKeyStore); } } } diff --git a/java/shared/java/org/signal/libsignal/protocol/groups/GroupSessionBuilder.java b/java/shared/java/org/signal/libsignal/protocol/groups/GroupSessionBuilder.java index 7e14b0c253..b258448684 100644 --- a/java/shared/java/org/signal/libsignal/protocol/groups/GroupSessionBuilder.java +++ b/java/shared/java/org/signal/libsignal/protocol/groups/GroupSessionBuilder.java @@ -1,36 +1,34 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.groups; +import java.util.UUID; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.groups.state.SenderKeyStore; import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; -import java.util.UUID; - /** * GroupSessionBuilder is responsible for setting up group SenderKey encrypted sessions. * - * Once a session has been established, {@link org.signal.libsignal.protocol.groups.GroupCipher} + *

    Once a session has been established, {@link org.signal.libsignal.protocol.groups.GroupCipher} * can be used to encrypt/decrypt messages in that session. - *

    - * The built sessions are unidirectional: they can be used either for sending or for receiving, + * + *

    The built sessions are unidirectional: they can be used either for sending or for receiving, * but not both. * - * Sessions are constructed per (senderName + deviceId) tuple, with sending additionally - * parameterized on a per-group distributionId. Remote logical users are identified by their + *

    Sessions are constructed per (senderName + deviceId) tuple, with sending additionally + * parameterized on a per-group distributionId. Remote logical users are identified by their * senderName, and each logical user can have multiple physical devices. * - * This class is not thread-safe. + *

    This class is not thread-safe. * * @author Moxie Marlinspike */ - public class GroupSessionBuilder { private final SenderKeyStore senderKeyStore; @@ -44,15 +42,12 @@ public GroupSessionBuilder(SenderKeyStore senderKeyStore) { * @param sender The address of the device that sent the message. * @param senderKeyDistributionMessage A received SenderKeyDistributionMessage. */ - public void process(SignalProtocolAddress sender, SenderKeyDistributionMessage senderKeyDistributionMessage) { - try ( - NativeHandleGuard senderGuard = new NativeHandleGuard(sender); - NativeHandleGuard skdmGuard = new NativeHandleGuard(senderKeyDistributionMessage); - ) { + public void process( + SignalProtocolAddress sender, SenderKeyDistributionMessage senderKeyDistributionMessage) { + try (NativeHandleGuard senderGuard = new NativeHandleGuard(sender); + NativeHandleGuard skdmGuard = new NativeHandleGuard(senderKeyDistributionMessage); ) { Native.GroupSessionBuilder_ProcessSenderKeyDistributionMessage( - senderGuard.nativeHandle(), - skdmGuard.nativeHandle(), - senderKeyStore); + senderGuard.nativeHandle(), skdmGuard.nativeHandle(), senderKeyStore); } } @@ -60,12 +55,16 @@ public void process(SignalProtocolAddress sender, SenderKeyDistributionMessage s * Construct a group session for sending messages. * * @param sender The address of the current client. - * @param distributionId An opaque identifier that uniquely identifies the group (but isn't the group ID). - * @return A SenderKeyDistributionMessage that is individually distributed to each member of the group. + * @param distributionId An opaque identifier that uniquely identifies the group (but isn't the + * group ID). + * @return A SenderKeyDistributionMessage that is individually distributed to each member of the + * group. */ public SenderKeyDistributionMessage create(SignalProtocolAddress sender, UUID distributionId) { try (NativeHandleGuard senderGuard = new NativeHandleGuard(sender)) { - return new SenderKeyDistributionMessage(Native.GroupSessionBuilder_CreateSenderKeyDistributionMessage(senderGuard.nativeHandle(), distributionId, senderKeyStore)); + return new SenderKeyDistributionMessage( + Native.GroupSessionBuilder_CreateSenderKeyDistributionMessage( + senderGuard.nativeHandle(), distributionId, senderKeyStore)); } } } diff --git a/java/shared/java/org/signal/libsignal/protocol/groups/InvalidSenderKeySessionException.java b/java/shared/java/org/signal/libsignal/protocol/groups/InvalidSenderKeySessionException.java index fa6bbd96c2..bf805f6570 100644 --- a/java/shared/java/org/signal/libsignal/protocol/groups/InvalidSenderKeySessionException.java +++ b/java/shared/java/org/signal/libsignal/protocol/groups/InvalidSenderKeySessionException.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2021 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2021 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.groups; import java.util.UUID; diff --git a/java/shared/java/org/signal/libsignal/protocol/groups/state/InMemorySenderKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/groups/state/InMemorySenderKeyStore.java index 1456f6d9de..75343e1037 100644 --- a/java/shared/java/org/signal/libsignal/protocol/groups/state/InMemorySenderKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/groups/state/InMemorySenderKeyStore.java @@ -1,21 +1,24 @@ -package org.signal.libsignal.protocol.groups.state; +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.protocol.groups.state.SenderKeyRecord; -import org.signal.libsignal.protocol.groups.state.SenderKeyStore; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.SignalProtocolAddress; -import org.signal.libsignal.protocol.util.Pair; +package org.signal.libsignal.protocol.groups.state; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.protocol.SignalProtocolAddress; +import org.signal.libsignal.protocol.util.Pair; public class InMemorySenderKeyStore implements SenderKeyStore { private final Map, SenderKeyRecord> store = new HashMap<>(); @Override - public void storeSenderKey(SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record) { + public void storeSenderKey( + SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record) { store.put(new Pair<>(sender, distributionId), record); } diff --git a/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyRecord.java b/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyRecord.java index 85cbe14382..2e8bd47b46 100644 --- a/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyRecord.java +++ b/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyRecord.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.groups.state; import org.signal.libsignal.internal.Native; @@ -10,15 +10,16 @@ import org.signal.libsignal.protocol.InvalidMessageException; /** - * A durable representation of a set of SenderKeyStates for a specific - * (senderName, deviceId, distributionId) tuple. + * A durable representation of a set of SenderKeyStates for a specific (senderName, deviceId, + * distributionId) tuple. * * @author Moxie Marlinspike */ public class SenderKeyRecord implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.SenderKeyRecord_Destroy(this.unsafeHandle); } diff --git a/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyStore.java index 466b11c4ca..d4dd0f82a4 100644 --- a/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/groups/state/SenderKeyStore.java @@ -1,13 +1,12 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ -package org.signal.libsignal.protocol.groups.state; +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.protocol.SignalProtocolAddress; +package org.signal.libsignal.protocol.groups.state; import java.util.UUID; +import org.signal.libsignal.protocol.SignalProtocolAddress; public interface SenderKeyStore { @@ -16,25 +15,29 @@ public interface SenderKeyStore { * given (distributionId + senderName + deviceId) tuple. * * @param sender The address of the current client. - * @param distributionId An opaque identifier that uniquely identifies the group (but isn't the group ID). - * @param record the current SenderKeyRecord for the specified (distributionId + senderName + deviceId) tuple. + * @param distributionId An opaque identifier that uniquely identifies the group (but isn't the + * group ID). + * @param record the current SenderKeyRecord for the specified (distributionId + senderName + + * deviceId) tuple. */ - public void storeSenderKey(SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record); + public void storeSenderKey( + SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record); /** * Returns a copy of the {@link org.signal.libsignal.protocol.groups.state.SenderKeyRecord} - * corresponding to the (distributionId + senderName + deviceId) tuple, or `null` if one does not + * corresponding to the (distributionId + senderName + deviceId) tuple, or `null` if one does not * exist. - * - * It is important that implementations return a copy of the current durable information. The + * + *

    It is important that implementations return a copy of the current durable information. The * returned SenderKeyRecord may be modified, but those changes should not have an effect on the - * durable session state (what is returned by subsequent calls to this method) without the - * store method being called here first. + * durable session state (what is returned by subsequent calls to this method) without the store + * method being called here first. * * @param sender The address of the current client. - * @param distributionId An opaque identifier that uniquely identifies the group (but isn't the group ID). - * @return a copy of the SenderKeyRecord corresponding to the (id + senderName + deviceId tuple, or - * `null` if one does not currently exist. + * @param distributionId An opaque identifier that uniquely identifies the group (but isn't the + * group ID). + * @return a copy of the SenderKeyRecord corresponding to the (id + senderName + deviceId tuple, + * or `null` if one does not currently exist. */ public SenderKeyRecord loadSenderKey(SignalProtocolAddress sender, UUID distributionId); } diff --git a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/ChunkSizeChoice.java b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/ChunkSizeChoice.java index f8edd2cf77..d60a68a150 100644 --- a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/ChunkSizeChoice.java +++ b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/ChunkSizeChoice.java @@ -9,37 +9,37 @@ public abstract class ChunkSizeChoice { - public abstract int getSizeInBytes(); + public abstract int getSizeInBytes(); - public static ChunkSizeChoice everyNthByte(int n) { - return new EveryN(n); - } + public static ChunkSizeChoice everyNthByte(int n) { + return new EveryN(n); + } - public static ChunkSizeChoice inferChunkSize(int dataSize) { - return new ChunksOf(dataSize); - } + public static ChunkSizeChoice inferChunkSize(int dataSize) { + return new ChunksOf(dataSize); + } - private static final class EveryN extends ChunkSizeChoice { - private int n; + private static final class EveryN extends ChunkSizeChoice { + private int n; - private EveryN(int n) { - this.n = n; - } + private EveryN(int n) { + this.n = n; + } - public int getSizeInBytes() { - return this.n; - } + public int getSizeInBytes() { + return this.n; } + } - private static final class ChunksOf extends ChunkSizeChoice { - private int dataSize; + private static final class ChunksOf extends ChunkSizeChoice { + private int dataSize; - private ChunksOf(int dataSize) { - this.dataSize = dataSize; - } + private ChunksOf(int dataSize) { + this.dataSize = dataSize; + } - public int getSizeInBytes() { - return Native.IncrementalMac_CalculateChunkSize(this.dataSize); - } + public int getSizeInBytes() { + return Native.IncrementalMac_CalculateChunkSize(this.dataSize); } + } } diff --git a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacInputStream.java b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacInputStream.java index 008c3016af..fd7f20ba42 100644 --- a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacInputStream.java +++ b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacInputStream.java @@ -5,53 +5,102 @@ package org.signal.libsignal.protocol.incrementalmac; -import org.signal.libsignal.internal.Native; - import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import org.signal.libsignal.internal.Native; public final class IncrementalMacInputStream extends InputStream { - private final long validatingMac; + private static final int MAX_BUFFER_SIZE = 8192; + private final long validatingMac; + + private final ReadableByteChannel inner; + private boolean closed = false; + + private ByteBuffer currentChunk; + + public IncrementalMacInputStream( + InputStream inner, byte[] key, ChunkSizeChoice sizeChoice, byte[] digest) { + int chunkSize = sizeChoice.getSizeInBytes(); + this.currentChunk = ByteBuffer.allocateDirect(chunkSize); + this.currentChunk.limit(0); + this.validatingMac = Native.ValidatingMac_Initialize(key, chunkSize, digest); + this.inner = Channels.newChannel(inner); + } + + @Override + public int read() throws IOException { + byte[] bytes = new byte[1]; + int read = this.readInternal(bytes, 0, 1); + return (read < 0) ? -1 : (int) bytes[0]; + } - private final InputStream inner; - private boolean closed = false; + @Override + public int read(byte[] bytes, int offset, int length) throws IOException { + return this.readInternal(bytes, offset, length); + } - public IncrementalMacInputStream(InputStream inner, byte[] key, ChunkSizeChoice sizeChoice, byte[] digest) { - int chunkSize = sizeChoice.getSizeInBytes(); - this.validatingMac = Native.ValidatingMac_Initialize(key, chunkSize, digest); - this.inner = inner; + @Override + public void close() throws IOException { + if (this.closed) { + return; } + this.inner.close(); + Native.ValidatingMac_Destroy(this.validatingMac); + this.closed = true; + } - @Override - public int read() throws IOException { - int read = this.inner.read(); - // Narrowing conversion to byte is expected and intentional - byte[] bytes = {(byte) read}; - int bytesLength = (read == -1) ? -1 : 1; - return handleRead(bytes, 0, bytesLength); + private int readInternal(byte[] bytes, int offset, int requestedLen) throws IOException { + if (!this.currentChunk.hasRemaining()) { + this.currentChunk.clear(); + int bytesRead = this.inner.read(this.currentChunk); + this.currentChunk.flip(); + if (bytesRead <= 0) { + return -1; + } + this.validateChunk(this.currentChunk.slice(), this.currentChunk.capacity()); } + int bytesToRead = Math.min(this.currentChunk.remaining(), requestedLen); + this.currentChunk.get(bytes, offset, bytesToRead); + return bytesToRead; + } - @Override - public int read(byte[] bytes, int offset, int length) throws IOException { - int read = this.inner.read(bytes, offset, length); - return handleRead(bytes, offset, read); + private void validateChunk(ByteBuffer chunk, int expectedChunkSize) throws IOException { + // Should only be called right after the chunk (full or incomplete) is read. + // chunk is a slice of this.currentChunk therefore limit() and capacity() + // can be used interchangeably. + assert chunk.limit() == chunk.capacity() : "Must be invoked with ByteBuffer.slice()"; + boolean isFullChunkAvailable = chunk.limit() == expectedChunkSize; + assertValidBytes(validateChunkImpl(chunk)); + if (!isFullChunkAvailable) { + assertValidBytes(Native.ValidatingMac_Finalize(this.validatingMac)); } + } - private int handleRead(byte[] bytes, int offset, int read) throws IOException { - boolean isValid = (read == -1) ? Native.ValidatingMac_Finalize(this.validatingMac) : Native.ValidatingMac_Update(this.validatingMac, bytes, offset, read); - if (!isValid) { - throw new InvalidMacException(); - } - return read; + private static void assertValidBytes(int validBytesCount) throws InvalidMacException { + if (validBytesCount < 0) { + throw new InvalidMacException(); } + } - @Override - public void close() throws IOException { - if (this.closed) { - return; - } - this.inner.close(); - Native.ValidatingMac_Destroy(this.validatingMac); - this.closed = true; + private int validateChunkImpl(ByteBuffer chunk) { + int validBytes = 0; + int bufferSize = Math.min(chunk.limit(), MAX_BUFFER_SIZE); + // Using a smaller buffer and a loop because ByteBuffer.get requires a + // managed byte[] but we want to avoid allocating whole chunks in managed + // heap + byte[] buffer = new byte[bufferSize]; + while (chunk.hasRemaining()) { + int currentlyValidating = Math.min(bufferSize, chunk.remaining()); + chunk.get(buffer, 0, currentlyValidating); + // Because we are reading one chunk at a time only the last update will return a non-zero + // value + validBytes = Native.ValidatingMac_Update(this.validatingMac, buffer, 0, currentlyValidating); + assert validBytes == 0 || validBytes == -1 || validBytes == chunk.limit() + : "Unexpected incremental mac update result"; } + return validBytes; + } } diff --git a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacOutputStream.java b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacOutputStream.java index 34f416aa24..bff3faa70f 100644 --- a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacOutputStream.java +++ b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/IncrementalMacOutputStream.java @@ -5,67 +5,70 @@ package org.signal.libsignal.protocol.incrementalmac; -import org.signal.libsignal.internal.Native; - import java.io.IOException; import java.io.OutputStream; +import org.signal.libsignal.internal.Native; public final class IncrementalMacOutputStream extends OutputStream { - private final long incrementalMac; - private final OutputStream digestStream; - private final OutputStream inner; - private boolean closed = false; + private final long incrementalMac; + private final OutputStream digestStream; + private final OutputStream inner; + private boolean closed = false; - public IncrementalMacOutputStream(OutputStream inner, byte[] key, ChunkSizeChoice sizeChoice, OutputStream digestStream) { - int chunkSize = sizeChoice.getSizeInBytes(); - this.incrementalMac = Native.IncrementalMac_Initialize(key, chunkSize); - this.inner = inner; - this.digestStream = digestStream; - } + public IncrementalMacOutputStream( + OutputStream inner, byte[] key, ChunkSizeChoice sizeChoice, OutputStream digestStream) { + int chunkSize = sizeChoice.getSizeInBytes(); + this.incrementalMac = Native.IncrementalMac_Initialize(key, chunkSize); + this.inner = inner; + this.digestStream = digestStream; + } - @Override - public void write(byte[] buffer) throws IOException { - this.inner.write(buffer); - byte[] digestIncrement = Native.IncrementalMac_Update(this.incrementalMac, buffer, 0, buffer.length); - digestStream.write(digestIncrement); - } + @Override + public void write(byte[] buffer) throws IOException { + this.inner.write(buffer); + byte[] digestIncrement = + Native.IncrementalMac_Update(this.incrementalMac, buffer, 0, buffer.length); + digestStream.write(digestIncrement); + } - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - this.inner.write(buffer, offset, length); - byte[] digestIncrement = Native.IncrementalMac_Update(this.incrementalMac, buffer, offset, length); - digestStream.write(digestIncrement); - } + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + this.inner.write(buffer, offset, length); + byte[] digestIncrement = + Native.IncrementalMac_Update(this.incrementalMac, buffer, offset, length); + digestStream.write(digestIncrement); + } - @Override - public void write(int b) throws IOException { - // According to the spec the narrowing conversion to byte is expected here - byte[] bytes = {(byte) b}; - byte[] digestIncrement = Native.IncrementalMac_Update(this.incrementalMac, bytes, 0, 1); - this.inner.write(b); - this.digestStream.write(digestIncrement); - } + @Override + public void write(int b) throws IOException { + // According to the spec the narrowing conversion to byte is expected here + byte[] bytes = {(byte) b}; + byte[] digestIncrement = Native.IncrementalMac_Update(this.incrementalMac, bytes, 0, 1); + this.inner.write(b); + this.digestStream.write(digestIncrement); + } - @Override - public void flush() throws IOException { - this.inner.flush(); - digestStream.flush(); - } + @Override + public void flush() throws IOException { + this.inner.flush(); + digestStream.flush(); + } - @Override - public void close() throws IOException { - if (this.closed) { - return; - } - try { - flush(); - } catch (IOException ignored) { - } - byte[] digestIncrement = Native.IncrementalMac_Finalize(this.incrementalMac); - digestStream.write(digestIncrement); - Native.IncrementalMac_Destroy(this.incrementalMac); - this.inner.close(); - this.digestStream.close(); - this.closed = true; + @Override + public void close() throws IOException { + if (this.closed) { + return; + } + try { + flush(); + } catch (IOException ignored) { } + byte[] digestIncrement = Native.IncrementalMac_Finalize(this.incrementalMac); + digestStream.write(digestIncrement); + Native.IncrementalMac_Destroy(this.incrementalMac); + // Intentionally not closing the inner stream, as it seems to be causing + // problems on Android + this.digestStream.close(); + this.closed = true; + } } diff --git a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/InvalidMacException.java b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/InvalidMacException.java index 15547632e3..f34105a47c 100644 --- a/java/shared/java/org/signal/libsignal/protocol/incrementalmac/InvalidMacException.java +++ b/java/shared/java/org/signal/libsignal/protocol/incrementalmac/InvalidMacException.java @@ -2,9 +2,9 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.protocol.incrementalmac; import java.io.IOException; -public class InvalidMacException extends IOException { -} +public class InvalidMacException extends IOException {} diff --git a/java/shared/java/org/signal/libsignal/protocol/kdf/HKDF.java b/java/shared/java/org/signal/libsignal/protocol/kdf/HKDF.java index e074b61f1a..70a3447d31 100644 --- a/java/shared/java/org/signal/libsignal/protocol/kdf/HKDF.java +++ b/java/shared/java/org/signal/libsignal/protocol/kdf/HKDF.java @@ -1,8 +1,7 @@ -/** - * Copyright (C) 2013-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2013-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// package org.signal.libsignal.protocol.kdf; @@ -13,7 +12,8 @@ public static byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] info, int out return Native.HKDF_DeriveSecrets(outputLength, inputKeyMaterial, info, null); } - public static byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info, int outputLength) { + public static byte[] deriveSecrets( + byte[] inputKeyMaterial, byte[] salt, byte[] info, int outputLength) { return Native.HKDF_DeriveSecrets(outputLength, inputKeyMaterial, info, salt); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/kdf/HKDFv3.java b/java/shared/java/org/signal/libsignal/protocol/kdf/HKDFv3.java index 4ecb4e7901..ac9aca10d7 100644 --- a/java/shared/java/org/signal/libsignal/protocol/kdf/HKDFv3.java +++ b/java/shared/java/org/signal/libsignal/protocol/kdf/HKDFv3.java @@ -1,13 +1,12 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.kdf; /** * @deprecated Use the static methods of {@link HKDF} instead. */ @Deprecated -public class HKDFv3 extends HKDF { -} +public class HKDFv3 extends HKDF {} diff --git a/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyPair.java b/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyPair.java index 589708602d..d8f251d9d0 100644 --- a/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyPair.java +++ b/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyPair.java @@ -2,6 +2,7 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.protocol.kem; import org.signal.libsignal.internal.Native; @@ -22,16 +23,16 @@ public KEMKeyPair(long nativeHandle) { this.unsafeHandle = nativeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.KyberKeyPair_Destroy(this.unsafeHandle); + Native.KyberKeyPair_Destroy(this.unsafeHandle); } public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - public KEMPublicKey getPublicKey() { return new KEMPublicKey(Native.KyberKeyPair_GetPublicKey(this.unsafeHandle)); } diff --git a/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyType.java b/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyType.java index e2683473f8..b50f7e011a 100644 --- a/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyType.java +++ b/java/shared/java/org/signal/libsignal/protocol/kem/KEMKeyType.java @@ -2,6 +2,7 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.protocol.kem; public enum KEMKeyType { diff --git a/java/shared/java/org/signal/libsignal/protocol/kem/KEMPublicKey.java b/java/shared/java/org/signal/libsignal/protocol/kem/KEMPublicKey.java index 1502a3d782..b13be8a2e7 100644 --- a/java/shared/java/org/signal/libsignal/protocol/kem/KEMPublicKey.java +++ b/java/shared/java/org/signal/libsignal/protocol/kem/KEMPublicKey.java @@ -5,10 +5,10 @@ package org.signal.libsignal.protocol.kem; +import java.util.Arrays; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.InvalidKeyException; -import java.util.Arrays; public class KEMPublicKey implements NativeHandleGuard.Owner { @@ -29,9 +29,10 @@ public KEMPublicKey(long nativeHandle) { this.unsafeHandle = nativeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.KyberPublicKey_Destroy(this.unsafeHandle); + Native.KyberPublicKey_Destroy(this.unsafeHandle); } public byte[] serialize() { @@ -46,12 +47,10 @@ public long unsafeNativeHandleWithoutGuard() { @Override public boolean equals(Object other) { - if (other == null) return false; + if (other == null) return false; if (!(other instanceof KEMPublicKey)) return false; - try ( - NativeHandleGuard thisGuard = new NativeHandleGuard(this); - NativeHandleGuard thatGuard = new NativeHandleGuard((KEMPublicKey)other); - ) { + try (NativeHandleGuard thisGuard = new NativeHandleGuard(this); + NativeHandleGuard thatGuard = new NativeHandleGuard((KEMPublicKey) other); ) { return Native.KyberPublicKey_Equals(thisGuard.nativeHandle(), thatGuard.nativeHandle()); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/kem/KEMSecretKey.java b/java/shared/java/org/signal/libsignal/protocol/kem/KEMSecretKey.java index a2402a2942..a7f99a304c 100644 --- a/java/shared/java/org/signal/libsignal/protocol/kem/KEMSecretKey.java +++ b/java/shared/java/org/signal/libsignal/protocol/kem/KEMSecretKey.java @@ -17,15 +17,16 @@ public KEMSecretKey(byte[] privateKey) throws InvalidKeyException { } public KEMSecretKey(long nativeHandle) { - if(nativeHandle == 0) { + if (nativeHandle == 0) { throw new NullPointerException(); } this.unsafeHandle = nativeHandle; } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.KyberSecretKey_Destroy(this.unsafeHandle); + Native.KyberSecretKey_Destroy(this.unsafeHandle); } public byte[] serialize() { diff --git a/java/shared/java/org/signal/libsignal/protocol/logging/Log.java b/java/shared/java/org/signal/libsignal/protocol/logging/Log.java index 13d98ddc2a..d8599e3130 100644 --- a/java/shared/java/org/signal/libsignal/protocol/logging/Log.java +++ b/java/shared/java/org/signal/libsignal/protocol/logging/Log.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.logging; import java.io.PrintWriter; @@ -86,6 +86,4 @@ private static void log(int priority, String tag, String msg) { logger.log(priority, tag, msg); } } - - } diff --git a/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLogger.java b/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLogger.java index c7371d0c43..3252fe652a 100644 --- a/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLogger.java +++ b/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLogger.java @@ -1,18 +1,18 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.logging; public interface SignalProtocolLogger { public static final int VERBOSE = 2; - public static final int DEBUG = 3; - public static final int INFO = 4; - public static final int WARN = 5; - public static final int ERROR = 6; - public static final int ASSERT = 7; + public static final int DEBUG = 3; + public static final int INFO = 4; + public static final int WARN = 5; + public static final int ERROR = 6; + public static final int ASSERT = 7; public void log(int priority, String tag, String message); } diff --git a/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLoggerProvider.java b/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLoggerProvider.java index 535ceaf845..49541a31c4 100644 --- a/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLoggerProvider.java +++ b/java/shared/java/org/signal/libsignal/protocol/logging/SignalProtocolLoggerProvider.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.logging; public class SignalProtocolLoggerProvider { diff --git a/java/shared/java/org/signal/libsignal/protocol/message/CiphertextMessage.java b/java/shared/java/org/signal/libsignal/protocol/message/CiphertextMessage.java index 0896b82ab5..ff7e69e0f1 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/CiphertextMessage.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/CiphertextMessage.java @@ -1,23 +1,26 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; public interface CiphertextMessage { - public static final int CURRENT_VERSION = 3; + public static final int CURRENT_VERSION = 3; - public static final int WHISPER_TYPE = 2; - public static final int PREKEY_TYPE = 3; - public static final int SENDERKEY_TYPE = 7; - public static final int PLAINTEXT_CONTENT_TYPE = 8; + public static final int WHISPER_TYPE = 2; + public static final int PREKEY_TYPE = 3; + public static final int SENDERKEY_TYPE = 7; + public static final int PLAINTEXT_CONTENT_TYPE = 8; - // This should be the worst case (worse than V2). So not always accurate, but good enough for padding. + // This should be the worst case (worse than V2). So not always accurate, but good enough for + // padding. public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53; public byte[] serialize(); + public int getType(); + public long unsafeNativeHandleWithoutGuard(); } diff --git a/java/shared/java/org/signal/libsignal/protocol/message/DecryptionErrorMessage.java b/java/shared/java/org/signal/libsignal/protocol/message/DecryptionErrorMessage.java index 7d8de2ed03..9032643cfe 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/DecryptionErrorMessage.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/DecryptionErrorMessage.java @@ -1,25 +1,24 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; +import java.util.Optional; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.ecc.ECPublicKey; -import java.util.Optional; - public final class DecryptionErrorMessage implements NativeHandleGuard.Owner { final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.DecryptionErrorMessage_Destroy(this.unsafeHandle); + Native.DecryptionErrorMessage_Destroy(this.unsafeHandle); } public long unsafeNativeHandleWithoutGuard() { @@ -34,9 +33,11 @@ public DecryptionErrorMessage(byte[] serialized) throws InvalidMessageException this.unsafeHandle = Native.DecryptionErrorMessage_Deserialize(serialized); } - public static DecryptionErrorMessage forOriginalMessage(byte[] originalBytes, int messageType, long timestamp, int originalSenderDeviceId) { + public static DecryptionErrorMessage forOriginalMessage( + byte[] originalBytes, int messageType, long timestamp, int originalSenderDeviceId) { return new DecryptionErrorMessage( - Native.DecryptionErrorMessage_ForOriginalMessage(originalBytes, messageType, timestamp, originalSenderDeviceId)); + Native.DecryptionErrorMessage_ForOriginalMessage( + originalBytes, messageType, timestamp, originalSenderDeviceId)); } public byte[] serialize() { @@ -69,8 +70,9 @@ public int getDeviceId() { } /// For testing only - public static DecryptionErrorMessage extractFromSerializedContent(byte[] serializedContentBytes) throws InvalidMessageException { + public static DecryptionErrorMessage extractFromSerializedContent(byte[] serializedContentBytes) + throws InvalidMessageException { return new DecryptionErrorMessage( - Native.DecryptionErrorMessage_ExtractFromSerializedContent(serializedContentBytes)); + Native.DecryptionErrorMessage_ExtractFromSerializedContent(serializedContentBytes)); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/message/PlaintextContent.java b/java/shared/java/org/signal/libsignal/protocol/message/PlaintextContent.java index 3bc922304a..f4b84f59ee 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/PlaintextContent.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/PlaintextContent.java @@ -1,13 +1,12 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidVersionException; @@ -15,9 +14,10 @@ public final class PlaintextContent implements CiphertextMessage, NativeHandleGu private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.PlaintextContent_Destroy(this.unsafeHandle); + Native.PlaintextContent_Destroy(this.unsafeHandle); } public long unsafeNativeHandleWithoutGuard() { @@ -32,11 +32,13 @@ private PlaintextContent(long unsafeHandle) { public PlaintextContent(DecryptionErrorMessage message) { try (NativeHandleGuard messageGuard = new NativeHandleGuard(message)) { - this.unsafeHandle = Native.PlaintextContent_FromDecryptionErrorMessage(messageGuard.nativeHandle()); + this.unsafeHandle = + Native.PlaintextContent_FromDecryptionErrorMessage(messageGuard.nativeHandle()); } } - public PlaintextContent(byte[] serialized) throws InvalidMessageException, InvalidVersionException { + public PlaintextContent(byte[] serialized) + throws InvalidMessageException, InvalidVersionException { unsafeHandle = Native.PlaintextContent_Deserialize(serialized); } diff --git a/java/shared/java/org/signal/libsignal/protocol/message/PreKeySignalMessage.java b/java/shared/java/org/signal/libsignal/protocol/message/PreKeySignalMessage.java index 329d264ef5..0747c8d2a4 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/PreKeySignalMessage.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/PreKeySignalMessage.java @@ -1,10 +1,11 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; +import java.util.Optional; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.IdentityKey; @@ -14,20 +15,21 @@ import org.signal.libsignal.protocol.LegacyMessageException; import org.signal.libsignal.protocol.ecc.ECPublicKey; -import java.util.Optional; - public class PreKeySignalMessage implements CiphertextMessage, NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.PreKeySignalMessage_Destroy(this.unsafeHandle); + Native.PreKeySignalMessage_Destroy(this.unsafeHandle); } public PreKeySignalMessage(byte[] serialized) - throws InvalidMessageException, InvalidVersionException, LegacyMessageException, InvalidKeyException - { + throws InvalidMessageException, + InvalidVersionException, + LegacyMessageException, + InvalidKeyException { this.unsafeHandle = Native.PreKeySignalMessage_Deserialize(serialized); } diff --git a/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyDistributionMessage.java b/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyDistributionMessage.java index 5f7554017a..4386000111 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyDistributionMessage.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyDistributionMessage.java @@ -1,35 +1,38 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; +import java.util.UUID; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidVersionException; import org.signal.libsignal.protocol.LegacyMessageException; import org.signal.libsignal.protocol.ecc.ECPublicKey; -import java.util.UUID; - public class SenderKeyDistributionMessage implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.SenderKeyDistributionMessage_Destroy(this.unsafeHandle); + Native.SenderKeyDistributionMessage_Destroy(this.unsafeHandle); } public SenderKeyDistributionMessage(long unsafeHandle) { this.unsafeHandle = unsafeHandle; } - public SenderKeyDistributionMessage(byte[] serialized) throws InvalidMessageException, InvalidVersionException, LegacyMessageException, InvalidKeyException { + public SenderKeyDistributionMessage(byte[] serialized) + throws InvalidMessageException, + InvalidVersionException, + LegacyMessageException, + InvalidKeyException { unsafeHandle = Native.SenderKeyDistributionMessage_Deserialize(serialized); } @@ -59,7 +62,8 @@ public byte[] getChainKey() { public ECPublicKey getSignatureKey() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - return new ECPublicKey(Native.SenderKeyDistributionMessage_GetSignatureKey(guard.nativeHandle())); + return new ECPublicKey( + Native.SenderKeyDistributionMessage_GetSignatureKey(guard.nativeHandle())); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyMessage.java b/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyMessage.java index c02821c09c..a0effcbdcc 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyMessage.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/SenderKeyMessage.java @@ -1,30 +1,26 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; +import java.util.UUID; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; - -import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidVersionException; import org.signal.libsignal.protocol.LegacyMessageException; -import org.signal.libsignal.protocol.ecc.ECPrivateKey; import org.signal.libsignal.protocol.ecc.ECPublicKey; -import java.text.ParseException; -import java.util.UUID; - public class SenderKeyMessage implements CiphertextMessage, NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.SenderKeyMessage_Destroy(this.unsafeHandle); + Native.SenderKeyMessage_Destroy(this.unsafeHandle); } public SenderKeyMessage(long unsafeHandle) { @@ -35,7 +31,8 @@ public long unsafeNativeHandleWithoutGuard() { return unsafeHandle; } - public SenderKeyMessage(byte[] serialized) throws InvalidMessageException, InvalidVersionException, LegacyMessageException { + public SenderKeyMessage(byte[] serialized) + throws InvalidMessageException, InvalidVersionException, LegacyMessageException { unsafeHandle = Native.SenderKeyMessage_Deserialize(serialized); } @@ -63,13 +60,9 @@ public byte[] getCipherText() { } } - public void verifySignature(ECPublicKey signatureKey) - throws InvalidMessageException - { - try ( - NativeHandleGuard guard = new NativeHandleGuard(this); - NativeHandleGuard keyGuard = new NativeHandleGuard(signatureKey); - ) { + public void verifySignature(ECPublicKey signatureKey) throws InvalidMessageException { + try (NativeHandleGuard guard = new NativeHandleGuard(this); + NativeHandleGuard keyGuard = new NativeHandleGuard(signatureKey); ) { if (!Native.SenderKeyMessage_VerifySignature(guard.nativeHandle(), keyGuard.nativeHandle())) { throw new InvalidMessageException("Invalid signature!"); } diff --git a/java/shared/java/org/signal/libsignal/protocol/message/SignalMessage.java b/java/shared/java/org/signal/libsignal/protocol/message/SignalMessage.java index 2b672aff41..76d0bd4478 100644 --- a/java/shared/java/org/signal/libsignal/protocol/message/SignalMessage.java +++ b/java/shared/java/org/signal/libsignal/protocol/message/SignalMessage.java @@ -1,10 +1,11 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.message; +import javax.crypto.spec.SecretKeySpec; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.IdentityKey; @@ -15,17 +16,20 @@ import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.util.ByteUtil; -import javax.crypto.spec.SecretKeySpec; - public class SignalMessage implements CiphertextMessage, NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { - Native.SignalMessage_Destroy(this.unsafeHandle); + Native.SignalMessage_Destroy(this.unsafeHandle); } - public SignalMessage(byte[] serialized) throws InvalidMessageException, InvalidVersionException, InvalidKeyException, LegacyMessageException { + public SignalMessage(byte[] serialized) + throws InvalidMessageException, + InvalidVersionException, + InvalidKeyException, + LegacyMessageException { unsafeHandle = Native.SignalMessage_Deserialize(serialized); } @@ -33,7 +37,7 @@ public SignalMessage(long unsafeHandle) { this.unsafeHandle = unsafeHandle; } - public ECPublicKey getSenderRatchetKey() { + public ECPublicKey getSenderRatchetKey() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { return new ECPublicKey(Native.SignalMessage_GetSenderRatchetKey(guard.nativeHandle())); } @@ -57,14 +61,14 @@ public byte[] getBody() { } } - public void verifyMac(IdentityKey senderIdentityKey, IdentityKey receiverIdentityKey, SecretKeySpec macKey) - throws InvalidMessageException, InvalidKeyException - { - try ( - NativeHandleGuard guard = new NativeHandleGuard(this); - NativeHandleGuard senderIdentityGuard = new NativeHandleGuard(senderIdentityKey.getPublicKey()); - NativeHandleGuard receiverIdentityGuard = new NativeHandleGuard(receiverIdentityKey.getPublicKey()); - ) { + public void verifyMac( + IdentityKey senderIdentityKey, IdentityKey receiverIdentityKey, SecretKeySpec macKey) + throws InvalidMessageException, InvalidKeyException { + try (NativeHandleGuard guard = new NativeHandleGuard(this); + NativeHandleGuard senderIdentityGuard = + new NativeHandleGuard(senderIdentityKey.getPublicKey()); + NativeHandleGuard receiverIdentityGuard = + new NativeHandleGuard(receiverIdentityKey.getPublicKey()); ) { if (!Native.SignalMessage_VerifyMac( guard.nativeHandle(), senderIdentityGuard.nativeHandle(), @@ -92,8 +96,8 @@ public long unsafeNativeHandleWithoutGuard() { } public static boolean isLegacy(byte[] message) { - return message != null && message.length >= 1 && - ByteUtil.highBitsToInt(message[0]) != CiphertextMessage.CURRENT_VERSION; + return message != null + && message.length >= 1 + && ByteUtil.highBitsToInt(message[0]) != CiphertextMessage.CURRENT_VERSION; } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/IdentityKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/IdentityKeyStore.java index f1c8871b3f..af3e8c916f 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/IdentityKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/IdentityKeyStore.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; import org.signal.libsignal.protocol.IdentityKey; @@ -17,7 +17,8 @@ public interface IdentityKeyStore { public enum Direction { - SENDING, RECEIVING + SENDING, + RECEIVING } /** @@ -29,47 +30,45 @@ public enum Direction { /** * Return the local client's registration ID. - *

    - * Clients should maintain a registration ID, a random number - * between 1 and 16380 that's generated once at install time. + * + *

    Clients should maintain a registration ID, a random number between 1 and 16380 that's + * generated once at install time. * * @return the local client's registration ID. */ - public int getLocalRegistrationId(); + public int getLocalRegistrationId(); /** * Save a remote client's identity key - *

    - * Store a remote client's identity key as trusted. * - * @param address The address of the remote client. + *

    Store a remote client's identity key as trusted. + * + * @param address The address of the remote client. * @param identityKey The remote client's identity key. * @return True if the identity key replaces a previous identity, false if not */ - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey); - + public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey); /** * Verify a remote client's identity key. - *

    - * Determine whether a remote client's identity is trusted. Convention is - * that the Signal Protocol is 'trust on first use.' This means that - * an identity key is considered 'trusted' if there is no entry for the recipient - * in the local store, or if it matches the saved key for a recipient in the local - * store. Only if it mismatches an entry in the local store is it considered - * 'untrusted.' * - * Clients may wish to make a distinction as to how keys are trusted based on the - * direction of travel. For instance, clients may wish to accept all 'incoming' identity - * key changes, while only blocking identity key changes when sending a message. + *

    Determine whether a remote client's identity is trusted. Convention is that the Signal + * Protocol is 'trust on first use.' This means that an identity key is considered 'trusted' if + * there is no entry for the recipient in the local store, or if it matches the saved key for a + * recipient in the local store. Only if it mismatches an entry in the local store is it + * considered 'untrusted.' + * + *

    Clients may wish to make a distinction as to how keys are trusted based on the direction of + * travel. For instance, clients may wish to accept all 'incoming' identity key changes, while + * only blocking identity key changes when sending a message. * - * @param address The address of the remote client. + * @param address The address of the remote client. * @param identityKey The identity key to verify. - * @param direction The direction (sending or receiving) this identity is being used for. + * @param direction The direction (sending or receiving) this identity is being used for. * @return true if trusted, false if untrusted. */ - public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction); - + public boolean isTrustedIdentity( + SignalProtocolAddress address, IdentityKey identityKey, Direction direction); /** * Return the saved public identity key for a remote client @@ -78,5 +77,4 @@ public enum Direction { * @return The public identity key, or null if absent */ public IdentityKey getIdentity(SignalProtocolAddress address); - } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyRecord.java b/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyRecord.java index 15fe893514..3f09d9b094 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyRecord.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyRecord.java @@ -2,6 +2,7 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.protocol.state; import org.signal.libsignal.internal.Native; @@ -12,20 +13,16 @@ public class KyberPreKeyRecord implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.KyberPreKeyRecord_Destroy(this.unsafeHandle); } public KyberPreKeyRecord(int id, long timestamp, KEMKeyPair keyPair, byte[] signature) { - try ( - NativeHandleGuard guard = new NativeHandleGuard(keyPair); - ) { - this.unsafeHandle = Native.KyberPreKeyRecord_New( - id, - timestamp, - guard.nativeHandle(), - signature); + try (NativeHandleGuard guard = new NativeHandleGuard(keyPair)) { + this.unsafeHandle = + Native.KyberPreKeyRecord_New(id, timestamp, guard.nativeHandle(), signature); } } @@ -67,5 +64,4 @@ public byte[] serialize() { public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyStore.java index ba0af84644..09ff66120f 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/KyberPreKeyStore.java @@ -2,15 +2,14 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // -package org.signal.libsignal.protocol.state; -import org.signal.libsignal.protocol.InvalidKeyIdException; +package org.signal.libsignal.protocol.state; import java.util.List; +import org.signal.libsignal.protocol.InvalidKeyIdException; public interface KyberPreKeyStore { - /** * Load a local KyberPreKeyRecord. * @@ -33,19 +32,20 @@ public interface KyberPreKeyStore { * @param kyberPreKeyId the ID of the KyberPreKeyRecord to store. * @param record the KyberPreKeyRecord. */ - public void storeKyberPreKey(int kyberPreKeyId, KyberPreKeyRecord record); + public void storeKyberPreKey(int kyberPreKeyId, KyberPreKeyRecord record); /** * @param kyberPreKeyId A KyberPreKeyRecord ID. * @return true if the store has a record for the kyberPreKeyId, otherwise false. */ - public boolean containsKyberPreKey(int kyberPreKeyId); + public boolean containsKyberPreKey(int kyberPreKeyId); /** * Mark a KyberPreKeyRecord in the local storage as used. - * Remove if it is a one-time pre key and noop if it is last-resort. + * + *

    Remove if it is a one-time pre key and noop if it is last-resort. * * @param kyberPreKeyId The ID of the KyberPreKeyRecord to marked. */ - public void markKyberPreKeyUsed(int kyberPreKeyId); + public void markKyberPreKeyUsed(int kyberPreKeyId); } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/PreKeyBundle.java b/java/shared/java/org/signal/libsignal/protocol/state/PreKeyBundle.java index 7818f6255f..d8e8e9b2f6 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/PreKeyBundle.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/PreKeyBundle.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; import org.signal.libsignal.internal.Native; @@ -12,8 +12,7 @@ import org.signal.libsignal.protocol.kem.KEMPublicKey; /** - * A class that contains a remote PreKey and collection - * of associated items. + * A class that contains a remote PreKey and collection of associated items. * * @author Moxie Marlinspike */ @@ -23,52 +22,65 @@ public class PreKeyBundle implements NativeHandleGuard.Owner { // -1 is treated as Option::None by the bridging layer public static final int NULL_PRE_KEY_ID = -1; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.PreKeyBundle_Destroy(this.unsafeHandle); } - public PreKeyBundle(int registrationId, int deviceId, int preKeyId, ECPublicKey preKeyPublic, - int signedPreKeyId, ECPublicKey signedPreKeyPublic, byte[] signedPreKeySignature, - IdentityKey identityKey) { + public PreKeyBundle( + int registrationId, + int deviceId, + int preKeyId, + ECPublicKey preKeyPublic, + int signedPreKeyId, + ECPublicKey signedPreKeyPublic, + byte[] signedPreKeySignature, + IdentityKey identityKey) { this( - registrationId, - deviceId, - preKeyId, - preKeyPublic, - signedPreKeyId, - signedPreKeyPublic, - signedPreKeySignature, - identityKey, - NULL_PRE_KEY_ID, - null, - null); - } - - public PreKeyBundle(int registrationId, int deviceId, int preKeyId, ECPublicKey preKeyPublic, - int signedPreKeyId, ECPublicKey signedPreKeyPublic, byte[] signedPreKeySignature, - IdentityKey identityKey, int kyberPreKeyId, KEMPublicKey kyberPreKeyPublic, - byte[] kyberPreKeySignature) - { - try ( - NativeHandleGuard preKeyPublicGuard = new NativeHandleGuard(preKeyPublic); - NativeHandleGuard signedPreKeyPublicGuard = new NativeHandleGuard(signedPreKeyPublic); - NativeHandleGuard identityKeyGuard = new NativeHandleGuard(identityKey.getPublicKey()); - NativeHandleGuard kyberPreKeyPublicGuard = new NativeHandleGuard(kyberPreKeyPublic); - ) { - byte[] kyberSignature = kyberPreKeySignature == null ? new byte[]{} : kyberPreKeySignature; - this.unsafeHandle = Native.PreKeyBundle_New( registrationId, deviceId, preKeyId, - preKeyPublicGuard.nativeHandle(), + preKeyPublic, signedPreKeyId, - signedPreKeyPublicGuard.nativeHandle(), + signedPreKeyPublic, signedPreKeySignature, - identityKeyGuard.nativeHandle(), - kyberPreKeyId, - kyberPreKeyPublicGuard.nativeHandle(), - kyberSignature); + identityKey, + NULL_PRE_KEY_ID, + null, + null); + } + + public PreKeyBundle( + int registrationId, + int deviceId, + int preKeyId, + ECPublicKey preKeyPublic, + int signedPreKeyId, + ECPublicKey signedPreKeyPublic, + byte[] signedPreKeySignature, + IdentityKey identityKey, + int kyberPreKeyId, + KEMPublicKey kyberPreKeyPublic, + byte[] kyberPreKeySignature) { + try (NativeHandleGuard preKeyPublicGuard = new NativeHandleGuard(preKeyPublic); + NativeHandleGuard signedPreKeyPublicGuard = new NativeHandleGuard(signedPreKeyPublic); + NativeHandleGuard identityKeyGuard = new NativeHandleGuard(identityKey.getPublicKey()); + NativeHandleGuard kyberPreKeyPublicGuard = new NativeHandleGuard(kyberPreKeyPublic); ) { + byte[] kyberSignature = kyberPreKeySignature == null ? new byte[] {} : kyberPreKeySignature; + this.unsafeHandle = + Native.PreKeyBundle_New( + registrationId, + deviceId, + preKeyId, + preKeyPublicGuard.nativeHandle(), + signedPreKeyId, + signedPreKeyPublicGuard.nativeHandle(), + signedPreKeySignature, + identityKeyGuard.nativeHandle(), + kyberPreKeyId, + kyberPreKeyPublicGuard.nativeHandle(), + kyberSignature); } } @@ -135,7 +147,8 @@ public byte[] getSignedPreKeySignature() { */ public IdentityKey getIdentityKey() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - return new IdentityKey(new ECPublicKey(Native.PreKeyBundle_GetIdentityKey(guard.nativeHandle()))); + return new IdentityKey( + new ECPublicKey(Native.PreKeyBundle_GetIdentityKey(guard.nativeHandle()))); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/PreKeyRecord.java b/java/shared/java/org/signal/libsignal/protocol/state/PreKeyRecord.java index 223a94877c..8b9966cef5 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/PreKeyRecord.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/PreKeyRecord.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; import org.signal.libsignal.internal.Native; @@ -16,17 +16,17 @@ public class PreKeyRecord implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.PreKeyRecord_Destroy(this.unsafeHandle); } public PreKeyRecord(int id, ECKeyPair keyPair) { - try ( - NativeHandleGuard publicKey = new NativeHandleGuard(keyPair.getPublicKey()); - NativeHandleGuard privateKey = new NativeHandleGuard(keyPair.getPrivateKey()); - ) { - this.unsafeHandle = Native.PreKeyRecord_New(id, publicKey.nativeHandle(), privateKey.nativeHandle()); + try (NativeHandleGuard publicKey = new NativeHandleGuard(keyPair.getPublicKey()); + NativeHandleGuard privateKey = new NativeHandleGuard(keyPair.getPrivateKey()); ) { + this.unsafeHandle = + Native.PreKeyRecord_New(id, publicKey.nativeHandle(), privateKey.nativeHandle()); } } @@ -43,8 +43,10 @@ public int getId() { public ECKeyPair getKeyPair() throws InvalidKeyException { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - ECPublicKey publicKey = new ECPublicKey(Native.PreKeyRecord_GetPublicKey(guard.nativeHandle())); - ECPrivateKey privateKey = new ECPrivateKey(Native.PreKeyRecord_GetPrivateKey(guard.nativeHandle())); + ECPublicKey publicKey = + new ECPublicKey(Native.PreKeyRecord_GetPublicKey(guard.nativeHandle())); + ECPrivateKey privateKey = + new ECPrivateKey(Native.PreKeyRecord_GetPrivateKey(guard.nativeHandle())); return new ECKeyPair(publicKey, privateKey); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/PreKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/PreKeyStore.java index a39f5d385b..beef380d2a 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/PreKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/PreKeyStore.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; import org.signal.libsignal.protocol.InvalidKeyIdException; @@ -29,19 +29,18 @@ public interface PreKeyStore { * @param preKeyId the ID of the PreKeyRecord to store. * @param record the PreKeyRecord. */ - public void storePreKey(int preKeyId, PreKeyRecord record); + public void storePreKey(int preKeyId, PreKeyRecord record); /** * @param preKeyId A PreKeyRecord ID. * @return true if the store has a record for the preKeyId, otherwise false. */ - public boolean containsPreKey(int preKeyId); + public boolean containsPreKey(int preKeyId); /** * Delete a PreKeyRecord from local storage. * * @param preKeyId The ID of the PreKeyRecord to remove. */ - public void removePreKey(int preKeyId); - + public void removePreKey(int preKeyId); } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/SessionRecord.java b/java/shared/java/org/signal/libsignal/protocol/state/SessionRecord.java index e63c6df768..cf86b17972 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/SessionRecord.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/SessionRecord.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; -import java.io.IOException; +import java.time.Instant; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; import org.signal.libsignal.protocol.IdentityKey; @@ -24,7 +24,8 @@ public class SessionRecord implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.SessionRecord_Destroy(this.unsafeHandle); } @@ -37,19 +38,14 @@ private SessionRecord(long unsafeHandle) { this.unsafeHandle = unsafeHandle; } - // FIXME: This shouldn't be considered a "message". - public static SessionRecord fromSingleSessionState(byte[] sessionStateBytes) throws InvalidMessageException { - return new SessionRecord(Native.SessionRecord_FromSingleSessionState(sessionStateBytes)); - } - // FIXME: This shouldn't be considered a "message". public SessionRecord(byte[] serialized) throws InvalidMessageException { this.unsafeHandle = Native.SessionRecord_Deserialize(serialized); } /** - * Move the current SessionState into the list of "previous" session states, and replace - * the current SessionState with a fresh reset instance. + * Move the current SessionState into the list of "previous" session states, and replace the + * current SessionState with a fresh reset instance. */ public void archiveCurrentState() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { @@ -98,22 +94,39 @@ public IdentityKey getLocalIdentityKey() { } } + /** + * Returns whether the current session can be used to send messages. + * + *

    If there is no current session, returns {@code false}. + */ public boolean hasSenderChain() { + return hasSenderChain(Instant.now()); + } + + /** + * Returns whether the current session can be used to send messages. + * + *

    If there is no current session, returns {@code false}. + * + *

    You should only use this overload if you need to test session expiration. + */ + public boolean hasSenderChain(Instant now) { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - return Native.SessionRecord_HasSenderChain(guard.nativeHandle()); + return Native.SessionRecord_HasUsableSenderChain(guard.nativeHandle(), now.toEpochMilli()); } } public boolean currentRatchetKeyMatches(ECPublicKey key) { - try ( - NativeHandleGuard guard = new NativeHandleGuard(this); - NativeHandleGuard keyGuard = new NativeHandleGuard(key); - ) { - return Native.SessionRecord_CurrentRatchetKeyMatches(guard.nativeHandle(), keyGuard.nativeHandle()); + try (NativeHandleGuard guard = new NativeHandleGuard(this); + NativeHandleGuard keyGuard = new NativeHandleGuard(key); ) { + return Native.SessionRecord_CurrentRatchetKeyMatches( + guard.nativeHandle(), keyGuard.nativeHandle()); } } - /** @return a serialized version of the current SessionRecord. */ + /** + * @return a serialized version of the current SessionRecord. + */ public byte[] serialize() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { return Native.SessionRecord_Serialize(guard.nativeHandle()); @@ -123,12 +136,10 @@ public byte[] serialize() { // Following functions are for internal or testing use and may be removed in the future: public byte[] getReceiverChainKeyValue(ECPublicKey senderEphemeral) { - try ( - NativeHandleGuard guard = new NativeHandleGuard(this); - NativeHandleGuard ephemeralGuard = new NativeHandleGuard(senderEphemeral); - ) { + try (NativeHandleGuard guard = new NativeHandleGuard(this); + NativeHandleGuard ephemeralGuard = new NativeHandleGuard(senderEphemeral); ) { return Native.SessionRecord_GetReceiverChainKeyValue( - guard.nativeHandle(), ephemeralGuard.nativeHandle()); + guard.nativeHandle(), ephemeralGuard.nativeHandle()); } } @@ -150,24 +161,25 @@ public static SessionRecord initializeAliceSession( IdentityKey theirIdentityKey, ECPublicKey theirSignedPreKey, ECPublicKey theirRatchetKey) { - try ( - NativeHandleGuard identityPrivateGuard = new NativeHandleGuard(identityKey.getPrivateKey()); - NativeHandleGuard identityPublicGuard = new NativeHandleGuard(identityKey.getPublicKey().getPublicKey()); - NativeHandleGuard basePrivateGuard = new NativeHandleGuard(baseKey.getPrivateKey()); - NativeHandleGuard basePublicGuard = new NativeHandleGuard(baseKey.getPublicKey()); - NativeHandleGuard theirIdentityGuard = new NativeHandleGuard(theirIdentityKey.getPublicKey()); - NativeHandleGuard theirSignedPreKeyGuard = new NativeHandleGuard(theirSignedPreKey); - NativeHandleGuard theirRatchetKeyGuard = new NativeHandleGuard(theirRatchetKey); - ) { + try (NativeHandleGuard identityPrivateGuard = + new NativeHandleGuard(identityKey.getPrivateKey()); + NativeHandleGuard identityPublicGuard = + new NativeHandleGuard(identityKey.getPublicKey().getPublicKey()); + NativeHandleGuard basePrivateGuard = new NativeHandleGuard(baseKey.getPrivateKey()); + NativeHandleGuard basePublicGuard = new NativeHandleGuard(baseKey.getPublicKey()); + NativeHandleGuard theirIdentityGuard = + new NativeHandleGuard(theirIdentityKey.getPublicKey()); + NativeHandleGuard theirSignedPreKeyGuard = new NativeHandleGuard(theirSignedPreKey); + NativeHandleGuard theirRatchetKeyGuard = new NativeHandleGuard(theirRatchetKey); ) { return new SessionRecord( - Native.SessionRecord_InitializeAliceSession( - identityPrivateGuard.nativeHandle(), - identityPublicGuard.nativeHandle(), - basePrivateGuard.nativeHandle(), - basePublicGuard.nativeHandle(), - theirIdentityGuard.nativeHandle(), - theirSignedPreKeyGuard.nativeHandle(), - theirRatchetKeyGuard.nativeHandle())); + Native.SessionRecord_InitializeAliceSession( + identityPrivateGuard.nativeHandle(), + identityPublicGuard.nativeHandle(), + basePrivateGuard.nativeHandle(), + basePublicGuard.nativeHandle(), + theirIdentityGuard.nativeHandle(), + theirSignedPreKeyGuard.nativeHandle(), + theirRatchetKeyGuard.nativeHandle())); } } @@ -177,26 +189,31 @@ public static SessionRecord initializeBobSession( ECKeyPair ephemeralKey, IdentityKey theirIdentityKey, ECPublicKey theirBaseKey) { - try ( - NativeHandleGuard identityPrivateGuard = new NativeHandleGuard(identityKey.getPrivateKey()); - NativeHandleGuard identityPublicGuard = new NativeHandleGuard(identityKey.getPublicKey().getPublicKey()); - NativeHandleGuard signedPreKeyPrivateGuard = new NativeHandleGuard(signedPreKey.getPrivateKey()); - NativeHandleGuard signedPreKeyPublicGuard = new NativeHandleGuard(signedPreKey.getPublicKey()); - NativeHandleGuard ephemeralPrivateGuard = new NativeHandleGuard(ephemeralKey.getPrivateKey()); - NativeHandleGuard ephemeralPublicGuard = new NativeHandleGuard(ephemeralKey.getPublicKey()); - NativeHandleGuard theirIdentityGuard = new NativeHandleGuard(theirIdentityKey.getPublicKey()); - NativeHandleGuard theirBaseKeyGuard = new NativeHandleGuard(theirBaseKey); - ) { + try (NativeHandleGuard identityPrivateGuard = + new NativeHandleGuard(identityKey.getPrivateKey()); + NativeHandleGuard identityPublicGuard = + new NativeHandleGuard(identityKey.getPublicKey().getPublicKey()); + NativeHandleGuard signedPreKeyPrivateGuard = + new NativeHandleGuard(signedPreKey.getPrivateKey()); + NativeHandleGuard signedPreKeyPublicGuard = + new NativeHandleGuard(signedPreKey.getPublicKey()); + NativeHandleGuard ephemeralPrivateGuard = + new NativeHandleGuard(ephemeralKey.getPrivateKey()); + NativeHandleGuard ephemeralPublicGuard = + new NativeHandleGuard(ephemeralKey.getPublicKey()); + NativeHandleGuard theirIdentityGuard = + new NativeHandleGuard(theirIdentityKey.getPublicKey()); + NativeHandleGuard theirBaseKeyGuard = new NativeHandleGuard(theirBaseKey); ) { return new SessionRecord( - Native.SessionRecord_InitializeBobSession( - identityPrivateGuard.nativeHandle(), - identityPublicGuard.nativeHandle(), - signedPreKeyPrivateGuard.nativeHandle(), - signedPreKeyPublicGuard.nativeHandle(), - ephemeralPrivateGuard.nativeHandle(), - ephemeralPublicGuard.nativeHandle(), - theirIdentityGuard.nativeHandle(), - theirBaseKeyGuard.nativeHandle())); + Native.SessionRecord_InitializeBobSession( + identityPrivateGuard.nativeHandle(), + identityPublicGuard.nativeHandle(), + signedPreKeyPrivateGuard.nativeHandle(), + signedPreKeyPublicGuard.nativeHandle(), + ephemeralPrivateGuard.nativeHandle(), + ephemeralPublicGuard.nativeHandle(), + theirIdentityGuard.nativeHandle(), + theirBaseKeyGuard.nativeHandle())); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/SessionStore.java b/java/shared/java/org/signal/libsignal/protocol/state/SessionStore.java index 9952bb6751..0c8f994586 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/SessionStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/SessionStore.java @@ -1,18 +1,16 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; +import java.util.List; import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SignalProtocolAddress; -import java.util.List; - /** - * The interface to the durable store of session state information - * for remote clients. + * The interface to the durable store of session state information for remote clients. * * @author Moxie Marlinspike */ @@ -21,15 +19,15 @@ public interface SessionStore { /** * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, * or a new SessionRecord if one does not currently exist. - *

    - * It is important that implementations return a copy of the current durable information. The + * + *

    It is important that implementations return a copy of the current durable information. The * returned SessionRecord may be modified, but those changes should not have an effect on the - * durable session state (what is returned by subsequent calls to this method) without the - * store method being called here first. + * durable session state (what is returned by subsequent calls to this method) without the store + * method being called here first. * * @param address The name and device ID of the remote client. - * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or - * a new SessionRecord if one does not currently exist. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or a new + * SessionRecord if one does not currently exist. */ public SessionRecord loadSession(SignalProtocolAddress address); @@ -40,7 +38,8 @@ public interface SessionStore { * @return the SessionRecords corresponding to each recipientId + deviceId tuple. * @throws NoSessionException if any address does not have an active session. */ - public List loadExistingSessions(List addresses) throws NoSessionException; + public List loadExistingSessions(List addresses) + throws NoSessionException; /** * Returns all known devices with active sessions for a recipient @@ -52,13 +51,16 @@ public interface SessionStore { /** * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * * @param address the address of the remote client. * @param record the current SessionRecord for the remote client. */ public void storeSession(SignalProtocolAddress address, SessionRecord record); /** - * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId + * tuple. + * * @param address the address of the remote client. * @return true if a {@link SessionRecord} exists, false otherwise. */ @@ -77,5 +79,4 @@ public interface SessionStore { * @param name the name of the remote client. */ public void deleteAllSessions(String name); - } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/SignalProtocolStore.java b/java/shared/java/org/signal/libsignal/protocol/state/SignalProtocolStore.java index db50825285..26fc4fd216 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/SignalProtocolStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/SignalProtocolStore.java @@ -1,13 +1,16 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; import org.signal.libsignal.protocol.groups.state.SenderKeyStore; public interface SignalProtocolStore - extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore, SenderKeyStore, KyberPreKeyStore -{ -} + extends IdentityKeyStore, + PreKeyStore, + SessionStore, + SignedPreKeyStore, + SenderKeyStore, + KyberPreKeyStore {} diff --git a/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyRecord.java b/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyRecord.java index 5c8120d120..14b6216714 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyRecord.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyRecord.java @@ -1,13 +1,12 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state; import org.signal.libsignal.internal.Native; import org.signal.libsignal.internal.NativeHandleGuard; -import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECPrivateKey; @@ -16,22 +15,18 @@ public class SignedPreKeyRecord implements NativeHandleGuard.Owner { private final long unsafeHandle; - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.SignedPreKeyRecord_Destroy(this.unsafeHandle); } public SignedPreKeyRecord(int id, long timestamp, ECKeyPair keyPair, byte[] signature) { - try ( - NativeHandleGuard publicGuard = new NativeHandleGuard(keyPair.getPublicKey()); - NativeHandleGuard privateGuard = new NativeHandleGuard(keyPair.getPrivateKey()); - ) { - this.unsafeHandle = Native.SignedPreKeyRecord_New( - id, - timestamp, - publicGuard.nativeHandle(), - privateGuard.nativeHandle(), - signature); + try (NativeHandleGuard publicGuard = new NativeHandleGuard(keyPair.getPublicKey()); + NativeHandleGuard privateGuard = new NativeHandleGuard(keyPair.getPrivateKey()); ) { + this.unsafeHandle = + Native.SignedPreKeyRecord_New( + id, timestamp, publicGuard.nativeHandle(), privateGuard.nativeHandle(), signature); } } @@ -54,8 +49,10 @@ public long getTimestamp() { public ECKeyPair getKeyPair() { try (NativeHandleGuard guard = new NativeHandleGuard(this)) { - ECPublicKey publicKey = new ECPublicKey(Native.SignedPreKeyRecord_GetPublicKey(guard.nativeHandle())); - ECPrivateKey privateKey = new ECPrivateKey(Native.SignedPreKeyRecord_GetPrivateKey(guard.nativeHandle())); + ECPublicKey publicKey = + new ECPublicKey(Native.SignedPreKeyRecord_GetPublicKey(guard.nativeHandle())); + ECPrivateKey privateKey = + new ECPrivateKey(Native.SignedPreKeyRecord_GetPrivateKey(guard.nativeHandle())); return new ECKeyPair(publicKey, privateKey); } } @@ -75,5 +72,4 @@ public byte[] serialize() { public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyStore.java index c770639180..d3436d7dd0 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/SignedPreKeyStore.java @@ -1,17 +1,15 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ -package org.signal.libsignal.protocol.state; +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.protocol.InvalidKeyIdException; +package org.signal.libsignal.protocol.state; import java.util.List; +import org.signal.libsignal.protocol.InvalidKeyIdException; public interface SignedPreKeyStore { - /** * Load a local SignedPreKeyRecord. * @@ -34,19 +32,18 @@ public interface SignedPreKeyStore { * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. * @param record the SignedPreKeyRecord. */ - public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record); + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record); /** * @param signedPreKeyId A SignedPreKeyRecord ID. * @return true if the store has a record for the signedPreKeyId, otherwise false. */ - public boolean containsSignedPreKey(int signedPreKeyId); + public boolean containsSignedPreKey(int signedPreKeyId); /** * Delete a SignedPreKeyRecord from local storage. * * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. */ - public void removeSignedPreKey(int signedPreKeyId); - + public void removeSignedPreKey(int signedPreKeyId); } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryIdentityKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryIdentityKeyStore.java index c0f083ed0b..4e3e67c3e8 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryIdentityKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryIdentityKeyStore.java @@ -1,27 +1,26 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state.impl; +import java.util.HashMap; +import java.util.Map; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.state.IdentityKeyStore; -import java.util.HashMap; -import java.util.Map; - public class InMemoryIdentityKeyStore implements IdentityKeyStore { private final Map trustedKeys = new HashMap<>(); private final IdentityKeyPair identityKeyPair; - private final int localRegistrationId; + private final int localRegistrationId; public InMemoryIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) { - this.identityKeyPair = identityKeyPair; + this.identityKeyPair = identityKeyPair; this.localRegistrationId = localRegistrationId; } @@ -48,7 +47,8 @@ public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityK } @Override - public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { + public boolean isTrustedIdentity( + SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { IdentityKey trusted = trustedKeys.get(address); return (trusted == null || trusted.equals(identityKey)); } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryKyberPreKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryKyberPreKeyStore.java index 118c2d408f..c888bba35b 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryKyberPreKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryKyberPreKeyStore.java @@ -2,12 +2,8 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // -package org.signal.libsignal.protocol.state.impl; -import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.state.KyberPreKeyRecord; -import org.signal.libsignal.protocol.state.KyberPreKeyStore; +package org.signal.libsignal.protocol.state.impl; import java.util.HashMap; import java.util.HashSet; @@ -15,6 +11,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.signal.libsignal.protocol.InvalidKeyIdException; +import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.protocol.state.KyberPreKeyRecord; +import org.signal.libsignal.protocol.state.KyberPreKeyStore; public class InMemoryKyberPreKeyStore implements KyberPreKeyStore { @@ -61,7 +61,7 @@ public boolean containsKyberPreKey(int kyberPreKeyId) { @Override public void markKyberPreKeyUsed(int kyberPreKeyId) { - //store.remove(kyberPreKeyId); + // store.remove(kyberPreKeyId); used.add(kyberPreKeyId); } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryPreKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryPreKeyStore.java index 0ad6380b88..a6fca935c8 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryPreKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemoryPreKeyStore.java @@ -1,18 +1,17 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state.impl; +import java.util.HashMap; +import java.util.Map; import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.PreKeyStore; -import java.util.HashMap; -import java.util.Map; - public class InMemoryPreKeyStore implements PreKeyStore { private final Map store = new HashMap<>(); diff --git a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySessionStore.java b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySessionStore.java index 7deb7ee582..4dc7364483 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySessionStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySessionStore.java @@ -1,21 +1,20 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state.impl; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.state.SessionRecord; import org.signal.libsignal.protocol.state.SessionStore; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - public class InMemorySessionStore implements SessionStore { private Map sessions = new HashMap<>(); @@ -36,7 +35,8 @@ public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddres } @Override - public synchronized List loadExistingSessions(List addresses) throws NoSessionException { + public synchronized List loadExistingSessions( + List addresses) throws NoSessionException { List resultSessions = new LinkedList<>(); for (SignalProtocolAddress remoteAddress : addresses) { byte[] serialized = sessions.get(remoteAddress); @@ -57,9 +57,7 @@ public synchronized List getSubDeviceSessions(String name) { List deviceIds = new LinkedList<>(); for (SignalProtocolAddress key : sessions.keySet()) { - if (key.getName().equals(name) && - key.getDeviceId() != 1) - { + if (key.getName().equals(name) && key.getDeviceId() != 1) { deviceIds.add(key.getDeviceId()); } } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignalProtocolStore.java b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignalProtocolStore.java index f576417b9a..229eb7971b 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignalProtocolStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignalProtocolStore.java @@ -1,35 +1,34 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.state.impl; -import org.signal.libsignal.protocol.SignalProtocolAddress; +import java.util.List; +import java.util.UUID; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.NoSessionException; +import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.groups.state.InMemorySenderKeyStore; import org.signal.libsignal.protocol.groups.state.SenderKeyRecord; -import org.signal.libsignal.protocol.state.SignalProtocolStore; +import org.signal.libsignal.protocol.state.KyberPreKeyRecord; import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.SessionRecord; +import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.signal.libsignal.protocol.state.KyberPreKeyRecord; - -import java.util.List; -import java.util.UUID; public class InMemorySignalProtocolStore implements SignalProtocolStore { - private final InMemoryPreKeyStore preKeyStore = new InMemoryPreKeyStore(); - private final InMemorySessionStore sessionStore = new InMemorySessionStore(); + private final InMemoryPreKeyStore preKeyStore = new InMemoryPreKeyStore(); + private final InMemorySessionStore sessionStore = new InMemorySessionStore(); private final InMemorySignedPreKeyStore signedPreKeyStore = new InMemorySignedPreKeyStore(); - private final InMemoryKyberPreKeyStore kyberPreKeyStore = new InMemoryKyberPreKeyStore(); - private final InMemorySenderKeyStore senderKeyStore = new InMemorySenderKeyStore(); + private final InMemoryKyberPreKeyStore kyberPreKeyStore = new InMemoryKyberPreKeyStore(); + private final InMemorySenderKeyStore senderKeyStore = new InMemorySenderKeyStore(); - private final InMemoryIdentityKeyStore identityKeyStore; + private final InMemoryIdentityKeyStore identityKeyStore; public InMemorySignalProtocolStore(IdentityKeyPair identityKeyPair, int registrationId) { this.identityKeyStore = new InMemoryIdentityKeyStore(identityKeyPair, registrationId); @@ -51,7 +50,8 @@ public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityK } @Override - public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { + public boolean isTrustedIdentity( + SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { return identityKeyStore.isTrustedIdentity(address, identityKey, direction); } @@ -86,7 +86,8 @@ public SessionRecord loadSession(SignalProtocolAddress address) { } @Override - public List loadExistingSessions(List addresses) throws NoSessionException { + public List loadExistingSessions(List addresses) + throws NoSessionException { return sessionStore.loadExistingSessions(addresses); } @@ -141,7 +142,8 @@ public void removeSignedPreKey(int signedPreKeyId) { } @Override - public void storeSenderKey(SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record) { + public void storeSenderKey( + SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record) { senderKeyStore.storeSenderKey(sender, distributionId, record); } diff --git a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignedPreKeyStore.java b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignedPreKeyStore.java index d60c763712..7a1c054880 100644 --- a/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignedPreKeyStore.java +++ b/java/shared/java/org/signal/libsignal/protocol/state/impl/InMemorySignedPreKeyStore.java @@ -1,19 +1,18 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ -package org.signal.libsignal.protocol.state.impl; +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.signal.libsignal.protocol.state.SignedPreKeyStore; +package org.signal.libsignal.protocol.state.impl; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.signal.libsignal.protocol.InvalidKeyIdException; +import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.protocol.state.SignedPreKeyRecord; +import org.signal.libsignal.protocol.state.SignedPreKeyStore; public class InMemorySignedPreKeyStore implements SignedPreKeyStore { diff --git a/java/shared/java/org/signal/libsignal/protocol/util/ByteUtil.java b/java/shared/java/org/signal/libsignal/protocol/util/ByteUtil.java index 6278e12905..c521d1d745 100644 --- a/java/shared/java/org/signal/libsignal/protocol/util/ByteUtil.java +++ b/java/shared/java/org/signal/libsignal/protocol/util/ByteUtil.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.util; import java.io.ByteArrayOutputStream; @@ -38,12 +38,14 @@ public static byte[][] split(byte[] input, int firstLength, int secondLength) { } public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength) - throws ParseException - { - if (input == null || firstLength < 0 || secondLength < 0 || thirdLength < 0 || - input.length < firstLength + secondLength + thirdLength) - { - throw new ParseException("Input too small: " + (input == null ? null : Hex.toString(input)), 0); + throws ParseException { + if (input == null + || firstLength < 0 + || secondLength < 0 + || thirdLength < 0 + || input.length < firstLength + secondLength + thirdLength) { + throw new ParseException( + "Input too small: " + (input == null ? null : Hex.toString(input)), 0); } byte[][] parts = new byte[3][]; @@ -68,7 +70,7 @@ public static byte[] trim(byte[] input, int length) { } public static byte intsToByteHighAndLow(int highValue, int lowValue) { - return (byte)((highValue << 4 | lowValue) & 0xFF); + return (byte) ((highValue << 4 | lowValue) & 0xFF); } public static int highBitsToInt(byte value) { @@ -77,24 +79,22 @@ public static int highBitsToInt(byte value) { public static byte[] longToByteArray(long value) { byte[] bytes = new byte[8]; - bytes[7] = (byte)value; - bytes[6] = (byte)(value >> 8); - bytes[5] = (byte)(value >> 16); - bytes[4] = (byte)(value >> 24); - bytes[3] = (byte)(value >> 32); - bytes[2] = (byte)(value >> 40); - bytes[1] = (byte)(value >> 48); - bytes[0] = (byte)(value >> 56); + bytes[7] = (byte) value; + bytes[6] = (byte) (value >> 8); + bytes[5] = (byte) (value >> 16); + bytes[4] = (byte) (value >> 24); + bytes[3] = (byte) (value >> 32); + bytes[2] = (byte) (value >> 40); + bytes[1] = (byte) (value >> 48); + bytes[0] = (byte) (value >> 56); return bytes; } public static long byteArray5ToLong(byte[] bytes, int offset) { - return - ((bytes[offset] & 0xffL) << 32) | - ((bytes[offset + 1] & 0xffL) << 24) | - ((bytes[offset + 2] & 0xffL) << 16) | - ((bytes[offset + 3] & 0xffL) << 8) | - ((bytes[offset + 4] & 0xffL)); + return ((bytes[offset] & 0xffL) << 32) + | ((bytes[offset + 1] & 0xffL) << 24) + | ((bytes[offset + 2] & 0xffL) << 16) + | ((bytes[offset + 3] & 0xffL) << 8) + | ((bytes[offset + 4] & 0xffL)); } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/util/Hex.java b/java/shared/java/org/signal/libsignal/protocol/util/Hex.java index c81e972ae4..c2fdad3c7d 100644 --- a/java/shared/java/org/signal/libsignal/protocol/util/Hex.java +++ b/java/shared/java/org/signal/libsignal/protocol/util/Hex.java @@ -1,18 +1,16 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.util; import java.io.IOException; -/** - * Utility for generating hex dumps. - */ +/** Utility for generating hex dumps. */ public class Hex { - private final static char[] HEX_DIGITS = { + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; @@ -31,7 +29,7 @@ public static String toString(byte[] bytes, int offset, int length) { public static String toStringCondensed(byte[] bytes) { StringBuffer buf = new StringBuffer(); - for (int i=0;i> 4) & 0xf]); buf.append(HEX_DIGITS[b & 0xf]); } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/util/KeyHelper.java b/java/shared/java/org/signal/libsignal/protocol/util/KeyHelper.java index 2153c4eb7e..35319e2763 100644 --- a/java/shared/java/org/signal/libsignal/protocol/util/KeyHelper.java +++ b/java/shared/java/org/signal/libsignal/protocol/util/KeyHelper.java @@ -1,22 +1,12 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ -package org.signal.libsignal.protocol.util; +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// -import org.signal.libsignal.protocol.IdentityKey; -import org.signal.libsignal.protocol.IdentityKeyPair; -import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; -import org.signal.libsignal.protocol.ecc.ECKeyPair; -import org.signal.libsignal.protocol.state.PreKeyRecord; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; +package org.signal.libsignal.protocol.util; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.LinkedList; -import java.util.List; /** * Helper class for generating keys of different types. @@ -28,24 +18,20 @@ public class KeyHelper { private KeyHelper() {} /** - * Generate a registration ID. Clients should only do this once, - * at install time. + * Generate a registration ID. Clients should only do this once, at install time. * - * @param extendedRange By default (false), the generated registration - * ID is sized to require the minimal possible protobuf - * encoding overhead. Specify true if the caller needs - * the full range of MAX_INT at the cost of slightly - * higher encoding overhead. + * @param extendedRange By default (false), the generated registration ID is sized to require the + * minimal possible protobuf encoding overhead. Specify true if the caller needs the full + * range of MAX_INT at the cost of slightly higher encoding overhead. * @return the generated registration ID. */ public static int generateRegistrationId(boolean extendedRange) { try { SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); if (extendedRange) return secureRandom.nextInt(Integer.MAX_VALUE - 1) + 1; - else return secureRandom.nextInt(16380) + 1; + else return secureRandom.nextInt(16380) + 1; } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/protocol/util/Medium.java b/java/shared/java/org/signal/libsignal/protocol/util/Medium.java index d51597076f..709153fc6e 100644 --- a/java/shared/java/org/signal/libsignal/protocol/util/Medium.java +++ b/java/shared/java/org/signal/libsignal/protocol/util/Medium.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.util; public class Medium { diff --git a/java/shared/java/org/signal/libsignal/protocol/util/Pair.java b/java/shared/java/org/signal/libsignal/protocol/util/Pair.java index 2cbf729498..42e75ebda5 100644 --- a/java/shared/java/org/signal/libsignal/protocol/util/Pair.java +++ b/java/shared/java/org/signal/libsignal/protocol/util/Pair.java @@ -1,8 +1,8 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ +// +// Copyright 2014-2016 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.protocol.util; public class Pair { @@ -14,18 +14,18 @@ public Pair(T1 v1, T2 v2) { this.v2 = v2; } - public T1 first(){ + public T1 first() { return v1; } - public T2 second(){ + public T2 second() { return v2; } public boolean equals(Object o) { - return o instanceof Pair && - equal(((Pair) o).first(), first()) && - equal(((Pair) o).second(), second()); + return o instanceof Pair + && equal(((Pair) o).first(), first()) + && equal(((Pair) o).second(), second()); } public int hashCode() { diff --git a/java/shared/java/org/signal/libsignal/quic/QuicClient.java b/java/shared/java/org/signal/libsignal/quic/QuicClient.java index 976ddb1637..fbda1fd50b 100644 --- a/java/shared/java/org/signal/libsignal/quic/QuicClient.java +++ b/java/shared/java/org/signal/libsignal/quic/QuicClient.java @@ -6,7 +6,6 @@ package org.signal.libsignal.quic; import java.util.Map; - import org.signal.libsignal.internal.Native; public class QuicClient { @@ -22,11 +21,12 @@ public QuicClient(String target) { this.unsafeHandle = Native.QuicClient_New(target); } - @Override @SuppressWarnings("deprecation") + @Override + @SuppressWarnings("deprecation") protected void finalize() { Native.QuicClient_Destroy(this.unsafeHandle); } - + public long unsafeNativeHandleWithoutGuard() { return this.unsafeHandle; } @@ -35,7 +35,8 @@ public byte[] sendMessage(byte[] data) { return Native.QuicClient_SendMessage(this.unsafeHandle, data); } - public void openControlledStream(String baseUrl, Map headers, QuicCallbackListener listener) { + public void openControlledStream( + String baseUrl, Map headers, QuicCallbackListener listener) { Native.QuicClient_OpenControlledStream(this.unsafeHandle, baseUrl, headers, listener); } diff --git a/java/shared/java/org/signal/libsignal/usernames/BadDiscriminatorException.java b/java/shared/java/org/signal/libsignal/usernames/BadDiscriminatorException.java index e2d79c84b4..a67d538175 100644 --- a/java/shared/java/org/signal/libsignal/usernames/BadDiscriminatorException.java +++ b/java/shared/java/org/signal/libsignal/usernames/BadDiscriminatorException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class BadDiscriminatorException extends BaseUsernameException { - public BadDiscriminatorException(String message) { - super(message); - } + public BadDiscriminatorException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/BadNicknameCharacterException.java b/java/shared/java/org/signal/libsignal/usernames/BadNicknameCharacterException.java index 629260c02c..aad2be104a 100644 --- a/java/shared/java/org/signal/libsignal/usernames/BadNicknameCharacterException.java +++ b/java/shared/java/org/signal/libsignal/usernames/BadNicknameCharacterException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class BadNicknameCharacterException extends BaseUsernameException { - public BadNicknameCharacterException(String message) { - super(message); - } + public BadNicknameCharacterException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/BaseUsernameException.java b/java/shared/java/org/signal/libsignal/usernames/BaseUsernameException.java index c77b98f70d..8dd0e6d9da 100644 --- a/java/shared/java/org/signal/libsignal/usernames/BaseUsernameException.java +++ b/java/shared/java/org/signal/libsignal/usernames/BaseUsernameException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public class BaseUsernameException extends Exception { - public BaseUsernameException(String message) { - super(message); - } -} \ No newline at end of file + public BaseUsernameException(String message) { + super(message); + } +} diff --git a/java/shared/java/org/signal/libsignal/usernames/CannotBeEmptyException.java b/java/shared/java/org/signal/libsignal/usernames/CannotBeEmptyException.java index 62b2c4f62b..5f7abdcefe 100644 --- a/java/shared/java/org/signal/libsignal/usernames/CannotBeEmptyException.java +++ b/java/shared/java/org/signal/libsignal/usernames/CannotBeEmptyException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class CannotBeEmptyException extends BaseUsernameException { - public CannotBeEmptyException(String message) { - super(message); - } + public CannotBeEmptyException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/CannotStartWithDigitException.java b/java/shared/java/org/signal/libsignal/usernames/CannotStartWithDigitException.java index aeb69fc049..d0c9a2cf19 100644 --- a/java/shared/java/org/signal/libsignal/usernames/CannotStartWithDigitException.java +++ b/java/shared/java/org/signal/libsignal/usernames/CannotStartWithDigitException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class CannotStartWithDigitException extends BaseUsernameException { - public CannotStartWithDigitException(String message) { - super(message); - } + public CannotStartWithDigitException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/MissingSeparatorException.java b/java/shared/java/org/signal/libsignal/usernames/MissingSeparatorException.java index 74014d0956..f7bac633cf 100644 --- a/java/shared/java/org/signal/libsignal/usernames/MissingSeparatorException.java +++ b/java/shared/java/org/signal/libsignal/usernames/MissingSeparatorException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class MissingSeparatorException extends BaseUsernameException { - public MissingSeparatorException(String message) { - super(message); - } + public MissingSeparatorException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/NicknameTooLongException.java b/java/shared/java/org/signal/libsignal/usernames/NicknameTooLongException.java index 6250969fc2..9c7aa8c405 100644 --- a/java/shared/java/org/signal/libsignal/usernames/NicknameTooLongException.java +++ b/java/shared/java/org/signal/libsignal/usernames/NicknameTooLongException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class NicknameTooLongException extends BaseUsernameException { - public NicknameTooLongException(String message) { - super(message); - } + public NicknameTooLongException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/NicknameTooShortException.java b/java/shared/java/org/signal/libsignal/usernames/NicknameTooShortException.java index 5e8f88e854..91bfb1eb98 100644 --- a/java/shared/java/org/signal/libsignal/usernames/NicknameTooShortException.java +++ b/java/shared/java/org/signal/libsignal/usernames/NicknameTooShortException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class NicknameTooShortException extends BaseUsernameException { - public NicknameTooShortException(String message) { - super(message); - } + public NicknameTooShortException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/ProofVerificationFailureException.java b/java/shared/java/org/signal/libsignal/usernames/ProofVerificationFailureException.java index 5c94b0e021..49dd1740d9 100644 --- a/java/shared/java/org/signal/libsignal/usernames/ProofVerificationFailureException.java +++ b/java/shared/java/org/signal/libsignal/usernames/ProofVerificationFailureException.java @@ -2,10 +2,11 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // + package org.signal.libsignal.usernames; public final class ProofVerificationFailureException extends BaseUsernameException { - public ProofVerificationFailureException(String message) { - super(message); - } + public ProofVerificationFailureException(String message) { + super(message); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/Username.java b/java/shared/java/org/signal/libsignal/usernames/Username.java index 42e0613c1d..cf2effe10b 100644 --- a/java/shared/java/org/signal/libsignal/usernames/Username.java +++ b/java/shared/java/org/signal/libsignal/usernames/Username.java @@ -2,135 +2,139 @@ // Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // -package org.signal.libsignal.usernames; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; -import org.signal.libsignal.internal.Native; +package org.signal.libsignal.usernames; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import org.signal.libsignal.internal.Native; public final class Username { - private final String username; - private final byte[] hash; - - public static class UsernameLink { - private final byte[] entropy; - private final byte[] encryptedUsername; - - public UsernameLink(final byte[] entropy, final byte[] encryptedUsername) { - this.entropy = Objects.requireNonNull(entropy, "entropy"); - this.encryptedUsername = Objects.requireNonNull(encryptedUsername, "encryptedUsername"); - } - - public byte[] getEntropy() { - return entropy; - } - - public byte[] getEncryptedUsername() { - return encryptedUsername; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final UsernameLink that = (UsernameLink) o; - return Arrays.equals(entropy, that.entropy) && - Arrays.equals(encryptedUsername, that.encryptedUsername); - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(entropy); - result = 31 * result + Arrays.hashCode(encryptedUsername); - return result; - } - } - - public Username(String username) throws BaseUsernameException { - this.username = Objects.requireNonNull(username, "username"); - this.hash = hash(username); - } - - public String getUsername() { - return this.username; - } - - public byte[] getHash() { - return this.hash; - } - - public static List candidatesFrom(String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException { - String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength); - ArrayList result = new ArrayList<>(); - for (String name : names.split(",")) { - result.add(new Username(name)); - } - return result; - } - - public static Username fromLink(final UsernameLink usernameLink) throws BaseUsernameException { - final String username = Native.UsernameLink_DecryptUsername(usernameLink.getEntropy(), usernameLink.getEncryptedUsername()); - return new Username(username); - } - - public byte[] generateProof() throws BaseUsernameException { - byte[] randomness = new byte[32]; - SecureRandom r = new SecureRandom(); - r.nextBytes(randomness); - return generateProofWithRandomness(randomness); - } - - public byte[] generateProofWithRandomness(byte[] randomness) throws BaseUsernameException { - return Native.Username_Proof(this.username, randomness); - } + private final String username; + private final byte[] hash; - public UsernameLink generateLink() throws BaseUsernameException { - final byte[] bytes = Native.UsernameLink_Create(username); - final byte[] entropy = Arrays.copyOfRange(bytes, 0, 32); - final byte[] enctyptedUsername = Arrays.copyOfRange(bytes, 32, bytes.length); - return new UsernameLink(entropy, enctyptedUsername); - } - - @Deprecated - public static List generateCandidates(String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException { - String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength); - return Arrays.asList(names.split(",")); - } + public static class UsernameLink { + private final byte[] entropy; + private final byte[] encryptedUsername; - @Deprecated - public static byte[] hash(String username) throws BaseUsernameException { - return Native.Username_Hash(username); + public UsernameLink(final byte[] entropy, final byte[] encryptedUsername) { + this.entropy = Objects.requireNonNull(entropy, "entropy"); + this.encryptedUsername = Objects.requireNonNull(encryptedUsername, "encryptedUsername"); } - @Deprecated - public static byte[] generateProof(String username, byte[] randomness) throws BaseUsernameException { - return Native.Username_Proof(username, randomness); + public byte[] getEntropy() { + return entropy; } - public static void verifyProof(byte[] proof, byte[] hash) throws BaseUsernameException { - Native.Username_Verify(proof, hash); - } - - @Override - public String toString() { - return this.username; + public byte[] getEncryptedUsername() { + return encryptedUsername; } @Override public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final Username username1 = (Username) o; - return username.equals(username1.username); + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final UsernameLink that = (UsernameLink) o; + return Arrays.equals(entropy, that.entropy) + && Arrays.equals(encryptedUsername, that.encryptedUsername); } @Override public int hashCode() { - return username.hashCode(); - } + int result = Arrays.hashCode(entropy); + result = 31 * result + Arrays.hashCode(encryptedUsername); + return result; + } + } + + public Username(String username) throws BaseUsernameException { + this.username = Objects.requireNonNull(username, "username"); + this.hash = hash(username); + } + + public String getUsername() { + return this.username; + } + + public byte[] getHash() { + return this.hash; + } + + public static List candidatesFrom( + String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException { + String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength); + ArrayList result = new ArrayList<>(); + for (String name : names.split(",")) { + result.add(new Username(name)); + } + return result; + } + + public static Username fromLink(final UsernameLink usernameLink) throws BaseUsernameException { + final String username = + Native.UsernameLink_DecryptUsername( + usernameLink.getEntropy(), usernameLink.getEncryptedUsername()); + return new Username(username); + } + + public byte[] generateProof() throws BaseUsernameException { + byte[] randomness = new byte[32]; + SecureRandom r = new SecureRandom(); + r.nextBytes(randomness); + return generateProofWithRandomness(randomness); + } + + public byte[] generateProofWithRandomness(byte[] randomness) throws BaseUsernameException { + return Native.Username_Proof(this.username, randomness); + } + + public UsernameLink generateLink() throws BaseUsernameException { + final byte[] bytes = Native.UsernameLink_Create(username); + final byte[] entropy = Arrays.copyOfRange(bytes, 0, 32); + final byte[] enctyptedUsername = Arrays.copyOfRange(bytes, 32, bytes.length); + return new UsernameLink(entropy, enctyptedUsername); + } + + @Deprecated + public static List generateCandidates( + String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException { + String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength); + return Arrays.asList(names.split(",")); + } + + @Deprecated + public static byte[] hash(String username) throws BaseUsernameException { + return Native.Username_Hash(username); + } + + @Deprecated + public static byte[] generateProof(String username, byte[] randomness) + throws BaseUsernameException { + return Native.Username_Proof(username, randomness); + } + + public static void verifyProof(byte[] proof, byte[] hash) throws BaseUsernameException { + Native.Username_Verify(proof, hash); + } + + @Override + public String toString() { + return this.username; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Username username1 = (Username) o; + return username.equals(username1.username); + } + + @Override + public int hashCode() { + return username.hashCode(); + } } diff --git a/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInputDataTooLong.java b/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInputDataTooLong.java index 74a757528e..188888ed74 100644 --- a/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInputDataTooLong.java +++ b/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInputDataTooLong.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.usernames; public class UsernameLinkInputDataTooLong extends BaseUsernameException { diff --git a/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidEntropyDataLength.java b/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidEntropyDataLength.java index d6a110065f..2d83600018 100644 --- a/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidEntropyDataLength.java +++ b/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidEntropyDataLength.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.usernames; public class UsernameLinkInvalidEntropyDataLength extends BaseUsernameException { diff --git a/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidLinkData.java b/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidLinkData.java index c13aff2c8e..ebebd2b61a 100644 --- a/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidLinkData.java +++ b/java/shared/java/org/signal/libsignal/usernames/UsernameLinkInvalidLinkData.java @@ -1,3 +1,8 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + package org.signal.libsignal.usernames; public class UsernameLinkInvalidLinkData extends BaseUsernameException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/GenericServerPublicParams.java b/java/shared/java/org/signal/libsignal/zkgroup/GenericServerPublicParams.java index 55e81f34a5..0aa8cf748c 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/GenericServerPublicParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/GenericServerPublicParams.java @@ -5,8 +5,8 @@ package org.signal.libsignal.zkgroup; -import org.signal.libsignal.zkgroup.internal.ByteArray; import org.signal.libsignal.internal.Native; +import org.signal.libsignal.zkgroup.internal.ByteArray; public final class GenericServerPublicParams extends ByteArray { public GenericServerPublicParams(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/GenericServerSecretParams.java b/java/shared/java/org/signal/libsignal/zkgroup/GenericServerSecretParams.java index 17e2910b41..429c0543eb 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/GenericServerSecretParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/GenericServerSecretParams.java @@ -5,11 +5,11 @@ package org.signal.libsignal.zkgroup; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; -import org.signal.libsignal.zkgroup.internal.ByteArray; import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; +import org.signal.libsignal.zkgroup.internal.ByteArray; public final class GenericServerSecretParams extends ByteArray { @@ -18,7 +18,7 @@ public static GenericServerSecretParams generate() { } public static GenericServerSecretParams generate(SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); byte[] newContents = Native.GenericServerSecretParams_GenerateDeterministic(random); @@ -27,10 +27,10 @@ public static GenericServerSecretParams generate(SecureRandom secureRandom) { return new GenericServerSecretParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } - public GenericServerSecretParams(byte[] contents) throws InvalidInputException { + public GenericServerSecretParams(byte[] contents) throws InvalidInputException { super(contents); Native.GenericServerSecretParams_CheckValidContents(contents); } @@ -41,7 +41,6 @@ public GenericServerPublicParams getPublicParams() { return new GenericServerPublicParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/InvalidInputException.java b/java/shared/java/org/signal/libsignal/zkgroup/InvalidInputException.java index 399032c7b8..2a44582c2d 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/InvalidInputException.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/InvalidInputException.java @@ -7,12 +7,9 @@ public class InvalidInputException extends Exception { - public InvalidInputException() { - - } + public InvalidInputException() {} public InvalidInputException(String message) { super(message); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/NotarySignature.java b/java/shared/java/org/signal/libsignal/zkgroup/NotarySignature.java index f28070e108..022f66659f 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/NotarySignature.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/NotarySignature.java @@ -14,5 +14,4 @@ public final class NotarySignature extends ByteArray { public NotarySignature(byte[] contents) throws InvalidInputException { super(contents, SIZE); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/ServerPublicParams.java b/java/shared/java/org/signal/libsignal/zkgroup/ServerPublicParams.java index c64d4fbb2e..a129da227a 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/ServerPublicParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/ServerPublicParams.java @@ -5,8 +5,8 @@ package org.signal.libsignal.zkgroup; -import org.signal.libsignal.zkgroup.internal.ByteArray; import org.signal.libsignal.internal.Native; +import org.signal.libsignal.zkgroup.internal.ByteArray; public final class ServerPublicParams extends ByteArray { public ServerPublicParams(byte[] contents) throws InvalidInputException { @@ -14,8 +14,9 @@ public ServerPublicParams(byte[] contents) throws InvalidInputException { Native.ServerPublicParams_CheckValidContents(contents); } - public void verifySignature(byte[] message, NotarySignature notarySignature) throws VerificationFailedException { - Native.ServerPublicParams_VerifySignature(contents, message, notarySignature.getInternalContentsForJNI()); + public void verifySignature(byte[] message, NotarySignature notarySignature) + throws VerificationFailedException { + Native.ServerPublicParams_VerifySignature( + contents, message, notarySignature.getInternalContentsForJNI()); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/ServerSecretParams.java b/java/shared/java/org/signal/libsignal/zkgroup/ServerSecretParams.java index 362cd6aa60..c42590c6be 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/ServerSecretParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/ServerSecretParams.java @@ -5,11 +5,11 @@ package org.signal.libsignal.zkgroup; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; -import org.signal.libsignal.zkgroup.internal.ByteArray; import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; +import org.signal.libsignal.zkgroup.internal.ByteArray; public final class ServerSecretParams extends ByteArray { @@ -18,7 +18,7 @@ public static ServerSecretParams generate() { } public static ServerSecretParams generate(SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); byte[] newContents = Native.ServerSecretParams_GenerateDeterministic(random); @@ -27,10 +27,10 @@ public static ServerSecretParams generate(SecureRandom secureRandom) { return new ServerSecretParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } - public ServerSecretParams(byte[] contents) throws InvalidInputException { + public ServerSecretParams(byte[] contents) throws InvalidInputException { super(contents); Native.ServerSecretParams_CheckValidContents(contents); } @@ -41,7 +41,7 @@ public ServerPublicParams getPublicParams() { return new ServerPublicParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } public NotarySignature sign(byte[] message) { @@ -49,7 +49,7 @@ public NotarySignature sign(byte[] message) { } public NotarySignature sign(SecureRandom secureRandom, byte[] message) { - byte[] random = new byte[RANDOM_LENGTH]; + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); byte[] newContents = Native.ServerSecretParams_SignDeterministic(contents, random, message); @@ -60,5 +60,4 @@ public NotarySignature sign(SecureRandom secureRandom, byte[] message) { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/VerificationFailedException.java b/java/shared/java/org/signal/libsignal/zkgroup/VerificationFailedException.java index 0aafb29bc3..4aebffff4b 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/VerificationFailedException.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/VerificationFailedException.java @@ -6,6 +6,11 @@ package org.signal.libsignal.zkgroup; public class VerificationFailedException extends Exception { - public VerificationFailedException() { super(); } - public VerificationFailedException(String msg) { super(msg); } + public VerificationFailedException() { + super(); + } + + public VerificationFailedException(String msg) { + super(msg); + } } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredential.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredential.java index e2a5a6a3fb..df5c3cd69d 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredential.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredential.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.auth; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class AuthCredential extends ByteArray { public AuthCredential(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialPresentation.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialPresentation.java index bfc0fbc0d1..e516b1cba7 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialPresentation.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialPresentation.java @@ -6,15 +6,19 @@ package org.signal.libsignal.zkgroup.auth; import java.time.Instant; - +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class AuthCredentialPresentation extends ByteArray { - public enum Version {V1, V2, V3, UNKNOWN}; + public enum Version { + V1, + V2, + V3, + UNKNOWN + }; public AuthCredentialPresentation(byte[] contents) throws InvalidInputException { super(contents); @@ -31,9 +35,7 @@ public UuidCiphertext getUuidCiphertext() { } } - /** - * Returns the PNI ciphertext for this credential. May be {@code null}. - */ + /** Returns the PNI ciphertext for this credential. May be {@code null}. */ public UuidCiphertext getPniCiphertext() { byte[] newContents = Native.AuthCredentialPresentation_GetPniCiphertext(contents); if (newContents == null) { @@ -53,11 +55,14 @@ public Instant getRedemptionTime() { public Version getVersion() { switch (this.contents[0]) { - case 0: return Version.V1; - case 1: return Version.V2; - case 2: return Version.V3; - default: return Version.UNKNOWN; + case 0: + return Version.V1; + case 1: + return Version.V2; + case 2: + return Version.V3; + default: + return Version.UNKNOWN; } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialResponse.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialResponse.java index 838a9cec5a..f51adaedca 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialResponse.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialResponse.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.auth; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class AuthCredentialResponse extends ByteArray { public AuthCredentialResponse(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPni.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPni.java index 6b5a505983..e6c12d575e 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPni.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPni.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.auth; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class AuthCredentialWithPni extends ByteArray { public AuthCredentialWithPni(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPniResponse.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPniResponse.java index c5b1f59c3b..c89cc94979 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPniResponse.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/AuthCredentialWithPniResponse.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.auth; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class AuthCredentialWithPniResponse extends ByteArray { public AuthCredentialWithPniResponse(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/ClientZkAuthOperations.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/ClientZkAuthOperations.java index 13ff92710e..9c12d8547c 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/ClientZkAuthOperations.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/ClientZkAuthOperations.java @@ -5,16 +5,16 @@ package org.signal.libsignal.zkgroup.auth; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.protocol.ServiceId.Pni; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.ServerPublicParams; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public class ClientZkAuthOperations { @@ -24,8 +24,15 @@ public ClientZkAuthOperations(ServerPublicParams serverPublicParams) { this.serverPublicParams = serverPublicParams; } - public AuthCredential receiveAuthCredential(Aci aci, int redemptionTime, AuthCredentialResponse authCredentialResponse) throws VerificationFailedException { - byte[] newContents = Native.ServerPublicParams_ReceiveAuthCredential(serverPublicParams.getInternalContentsForJNI(), aci.toServiceIdFixedWidthBinary(), redemptionTime, authCredentialResponse.getInternalContentsForJNI()); + public AuthCredential receiveAuthCredential( + Aci aci, int redemptionTime, AuthCredentialResponse authCredentialResponse) + throws VerificationFailedException { + byte[] newContents = + Native.ServerPublicParams_ReceiveAuthCredential( + serverPublicParams.getInternalContentsForJNI(), + aci.toServiceIdFixedWidthBinary(), + redemptionTime, + authCredentialResponse.getInternalContentsForJNI()); try { return new AuthCredential(newContents); @@ -36,11 +43,20 @@ public AuthCredential receiveAuthCredential(Aci aci, int redemptionTime, AuthCre /** * Produces the AuthCredentialWithPni from a server-generated AuthCredentialWithPniResponse. - * - * @param redemptionTime This is provided by the server as an integer, and should be passed through directly. + * + * @param redemptionTime This is provided by the server as an integer, and should be passed + * through directly. */ - public AuthCredentialWithPni receiveAuthCredentialWithPniAsServiceId(Aci aci, Pni pni, long redemptionTime, AuthCredentialWithPniResponse authCredentialResponse) throws VerificationFailedException { - byte[] newContents = Native.ServerPublicParams_ReceiveAuthCredentialWithPniAsServiceId(serverPublicParams.getInternalContentsForJNI(), aci.toServiceIdFixedWidthBinary(), pni.toServiceIdFixedWidthBinary(), redemptionTime, authCredentialResponse.getInternalContentsForJNI()); + public AuthCredentialWithPni receiveAuthCredentialWithPniAsServiceId( + Aci aci, Pni pni, long redemptionTime, AuthCredentialWithPniResponse authCredentialResponse) + throws VerificationFailedException { + byte[] newContents = + Native.ServerPublicParams_ReceiveAuthCredentialWithPniAsServiceId( + serverPublicParams.getInternalContentsForJNI(), + aci.toServiceIdFixedWidthBinary(), + pni.toServiceIdFixedWidthBinary(), + redemptionTime, + authCredentialResponse.getInternalContentsForJNI()); try { return new AuthCredentialWithPni(newContents); @@ -52,13 +68,22 @@ public AuthCredentialWithPni receiveAuthCredentialWithPniAsServiceId(Aci aci, Pn /** * Produces the AuthCredentialWithPni from a server-generated AuthCredentialWithPniResponse. * - * This older style of AuthCredentialWithPni will not actually have a usable PNI field, - * but can still be used for authenticating with an ACI. - * - * @param redemptionTime This is provided by the server as an integer, and should be passed through directly. + *

    This older style of AuthCredentialWithPni will not actually have a usable PNI field, but can + * still be used for authenticating with an ACI. + * + * @param redemptionTime This is provided by the server as an integer, and should be passed + * through directly. */ - public AuthCredentialWithPni receiveAuthCredentialWithPniAsAci(Aci aci, Pni pni, long redemptionTime, AuthCredentialWithPniResponse authCredentialResponse) throws VerificationFailedException { - byte[] newContents = Native.ServerPublicParams_ReceiveAuthCredentialWithPniAsAci(serverPublicParams.getInternalContentsForJNI(), aci.toServiceIdFixedWidthBinary(), pni.toServiceIdFixedWidthBinary(), redemptionTime, authCredentialResponse.getInternalContentsForJNI()); + public AuthCredentialWithPni receiveAuthCredentialWithPniAsAci( + Aci aci, Pni pni, long redemptionTime, AuthCredentialWithPniResponse authCredentialResponse) + throws VerificationFailedException { + byte[] newContents = + Native.ServerPublicParams_ReceiveAuthCredentialWithPniAsAci( + serverPublicParams.getInternalContentsForJNI(), + aci.toServiceIdFixedWidthBinary(), + pni.toServiceIdFixedWidthBinary(), + redemptionTime, + authCredentialResponse.getInternalContentsForJNI()); try { return new AuthCredentialWithPni(newContents); @@ -67,15 +92,24 @@ public AuthCredentialWithPni receiveAuthCredentialWithPniAsAci(Aci aci, Pni pni, } } - public AuthCredentialPresentation createAuthCredentialPresentation(GroupSecretParams groupSecretParams, AuthCredential authCredential) { + public AuthCredentialPresentation createAuthCredentialPresentation( + GroupSecretParams groupSecretParams, AuthCredential authCredential) { return createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredential); } - public AuthCredentialPresentation createAuthCredentialPresentation(SecureRandom secureRandom, GroupSecretParams groupSecretParams, AuthCredential authCredential) { - byte[] random = new byte[RANDOM_LENGTH]; + public AuthCredentialPresentation createAuthCredentialPresentation( + SecureRandom secureRandom, + GroupSecretParams groupSecretParams, + AuthCredential authCredential) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerPublicParams_CreateAuthCredentialPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, groupSecretParams.getInternalContentsForJNI(), authCredential.getInternalContentsForJNI()); + byte[] newContents = + Native.ServerPublicParams_CreateAuthCredentialPresentationDeterministic( + serverPublicParams.getInternalContentsForJNI(), + random, + groupSecretParams.getInternalContentsForJNI(), + authCredential.getInternalContentsForJNI()); try { return new AuthCredentialPresentation(newContents); @@ -84,15 +118,24 @@ public AuthCredentialPresentation createAuthCredentialPresentation(SecureRandom } } - public AuthCredentialPresentation createAuthCredentialPresentation(GroupSecretParams groupSecretParams, AuthCredentialWithPni authCredential) { + public AuthCredentialPresentation createAuthCredentialPresentation( + GroupSecretParams groupSecretParams, AuthCredentialWithPni authCredential) { return createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredential); } - public AuthCredentialPresentation createAuthCredentialPresentation(SecureRandom secureRandom, GroupSecretParams groupSecretParams, AuthCredentialWithPni authCredential) { - byte[] random = new byte[RANDOM_LENGTH]; + public AuthCredentialPresentation createAuthCredentialPresentation( + SecureRandom secureRandom, + GroupSecretParams groupSecretParams, + AuthCredentialWithPni authCredential) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerPublicParams_CreateAuthCredentialWithPniPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, groupSecretParams.getInternalContentsForJNI(), authCredential.getInternalContentsForJNI()); + byte[] newContents = + Native.ServerPublicParams_CreateAuthCredentialWithPniPresentationDeterministic( + serverPublicParams.getInternalContentsForJNI(), + random, + groupSecretParams.getInternalContentsForJNI(), + authCredential.getInternalContentsForJNI()); try { return new AuthCredentialPresentation(newContents); diff --git a/java/shared/java/org/signal/libsignal/zkgroup/auth/ServerZkAuthOperations.java b/java/shared/java/org/signal/libsignal/zkgroup/auth/ServerZkAuthOperations.java index 3e834ac990..afc44845e6 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/auth/ServerZkAuthOperations.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/auth/ServerZkAuthOperations.java @@ -5,17 +5,17 @@ package org.signal.libsignal.zkgroup.auth; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.protocol.ServiceId.Pni; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.GroupPublicParams; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public class ServerZkAuthOperations { @@ -29,12 +29,18 @@ public AuthCredentialResponse issueAuthCredential(Aci aci, int redemptionTime) { return issueAuthCredential(new SecureRandom(), aci, redemptionTime); } - public AuthCredentialResponse issueAuthCredential(SecureRandom secureRandom, Aci aci, int redemptionTime) { - byte[] random = new byte[RANDOM_LENGTH]; + public AuthCredentialResponse issueAuthCredential( + SecureRandom secureRandom, Aci aci, int redemptionTime) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerSecretParams_IssueAuthCredentialDeterministic(serverSecretParams.getInternalContentsForJNI(), random, aci.toServiceIdFixedWidthBinary(), redemptionTime); + byte[] newContents = + Native.ServerSecretParams_IssueAuthCredentialDeterministic( + serverSecretParams.getInternalContentsForJNI(), + random, + aci.toServiceIdFixedWidthBinary(), + redemptionTime); try { return new AuthCredentialResponse(newContents); @@ -43,16 +49,24 @@ public AuthCredentialResponse issueAuthCredential(SecureRandom secureRandom, Aci } } - public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsServiceId(Aci aci, Pni pni, Instant redemptionTime) { + public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsServiceId( + Aci aci, Pni pni, Instant redemptionTime) { return issueAuthCredentialWithPniAsServiceId(new SecureRandom(), aci, pni, redemptionTime); } - public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsServiceId(SecureRandom secureRandom, Aci aci, Pni pni, Instant redemptionTime) { - byte[] random = new byte[RANDOM_LENGTH]; + public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsServiceId( + SecureRandom secureRandom, Aci aci, Pni pni, Instant redemptionTime) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerSecretParams_IssueAuthCredentialWithPniAsServiceIdDeterministic(serverSecretParams.getInternalContentsForJNI(), random, aci.toServiceIdFixedWidthBinary(), pni.toServiceIdFixedWidthBinary(), redemptionTime.getEpochSecond()); + byte[] newContents = + Native.ServerSecretParams_IssueAuthCredentialWithPniAsServiceIdDeterministic( + serverSecretParams.getInternalContentsForJNI(), + random, + aci.toServiceIdFixedWidthBinary(), + pni.toServiceIdFixedWidthBinary(), + redemptionTime.getEpochSecond()); try { return new AuthCredentialWithPniResponse(newContents); @@ -61,16 +75,24 @@ public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsServiceId(Secur } } - public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsAci(Aci aci, Pni pni, Instant redemptionTime) { + public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsAci( + Aci aci, Pni pni, Instant redemptionTime) { return issueAuthCredentialWithPniAsAci(new SecureRandom(), aci, pni, redemptionTime); } - public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsAci(SecureRandom secureRandom, Aci aci, Pni pni, Instant redemptionTime) { - byte[] random = new byte[RANDOM_LENGTH]; + public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsAci( + SecureRandom secureRandom, Aci aci, Pni pni, Instant redemptionTime) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerSecretParams_IssueAuthCredentialWithPniAsAciDeterministic(serverSecretParams.getInternalContentsForJNI(), random, aci.toServiceIdFixedWidthBinary(), pni.toServiceIdFixedWidthBinary(), redemptionTime.getEpochSecond()); + byte[] newContents = + Native.ServerSecretParams_IssueAuthCredentialWithPniAsAciDeterministic( + serverSecretParams.getInternalContentsForJNI(), + random, + aci.toServiceIdFixedWidthBinary(), + pni.toServiceIdFixedWidthBinary(), + redemptionTime.getEpochSecond()); try { return new AuthCredentialWithPniResponse(newContents); @@ -79,12 +101,21 @@ public AuthCredentialWithPniResponse issueAuthCredentialWithPniAsAci(SecureRando } } - public void verifyAuthCredentialPresentation(GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation) throws VerificationFailedException { - verifyAuthCredentialPresentation(groupPublicParams, authCredentialPresentation, Instant.now()); - } - - public void verifyAuthCredentialPresentation(GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation, Instant currentTime) throws VerificationFailedException { - Native.ServerSecretParams_VerifyAuthCredentialPresentation(serverSecretParams.getInternalContentsForJNI(), groupPublicParams.getInternalContentsForJNI(), authCredentialPresentation.getInternalContentsForJNI(), currentTime.getEpochSecond()); + public void verifyAuthCredentialPresentation( + GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation) + throws VerificationFailedException { + verifyAuthCredentialPresentation(groupPublicParams, authCredentialPresentation, Instant.now()); } + public void verifyAuthCredentialPresentation( + GroupPublicParams groupPublicParams, + AuthCredentialPresentation authCredentialPresentation, + Instant currentTime) + throws VerificationFailedException { + Native.ServerSecretParams_VerifyAuthCredentialPresentation( + serverSecretParams.getInternalContentsForJNI(), + groupPublicParams.getInternalContentsForJNI(), + authCredentialPresentation.getInternalContentsForJNI(), + currentTime.getEpochSecond()); + } } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredential.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredential.java index ffb17b5b29..65896a7001 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredential.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredential.java @@ -5,16 +5,15 @@ package org.signal.libsignal.zkgroup.calllinks; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + +import java.security.SecureRandom; +import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.GenericServerPublicParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.security.SecureRandom; -import java.time.Instant; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public final class CallLinkAuthCredential extends ByteArray { @@ -23,15 +22,31 @@ public CallLinkAuthCredential(byte[] contents) throws InvalidInputException { Native.CallLinkAuthCredential_CheckValidContents(contents); } - public CallLinkAuthCredentialPresentation present(Aci userId, Instant redemptionTime, GenericServerPublicParams serverParams, CallLinkSecretParams callLinkParams) { + public CallLinkAuthCredentialPresentation present( + Aci userId, + Instant redemptionTime, + GenericServerPublicParams serverParams, + CallLinkSecretParams callLinkParams) { return present(userId, redemptionTime, serverParams, callLinkParams, new SecureRandom()); } - public CallLinkAuthCredentialPresentation present(Aci userId, Instant redemptionTime, GenericServerPublicParams serverParams, CallLinkSecretParams callLinkParams, SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + public CallLinkAuthCredentialPresentation present( + Aci userId, + Instant redemptionTime, + GenericServerPublicParams serverParams, + CallLinkSecretParams callLinkParams, + SecureRandom secureRandom) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.CallLinkAuthCredential_PresentDeterministic(getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary(), redemptionTime.getEpochSecond(), serverParams.getInternalContentsForJNI(), callLinkParams.getInternalContentsForJNI(), random); + byte[] newContents = + Native.CallLinkAuthCredential_PresentDeterministic( + getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary(), + redemptionTime.getEpochSecond(), + serverParams.getInternalContentsForJNI(), + callLinkParams.getInternalContentsForJNI(), + random); try { return new CallLinkAuthCredentialPresentation(newContents); @@ -39,5 +54,4 @@ public CallLinkAuthCredentialPresentation present(Aci userId, Instant redemption throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialPresentation.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialPresentation.java index 168a0fa3fc..dfcbb1aa47 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialPresentation.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialPresentation.java @@ -5,14 +5,13 @@ package org.signal.libsignal.zkgroup.calllinks; +import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.GenericServerSecretParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.time.Instant; public final class CallLinkAuthCredentialPresentation extends ByteArray { @@ -21,12 +20,21 @@ public CallLinkAuthCredentialPresentation(byte[] contents) throws InvalidInputEx Native.CallLinkAuthCredentialPresentation_CheckValidContents(contents); } - public void verify(GenericServerSecretParams serverParams, CallLinkPublicParams callLinkParams) throws VerificationFailedException { + public void verify(GenericServerSecretParams serverParams, CallLinkPublicParams callLinkParams) + throws VerificationFailedException { verify(Instant.now(), serverParams, callLinkParams); } - public void verify(Instant currentTime, GenericServerSecretParams serverParams, CallLinkPublicParams callLinkParams) throws VerificationFailedException { - Native.CallLinkAuthCredentialPresentation_Verify(getInternalContentsForJNI(), currentTime.getEpochSecond(), serverParams.getInternalContentsForJNI(), callLinkParams.getInternalContentsForJNI()); + public void verify( + Instant currentTime, + GenericServerSecretParams serverParams, + CallLinkPublicParams callLinkParams) + throws VerificationFailedException { + Native.CallLinkAuthCredentialPresentation_Verify( + getInternalContentsForJNI(), + currentTime.getEpochSecond(), + serverParams.getInternalContentsForJNI(), + callLinkParams.getInternalContentsForJNI()); } public UuidCiphertext getUserId() { @@ -38,5 +46,4 @@ public UuidCiphertext getUserId() { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialResponse.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialResponse.java index bc30f14898..8738ca9ed7 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialResponse.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkAuthCredentialResponse.java @@ -5,18 +5,17 @@ package org.signal.libsignal.zkgroup.calllinks; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + +import java.security.SecureRandom; +import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; -import org.signal.libsignal.zkgroup.InvalidInputException; -import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.GenericServerPublicParams; import org.signal.libsignal.zkgroup.GenericServerSecretParams; +import org.signal.libsignal.zkgroup.InvalidInputException; +import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.security.SecureRandom; -import java.time.Instant; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public final class CallLinkAuthCredentialResponse extends ByteArray { public CallLinkAuthCredentialResponse(byte[] contents) throws InvalidInputException { @@ -24,15 +23,25 @@ public CallLinkAuthCredentialResponse(byte[] contents) throws InvalidInputExcept Native.CallLinkAuthCredentialResponse_CheckValidContents(contents); } - public static CallLinkAuthCredentialResponse issueCredential(Aci userId, Instant redemptionTime, GenericServerSecretParams params) { + public static CallLinkAuthCredentialResponse issueCredential( + Aci userId, Instant redemptionTime, GenericServerSecretParams params) { return issueCredential(userId, redemptionTime, params, new SecureRandom()); } - public static CallLinkAuthCredentialResponse issueCredential(Aci userId, Instant redemptionTime, GenericServerSecretParams params, SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + public static CallLinkAuthCredentialResponse issueCredential( + Aci userId, + Instant redemptionTime, + GenericServerSecretParams params, + SecureRandom secureRandom) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.CallLinkAuthCredentialResponse_IssueDeterministic(userId.toServiceIdFixedWidthBinary(), redemptionTime.getEpochSecond(), params.getInternalContentsForJNI(), random); + byte[] newContents = + Native.CallLinkAuthCredentialResponse_IssueDeterministic( + userId.toServiceIdFixedWidthBinary(), + redemptionTime.getEpochSecond(), + params.getInternalContentsForJNI(), + random); try { return new CallLinkAuthCredentialResponse(newContents); @@ -41,8 +50,15 @@ public static CallLinkAuthCredentialResponse issueCredential(Aci userId, Instant } } - public CallLinkAuthCredential receive(Aci userId, Instant redemptionTime, GenericServerPublicParams params) throws VerificationFailedException { - byte[] newContents = Native.CallLinkAuthCredentialResponse_Receive(getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary(), redemptionTime.getEpochSecond(), params.getInternalContentsForJNI()); + public CallLinkAuthCredential receive( + Aci userId, Instant redemptionTime, GenericServerPublicParams params) + throws VerificationFailedException { + byte[] newContents = + Native.CallLinkAuthCredentialResponse_Receive( + getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary(), + redemptionTime.getEpochSecond(), + params.getInternalContentsForJNI()); try { return new CallLinkAuthCredential(newContents); diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkPublicParams.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkPublicParams.java index 25934eab8f..0511356616 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkPublicParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkPublicParams.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.calllinks; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class CallLinkPublicParams extends ByteArray { public CallLinkPublicParams(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkSecretParams.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkSecretParams.java index 9732949f6a..41b268a189 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkSecretParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CallLinkSecretParams.java @@ -5,13 +5,13 @@ package org.signal.libsignal.zkgroup.calllinks; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class CallLinkSecretParams extends ByteArray { @@ -22,10 +22,10 @@ public static CallLinkSecretParams deriveFromRootKey(byte[] rootKey) { return new CallLinkSecretParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } - public CallLinkSecretParams(byte[] contents) throws InvalidInputException { + public CallLinkSecretParams(byte[] contents) throws InvalidInputException { super(contents); Native.CallLinkSecretParams_CheckValidContents(contents); } @@ -42,10 +42,11 @@ public CallLinkPublicParams getPublicParams() { public Aci decryptUserId(UuidCiphertext ciphertext) throws VerificationFailedException { try { - return Aci.parseFromFixedWidthBinary(Native.CallLinkSecretParams_DecryptUserId(getInternalContentsForJNI(), ciphertext.getInternalContentsForJNI())); + return Aci.parseFromFixedWidthBinary( + Native.CallLinkSecretParams_DecryptUserId( + getInternalContentsForJNI(), ciphertext.getInternalContentsForJNI())); } catch (ServiceId.InvalidServiceIdException e) { throw new VerificationFailedException(); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredential.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredential.java index 562a10b802..3eaebef054 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredential.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredential.java @@ -5,16 +5,14 @@ package org.signal.libsignal.zkgroup.calllinks; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + +import java.security.SecureRandom; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.GenericServerPublicParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.security.SecureRandom; -import java.time.Instant; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public final class CreateCallLinkCredential extends ByteArray { @@ -23,15 +21,31 @@ public CreateCallLinkCredential(byte[] contents) throws InvalidInputException { Native.CreateCallLinkCredential_CheckValidContents(contents); } - public CreateCallLinkCredentialPresentation present(byte[] roomId, Aci userId, GenericServerPublicParams serverParams, CallLinkSecretParams callLinkParams) { + public CreateCallLinkCredentialPresentation present( + byte[] roomId, + Aci userId, + GenericServerPublicParams serverParams, + CallLinkSecretParams callLinkParams) { return present(roomId, userId, serverParams, callLinkParams, new SecureRandom()); } - public CreateCallLinkCredentialPresentation present(byte[] roomId, Aci userId, GenericServerPublicParams serverParams, CallLinkSecretParams callLinkParams, SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + public CreateCallLinkCredentialPresentation present( + byte[] roomId, + Aci userId, + GenericServerPublicParams serverParams, + CallLinkSecretParams callLinkParams, + SecureRandom secureRandom) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.CreateCallLinkCredential_PresentDeterministic(getInternalContentsForJNI(), roomId, userId.toServiceIdFixedWidthBinary(), serverParams.getInternalContentsForJNI(), callLinkParams.getInternalContentsForJNI(), random); + byte[] newContents = + Native.CreateCallLinkCredential_PresentDeterministic( + getInternalContentsForJNI(), + roomId, + userId.toServiceIdFixedWidthBinary(), + serverParams.getInternalContentsForJNI(), + callLinkParams.getInternalContentsForJNI(), + random); try { return new CreateCallLinkCredentialPresentation(newContents); @@ -39,5 +53,4 @@ public CreateCallLinkCredentialPresentation present(byte[] roomId, Aci userId, G throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialPresentation.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialPresentation.java index 0f0e3dd1ae..a56ce27c1d 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialPresentation.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialPresentation.java @@ -5,13 +5,12 @@ package org.signal.libsignal.zkgroup.calllinks; +import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.GenericServerSecretParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.time.Instant; public final class CreateCallLinkCredentialPresentation extends ByteArray { @@ -20,12 +19,23 @@ public CreateCallLinkCredentialPresentation(byte[] contents) throws InvalidInput Native.CreateCallLinkCredentialPresentation_CheckValidContents(contents); } - public void verify(byte[] roomId, GenericServerSecretParams serverParams, CallLinkPublicParams callLinkParams) throws VerificationFailedException { + public void verify( + byte[] roomId, GenericServerSecretParams serverParams, CallLinkPublicParams callLinkParams) + throws VerificationFailedException { verify(roomId, Instant.now(), serverParams, callLinkParams); } - public void verify(byte[] roomId, Instant currentTime, GenericServerSecretParams serverParams, CallLinkPublicParams callLinkParams) throws VerificationFailedException { - Native.CreateCallLinkCredentialPresentation_Verify(getInternalContentsForJNI(), roomId, currentTime.getEpochSecond(), serverParams.getInternalContentsForJNI(), callLinkParams.getInternalContentsForJNI()); + public void verify( + byte[] roomId, + Instant currentTime, + GenericServerSecretParams serverParams, + CallLinkPublicParams callLinkParams) + throws VerificationFailedException { + Native.CreateCallLinkCredentialPresentation_Verify( + getInternalContentsForJNI(), + roomId, + currentTime.getEpochSecond(), + serverParams.getInternalContentsForJNI(), + callLinkParams.getInternalContentsForJNI()); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequest.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequest.java index ec4b13e557..d4628321b5 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequest.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequest.java @@ -5,16 +5,15 @@ package org.signal.libsignal.zkgroup.calllinks; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + +import java.security.SecureRandom; +import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.GenericServerSecretParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.security.SecureRandom; -import java.time.Instant; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public final class CreateCallLinkCredentialRequest extends ByteArray { @@ -28,31 +27,38 @@ public CreateCallLinkCredentialRequest(byte[] contents) throws InvalidInputExcep * * @param userId The ACI of the user who will be creating the call link. * @param timestamp Must be a round number of days. Use {@link java.time.Instant#truncatedTo} to - * ensure this. + * ensure this. * @param params The params that will be used by the calling server to verify this credential. */ - public CreateCallLinkCredentialResponse issueCredential(Aci userId, Instant timestamp, GenericServerSecretParams params) { + public CreateCallLinkCredentialResponse issueCredential( + Aci userId, Instant timestamp, GenericServerSecretParams params) { return issueCredential(userId, timestamp, params, new SecureRandom()); } /** * Issues a CreateCallLinkCredential, using a dedicated source of randomness. * - * This can be used to make tests deterministic. Prefer - * {@link #issueCredential(Aci, Instant, GenericServerSecretParams)} if the source of randomness - * doesn't matter. + *

    This can be used to make tests deterministic. Prefer {@link #issueCredential(Aci, Instant, + * GenericServerSecretParams)} if the source of randomness doesn't matter. * * @param userId The ACI of the user who will be creating the call link. * @param timestamp Must be a round number of days. Use {@link java.time.Instant#truncatedTo} to - * ensure this. + * ensure this. * @param params The params that will be used by the calling server to verify this credential. * @param secureRandom Used to hide the server's secrets and make the issued credential unique. */ - public CreateCallLinkCredentialResponse issueCredential(Aci userId, Instant timestamp, GenericServerSecretParams params, SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + public CreateCallLinkCredentialResponse issueCredential( + Aci userId, Instant timestamp, GenericServerSecretParams params, SecureRandom secureRandom) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.CreateCallLinkCredentialRequest_IssueDeterministic(getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary(), timestamp.getEpochSecond(), params.getInternalContentsForJNI(), random); + byte[] newContents = + Native.CreateCallLinkCredentialRequest_IssueDeterministic( + getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary(), + timestamp.getEpochSecond(), + params.getInternalContentsForJNI(), + random); try { return new CreateCallLinkCredentialResponse(newContents); @@ -60,5 +66,4 @@ public CreateCallLinkCredentialResponse issueCredential(Aci userId, Instant time throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequestContext.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequestContext.java index fa353b8ef6..3d95a15c9b 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequestContext.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialRequestContext.java @@ -5,16 +5,15 @@ package org.signal.libsignal.zkgroup.calllinks; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + +import java.security.SecureRandom; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.GenericServerPublicParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.security.SecureRandom; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public final class CreateCallLinkCredentialRequestContext extends ByteArray { @@ -27,17 +26,19 @@ public static CreateCallLinkCredentialRequestContext forRoom(byte[] roomId) { return forRoom(roomId, new SecureRandom()); } - public static CreateCallLinkCredentialRequestContext forRoom(byte[] roomId, SecureRandom secureRandom) { + public static CreateCallLinkCredentialRequestContext forRoom( + byte[] roomId, SecureRandom secureRandom) { byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.CreateCallLinkCredentialRequestContext_NewDeterministic(roomId, random); + byte[] newContents = + Native.CreateCallLinkCredentialRequestContext_NewDeterministic(roomId, random); try { return new CreateCallLinkCredentialRequestContext(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } public CreateCallLinkCredentialRequest getRequest() { @@ -50,8 +51,15 @@ public CreateCallLinkCredentialRequest getRequest() { } } - public CreateCallLinkCredential receiveResponse(CreateCallLinkCredentialResponse response, Aci userId, GenericServerPublicParams params) throws VerificationFailedException { - byte[] newContents = Native.CreateCallLinkCredentialRequestContext_ReceiveResponse(getInternalContentsForJNI(), response.getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary(), params.getInternalContentsForJNI()); + public CreateCallLinkCredential receiveResponse( + CreateCallLinkCredentialResponse response, Aci userId, GenericServerPublicParams params) + throws VerificationFailedException { + byte[] newContents = + Native.CreateCallLinkCredentialRequestContext_ReceiveResponse( + getInternalContentsForJNI(), + response.getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary(), + params.getInternalContentsForJNI()); try { return new CreateCallLinkCredential(newContents); @@ -59,5 +67,4 @@ public CreateCallLinkCredential receiveResponse(CreateCallLinkCredentialResponse throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialResponse.java b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialResponse.java index 1415f76a3e..f04ae091fa 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialResponse.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/calllinks/CreateCallLinkCredentialResponse.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.calllinks; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class CreateCallLinkCredentialResponse extends ByteArray { public CreateCallLinkCredentialResponse(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/ClientZkGroupCipher.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/ClientZkGroupCipher.java index c85b49b7f2..08e675b02a 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/ClientZkGroupCipher.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/ClientZkGroupCipher.java @@ -5,17 +5,15 @@ package org.signal.libsignal.zkgroup.groups; -import java.nio.ByteBuffer; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; -import java.util.UUID; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; -import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.profiles.ProfileKey; -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; - public class ClientZkGroupCipher { private final GroupSecretParams groupSecretParams; @@ -25,7 +23,9 @@ public ClientZkGroupCipher(GroupSecretParams groupSecretParams) { } public UuidCiphertext encrypt(ServiceId serviceId) { - byte[] newContents = Native.GroupSecretParams_EncryptServiceId(groupSecretParams.getInternalContentsForJNI(), serviceId.toServiceIdFixedWidthBinary()); + byte[] newContents = + Native.GroupSecretParams_EncryptServiceId( + groupSecretParams.getInternalContentsForJNI(), serviceId.toServiceIdFixedWidthBinary()); try { return new UuidCiphertext(newContents); @@ -36,14 +36,21 @@ public UuidCiphertext encrypt(ServiceId serviceId) { public ServiceId decrypt(UuidCiphertext uuidCiphertext) throws VerificationFailedException { try { - return ServiceId.parseFromFixedWidthBinary(Native.GroupSecretParams_DecryptServiceId(groupSecretParams.getInternalContentsForJNI(), uuidCiphertext.getInternalContentsForJNI())); + return ServiceId.parseFromFixedWidthBinary( + Native.GroupSecretParams_DecryptServiceId( + groupSecretParams.getInternalContentsForJNI(), + uuidCiphertext.getInternalContentsForJNI())); } catch (ServiceId.InvalidServiceIdException e) { throw new VerificationFailedException(); } } public ProfileKeyCiphertext encryptProfileKey(ProfileKey profileKey, ServiceId.Aci userId) { - byte[] newContents = Native.GroupSecretParams_EncryptProfileKey(groupSecretParams.getInternalContentsForJNI(), profileKey.getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary()); + byte[] newContents = + Native.GroupSecretParams_EncryptProfileKey( + groupSecretParams.getInternalContentsForJNI(), + profileKey.getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary()); try { return new ProfileKeyCiphertext(newContents); @@ -52,8 +59,14 @@ public ProfileKeyCiphertext encryptProfileKey(ProfileKey profileKey, ServiceId.A } } - public ProfileKey decryptProfileKey(ProfileKeyCiphertext profileKeyCiphertext, ServiceId.Aci userId) throws VerificationFailedException { - byte[] newContents = Native.GroupSecretParams_DecryptProfileKey(groupSecretParams.getInternalContentsForJNI(), profileKeyCiphertext.getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary()); + public ProfileKey decryptProfileKey( + ProfileKeyCiphertext profileKeyCiphertext, ServiceId.Aci userId) + throws VerificationFailedException { + byte[] newContents = + Native.GroupSecretParams_DecryptProfileKey( + groupSecretParams.getInternalContentsForJNI(), + profileKeyCiphertext.getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary()); try { return new ProfileKey(newContents); @@ -66,14 +79,16 @@ public byte[] encryptBlob(byte[] plaintext) throws VerificationFailedException { return encryptBlob(new SecureRandom(), plaintext); } - public byte[] encryptBlob(SecureRandom secureRandom, byte[] plaintext) throws VerificationFailedException { - byte[] random = new byte[RANDOM_LENGTH]; + public byte[] encryptBlob(SecureRandom secureRandom, byte[] plaintext) + throws VerificationFailedException { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - return Native.GroupSecretParams_EncryptBlobWithPaddingDeterministic(groupSecretParams.getInternalContentsForJNI(), random, plaintext, 0); + return Native.GroupSecretParams_EncryptBlobWithPaddingDeterministic( + groupSecretParams.getInternalContentsForJNI(), random, plaintext, 0); } public byte[] decryptBlob(byte[] blobCiphertext) throws VerificationFailedException { - return Native.GroupSecretParams_DecryptBlobWithPadding(groupSecretParams.getInternalContentsForJNI(), blobCiphertext); + return Native.GroupSecretParams_DecryptBlobWithPadding( + groupSecretParams.getInternalContentsForJNI(), blobCiphertext); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupIdentifier.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupIdentifier.java index e080a6606f..19b1448f11 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupIdentifier.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupIdentifier.java @@ -15,5 +15,4 @@ public final class GroupIdentifier extends ByteArray { public GroupIdentifier(byte[] contents) throws InvalidInputException { super(contents, SIZE); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupMasterKey.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupMasterKey.java index 2bf3a9502e..73ac754541 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupMasterKey.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupMasterKey.java @@ -15,5 +15,4 @@ public final class GroupMasterKey extends ByteArray { public GroupMasterKey(byte[] contents) throws InvalidInputException { super(contents, SIZE); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupPublicParams.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupPublicParams.java index 5edd948c90..2c40431dc4 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupPublicParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupPublicParams.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.groups; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class GroupPublicParams extends ByteArray { @@ -25,5 +25,4 @@ public GroupIdentifier getGroupIdentifier() { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupSecretParams.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupSecretParams.java index 1c241ef873..2bb1f3aff1 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupSecretParams.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/GroupSecretParams.java @@ -5,12 +5,12 @@ package org.signal.libsignal.zkgroup.groups; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public final class GroupSecretParams extends ByteArray { @@ -19,7 +19,7 @@ public static GroupSecretParams generate() { } public static GroupSecretParams generate(SecureRandom secureRandom) { - byte[] random = new byte[RANDOM_LENGTH]; + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); byte[] newContents = Native.GroupSecretParams_GenerateDeterministic(random); @@ -28,20 +28,21 @@ public static GroupSecretParams generate(SecureRandom secureRandom) { return new GroupSecretParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } public static GroupSecretParams deriveFromMasterKey(GroupMasterKey groupMasterKey) { - byte[] newContents = Native.GroupSecretParams_DeriveFromMasterKey(groupMasterKey.getInternalContentsForJNI()); + byte[] newContents = + Native.GroupSecretParams_DeriveFromMasterKey(groupMasterKey.getInternalContentsForJNI()); try { return new GroupSecretParams(newContents); } catch (InvalidInputException e) { throw new AssertionError(e); - } + } } - public GroupSecretParams(byte[] contents) throws InvalidInputException { + public GroupSecretParams(byte[] contents) throws InvalidInputException { super(contents); Native.GroupSecretParams_CheckValidContents(contents); } @@ -65,5 +66,4 @@ public GroupPublicParams getPublicParams() { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/ProfileKeyCiphertext.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/ProfileKeyCiphertext.java index 74c8d372c0..877f0dc606 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/ProfileKeyCiphertext.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/ProfileKeyCiphertext.java @@ -5,13 +5,13 @@ package org.signal.libsignal.zkgroup.groups; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ProfileKeyCiphertext extends ByteArray { public ProfileKeyCiphertext(byte[] contents) throws InvalidInputException { super(contents); - Native.ProfileKeyCiphertext_CheckValidContents(contents); + Native.ProfileKeyCiphertext_CheckValidContents(contents); } } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/groups/UuidCiphertext.java b/java/shared/java/org/signal/libsignal/zkgroup/groups/UuidCiphertext.java index 64edf60c69..5aa2dfec2f 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/groups/UuidCiphertext.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/groups/UuidCiphertext.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.groups; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class UuidCiphertext extends ByteArray { public UuidCiphertext(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/internal/ByteArray.java b/java/shared/java/org/signal/libsignal/zkgroup/internal/ByteArray.java index e12f89fde3..900761b1b0 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/internal/ByteArray.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/internal/ByteArray.java @@ -5,10 +5,9 @@ package org.signal.libsignal.zkgroup.internal; -import org.signal.libsignal.zkgroup.InvalidInputException; - import java.util.Arrays; import java.util.Locale; +import org.signal.libsignal.zkgroup.InvalidInputException; public abstract class ByteArray { @@ -22,9 +21,15 @@ protected ByteArray(byte[] contents, int expectedLength) throws InvalidInputExce this.contents = cloneArrayOfLength(contents, expectedLength); } - private static byte[] cloneArrayOfLength(byte[] bytes, int expectedLength) throws InvalidInputException { + private static byte[] cloneArrayOfLength(byte[] bytes, int expectedLength) + throws InvalidInputException { if (bytes.length != expectedLength) { - throw new InvalidInputException(String.format(Locale.US, "Length of array supplied was %d expected %d", bytes.length, expectedLength)); + throw new InvalidInputException( + String.format( + Locale.US, + "Length of array supplied was %d expected %d", + bytes.length, + expectedLength)); } return bytes.clone(); diff --git a/java/shared/java/org/signal/libsignal/zkgroup/internal/Constants.java b/java/shared/java/org/signal/libsignal/zkgroup/internal/Constants.java index 5130d91606..effd7f75c2 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/internal/Constants.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/internal/Constants.java @@ -6,5 +6,5 @@ package org.signal.libsignal.zkgroup.internal; public class Constants { - public static final int RANDOM_LENGTH = 32; + public static final int RANDOM_LENGTH = 32; } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ClientZkProfileOperations.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ClientZkProfileOperations.java index d9ea7ac6b4..aca43a80e2 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ClientZkProfileOperations.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ClientZkProfileOperations.java @@ -5,16 +5,16 @@ package org.signal.libsignal.zkgroup.profiles; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.ServerPublicParams; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public class ClientZkProfileOperations { @@ -24,15 +24,22 @@ public ClientZkProfileOperations(ServerPublicParams serverPublicParams) { this.serverPublicParams = serverPublicParams; } - public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestContext(Aci userId, ProfileKey profileKey) { + public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestContext( + Aci userId, ProfileKey profileKey) { return createProfileKeyCredentialRequestContext(new SecureRandom(), userId, profileKey); } - public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestContext(SecureRandom secureRandom, Aci userId, ProfileKey profileKey) { - byte[] random = new byte[RANDOM_LENGTH]; + public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestContext( + SecureRandom secureRandom, Aci userId, ProfileKey profileKey) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerPublicParams_CreateProfileKeyCredentialRequestContextDeterministic(serverPublicParams.getInternalContentsForJNI(), random, userId.toServiceIdFixedWidthBinary(), profileKey.getInternalContentsForJNI()); + byte[] newContents = + Native.ServerPublicParams_CreateProfileKeyCredentialRequestContextDeterministic( + serverPublicParams.getInternalContentsForJNI(), + random, + userId.toServiceIdFixedWidthBinary(), + profileKey.getInternalContentsForJNI()); try { return new ProfileKeyCredentialRequestContext(newContents); @@ -41,16 +48,29 @@ public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestConte } } - public ExpiringProfileKeyCredential receiveExpiringProfileKeyCredential(ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext, ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse) throws VerificationFailedException { - return receiveExpiringProfileKeyCredential(profileKeyCredentialRequestContext, profileKeyCredentialResponse, Instant.now()); + public ExpiringProfileKeyCredential receiveExpiringProfileKeyCredential( + ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext, + ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse) + throws VerificationFailedException { + return receiveExpiringProfileKeyCredential( + profileKeyCredentialRequestContext, profileKeyCredentialResponse, Instant.now()); } - public ExpiringProfileKeyCredential receiveExpiringProfileKeyCredential(ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext, ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse, Instant now) throws VerificationFailedException { + public ExpiringProfileKeyCredential receiveExpiringProfileKeyCredential( + ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext, + ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse, + Instant now) + throws VerificationFailedException { if (profileKeyCredentialResponse == null) { throw new VerificationFailedException(); } - byte[] newContents = Native.ServerPublicParams_ReceiveExpiringProfileKeyCredential(serverPublicParams.getInternalContentsForJNI(), profileKeyCredentialRequestContext.getInternalContentsForJNI(), profileKeyCredentialResponse.getInternalContentsForJNI(), now.getEpochSecond()); + byte[] newContents = + Native.ServerPublicParams_ReceiveExpiringProfileKeyCredential( + serverPublicParams.getInternalContentsForJNI(), + profileKeyCredentialRequestContext.getInternalContentsForJNI(), + profileKeyCredentialResponse.getInternalContentsForJNI(), + now.getEpochSecond()); try { return new ExpiringProfileKeyCredential(newContents); @@ -59,15 +79,25 @@ public ExpiringProfileKeyCredential receiveExpiringProfileKeyCredential(ProfileK } } - public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation(GroupSecretParams groupSecretParams, ExpiringProfileKeyCredential profileKeyCredential) { - return createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, profileKeyCredential); + public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation( + GroupSecretParams groupSecretParams, ExpiringProfileKeyCredential profileKeyCredential) { + return createProfileKeyCredentialPresentation( + new SecureRandom(), groupSecretParams, profileKeyCredential); } - public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation(SecureRandom secureRandom, GroupSecretParams groupSecretParams, ExpiringProfileKeyCredential profileKeyCredential) { - byte[] random = new byte[RANDOM_LENGTH]; + public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation( + SecureRandom secureRandom, + GroupSecretParams groupSecretParams, + ExpiringProfileKeyCredential profileKeyCredential) { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerPublicParams_CreateExpiringProfileKeyCredentialPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, groupSecretParams.getInternalContentsForJNI(), profileKeyCredential.getInternalContentsForJNI()); + byte[] newContents = + Native.ServerPublicParams_CreateExpiringProfileKeyCredentialPresentationDeterministic( + serverPublicParams.getInternalContentsForJNI(), + random, + groupSecretParams.getInternalContentsForJNI(), + profileKeyCredential.getInternalContentsForJNI()); try { return new ProfileKeyCredentialPresentation(newContents); @@ -75,5 +105,4 @@ public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation(S throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredential.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredential.java index 40d02949e5..ca34e2b8fd 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredential.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredential.java @@ -5,19 +5,19 @@ package org.signal.libsignal.zkgroup.profiles; +import java.time.Instant; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; - -import java.time.Instant; public final class ExpiringProfileKeyCredential extends ByteArray { public ExpiringProfileKeyCredential(byte[] contents) throws InvalidInputException { super(contents); Native.ExpiringProfileKeyCredential_CheckValidContents(contents); } - + public Instant getExpirationTime() { - return Instant.ofEpochSecond(Native.ExpiringProfileKeyCredential_GetExpirationTime(this.contents)); + return Instant.ofEpochSecond( + Native.ExpiringProfileKeyCredential_GetExpirationTime(this.contents)); } } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredentialResponse.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredentialResponse.java index 6e872cd06f..e47c92a256 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredentialResponse.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ExpiringProfileKeyCredentialResponse.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.profiles; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ExpiringProfileKeyCredentialResponse extends ByteArray { public ExpiringProfileKeyCredentialResponse(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKey.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKey.java index 4713d7a43d..8f1bf94e39 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKey.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKey.java @@ -5,11 +5,10 @@ package org.signal.libsignal.zkgroup.profiles; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.InvalidInputException; -import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ProfileKey extends ByteArray { @@ -19,7 +18,8 @@ public ProfileKey(byte[] contents) throws InvalidInputException { } public ProfileKeyCommitment getCommitment(Aci userId) { - byte[] newContents = Native.ProfileKey_GetCommitment(contents, userId.toServiceIdFixedWidthBinary()); + byte[] newContents = + Native.ProfileKey_GetCommitment(contents, userId.toServiceIdFixedWidthBinary()); try { return new ProfileKeyCommitment(newContents); @@ -29,7 +29,8 @@ public ProfileKeyCommitment getCommitment(Aci userId) { } public ProfileKeyVersion getProfileKeyVersion(Aci userId) { - byte[] newContents = Native.ProfileKey_GetProfileKeyVersion(contents, userId.toServiceIdFixedWidthBinary()); + byte[] newContents = + Native.ProfileKey_GetProfileKeyVersion(contents, userId.toServiceIdFixedWidthBinary()); try { return new ProfileKeyVersion(newContents); @@ -41,5 +42,4 @@ public ProfileKeyVersion getProfileKeyVersion(Aci userId) { public byte[] deriveAccessKey() { return Native.ProfileKey_DeriveAccessKey(contents); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCommitment.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCommitment.java index 5ea4150ebc..4def88cff9 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCommitment.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCommitment.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.profiles; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ProfileKeyCommitment extends ByteArray { public ProfileKeyCommitment(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialPresentation.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialPresentation.java index 4beb1965d2..1f15f6255d 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialPresentation.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialPresentation.java @@ -5,15 +5,20 @@ package org.signal.libsignal.zkgroup.profiles; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.groups.ProfileKeyCiphertext; import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ProfileKeyCredentialPresentation extends ByteArray { - public enum Version {V1, V2, V3, UNKNOWN}; + public enum Version { + V1, + V2, + V3, + UNKNOWN + }; public ProfileKeyCredentialPresentation(byte[] contents) throws InvalidInputException { super(contents); @@ -41,16 +46,20 @@ public ProfileKeyCiphertext getProfileKeyCiphertext() { } public byte[] getStructurallyValidV1PresentationBytes() { - return Native.ProfileKeyCredentialPresentation_GetStructurallyValidV1PresentationBytes(contents); + return Native.ProfileKeyCredentialPresentation_GetStructurallyValidV1PresentationBytes( + contents); } public Version getVersion() { switch (this.contents[0]) { - case 0: return Version.V1; - case 1: return Version.V2; - case 2: return Version.V3; - default: return Version.UNKNOWN; + case 0: + return Version.V1; + case 1: + return Version.V2; + case 2: + return Version.V3; + default: + return Version.UNKNOWN; } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequest.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequest.java index 9ccebe039b..0f9c702052 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequest.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequest.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.profiles; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ProfileKeyCredentialRequest extends ByteArray { public ProfileKeyCredentialRequest(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequestContext.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequestContext.java index ecf442fffc..d0a68a2d35 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequestContext.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyCredentialRequestContext.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.profiles; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ProfileKeyCredentialRequestContext extends ByteArray { public ProfileKeyCredentialRequestContext(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyVersion.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyVersion.java index f0426227dc..a00e0f3bba 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyVersion.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ProfileKeyVersion.java @@ -5,9 +5,8 @@ package org.signal.libsignal.zkgroup.profiles; -import org.signal.libsignal.zkgroup.InvalidInputException; -import org.signal.libsignal.zkgroup.internal.ByteArray; import java.io.UnsupportedEncodingException; +import org.signal.libsignal.zkgroup.InvalidInputException; public final class ProfileKeyVersion { @@ -20,7 +19,8 @@ public ProfileKeyVersion(byte[] contents) throws InvalidInputException { this.contents = contents.clone(); } - public ProfileKeyVersion(String contents) throws InvalidInputException, UnsupportedEncodingException { + public ProfileKeyVersion(String contents) + throws InvalidInputException, UnsupportedEncodingException { this(contents.getBytes("UTF-8")); } @@ -31,5 +31,4 @@ public String serialize() { throw new AssertionError(); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ServerZkProfileOperations.java b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ServerZkProfileOperations.java index c4aed98f89..1c26312aac 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/profiles/ServerZkProfileOperations.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/profiles/ServerZkProfileOperations.java @@ -5,17 +5,17 @@ package org.signal.libsignal.zkgroup.profiles; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; import java.time.Instant; import java.time.temporal.ChronoUnit; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.protocol.ServiceId.Aci; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.groups.GroupPublicParams; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public class ServerZkProfileOperations { @@ -25,23 +25,42 @@ public ServerZkProfileOperations(ServerSecretParams serverSecretParams) { this.serverSecretParams = serverSecretParams; } - public ExpiringProfileKeyCredentialResponse issueExpiringProfileKeyCredential(ProfileKeyCredentialRequest profileKeyCredentialRequest, Aci userId, ProfileKeyCommitment profileKeyCommitment, Instant expiration) throws VerificationFailedException { - return issueExpiringProfileKeyCredential(new SecureRandom(), profileKeyCredentialRequest, userId, profileKeyCommitment, expiration); + public ExpiringProfileKeyCredentialResponse issueExpiringProfileKeyCredential( + ProfileKeyCredentialRequest profileKeyCredentialRequest, + Aci userId, + ProfileKeyCommitment profileKeyCommitment, + Instant expiration) + throws VerificationFailedException { + return issueExpiringProfileKeyCredential( + new SecureRandom(), profileKeyCredentialRequest, userId, profileKeyCommitment, expiration); } /** * Issues an ExpiringProfileKeyCredential. * * @param expiration Must be a round number of days. Use {@link java.time.Instant#truncatedTo} to - * ensure this. + * ensure this. */ - public ExpiringProfileKeyCredentialResponse issueExpiringProfileKeyCredential(SecureRandom secureRandom, ProfileKeyCredentialRequest profileKeyCredentialRequest, Aci userId, ProfileKeyCommitment profileKeyCommitment, Instant expiration) throws VerificationFailedException { + public ExpiringProfileKeyCredentialResponse issueExpiringProfileKeyCredential( + SecureRandom secureRandom, + ProfileKeyCredentialRequest profileKeyCredentialRequest, + Aci userId, + ProfileKeyCommitment profileKeyCommitment, + Instant expiration) + throws VerificationFailedException { assert expiration.equals(expiration.truncatedTo(ChronoUnit.DAYS)); - byte[] random = new byte[RANDOM_LENGTH]; + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerSecretParams_IssueExpiringProfileKeyCredentialDeterministic(serverSecretParams.getInternalContentsForJNI(), random, profileKeyCredentialRequest.getInternalContentsForJNI(), userId.toServiceIdFixedWidthBinary(), profileKeyCommitment.getInternalContentsForJNI(), expiration.getEpochSecond()); + byte[] newContents = + Native.ServerSecretParams_IssueExpiringProfileKeyCredentialDeterministic( + serverSecretParams.getInternalContentsForJNI(), + random, + profileKeyCredentialRequest.getInternalContentsForJNI(), + userId.toServiceIdFixedWidthBinary(), + profileKeyCommitment.getInternalContentsForJNI(), + expiration.getEpochSecond()); try { return new ExpiringProfileKeyCredentialResponse(newContents); @@ -50,12 +69,23 @@ public ExpiringProfileKeyCredentialResponse issueExpiringProfileKeyCredential(Se } } - public void verifyProfileKeyCredentialPresentation(GroupPublicParams groupPublicParams, ProfileKeyCredentialPresentation profileKeyCredentialPresentation) throws VerificationFailedException { - verifyProfileKeyCredentialPresentation(groupPublicParams, profileKeyCredentialPresentation, Instant.now()); + public void verifyProfileKeyCredentialPresentation( + GroupPublicParams groupPublicParams, + ProfileKeyCredentialPresentation profileKeyCredentialPresentation) + throws VerificationFailedException { + verifyProfileKeyCredentialPresentation( + groupPublicParams, profileKeyCredentialPresentation, Instant.now()); } - public void verifyProfileKeyCredentialPresentation(GroupPublicParams groupPublicParams, ProfileKeyCredentialPresentation profileKeyCredentialPresentation, Instant now) throws VerificationFailedException { - Native.ServerSecretParams_VerifyProfileKeyCredentialPresentation(serverSecretParams.getInternalContentsForJNI(), groupPublicParams.getInternalContentsForJNI(), profileKeyCredentialPresentation.getInternalContentsForJNI(), now.getEpochSecond()); + public void verifyProfileKeyCredentialPresentation( + GroupPublicParams groupPublicParams, + ProfileKeyCredentialPresentation profileKeyCredentialPresentation, + Instant now) + throws VerificationFailedException { + Native.ServerSecretParams_VerifyProfileKeyCredentialPresentation( + serverSecretParams.getInternalContentsForJNI(), + groupPublicParams.getInternalContentsForJNI(), + profileKeyCredentialPresentation.getInternalContentsForJNI(), + now.getEpochSecond()); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ClientZkReceiptOperations.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ClientZkReceiptOperations.java index b444932cc7..d65ef66275 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ClientZkReceiptOperations.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ClientZkReceiptOperations.java @@ -5,13 +5,13 @@ package org.signal.libsignal.zkgroup.receipts; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.ServerPublicParams; import org.signal.libsignal.zkgroup.VerificationFailedException; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public class ClientZkReceiptOperations { @@ -21,15 +21,21 @@ public ClientZkReceiptOperations(ServerPublicParams serverPublicParams) { this.serverPublicParams = serverPublicParams; } - public ReceiptCredentialRequestContext createReceiptCredentialRequestContext(ReceiptSerial receiptSerial) throws VerificationFailedException { + public ReceiptCredentialRequestContext createReceiptCredentialRequestContext( + ReceiptSerial receiptSerial) throws VerificationFailedException { return createReceiptCredentialRequestContext(new SecureRandom(), receiptSerial); } - public ReceiptCredentialRequestContext createReceiptCredentialRequestContext(SecureRandom secureRandom, ReceiptSerial receiptSerial) throws VerificationFailedException { - byte[] random = new byte[RANDOM_LENGTH]; + public ReceiptCredentialRequestContext createReceiptCredentialRequestContext( + SecureRandom secureRandom, ReceiptSerial receiptSerial) throws VerificationFailedException { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerPublicParams_CreateReceiptCredentialRequestContextDeterministic(serverPublicParams.getInternalContentsForJNI(), random, receiptSerial.getInternalContentsForJNI()); + byte[] newContents = + Native.ServerPublicParams_CreateReceiptCredentialRequestContextDeterministic( + serverPublicParams.getInternalContentsForJNI(), + random, + receiptSerial.getInternalContentsForJNI()); try { return new ReceiptCredentialRequestContext(newContents); @@ -38,8 +44,15 @@ public ReceiptCredentialRequestContext createReceiptCredentialRequestContext(Sec } } - public ReceiptCredential receiveReceiptCredential(ReceiptCredentialRequestContext receiptCredentialRequestContext, ReceiptCredentialResponse receiptCredentialResponse) throws VerificationFailedException { - byte[] newContents = Native.ServerPublicParams_ReceiveReceiptCredential(serverPublicParams.getInternalContentsForJNI(), receiptCredentialRequestContext.getInternalContentsForJNI(), receiptCredentialResponse.getInternalContentsForJNI()); + public ReceiptCredential receiveReceiptCredential( + ReceiptCredentialRequestContext receiptCredentialRequestContext, + ReceiptCredentialResponse receiptCredentialResponse) + throws VerificationFailedException { + byte[] newContents = + Native.ServerPublicParams_ReceiveReceiptCredential( + serverPublicParams.getInternalContentsForJNI(), + receiptCredentialRequestContext.getInternalContentsForJNI(), + receiptCredentialResponse.getInternalContentsForJNI()); try { return new ReceiptCredential(newContents); @@ -48,15 +61,22 @@ public ReceiptCredential receiveReceiptCredential(ReceiptCredentialRequestContex } } - public ReceiptCredentialPresentation createReceiptCredentialPresentation(ReceiptCredential receiptCredential) throws VerificationFailedException { + public ReceiptCredentialPresentation createReceiptCredentialPresentation( + ReceiptCredential receiptCredential) throws VerificationFailedException { return createReceiptCredentialPresentation(new SecureRandom(), receiptCredential); } - public ReceiptCredentialPresentation createReceiptCredentialPresentation(SecureRandom secureRandom, ReceiptCredential receiptCredential) throws VerificationFailedException { - byte[] random = new byte[RANDOM_LENGTH]; + public ReceiptCredentialPresentation createReceiptCredentialPresentation( + SecureRandom secureRandom, ReceiptCredential receiptCredential) + throws VerificationFailedException { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerPublicParams_CreateReceiptCredentialPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, receiptCredential.getInternalContentsForJNI()); + byte[] newContents = + Native.ServerPublicParams_CreateReceiptCredentialPresentationDeterministic( + serverPublicParams.getInternalContentsForJNI(), + random, + receiptCredential.getInternalContentsForJNI()); try { return new ReceiptCredentialPresentation(newContents); @@ -64,5 +84,4 @@ public ReceiptCredentialPresentation createReceiptCredentialPresentation(SecureR throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredential.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredential.java index 67bde0e1e4..0607f4ddc4 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredential.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredential.java @@ -5,10 +5,9 @@ package org.signal.libsignal.zkgroup.receipts; -import java.nio.ByteBuffer; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ReceiptCredential extends ByteArray { @@ -24,5 +23,4 @@ public long getReceiptExpirationTime() { public long getReceiptLevel() { return Native.ReceiptCredential_GetReceiptLevel(contents); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialPresentation.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialPresentation.java index 67c7ce8321..9df1a949d5 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialPresentation.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialPresentation.java @@ -5,10 +5,9 @@ package org.signal.libsignal.zkgroup.receipts; -import java.nio.ByteBuffer; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ReceiptCredentialPresentation extends ByteArray { public ReceiptCredentialPresentation(byte[] contents) throws InvalidInputException { @@ -33,5 +32,4 @@ public ReceiptSerial getReceiptSerial() { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequest.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequest.java index 0120fc1900..55ee2e82e6 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequest.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequest.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.receipts; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ReceiptCredentialRequest extends ByteArray { public ReceiptCredentialRequest(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequestContext.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequestContext.java index b746cde056..494efc30c3 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequestContext.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialRequestContext.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.receipts; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ReceiptCredentialRequestContext extends ByteArray { @@ -27,5 +27,4 @@ public ReceiptCredentialRequest getRequest() { throw new AssertionError(e); } } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialResponse.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialResponse.java index 3dc7db637b..d07c3c4724 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialResponse.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptCredentialResponse.java @@ -5,9 +5,9 @@ package org.signal.libsignal.zkgroup.receipts; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.internal.ByteArray; -import org.signal.libsignal.internal.Native; public final class ReceiptCredentialResponse extends ByteArray { public ReceiptCredentialResponse(byte[] contents) throws InvalidInputException { diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptSerial.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptSerial.java index 6edf7cb2e6..98ea332225 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptSerial.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ReceiptSerial.java @@ -15,5 +15,4 @@ public final class ReceiptSerial extends ByteArray { public ReceiptSerial(byte[] contents) throws InvalidInputException { super(contents, SIZE); } - } diff --git a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ServerZkReceiptOperations.java b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ServerZkReceiptOperations.java index 21b4888054..9247768969 100644 --- a/java/shared/java/org/signal/libsignal/zkgroup/receipts/ServerZkReceiptOperations.java +++ b/java/shared/java/org/signal/libsignal/zkgroup/receipts/ServerZkReceiptOperations.java @@ -5,13 +5,13 @@ package org.signal.libsignal.zkgroup.receipts; +import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; + import java.security.SecureRandom; +import org.signal.libsignal.internal.Native; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.VerificationFailedException; -import org.signal.libsignal.internal.Native; - -import static org.signal.libsignal.zkgroup.internal.Constants.RANDOM_LENGTH; public class ServerZkReceiptOperations { @@ -21,15 +21,31 @@ public ServerZkReceiptOperations(ServerSecretParams serverSecretParams) { this.serverSecretParams = serverSecretParams; } - public ReceiptCredentialResponse issueReceiptCredential(ReceiptCredentialRequest receiptCredentialRequest, long receiptExpirationTime, long receiptLevel) throws VerificationFailedException { - return issueReceiptCredential(new SecureRandom(), receiptCredentialRequest, receiptExpirationTime, receiptLevel); + public ReceiptCredentialResponse issueReceiptCredential( + ReceiptCredentialRequest receiptCredentialRequest, + long receiptExpirationTime, + long receiptLevel) + throws VerificationFailedException { + return issueReceiptCredential( + new SecureRandom(), receiptCredentialRequest, receiptExpirationTime, receiptLevel); } - public ReceiptCredentialResponse issueReceiptCredential(SecureRandom secureRandom, ReceiptCredentialRequest receiptCredentialRequest, long receiptExpirationTime, long receiptLevel) throws VerificationFailedException { - byte[] random = new byte[RANDOM_LENGTH]; + public ReceiptCredentialResponse issueReceiptCredential( + SecureRandom secureRandom, + ReceiptCredentialRequest receiptCredentialRequest, + long receiptExpirationTime, + long receiptLevel) + throws VerificationFailedException { + byte[] random = new byte[RANDOM_LENGTH]; secureRandom.nextBytes(random); - byte[] newContents = Native.ServerSecretParams_IssueReceiptCredentialDeterministic(serverSecretParams.getInternalContentsForJNI(), random, receiptCredentialRequest.getInternalContentsForJNI(), receiptExpirationTime, receiptLevel); + byte[] newContents = + Native.ServerSecretParams_IssueReceiptCredentialDeterministic( + serverSecretParams.getInternalContentsForJNI(), + random, + receiptCredentialRequest.getInternalContentsForJNI(), + receiptExpirationTime, + receiptLevel); try { return new ReceiptCredentialResponse(newContents); @@ -38,8 +54,11 @@ public ReceiptCredentialResponse issueReceiptCredential(SecureRandom secureRando } } - public void verifyReceiptCredentialPresentation(ReceiptCredentialPresentation receiptCredentialPresentation) throws VerificationFailedException { - Native.ServerSecretParams_VerifyReceiptCredentialPresentation(serverSecretParams.getInternalContentsForJNI(), receiptCredentialPresentation.getInternalContentsForJNI()); + public void verifyReceiptCredentialPresentation( + ReceiptCredentialPresentation receiptCredentialPresentation) + throws VerificationFailedException { + Native.ServerSecretParams_VerifyReceiptCredentialPresentation( + serverSecretParams.getInternalContentsForJNI(), + receiptCredentialPresentation.getInternalContentsForJNI()); } - } diff --git a/node/Native.d.ts b/node/Native.d.ts index 1400d4482f..47df64ff2f 100644 --- a/node/Native.d.ts +++ b/node/Native.d.ts @@ -313,16 +313,16 @@ export function ServiceId_ParseFromServiceIdString(input: string): Buffer; export function ServiceId_ServiceIdBinary(value: Buffer): Buffer; export function ServiceId_ServiceIdLog(value: Buffer): string; export function ServiceId_ServiceIdString(value: Buffer): string; -export function SessionBuilder_ProcessPreKeyBundle(bundle: Wrapper, protocolAddress: Wrapper, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore): Promise; +export function SessionBuilder_ProcessPreKeyBundle(bundle: Wrapper, protocolAddress: Wrapper, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore, now: Timestamp): Promise; export function SessionCipher_DecryptPreKeySignalMessage(message: Wrapper, protocolAddress: Wrapper, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore, prekeyStore: PreKeyStore, signedPrekeyStore: SignedPreKeyStore, kyberPrekeyStore: KyberPreKeyStore): Promise; export function SessionCipher_DecryptSignalMessage(message: Wrapper, protocolAddress: Wrapper, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore): Promise; -export function SessionCipher_EncryptMessage(ptext: Buffer, protocolAddress: Wrapper, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore): Promise; +export function SessionCipher_EncryptMessage(ptext: Buffer, protocolAddress: Wrapper, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore, now: Timestamp): Promise; export function SessionRecord_ArchiveCurrentState(sessionRecord: Wrapper): void; export function SessionRecord_CurrentRatchetKeyMatches(s: Wrapper, key: Wrapper): boolean; export function SessionRecord_Deserialize(data: Buffer): SessionRecord; export function SessionRecord_GetLocalRegistrationId(obj: Wrapper): number; export function SessionRecord_GetRemoteRegistrationId(obj: Wrapper): number; -export function SessionRecord_HasCurrentState(obj: Wrapper): boolean; +export function SessionRecord_HasUsableSenderChain(s: Wrapper, now: Timestamp): boolean; export function SessionRecord_Serialize(obj: Wrapper): Buffer; export function SgxClientState_CompleteHandshake(cli: Wrapper, handshakeReceived: Buffer): void; export function SgxClientState_EstablishedRecv(cli: Wrapper, receivedCiphertext: Buffer): Buffer; @@ -359,9 +359,9 @@ export function Username_Hash(username: string): Buffer; export function Username_Proof(username: string, randomness: Buffer): Buffer; export function Username_Verify(proof: Buffer, hash: Buffer): void; export function UuidCiphertext_CheckValidContents(buffer: Buffer): void; -export function ValidatingMac_Finalize(mac: Wrapper): boolean; +export function ValidatingMac_Finalize(mac: Wrapper): number; export function ValidatingMac_Initialize(key: Buffer, chunkSize: number, digests: Buffer): ValidatingMac; -export function ValidatingMac_Update(mac: Wrapper, bytes: Buffer, offset: number, length: number): boolean; +export function ValidatingMac_Update(mac: Wrapper, bytes: Buffer, offset: number, length: number): number; export function initLogger(maxLevel: LogLevel, callback: (level: LogLevel, target: string, file: string | null, line: number | null, message: string) => void): void interface Aes256GcmSiv { readonly __type: unique symbol; } interface AuthCredential { readonly __type: unique symbol; } diff --git a/node/build_node_bridge.py b/node/build_node_bridge.py index bfa0cb4f4d..823b17c9ec 100755 --- a/node/build_node_bridge.py +++ b/node/build_node_bridge.py @@ -69,6 +69,7 @@ def main(args=None): print("Running '%s'" % (' '.join(cmdline))) cargo_env = os.environ.copy() + cargo_env['RUSTFLAGS'] = cargo_env.get('RUSTFLAGS') or '' cargo_env['CARGO_BUILD_TARGET_DIR'] = options.cargo_build_dir cargo_env['MACOSX_DEPLOYMENT_TARGET'] = '10.13' # On Linux, cdylibs don't include public symbols from their dependencies, @@ -76,11 +77,13 @@ def main(args=None): # Using LTO works around this at the cost of a slightly slower build. # https://github.com/rust-lang/rfcs/issues/2771 cargo_env['CARGO_PROFILE_RELEASE_LTO'] = 'thin' + # Enable ARMv8 cryptography acceleration when available + cargo_env['RUSTFLAGS'] += ' --cfg aes_armv8 --cfg polyval_armv8' if node_os_name == 'win32': # By default, Rust on Windows depends on an MSVC component for the C runtime. # Link it statically to avoid propagating that dependency. - cargo_env['RUSTFLAGS'] = '-C target-feature=+crt-static' + cargo_env['RUSTFLAGS'] += ' -C target-feature=+crt-static' abs_build_dir = os.path.abspath(options.cargo_build_dir) if 'GITHUB_WORKSPACE' in cargo_env: diff --git a/node/package.json b/node/package.json index 6d4b734515..c0220b07be 100644 --- a/node/package.json +++ b/node/package.json @@ -1,6 +1,6 @@ { "name": "@signalapp/libsignal-client", - "version": "0.31.0", + "version": "0.32.1", "license": "AGPL-3.0-only", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/node/ts/incremental_mac.ts b/node/ts/incremental_mac.ts index eae0edaf56..70945dd0a2 100644 --- a/node/ts/incremental_mac.ts +++ b/node/ts/incremental_mac.ts @@ -65,6 +65,8 @@ export class DigestingWritable extends stream.Writable { export class ValidatingWritable extends stream.Writable { _nativeHandle: Native.ValidatingMac; + _validatedBytes = 0; + constructor(key: Buffer, sizeChoice: ChunkSizeChoice, digest: Buffer) { super(); this._nativeHandle = Native.ValidatingMac_Initialize( @@ -74,6 +76,10 @@ export class ValidatingWritable extends stream.Writable { ); } + validatedSize(): number { + return this._validatedBytes; + } + _write( // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types chunk: any, @@ -82,7 +88,14 @@ export class ValidatingWritable extends stream.Writable { ): void { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const buffer = Buffer.from(chunk, 'binary'); - if (Native.ValidatingMac_Update(this, buffer, 0, buffer.length)) { + const validBytes = Native.ValidatingMac_Update( + this, + buffer, + 0, + buffer.length + ); + if (validBytes >= 0) { + this._validatedBytes += validBytes; callback(); } else { callback(makeVerificationError('Corrupted input data')); @@ -90,7 +103,9 @@ export class ValidatingWritable extends stream.Writable { } _final(callback: (error?: Error | null) => void): void { - if (Native.ValidatingMac_Finalize(this)) { + const validBytes = Native.ValidatingMac_Finalize(this); + if (validBytes >= 0) { + this._validatedBytes += validBytes; callback(); } else { callback(makeVerificationError('Corrupted input data (finalize)')); diff --git a/node/ts/index.ts b/node/ts/index.ts index 43144fe8f1..657f7ec967 100644 --- a/node/ts/index.ts +++ b/node/ts/index.ts @@ -748,8 +748,13 @@ export class SessionRecord { return Native.SessionRecord_GetRemoteRegistrationId(this); } - hasCurrentState(): boolean { - return Native.SessionRecord_HasCurrentState(this); + /** + * Returns whether the current session can be used to send messages. + * + * If there is no current session, returns false. + */ + hasCurrentState(now: Date = new Date()): boolean { + return Native.SessionRecord_HasUsableSenderChain(this, now.getTime()); } currentRatchetKeyMatches(key: PublicKey): boolean { @@ -1500,13 +1505,15 @@ export function processPreKeyBundle( bundle: PreKeyBundle, address: ProtocolAddress, sessionStore: SessionStore, - identityStore: IdentityKeyStore + identityStore: IdentityKeyStore, + now: Date = new Date() ): Promise { return Native.SessionBuilder_ProcessPreKeyBundle( bundle, address, sessionStore, - identityStore + identityStore, + now.getTime() ); } @@ -1514,14 +1521,16 @@ export async function signalEncrypt( message: Buffer, address: ProtocolAddress, sessionStore: SessionStore, - identityStore: IdentityKeyStore + identityStore: IdentityKeyStore, + now: Date = new Date() ): Promise { return CiphertextMessage._fromNativeHandle( await Native.SessionCipher_EncryptMessage( message, address, sessionStore, - identityStore + identityStore, + now.getTime() ) ); } diff --git a/node/ts/test/IncrementalMacTest.ts b/node/ts/test/IncrementalMacTest.ts index a2437187d2..e578045322 100644 --- a/node/ts/test/IncrementalMacTest.ts +++ b/node/ts/test/IncrementalMacTest.ts @@ -36,10 +36,14 @@ const CHUNK_SIZE = everyNthByte(32); describe('Incremental MAC', () => { it('calculates the chunk size', () => { - assert.equal(42, chunkSizeInBytes(inferChunkSize(42))); - assert.equal(1024, chunkSizeInBytes(inferChunkSize(1024))); - assert.equal(8192, chunkSizeInBytes(inferChunkSize(10 * 1024))); - assert.equal(3276800, chunkSizeInBytes(inferChunkSize(100 * 1024 * 1024))); + assert.equal(64 * 1024, chunkSizeInBytes(inferChunkSize(0))); + assert.equal(64 * 1024, chunkSizeInBytes(inferChunkSize(42))); + assert.equal(64 * 1024, chunkSizeInBytes(inferChunkSize(1024))); + assert.equal(64 * 1024, chunkSizeInBytes(inferChunkSize(10 * 1024))); + assert.equal( + 400 * 1024, + chunkSizeInBytes(inferChunkSize(100 * 1024 * 1024)) + ); }); describe('DigestingWritable', () => { @@ -106,6 +110,22 @@ describe('Incremental MAC', () => { ); await assert.isRejected(promise, LibSignalErrorBase); }); + + it('keeps track of validated size', () => { + const validating = new ValidatingWritable( + TEST_KEY, + CHUNK_SIZE, + TEST_DIGEST + ); + validating.write(Buffer.from(TEST_INPUT[0])); + assert.equal(0, validating.validatedSize()); + validating.write(Buffer.from(TEST_INPUT[1])); + assert.equal(32, validating.validatedSize()); + validating.write(Buffer.from(TEST_INPUT[2])); + assert.equal(32, validating.validatedSize()); + validating.end(); + assert.equal(50, validating.validatedSize()); + }); }); }); diff --git a/node/ts/test/PublicAPITest.ts b/node/ts/test/PublicAPITest.ts index d3bb63e40e..bc4848e68b 100644 --- a/node/ts/test/PublicAPITest.ts +++ b/node/ts/test/PublicAPITest.ts @@ -1158,6 +1158,55 @@ describe('SignalClient', () => { assert.exists(err.stack); // Make sure we're still getting the benefits of Error. } }); + + it('expires unacknowledged sessions', async () => { + const aliceStores = new TestStores(); + const bobStores = new TestStores(); + + const bAddress = SignalClient.ProtocolAddress.new('+19192222222', 1); + + const bPreKeyBundle = await testCase.makeBundle(bAddress, bobStores); + + await SignalClient.processPreKeyBundle( + bPreKeyBundle, + bAddress, + aliceStores.session, + aliceStores.identity, + new Date('2020-01-01') + ); + + const initialSession = await aliceStores.session.getSession(bAddress); + assert.isTrue(initialSession?.hasCurrentState(new Date('2020-01-01'))); + assert.isFalse(initialSession?.hasCurrentState(new Date('2023-01-01'))); + + const aMessage = Buffer.from('Greetings hoo-man', 'utf8'); + const aCiphertext = await SignalClient.signalEncrypt( + aMessage, + bAddress, + aliceStores.session, + aliceStores.identity, + new Date('2020-01-01') + ); + + assert.deepEqual( + aCiphertext.type(), + SignalClient.CiphertextMessageType.PreKey + ); + + const updatedSession = await aliceStores.session.getSession(bAddress); + assert.isTrue(updatedSession?.hasCurrentState(new Date('2020-01-01'))); + assert.isFalse(updatedSession?.hasCurrentState(new Date('2023-01-01'))); + + await assert.isRejected( + SignalClient.signalEncrypt( + aMessage, + bAddress, + aliceStores.session, + aliceStores.identity, + new Date('2023-01-01') + ) + ); + }); }); } diff --git a/rust-toolchain b/rust-toolchain index 852204764e..2540aa02c2 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2023-03-17 +nightly-2023-09-01 diff --git a/rust/attest/Cargo.toml b/rust/attest/Cargo.toml index 0331e224ad..77b23fe2e3 100644 --- a/rust/attest/Cargo.toml +++ b/rust/attest/Cargo.toml @@ -7,7 +7,7 @@ name = "attest" version = "0.1.0" authors = ["Signal Messenger LLC"] -edition = "2018" +edition = "2021" license = "AGPL-3.0-only" [dependencies] @@ -16,8 +16,9 @@ boring-sys = { git = "https://github.com/signalapp/boring", branch = "libsignal" asn1 = "0.15.4" bitflags = "2.3.3" -chacha20poly1305 = "0.9.1" +chacha20poly1305 = "0.10.1" chrono = { version = "0.4", features = ["serde"] } +ciborium = "0.2" displaydoc = "0.2" hex = { version = "0.4", features = ["serde"] } hex-literal = "0.4.1" @@ -30,16 +31,15 @@ rand_core = { version = "0.6", features = ["getrandom"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } sha2 = "0.10" -# Here and elsewhere version 0.9.0 allows patching in snow's 'master' branch -# See top-level Cargo.toml -snow = { version = "0.9.0", default-features = false } +snow = { version = "0.9.3", default-features = false } static_assertions = "1.1" +subtle = "2.5" uuid = "1.1.2" variant_count = "1.1" -x25519-dalek = "2.0.0-rc.3" +x25519-dalek = "2.0.0" [dev-dependencies] -snow = { version = "0.9.0", features = ["default-resolver"] } +snow = { version = "0.9.3", features = ["default-resolver"] } [build-dependencies] prost-build = "0.9" diff --git a/rust/attest/src/cds2.rs b/rust/attest/src/cds2.rs index 88712a1a5b..f291e637e7 100644 --- a/rust/attest/src/cds2.rs +++ b/rust/attest/src/cds2.rs @@ -4,27 +4,29 @@ // use std::collections::HashMap; -use std::convert::From; use hex_literal::hex; -use lazy_static::lazy_static; use prost::Message; use sgx_session::Result; use crate::dcap::MREnclave; use crate::proto::cds2; +use crate::util::SmallMap; use crate::{dcap, sgx_session}; -lazy_static! { - /// Map from MREnclave to intel SW advisories that are known to be mitigated in the - /// build with that MREnclave value - static ref ACCEPTABLE_SW_ADVISORIES: HashMap = { - HashMap::from([ - (hex!("7b75dd6e862decef9b37132d54be082441917a7790e82fe44f9cf653de03a75f"), &["INTEL-SA-00657"] as &[&str]), - (hex!("0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"), &["INTEL-SA-00615", "INTEL-SA-00657"] as &[&str]), - ]) - }; -} +/// Map from MREnclave to intel SW advisories that are known to be mitigated in the +/// build with that MREnclave value. +const ACCEPTABLE_SW_ADVISORIES: &SmallMap = + &SmallMap::new([ + ( + hex!("7b75dd6e862decef9b37132d54be082441917a7790e82fe44f9cf653de03a75f"), + &["INTEL-SA-00657"] as &[&str], + ), + ( + hex!("0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"), + &["INTEL-SA-00615", "INTEL-SA-00657"] as &[&str], + ), + ]); /// SW advisories known to be mitigated by default. If an MREnclave is provided that /// is not contained in `ACCEPTABLE_SW_ADVISORIES`, this will be used diff --git a/rust/attest/src/ias.rs b/rust/attest/src/ias.rs index 08c9777eb4..6544789334 100644 --- a/rust/attest/src/ias.rs +++ b/rust/attest/src/ias.rs @@ -84,7 +84,7 @@ pub fn verify_signature( } impl CertChain { - const COMMON_NAME: &str = "Intel SGX Attestation Report Signing"; + const COMMON_NAME: &'static str = "Intel SGX Attestation Report Signing"; const ORGANIZATION_NAME: &'static str = "Intel Corporation"; const LOCALITY_NAME: &'static str = "Santa Clara"; const STATE_NAME: &'static str = "CA"; diff --git a/rust/attest/src/lib.rs b/rust/attest/src/lib.rs index 6e4b4a091f..eac4fd5241 100644 --- a/rust/attest/src/lib.rs +++ b/rust/attest/src/lib.rs @@ -10,6 +10,7 @@ pub mod hsm_enclave; pub mod ias; pub mod sgx_session; pub mod svr2; +pub mod svr3; mod endian; mod error; diff --git a/rust/attest/src/snow_resolver.rs b/rust/attest/src/snow_resolver.rs index 41fe57f23c..0f6515d665 100644 --- a/rust/attest/src/snow_resolver.rs +++ b/rust/attest/src/snow_resolver.rs @@ -5,8 +5,7 @@ use std::convert::TryInto; -use chacha20poly1305::aead::{AeadInPlace, NewAead}; -use chacha20poly1305::ChaCha20Poly1305; +use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit}; use rand_core::{CryptoRng, RngCore}; use sha2::{Digest, Sha256}; use snow::error::Error as SnowError; diff --git a/rust/attest/src/svr2.rs b/rust/attest/src/svr2.rs index 756d7405ef..20c5a6c1fb 100644 --- a/rust/attest/src/svr2.rs +++ b/rust/attest/src/svr2.rs @@ -3,25 +3,29 @@ // SPDX-License-Identifier: AGPL-3.0-only // -use std::collections::HashMap; - use hex_literal::hex; -use lazy_static::lazy_static; + use prost::Message; use crate::dcap::MREnclave; use crate::proto::svr2; use crate::sgx_session; use crate::sgx_session::{Error, Result}; - -lazy_static! { - /// Map from MREnclave to intel SW advisories that are known to be mitigated in the - /// build with that MREnclave value - static ref ACCEPTABLE_SW_ADVISORIES: HashMap = HashMap::from([ - (hex!("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"), &["INTEL-SA-00615", "INTEL-SA-00657"] as &[&str]), - (hex!("6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094"), &["INTEL-SA-00615", "INTEL-SA-00657"] as &[&str]), +use crate::util::SmallMap; + +/// Map from MREnclave to intel SW advisories that are known to be mitigated in the +/// build with that MREnclave value +const ACCEPTABLE_SW_ADVISORIES: &SmallMap = + &SmallMap::new([ + ( + hex!("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"), + &["INTEL-SA-00615", "INTEL-SA-00657"] as &[&str], + ), + ( + hex!("6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094"), + &["INTEL-SA-00615", "INTEL-SA-00657"] as &[&str], + ), ]); -} /// SW advisories known to be mitigated by default. If an MREnclave is provided that /// is not contained in `ACCEPTABLE_SW_ADVISORIES`, this will be used @@ -45,23 +49,27 @@ impl PartialEq for RaftConfig { } } -lazy_static! { - /// Expected raft configuration for a given enclave. - static ref EXPECTED_RAFT_CONFIG: HashMap = HashMap::from([ - (hex!("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"), &RaftConfig { +/// Expected raft configuration for a given enclave. +static EXPECTED_RAFT_CONFIG: SmallMap = SmallMap::new([ + ( + hex!("a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"), + &RaftConfig { min_voting_replicas: 3, max_voting_replicas: 5, super_majority: 0, - group_id: 15525669046665930652 - }), - (hex!("6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094"), &RaftConfig { + group_id: 15525669046665930652, + }, + ), + ( + hex!("6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094"), + &RaftConfig { min_voting_replicas: 4, max_voting_replicas: 7, super_majority: 2, - group_id: 3950115602363750357 - }), - ]); -} + group_id: 3950115602363750357, + }, + ), +]); pub struct Svr2Handshake { /// The attested handshake that can be used to establish a noise connection diff --git a/rust/attest/src/svr3.rs b/rust/attest/src/svr3.rs new file mode 100644 index 0000000000..4bf5a1bba8 --- /dev/null +++ b/rust/attest/src/svr3.rs @@ -0,0 +1,5 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +pub mod nitro; diff --git a/rust/attest/src/svr3/nitro.rs b/rust/attest/src/svr3/nitro.rs new file mode 100644 index 0000000000..555e77734a --- /dev/null +++ b/rust/attest/src/svr3/nitro.rs @@ -0,0 +1,603 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// +use boring::bn::BigNum; +use boring::ecdsa::EcdsaSig; +use boring::stack; +use boring::x509::store::X509StoreBuilder; +use boring::x509::{X509StoreContext, X509}; +use ciborium::value::{Integer, Value}; +use sha2::{Digest, Sha384}; +use std::collections::HashMap; +use std::time::SystemTime; +use subtle::ConstantTimeEq; + +const PUBLIC_KEY_LENGTH: usize = 32; + +pub type PublicKeyBytes = [u8; PUBLIC_KEY_LENGTH]; + +pub fn attest( + evidence: &[u8], + expected_pcrs: &HashMap>, + now: SystemTime, +) -> Result { + let cose_sign1 = CoseSign1::from_bytes(evidence)?; + let doc = cose_sign1.extract_attestation_doc(now)?; + doc.extract_public_key(expected_pcrs) +} + +#[derive(Debug, displaydoc::Display, PartialEq, Eq)] +pub enum NitroError { + /// Invalid CBOR + InvalidCbor, + /// Invalid COSE_Sign1 + InvalidCoseSign1, + /// Invalid signature + InvalidSignature, + /// Invalid attestation document + InvalidAttestationDoc, + /// Invalid certificate: {0} + InvalidCertificate(String), + /// Invalid PCRs + InvalidPcrs, + /// Invalid Public Key + InvalidPublicKey, +} + +impl std::error::Error for NitroError {} + +impl From> for NitroError { + fn from(_err: ciborium::de::Error) -> NitroError { + NitroError::InvalidCbor + } +} + +impl From for NitroError { + fn from(err: boring::error::ErrorStack) -> NitroError { + NitroError::InvalidCertificate(err.to_string()) + } +} + +#[derive(Debug)] +pub struct CoseSign1 { + protected_header: Vec, + // nitro has no unprotected header + payload: Vec, + signature: Vec, +} + +impl CoseSign1 { + pub fn from_bytes(bytes: &[u8]) -> Result { + let value: Value = ciborium::from_reader(bytes)?; + value.try_into() + } + + pub fn extract_attestation_doc(&self, now: SystemTime) -> Result { + let hash = Sha384::digest(self.to_canonical()); + let r = BigNum::from_slice(&self.signature[..48]).expect("can extract r"); + let s = BigNum::from_slice(&self.signature[48..]).expect("can extract s"); + let sig = EcdsaSig::from_private_components(r, s).expect("can initialize signature"); + + let doc = AttestationDoc::from_bytes(self.payload.as_slice()).expect("can parse doc"); + let cert = doc.verified_cert(now)?; + let key = cert + .public_key() + .and_then(|pub_key| pub_key.ec_key()) + .expect("has EC key"); + let is_valid = sig.verify(hash.as_slice(), &key).expect("can verify"); + if !is_valid { + return Err(NitroError::InvalidSignature); + } + Ok(doc) + } + + fn validating_new( + protected_header: Vec, + payload: Vec, + signature: Vec, + ) -> Result { + let is_valid = { + let mut is_valid = true; + is_valid &= Self::is_valid_protected_header(&protected_header); + is_valid &= (1..16384).contains(&payload.len()); + is_valid &= signature.len() == 96; + is_valid + }; + if !is_valid { + return Err(NitroError::InvalidCoseSign1); + } + Ok(CoseSign1 { + protected_header, + payload, + signature, + }) + } + + fn is_valid_protected_header(bytes: &[u8]) -> bool { + let signing_algorithm: Integer = Integer::from(1); + let ecdsa_sha_384: Integer = Integer::from(-35); + let value: Value = ciborium::from_reader(bytes).expect("valid cbor"); + match value { + Value::Map(vec) => match &vec[..] { + [(Value::Integer(key), Value::Integer(val))] => { + key == &signing_algorithm && val == &ecdsa_sha_384 + } + _ => false, + }, + _ => false, + } + } + + fn to_canonical(&self) -> Vec { + let value = Value::Array(vec![ + Value::Text("Signature1".to_string()), + Value::Bytes(self.protected_header.clone()), + Value::Bytes(vec![]), + Value::Bytes(self.payload.clone()), + ]); + let mut bytes = Vec::with_capacity(self.protected_header.len() + self.payload.len()); + ciborium::into_writer(&value, &mut bytes).expect("can write bytes"); + bytes + } +} + +impl TryFrom for CoseSign1 { + type Error = NitroError; + + // Assumes tagged CBOR encoding of COSE_Sign1 + fn try_from(value: Value) -> Result { + let parts: [Value; 4] = value + .into_array() + .ok() + .and_then(|vs| vs.try_into().ok()) + .ok_or(NitroError::InvalidCoseSign1)?; + match parts { + [Value::Bytes(protected_header), Value::Map(_), Value::Bytes(payload), Value::Bytes(signature)] => { + CoseSign1::validating_new(protected_header, payload, signature) + } + _ => Err(NitroError::InvalidCoseSign1), + } + } +} + +#[derive(Debug)] +pub struct AttestationDoc { + pub module_id: String, + pub digest: String, + pub timestamp: i64, + pub pcrs: Vec<(usize, Vec)>, + certificate: Vec, + cabundle: Vec>, + public_key: Option>, + pub user_data: Option>, + pub nonce: Option>, +} + +impl TryFrom for AttestationDoc { + type Error = NitroError; + + fn try_from(value: Value) -> Result { + let map = AttestationDoc::parse_as_cbor_map(value)?; + Self::from_cbor_map(map) + } +} + +type CborMap = HashMap; + +impl AttestationDoc { + pub fn from_bytes(bytes: &[u8]) -> Result { + let value: Value = ciborium::from_reader(bytes)?; + value.try_into() + } + + fn parse_as_cbor_map(value: Value) -> Result { + value + .into_map() + .map_err(|_| NitroError::InvalidAttestationDoc)? + .into_iter() + .map(|(k, v)| { + let k = k + .into_text() + .map_err(|_| NitroError::InvalidAttestationDoc)?; + Ok((k, v)) + }) + .collect() + } + + fn from_cbor_map(mut map: CborMap) -> Result { + let module_id = map + .remove("module_id") + .and_then(|value| value.into_text().ok()) + .filter(|s| !s.is_empty()) + .ok_or(NitroError::InvalidAttestationDoc)?; + let digest = map + .remove("digest") + .and_then(|value| value.into_text().ok()) + .filter(|s| s == "SHA384") + .ok_or(NitroError::InvalidAttestationDoc)?; + let timestamp = map + .remove("timestamp") + .and_then(|value| value.into_integer().ok()) + .and_then(|integer| i64::try_from(integer).ok()) + .filter(|i| i.is_positive()) + .ok_or(NitroError::InvalidAttestationDoc)?; + let pcrs: Vec<(usize, Vec)> = map + .remove("pcrs") + .and_then(|value| value.into_map().ok()) + .and_then(|pairs| { + if !(1..=32).contains(&pairs.len()) { + return None; + } + let mut pcrs = Vec::with_capacity(pairs.len()); + for (key, value) in pairs.into_iter() { + let index = key + .into_integer() + .ok() + .and_then(|n| usize::try_from(n).ok()) + .filter(|n| (0..32).contains(n))?; + let bytes = value + .into_bytes() + .ok() + .filter(|bs| [32, 48, 64].contains(&bs.len()))?; + pcrs.push((index, bytes)) + } + Some(pcrs) + }) + .ok_or(NitroError::InvalidAttestationDoc)?; + + fn into_valid_cert_bytes(value: Value) -> Option> { + value + .into_bytes() + .ok() + .filter(|bs| (1..=1024).contains(&bs.len())) + } + + let certificate = map + .remove("certificate") + .and_then(into_valid_cert_bytes) + .ok_or(NitroError::InvalidAttestationDoc)?; + + let cabundle = map + .remove("cabundle") + .and_then(|value| value.into_array().ok()) + .and_then(|vals| { + let certs: Vec<_> = vals.into_iter().filter_map(into_valid_cert_bytes).collect(); + if certs.is_empty() { + return None; + } + Some(certs) + }) + .ok_or(NitroError::InvalidAttestationDoc)?; + + fn into_valid_optional_bytes( + value: Value, + expected_length: usize, + ) -> Result, NitroError> { + match value.into_bytes() { + Ok(bytes) if bytes.len() <= expected_length => Ok(bytes), + Err(Value::Null) => Ok(vec![]), + Ok(_) | Err(_) => Err(NitroError::InvalidAttestationDoc), + } + } + + let public_key = map + .remove("public_key") // option + .map(|value| into_valid_optional_bytes(value, 1024)) + .transpose()?; + + let user_data = map + .remove("user_data") + .map(|value| into_valid_optional_bytes(value, 512)) + .transpose()?; + + let nonce = map + .remove("nonce") + .map(|value| into_valid_optional_bytes(value, 10)) + .transpose()?; + + Ok(AttestationDoc { + module_id, + digest, + timestamp, + pcrs, + certificate, + cabundle, + public_key, + user_data, + nonce, + }) + } + + fn verified_cert(&self, now: SystemTime) -> Result { + let mut context = X509StoreContext::new()?; + let certificate = X509::from_der(&self.certificate)?; + let mut stack = stack::Stack::::new()?; + for der in self.cabundle.iter() { + let cert = X509::from_der(der)?; + stack.push(cert)?; + } + let stack = stack; + let trust = { + let root = X509::from_pem(ROOT_CERTIFICATE_PEM)?; + let mut builder = X509StoreBuilder::new()?; + builder.param_mut().set_time( + now.duration_since(SystemTime::UNIX_EPOCH) + .expect("current time is after 1970") + .as_secs() + .try_into() + .expect("haven't yet overflowed time_t"), + ); + builder.add_cert(root)?; + builder.build() + }; + let is_valid = context.init(&trust, &certificate, &stack, |ctx| ctx.verify_cert())?; + if !is_valid { + let message = context.error().to_string(); + return Err(NitroError::InvalidCertificate(message)); + } + Ok(certificate) + } + + pub fn extract_public_key( + &self, + expected_pcrs: &HashMap>, + ) -> Result { + let mut is_match = true; + for (index, pcr) in self.pcrs.iter() { + is_match &= expected_pcrs + .get(index) + .map(|expected| expected.ct_eq(pcr).into()) + // if the index is missing from the expected_pcrs we do not check it + .unwrap_or(true); + } + if !is_match { + return Err(NitroError::InvalidPcrs); + } + self.public_key + .clone() + .ok_or(NitroError::InvalidPublicKey)? + .try_into() + .map_err(|_| NitroError::InvalidPublicKey) + } +} + +#[cfg(test)] +mod test { + use super::*; + use hex_literal::hex; + use std::time::Duration; + + #[test] + fn test_extract_attestation_doc() { + let timestamp = SystemTime::UNIX_EPOCH + Duration::from_secs(1684362463); + let cose_sign1 = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("can parse"); + cose_sign1 + .extract_attestation_doc(timestamp) + .expect("valid signature"); + } + + #[test] + fn test_attestation() { + let timestamp = SystemTime::UNIX_EPOCH + Duration::from_secs(1684948138); + let _pk = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_2) + .expect("can parse") + .extract_attestation_doc(timestamp) + .expect("valid signature") + .extract_public_key(&get_test_pcrs()) + .expect("valid pcrs"); + } + + #[test] + fn test_expired_cert() { + let cose_sign1 = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("can parse"); + let err = cose_sign1 + .extract_attestation_doc(SystemTime::now()) + .unwrap_err(); + assert!(format!("{err:?}").contains("expired")); + } + + #[test] + fn test_not_yet_valid_cert() { + let cose_sign1 = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("can parse"); + let err = cose_sign1 + .extract_attestation_doc(SystemTime::UNIX_EPOCH) + .unwrap_err(); + assert!(format!("{err:?}").contains("not yet valid")); + } + + #[test] + fn test_invalid_signature() { + let timestamp = SystemTime::UNIX_EPOCH + Duration::from_secs(1684362463); + let mut cose_sign1 = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("can parse"); + cose_sign1.signature[0] ^= 0xff; + let err = cose_sign1.extract_attestation_doc(timestamp).unwrap_err(); + assert_eq!(NitroError::InvalidSignature, err); + } + + fn invalid_cose_sign1_test(mut f: F) + where + F: FnMut(&mut CoseSign1), + { + let mut subject = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("can parse"); + f(&mut subject); + let err = + CoseSign1::validating_new(subject.protected_header, subject.payload, subject.signature) + .unwrap_err(); + assert_eq!(NitroError::InvalidCoseSign1, err); + } + + #[test] + fn test_invalid_cose_sign1_signature_len() { + invalid_cose_sign1_test(|subject| subject.signature.push(0x00)); + } + + #[test] + fn test_invalid_cose_sign1_empty_payload() { + invalid_cose_sign1_test(|subject| subject.payload = vec![]); + } + + #[test] + fn test_invalid_cose_sign1_payload_too_large() { + invalid_cose_sign1_test(|subject| subject.payload = [0; 16384].to_vec()); + } + + #[test] + fn test_invalid_cose_sign1_invalid_header() { + invalid_cose_sign1_test(|subject| subject.protected_header = vec![1, 2, 3]); + } + + #[test] + fn test_canonical_serialization() { + let subject = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("can parse"); + assert_eq!(subject.to_canonical(), VALID_DOCUMENT_BYTES_1_CANONICAL); + } + + #[test] + fn test_non_string_keys() { + let value: Value = Value::Map(vec![(Value::Integer(42.into()), Value::Integer(42.into()))]); + let err = + AttestationDoc::parse_as_cbor_map(value).expect_err("Should have failed validation"); + assert_eq!(err, NitroError::InvalidAttestationDoc); + } + + fn invalid_attestation_doc_test(mut f: F) + where + F: FnMut(&mut CborMap), + { + let cose_sign1 = CoseSign1::from_bytes(VALID_DOCUMENT_BYTES_1).expect("valid cose_sign1"); + let value: Value = + ciborium::from_reader(cose_sign1.payload.as_slice()).expect("valid cbor"); + let mut map = AttestationDoc::parse_as_cbor_map(value).expect("valid cbor map"); + f(&mut map); + let err = AttestationDoc::from_cbor_map(map).unwrap_err(); + assert_eq!(NitroError::InvalidAttestationDoc, err); + } + + #[test] + fn test_empty_module_id() { + invalid_attestation_doc_test(|map| { + *map.get_mut("module_id").unwrap() = Value::Text("".to_string()); + }); + } + + #[test] + fn test_invalid_digest() { + invalid_attestation_doc_test(|map| { + *map.get_mut("digest").unwrap() = Value::Text("not sha384".to_string()); + }); + } + + #[test] + fn test_zero_timestamp() { + invalid_attestation_doc_test(|map| { + *map.get_mut("timestamp").unwrap() = Value::Integer(0.into()); + }); + } + + #[test] + fn test_empty_pcrs() { + invalid_attestation_doc_test(|map| { + *map.get_mut("pcrs").unwrap() = Value::Array(vec![]); + }); + } + + #[test] + fn test_too_many_pcrs() { + invalid_attestation_doc_test(|map| { + *map.get_mut("pcrs").unwrap() = Value::Array( + (1..33) + // Should be a byte array, but any Value would do for length validation + .map(|i| Value::Integer(i.into())) + .collect(), + ); + }); + } + + #[test] + fn test_invalid_pcr_index() { + invalid_attestation_doc_test(|map| { + let pcrs = map.get_mut("pcrs").unwrap(); + let pcr = pcrs.as_map_mut().unwrap(); + pcr[0] = (Value::Integer(32.into()), pcr[0].1.clone()); + }); + } + + #[test] + fn test_invalid_pcr_length() { + invalid_attestation_doc_test(|map| { + let pcrs = map.get_mut("pcrs").unwrap(); + let pcr = pcrs.as_map_mut().unwrap(); + pcr[0] = (pcr[0].0.clone(), Value::Bytes(b"00010203".to_vec())); + }); + } + + #[test] + fn test_empty_certificate() { + invalid_attestation_doc_test(|map| { + *map.get_mut("certificate").unwrap() = Value::Bytes(vec![]); + }); + } + + #[test] + fn test_certificate_too_long() { + invalid_attestation_doc_test(|map| { + *map.get_mut("certificate").unwrap() = Value::Bytes([0; 1025].to_vec()); + }); + } + + #[test] + fn test_public_key_too_long() { + invalid_attestation_doc_test(|map| { + map.insert("public_key".to_string(), Value::Bytes([0; 1025].to_vec())); + }); + } + + #[test] + fn test_user_data_too_long() { + invalid_attestation_doc_test(|map| { + map.insert("user_data".to_string(), Value::Bytes([0; 513].to_vec())); + }); + } + + #[test] + fn test_nonce_too_long() { + invalid_attestation_doc_test(|map| { + map.insert("nonce".to_string(), Value::Bytes([0; 513].to_vec())); + }); + } + + const VALID_DOCUMENT_BYTES_1: &[u8] = include_bytes!("../../tests/data/test_cose_sign1_01.dat"); + const VALID_DOCUMENT_BYTES_1_CANONICAL: &[u8] = + include_bytes!("../../tests/data/cose_sign1_canonical.dat"); + const VALID_DOCUMENT_BYTES_2: &[u8] = include_bytes!("../../tests/data/test_cose_sign1_02.dat"); + + fn get_test_pcrs() -> HashMap> { + let mut map = HashMap::::new(); + map.insert(0, hex!("28de6557cce896cf8c580d8674fbc13c45c1a7636545ef022a01007336b8752b9a1cd9ce69df2ecacc7696a1203b45c2").to_vec()); + map.insert(1, hex!("52b919754e1643f4027eeee8ec39cc4a2cb931723de0c93ce5cc8d407467dc4302e86490c01c0d755acfe10dbf657546").to_vec()); + map.insert(2, hex!("3bc780f5f2adc596f55c5d8b85760f1e9e585c7016957673616c0611280c4b99c8877caff00d70567a96979abe59dc0a").to_vec()); + map.insert(3, hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec()); + map.insert(4, hex!("17354aa3f163d6882a4ff746e5821c5be66f1658472feac83268cf4b7461015ea47993c07025ebb5e134cbc13b16ac97").to_vec()); + map + } +} + +// From https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip +// Zip hash (SHA256): 8cf60e2b2efca96c6a9e71e851d00c1b6991cc09eadbe64a6a1d1b1eb9faff7c +const ROOT_CERTIFICATE_PEM: &[u8] = br" +-----BEGIN CERTIFICATE----- +MIICETCCAZagAwIBAgIRAPkxdWgbkK/hHUbMtOTn+FYwCgYIKoZIzj0EAwMwSTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoMBkFtYXpvbjEMMAoGA1UECwwDQVdTMRswGQYD +VQQDDBJhd3Mubml0cm8tZW5jbGF2ZXMwHhcNMTkxMDI4MTMyODA1WhcNNDkxMDI4 +MTQyODA1WjBJMQswCQYDVQQGEwJVUzEPMA0GA1UECgwGQW1hem9uMQwwCgYDVQQL +DANBV1MxGzAZBgNVBAMMEmF3cy5uaXRyby1lbmNsYXZlczB2MBAGByqGSM49AgEG +BSuBBAAiA2IABPwCVOumCMHzaHDimtqQvkY4MpJzbolL//Zy2YlES1BR5TSksfbb +48C8WBoyt7F2Bw7eEtaaP+ohG2bnUs990d0JX28TcPQXCEPZ3BABIeTPYwEoCWZE +h8l5YoQwTcU/9KNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUkCW1DdkF +R+eWw5b6cp3PmanfS5YwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYC +MQCjfy+Rocm9Xue4YnwWmNJVA44fA0P5W2OpYow9OYCVRaEevL8uO1XYru5xtMPW +rfMCMQCi85sWBbJwKKXdS6BptQFuZbT73o/gBh1qUxl/nNr12UO8Yfwr6wPLb+6N +IwLz3/Y= +-----END CERTIFICATE----- +"; diff --git a/rust/attest/src/util.rs b/rust/attest/src/util.rs index b248059cc2..57c71d1388 100644 --- a/rust/attest/src/util.rs +++ b/rust/attest/src/util.rs @@ -9,6 +9,40 @@ use std::time::SystemTime; use boring::asn1::Asn1Time; use libc::time_t; +/// A replacement for [`std::collections::HashMap`] that performs linear lookups. +/// +/// This can be used in place of `HashMap` for supporting lookup in `const` +/// arrays. For small `N`, the linear search will be faster than a hash lookup. +pub(crate) struct SmallMap([(K, V); N]); + +impl SmallMap { + /// The maximum number of elements allowed in a `SmallMap`. + const MAX_SIZE: usize = 4; + + /// Checks at compile-time (via `const`) that `N` is small enough. + const CHECK_MAX_SIZE: () = assert!( + N <= Self::MAX_SIZE, + "use a HashMap for more than MAX_SIZE items" + ); + + /// Creates a new `SmallMap` with the given contents. + pub(crate) const fn new(items: [(K, V); N]) -> Self { + // Evaluate CHECK_MAX_SIZE; this will fail compilation if `N` is too + // large. + // + // TODO(https://github.com/rust-lang/rust-clippy/issues/9048): Remove + // the unnecessary #[allow]. + #[allow(clippy::let_unit_value)] + let _: () = Self::CHECK_MAX_SIZE; + Self(items) + } + + /// Gets the value for the first key that matches `key`, or `None`. + pub(crate) fn get + ?Sized>(&self, key: &Q) -> Option<&V> { + self.0.iter().find_map(|(k, v)| (key == k).then_some(v)) + } +} + /// Removes a trailing null byte, if one exists pub(crate) fn strip_trailing_null_byte(bytes: &mut &[u8]) { *bytes = bytes.strip_suffix(&[0]).unwrap_or(bytes); diff --git a/rust/attest/tests/data/cose_sign1_canonical.dat b/rust/attest/tests/data/cose_sign1_canonical.dat new file mode 100644 index 0000000000..5b3b085882 Binary files /dev/null and b/rust/attest/tests/data/cose_sign1_canonical.dat differ diff --git a/rust/attest/tests/data/test_cose_sign1_01.dat b/rust/attest/tests/data/test_cose_sign1_01.dat new file mode 100644 index 0000000000..db66f287f1 Binary files /dev/null and b/rust/attest/tests/data/test_cose_sign1_01.dat differ diff --git a/rust/attest/tests/data/test_cose_sign1_02.dat b/rust/attest/tests/data/test_cose_sign1_02.dat new file mode 100644 index 0000000000..9a84d30419 Binary files /dev/null and b/rust/attest/tests/data/test_cose_sign1_02.dat differ diff --git a/rust/bridge/ffi/Cargo.toml b/rust/bridge/ffi/Cargo.toml index 35fd71c4eb..18ed51f55b 100644 --- a/rust/bridge/ffi/Cargo.toml +++ b/rust/bridge/ffi/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "libsignal-ffi" -version = "0.31.0" +version = "0.32.1" authors = ["Signal Messenger LLC"] edition = "2018" license = "AGPL-3.0-only" @@ -15,9 +15,6 @@ name = "signal_ffi" crate-type = ["staticlib"] [features] -default = ["armv8"] -armv8 = ["libsignal-protocol/armv8", "signal-crypto/armv8"] - # Testing the Swift side of this requires compiling with SIGNAL_MEDIA_SUPPORTED enabled for both Swift and C: # swift test -Xswiftc -DSIGNAL_MEDIA_SUPPORTED -Xcc -DSIGNAL_MEDIA_SUPPORTED signal-media = ["libsignal-bridge/signal-media"] diff --git a/rust/bridge/ffi/src/lib.rs b/rust/bridge/ffi/src/lib.rs index 23e01133b6..81b2d8c04d 100644 --- a/rust/bridge/ffi/src/lib.rs +++ b/rust/bridge/ffi/src/lib.rs @@ -171,7 +171,7 @@ pub unsafe extern "C" fn signal_sealed_session_cipher_decrypt( let mut identity_store = identity_store.as_ref().ok_or(SignalFfiError::NullPointer)?; let mut session_store = session_store.as_ref().ok_or(SignalFfiError::NullPointer)?; let mut prekey_store = prekey_store.as_ref().ok_or(SignalFfiError::NullPointer)?; - let mut signed_prekey_store = signed_prekey_store + let signed_prekey_store = signed_prekey_store .as_ref() .ok_or(SignalFfiError::NullPointer)?; @@ -188,7 +188,7 @@ pub unsafe extern "C" fn signal_sealed_session_cipher_decrypt( &mut identity_store, &mut session_store, &mut prekey_store, - &mut signed_prekey_store, + &signed_prekey_store, &mut kyber_pre_key_store, ) .now_or_never() diff --git a/rust/bridge/jni/Cargo.toml b/rust/bridge/jni/Cargo.toml index d24ddd3d24..303bfd3357 100644 --- a/rust/bridge/jni/Cargo.toml +++ b/rust/bridge/jni/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "libsignal-jni" -version = "0.31.0" +version = "0.32.1" authors = ["Signal Messenger LLC"] edition = "2018" license = "AGPL-3.0-only" @@ -14,17 +14,13 @@ license = "AGPL-3.0-only" name = "signal_jni" crate-type = ["cdylib"] -[features] -default = ["armv8"] -armv8 = ["libsignal-protocol/armv8", "signal-crypto/armv8"] - [dependencies] libsignal-protocol = { path = "../../protocol" } signal-crypto = { path = "../../crypto" } libsignal-bridge = { path = "../shared", features = ["jni", "signal-media"] } async-trait = "0.1.41" cfg-if = "1.0.0" -jni = "0.19.0" +jni = "0.21.0" rand = "0.8" log = "0.4" log-panics = { version = "2.1.0", features = ["with-backtrace"] } diff --git a/rust/bridge/jni/bin/gen_java_decl.py b/rust/bridge/jni/bin/gen_java_decl.py index 76884ab6ac..13bced5d0a 100755 --- a/rust/bridge/jni/bin/gen_java_decl.py +++ b/rust/bridge/jni/bin/gen_java_decl.py @@ -62,15 +62,13 @@ def print_usage_and_exit(): def translate_to_java(typ): - # jobject as a return type is not given here; instead use a type type_map = { "void": "void", - "jstring": "String", "JString": "String", "JObject": "Object", "JClass": "Class", - "jbyteArray": "byte[]", - "jlongArray": "long[]", + "JByteArray": "byte[]", + "JLongArray": "long[]", "ObjectHandle": "long", "jint": "int", "jlong": "long", @@ -81,8 +79,6 @@ def translate_to_java(typ): return type_map[typ] # Assume anything prefixed with "Java" refers to an object - if typ.startswith('JavaReturn'): - return typ[10:] if typ.startswith('Java'): return typ[4:] diff --git a/rust/bridge/jni/src/lib.rs b/rust/bridge/jni/src/lib.rs index 66b8129c1e..552266db22 100644 --- a/rust/bridge/jni/src/lib.rs +++ b/rust/bridge/jni/src/lib.rs @@ -6,8 +6,7 @@ #![allow(clippy::missing_safety_doc)] #![deny(clippy::unwrap_used)] -use jni::objects::{JClass, JObject}; -use jni::sys::{jbyteArray, jlongArray}; +use jni::objects::{JByteArray, JClass, JLongArray, JObject}; use jni::JNIEnv; use std::convert::TryFrom; @@ -17,21 +16,23 @@ use libsignal_protocol::*; pub mod logging; #[no_mangle] -pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_IdentityKeyPair_1Deserialize( - env: JNIEnv, +pub unsafe extern "C" fn Java_org_signal_libsignal_internal_Native_IdentityKeyPair_1Deserialize< + 'local, +>( + mut env: JNIEnv<'local>, _class: JClass, - data: jbyteArray, -) -> jlongArray { - run_ffi_safe(&env, || { + data: JByteArray, +) -> JLongArray<'local> { + run_ffi_safe(&mut env, |env| { let data = env.convert_byte_array(data)?; let key = IdentityKeyPair::try_from(data.as_ref())?; - let public_key_handle = key.identity_key().public_key().convert_into(&env)?; - let private_key_handle = key.private_key().convert_into(&env)?; + let public_key_handle = key.identity_key().public_key().convert_into(env)?; + let private_key_handle = key.private_key().convert_into(env)?; let tuple = [public_key_handle, private_key_handle]; let result = env.new_long_array(2)?; - env.set_long_array_region(result, 0, &tuple)?; + env.set_long_array_region(&result, 0, &tuple)?; Ok(result) }) } diff --git a/rust/bridge/jni/src/logging.rs b/rust/bridge/jni/src/logging.rs index 12bdde4641..2859a58f0c 100644 --- a/rust/bridge/jni/src/logging.rs +++ b/rust/bridge/jni/src/logging.rs @@ -40,7 +40,7 @@ impl From for jint { } } -impl From for JValue<'_> { +impl From for JValue<'_, '_> { fn from(level: JavaLogLevel) -> Self { Self::Int(level.into()) } @@ -73,7 +73,7 @@ impl JniLogger { } fn log_impl(&self, record: &log::Record) -> jni::errors::Result<()> { - let env = self.vm.attach_current_thread()?; + let mut env = self.vm.attach_current_thread()?; let level: JavaLogLevel = record.level().into(); let message = format!( "{}:{}: {}", @@ -81,10 +81,12 @@ impl JniLogger { record.line().unwrap_or(0), record.args(), ); + let message = env.new_string(message)?; + let module = env.new_string("libsignal")?; let args = jni_args!(( level.into() => int, - env.new_string("libsignal")? => java.lang.String, - env.new_string(message)? => java.lang.String, + module => java.lang.String, + message => java.lang.String, ) -> void); let result = env.call_static_method(&self.logger_class, "log", args.sig, &args.args); diff --git a/rust/bridge/node/Cargo.toml b/rust/bridge/node/Cargo.toml index 53f3eff3ee..6241cc9b2b 100644 --- a/rust/bridge/node/Cargo.toml +++ b/rust/bridge/node/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "libsignal-node" -version = "0.31.0" +version = "0.32.1" authors = ["Signal Messenger LLC"] license = "AGPL-3.0-only" edition = "2018" @@ -14,10 +14,6 @@ edition = "2018" name = "signal_node" crate-type = ["cdylib"] -[features] -default = ["armv8"] -armv8 = ["libsignal-protocol/armv8", "libsignal-bridge/armv8"] - [dependencies] libsignal-protocol = { path = "../../protocol" } libsignal-bridge = { path = "../shared", features = ["node", "signal-media"] } diff --git a/rust/bridge/node/futures/tests-node-module/src/store_like.rs b/rust/bridge/node/futures/tests-node-module/src/store_like.rs index 25de6fca44..0adc7f220c 100644 --- a/rust/bridge/node/futures/tests-node-module/src/store_like.rs +++ b/rust/bridge/node/futures/tests-node-module/src/store_like.rs @@ -51,7 +51,7 @@ impl Finalize for NameStore { } } -async fn double_name_from_store_impl(store: &mut NameStore) -> Result { +async fn double_name_from_store_impl(store: &NameStore) -> Result { Ok(format!( "{0} {1}", store.get_name().await?, @@ -62,10 +62,10 @@ async fn double_name_from_store_impl(store: &mut NameStore) -> Result Promise }): Promise pub fn double_name_from_store(mut cx: FunctionContext) -> JsResult { let js_store = cx.argument(0)?; - let mut store = NameStore::new(&mut cx, js_store); + let store = NameStore::new(&mut cx, js_store); promise(&mut cx, async move { - let future = AssertUnwindSafe(double_name_from_store_impl(&mut store)); + let future = AssertUnwindSafe(double_name_from_store_impl(&store)); let result = future.await; settle_promise(move |cx| { store.finalize(cx); @@ -77,7 +77,7 @@ pub fn double_name_from_store(mut cx: FunctionContext) -> JsResult { }) } -async fn double_name_from_store_using_join_impl(store: &mut NameStore) -> Result { +async fn double_name_from_store_using_join_impl(store: &NameStore) -> Result { let names = try_join!(store.get_name(), store.get_name())?; Ok(format!("{0} {1}", names.0, names.1)) } @@ -85,10 +85,10 @@ async fn double_name_from_store_using_join_impl(store: &mut NameStore) -> Result // function doubleNameFromStoreUsingJoin(store: { getName: () => Promise }): Promise pub fn double_name_from_store_using_join(mut cx: FunctionContext) -> JsResult { let js_store = cx.argument(0)?; - let mut store = NameStore::new(&mut cx, js_store); + let store = NameStore::new(&mut cx, js_store); promise(&mut cx, async move { - let future = AssertUnwindSafe(double_name_from_store_using_join_impl(&mut store)); + let future = AssertUnwindSafe(double_name_from_store_using_join_impl(&store)); let result = future.await; settle_promise(move |cx| { store.finalize(cx); diff --git a/rust/bridge/shared/Cargo.toml b/rust/bridge/shared/Cargo.toml index 30aa390093..62664f60ca 100644 --- a/rust/bridge/shared/Cargo.toml +++ b/rust/bridge/shared/Cargo.toml @@ -23,13 +23,12 @@ libsignal-bridge-macros = { path = "macros" } signal-chat = { path = "../../chat" } signal-grpc = { path = "../../grpc" } signal-quic = { path = "../../quic" } -aes-gcm-siv = "0.10.1" +aes-gcm-siv = "0.11.1" async-trait = "0.1.41" bincode = "1.0" futures-util = "0.3.7" hkdf = "0.12" hmac = "0.12.0" -typenum = "1.12.0" log = "0.4" paste = "1.0" rand = "0.8" @@ -41,14 +40,12 @@ uuid = "1.1.2" bytemuck = { version = "1.13.0", optional = true } libc = { version = "0.2", optional = true } -jni_crate = { version = "0.19", package = "jni", optional = true } +jni = { version = "0.21", package = "jni", optional = true } neon = { version = "0.10.0", optional = true, default-features = false, features = ["napi-6", "promise-api"] } linkme = { version = "0.3.9", optional = true } signal-neon-futures = { path = "../node/futures", optional = true } [features] -default = ["armv8"] ffi = ["libc", "libsignal-bridge-macros/ffi"] -jni = ["jni_crate", "libsignal-bridge-macros/jni", "bytemuck"] +jni = ["dep:jni", "libsignal-bridge-macros/jni", "bytemuck"] node = ["neon", "linkme", "signal-neon-futures", "libsignal-bridge-macros/node"] -armv8 = ["aes-gcm-siv/armv8", "libsignal-protocol/armv8", "signal-crypto/armv8"] diff --git a/rust/bridge/shared/macros/src/jni.rs b/rust/bridge/shared/macros/src/jni.rs index 1a52169086..1f3aee7fd0 100644 --- a/rust/bridge/shared/macros/src/jni.rs +++ b/rust/bridge/shared/macros/src/jni.rs @@ -48,8 +48,8 @@ pub(crate) fn bridge_fn(name: String, sig: &Signature, result_kind: ResultKind) name.ident.clone(), quote!(#(#attrs)* #name #colon_token jni_arg_type!(#ty)), quote! { - let mut #name = <#ty as jni::ArgTypeInfo>::borrow(&env, #name)?; - let #name = <#ty as jni::ArgTypeInfo>::load_from(&env, &mut #name)? + let mut #name = <#ty as jni::ArgTypeInfo>::borrow(env, &#name)?; + let #name = <#ty as jni::ArgTypeInfo>::load_from(&mut #name) }, ) } else { @@ -68,16 +68,16 @@ pub(crate) fn bridge_fn(name: String, sig: &Signature, result_kind: ResultKind) quote! { #[no_mangle] - pub unsafe extern "C" fn #name( - env: jni::JNIEnv, + pub unsafe extern "C" fn #name<'local>( + mut env: jni::JNIEnv<'local>, _class: jni::JClass, #(#input_args),* ) #output { - jni::run_ffi_safe(&env, || { + jni::run_ffi_safe(&mut env, |env| { #(#input_processing);*; let __result = #orig_name(#(#input_names),*); #await_if_needed; - jni::ResultTypeInfo::convert_into(__result, &env) + jni::ResultTypeInfo::convert_into(__result, env) }) } } diff --git a/rust/bridge/shared/macros/src/lib.rs b/rust/bridge/shared/macros/src/lib.rs index 285231696a..bfd2041f3f 100644 --- a/rust/bridge/shared/macros/src/lib.rs +++ b/rust/bridge/shared/macros/src/lib.rs @@ -219,7 +219,7 @@ fn bridge_fn_impl(attr: TokenStream, item: TokenStream, result_kind: ResultKind) let node_fn = node_name.map(|name| node::bridge_fn(name, &function.sig, result_kind)); quote!( - #[allow(non_snake_case)] + #[allow(non_snake_case, clippy::needless_pass_by_ref_mut)] #[cfg(any(#(#feature_list,)*))] #[inline(always)] #function diff --git a/rust/bridge/shared/src/crypto.rs b/rust/bridge/shared/src/crypto.rs index d0679242e8..a3d245d15f 100644 --- a/rust/bridge/shared/src/crypto.rs +++ b/rust/bridge/shared/src/crypto.rs @@ -8,7 +8,7 @@ use libsignal_bridge_macros::*; use signal_crypto::*; use aes_gcm_siv::aead::generic_array::typenum::Unsigned; -use aes_gcm_siv::aead::{AeadCore, AeadInPlace, NewAead}; +use aes_gcm_siv::{AeadCore, AeadInPlace, KeyInit}; use crate::support::*; use crate::*; diff --git a/rust/bridge/shared/src/ffi/convert.rs b/rust/bridge/shared/src/ffi/convert.rs index a1aa739d75..ea28f2f57d 100644 --- a/rust/bridge/shared/src/ffi/convert.rs +++ b/rust/bridge/shared/src/ffi/convert.rs @@ -307,78 +307,10 @@ store!(SignedPreKeyStore); store!(KyberPreKeyStore); store!(InputStream); -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -#[cfg(feature = "signal-media")] -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self) -> SignalFfiResult { - T::convert_into(self?) - } -} - -impl ResultTypeInfo for Result { +impl ResultTypeInfo for Result +where + SignalFfiError: From, +{ type ResultType = T::ResultType; fn convert_into(self) -> SignalFfiResult { T::convert_into(self?) diff --git a/rust/bridge/shared/src/incremental_mac.rs b/rust/bridge/shared/src/incremental_mac.rs index e9d2fb5340..0dc620b4f9 100644 --- a/rust/bridge/shared/src/incremental_mac.rs +++ b/rust/bridge/shared/src/incremental_mac.rs @@ -7,8 +7,8 @@ use hmac::digest::{crypto_common, OutputSizeUser}; use std::convert::TryInto; use crypto_common::KeyInit; +use hmac::digest::typenum::Unsigned; use hmac::Hmac; -use typenum::Unsigned; use libsignal_bridge_macros::*; use libsignal_protocol::incremental_mac::{calculate_chunk_size, Incremental, Validating}; @@ -84,23 +84,27 @@ pub fn ValidatingMac_Update( bytes: &[u8], offset: u32, length: u32, -) -> bool { +) -> i32 { let offset = offset as usize; let length = length as usize; mac.0 .as_mut() .expect("MAC used after finalize") .update(&bytes[offset..][..length]) - .is_ok() + .ok() + .and_then(|n| n.try_into().ok()) + .unwrap_or(-1) } #[bridge_fn] -pub fn ValidatingMac_Finalize(mac: &mut ValidatingMac) -> bool { +pub fn ValidatingMac_Finalize(mac: &mut ValidatingMac) -> i32 { mac.0 .take() .expect("MAC used after finalize") .finalize() - .is_ok() + .ok() + .and_then(|n| n.try_into().ok()) + .unwrap_or(-1) } impl Drop for IncrementalMac { @@ -111,14 +115,6 @@ impl Drop for IncrementalMac { } } -impl Drop for ValidatingMac { - fn drop(&mut self) { - if self.0.is_some() { - report_unexpected_drop() - } - } -} - static UNEXPECTED_DROP_MESSAGE: &str = "MAC is dropped without calling finalize"; fn report_unexpected_drop() { diff --git a/rust/bridge/shared/src/jni/args.rs b/rust/bridge/shared/src/jni/args.rs index 083ea96fcb..bf1c86de7c 100644 --- a/rust/bridge/shared/src/jni/args.rs +++ b/rust/bridge/shared/src/jni/args.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; -use jni_crate::objects::JValue; +use jni::objects::{JObject, JValue}; /// Takes a Java-esque class name of the form `org.signal.Outer::Inner` and turns it into a /// JNI-style name `org/signal/Outer$Inner`. @@ -153,7 +153,7 @@ macro_rules! jni_arg { }; // Assume anything else is an object. This includes arrays and classes. ( $arg:expr => $($_:tt)+) => { - JValue::Object($arg.into()) + JValue::Object($arg.as_ref()) }; } @@ -169,7 +169,7 @@ fn test_jni_arg() { assert!(matches!(jni_arg!(-8.5 => float), JValue::Float(val) if val == -8.5)); assert!(matches!(jni_arg!(-8.5 => double), JValue::Double(val) if val == -8.5)); assert!(matches!( - jni_arg!(jni_crate::objects::JObject::null() => java.lang.Object), + jni_arg!(jni::objects::JObject::null() => java.lang.Object), JValue::Object(val) if val.is_null() )); } @@ -222,20 +222,36 @@ pub type PhantomReturnType = PhantomData R>; /// A JNI argument list, type-checked with its signature. #[derive(Debug, Clone, Copy)] -pub struct JniArgs<'a, R, const LEN: usize> { +pub struct JniArgs<'local, 'obj_ref, R, const LEN: usize> { pub sig: &'static str, - pub args: [JValue<'a>; LEN], + pub args: [JValue<'local, 'obj_ref>; LEN], pub _return: PhantomReturnType, } +impl<'local, 'obj_ref, 'output, const LEN: usize> JniArgs<'local, 'obj_ref, JObject<'output>, LEN> { + /// Updates the lifetime of the return type. + /// + /// May be necessary when passing JniArgs into a "local frame" + /// ([`jni::JNIEnv::with_local_frame`]). + pub fn for_nested_frame<'new_output: 'output>( + self, + ) -> JniArgs<'local, 'obj_ref, JObject<'new_output>, LEN> { + JniArgs { + sig: self.sig, + args: self.args, + _return: PhantomData, + } + } +} + /// Produces a JniArgs struct from the given arguments and return type. /// /// # Example /// /// ``` /// # use libsignal_bridge::jni_args; -/// # use jni_crate::objects::JValue; -/// # let name = jni_crate::objects::JObject::null(); +/// # use jni::objects::JValue; +/// # let name = jni::objects::JObject::null(); /// let args = jni_args!((name => java.lang.String, 0x3FFF => short) -> void); /// assert_eq!(args.sig, "(Ljava/lang/String;S)V"); /// assert_eq!(args.args.len(), 2); @@ -258,3 +274,6 @@ macro_rules! jni_args { } } } +// Expose this for doc comments. +#[cfg(doc)] +use jni_args; diff --git a/rust/bridge/shared/src/jni/convert.rs b/rust/bridge/shared/src/jni/convert.rs index 3e88f612ad..785dfa1944 100644 --- a/rust/bridge/shared/src/jni/convert.rs +++ b/rust/bridge/shared/src/jni/convert.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // -use jni::objects::{JObject, JString}; use jni::sys::{jbyte, JNI_FALSE, JNI_TRUE}; use jni::JNIEnv; use libsignal_protocol::*; @@ -25,37 +24,42 @@ use super::*; /// /// ```no_run /// # use libsignal_bridge::jni::*; -/// # use jni_crate::JNIEnv; +/// # use jni::JNIEnv; /// # struct Foo; /// # impl SimpleArgTypeInfo<'_> for Foo { /// # type ArgType = isize; -/// # fn convert_from(env: &JNIEnv, foreign: isize) -> SignalJniResult { Ok(Foo) } +/// # fn convert_from(env: &mut JNIEnv, foreign: &isize) -> SignalJniResult { Ok(Foo) } /// # } -/// # fn test(env: &JNIEnv, jni_arg: isize) -> SignalJniResult<()> { -/// let mut jni_arg_borrowed = Foo::borrow(env, jni_arg)?; -/// let rust_arg = Foo::load_from(env, &mut jni_arg_borrowed)?; +/// # fn test(env: &mut JNIEnv, jni_arg: isize) -> SignalJniResult<()> { +/// let mut jni_arg_borrowed = Foo::borrow(env, &jni_arg)?; +/// let rust_arg = Foo::load_from(&mut jni_arg_borrowed); /// # Ok(()) /// # } /// ``` /// /// The `'context` lifetime allows for borrowed values to depend on the current JNI stack frame; -/// that is, they can be assured that referenced objects will not be GC'd out from under them. +/// that is, they can be assured that referenced objects will not be GC'd out from under them. The +/// `'param` lifetime allows for depending on the current *Rust* stack frame, which is necessary for +/// some `JNIEnv` APIs. /// /// `ArgTypeInfo` is used to implement the `bridge_fn` macro, but can also be used outside it. /// -/// If the Rust type can be directly loaded from `ArgType` with no local storage needed, -/// implement [`SimpleArgTypeInfo`] instead. +/// If the Rust type can be directly loaded from `ArgType` with no local storage needed, implement +/// [`SimpleArgTypeInfo`] instead. /// /// Implementers should also see the `jni_arg_type` macro in `convert.rs`. -pub trait ArgTypeInfo<'storage, 'context: 'storage>: Sized { +pub trait ArgTypeInfo<'storage, 'param: 'storage, 'context: 'param>: Sized { /// The JNI form of the argument (e.g. `jni::jint`). - type ArgType; + type ArgType: 'param; /// Local storage for the argument (ideally borrowed rather than copied). type StoredType: 'storage; /// "Borrows" the data in `foreign`, usually to establish a local lifetime or owning type. - fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult; + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult; /// Loads the Rust value from the data that's been `stored` by [`borrow()`](Self::borrow()). - fn load_from(env: &JNIEnv, stored: &'storage mut Self::StoredType) -> SignalJniResult; + fn load_from(stored: &'storage mut Self::StoredType) -> Self; } /// A simpler interface for [`ArgTypeInfo`] for when no local storage is needed. @@ -64,19 +68,19 @@ pub trait ArgTypeInfo<'storage, 'context: 'storage>: Sized { /// /// ```no_run /// # use libsignal_bridge::jni::*; -/// # use jni_crate::objects::JObject; -/// # use jni_crate::JNIEnv; +/// # use jni::objects::JObject; +/// # use jni::JNIEnv; /// # struct Foo; /// impl<'a> SimpleArgTypeInfo<'a> for Foo { /// type ArgType = JObject<'a>; -/// fn convert_from(env: &JNIEnv, foreign: JObject<'a>) -> SignalJniResult { +/// fn convert_from(env: &mut JNIEnv, foreign: &JObject<'a>) -> SignalJniResult { /// // ... /// # Ok(Foo) /// } /// } /// -/// # fn test<'a>(env: &JNIEnv<'a>, jni_arg: JObject<'a>) -> SignalJniResult<()> { -/// let rust_arg = Foo::convert_from(env, jni_arg)?; +/// # fn test<'a>(env: &mut JNIEnv<'a>, jni_arg: JObject<'a>) -> SignalJniResult<()> { +/// let rust_arg = Foo::convert_from(env, &jni_arg)?; /// # Ok(()) /// # } /// ``` @@ -84,24 +88,25 @@ pub trait ArgTypeInfo<'storage, 'context: 'storage>: Sized { /// However, some types do need the full flexibility of `ArgTypeInfo`. pub trait SimpleArgTypeInfo<'a>: Sized { /// The JNI form of the argument (e.g. `jint`). - /// - /// Must be [`Copy`] to help the compiler optimize out local storage. - type ArgType: Copy + 'a; + type ArgType: 'a; /// Converts the data in `foreign` to the Rust type. - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult; + fn convert_from(env: &mut JNIEnv<'a>, foreign: &Self::ArgType) -> SignalJniResult; } -impl<'a, T> ArgTypeInfo<'a, 'a> for T +impl<'storage, 'param: 'storage, 'context: 'param, T> ArgTypeInfo<'storage, 'param, 'context> for T where - T: SimpleArgTypeInfo<'a>, + T: SimpleArgTypeInfo<'context> + 'storage, { - type ArgType = >::ArgType; - type StoredType = Self::ArgType; - fn borrow(_env: &'a JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - Ok(foreign) + type ArgType = >::ArgType; + type StoredType = Option; + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult { + Ok(Some(Self::convert_from(env, foreign)?)) } - fn load_from(env: &JNIEnv, stored: &'a mut Self::StoredType) -> SignalJniResult { - Self::convert_from(env, *stored) + fn load_from(stored: &'storage mut Self::StoredType) -> Self { + stored.take().expect("only called once") } } @@ -111,56 +116,52 @@ where /// /// ```no_run /// # use libsignal_bridge::jni::*; -/// # use jni_crate::JNIEnv; -/// # use jni_crate::objects::JObject; +/// # use jni::JNIEnv; +/// # use jni::objects::JString; /// # struct Foo; -/// # impl ResultTypeInfo for Foo { -/// # type ResultType = isize; -/// # fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { Ok(1) } -/// # fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { JObject::null() } +/// # impl<'a> ResultTypeInfo<'a> for Foo { +/// # type ResultType = JString<'a>; +/// # fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult> { todo!() } /// # } -/// # fn test<'a>(env: &JNIEnv<'a>) -> SignalJniResult<()> { +/// # fn test<'a>(env: &mut JNIEnv<'a>) -> SignalJniResult<()> { /// # let rust_result = Foo; -/// # let jni_result = rust_result.convert_into(env)?; +/// let jni_result = rust_result.convert_into(env)?; /// # Ok(()) /// # } /// ``` /// /// Implementers should also see the `jni_result_type` macro in `convert.rs`. -pub trait ResultTypeInfo: Sized { +pub trait ResultTypeInfo<'a>: Sized { /// The JNI form of the result (e.g. `jint`). type ResultType; /// Converts the data in `self` to the JNI type, similar to `try_into()`. - fn convert_into(self, env: &JNIEnv) -> SignalJniResult; - /// Converts the data in `self` into a JObject type for preserving while popping the local - /// frame. - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult; } /// Supports values `0..=Integer.MAX_VALUE`. /// /// Negative `int` values are *not* reinterpreted as large `u32` values. /// Note that this is different from the implementation of [`ResultTypeInfo`] for `u32`. -impl<'a> SimpleArgTypeInfo<'a> for u32 { +impl SimpleArgTypeInfo<'_> for u32 { type ArgType = jint; - fn convert_from(_env: &JNIEnv, foreign: jint) -> SignalJniResult { - if foreign < 0 { + fn convert_from(_env: &mut JNIEnv, foreign: &jint) -> SignalJniResult { + if *foreign < 0 { return Err(SignalJniError::IntegerOverflow(format!( "{} to u32", foreign ))); } - Ok(foreign as u32) + Ok(*foreign as u32) } } /// Supports values `0..=Integer.MAX_VALUE`. Negative values are considered `None`. /// /// Note that this is different from the implementation of [`ResultTypeInfo`] for `Option`. -impl<'a> SimpleArgTypeInfo<'a> for Option { +impl SimpleArgTypeInfo<'_> for Option { type ArgType = jint; - fn convert_from(env: &JNIEnv, foreign: jint) -> SignalJniResult { - if foreign < 0 { + fn convert_from(env: &mut JNIEnv, foreign: &jint) -> SignalJniResult { + if *foreign < 0 { Ok(None) } else { u32::convert_from(env, foreign).map(Some) @@ -169,10 +170,10 @@ impl<'a> SimpleArgTypeInfo<'a> for Option { } /// Reinterprets the bits of the Java `long` as a `u64`. -impl<'a> SimpleArgTypeInfo<'a> for u64 { +impl SimpleArgTypeInfo<'_> for u64 { type ArgType = jlong; - fn convert_from(_env: &JNIEnv, foreign: jlong) -> SignalJniResult { - Ok(foreign as u64) + fn convert_from(_env: &mut JNIEnv, foreign: &jlong) -> SignalJniResult { + Ok(*foreign as u64) } } @@ -180,16 +181,16 @@ impl<'a> SimpleArgTypeInfo<'a> for u64 { /// /// Negative `long` values are *not* reinterpreted as large `u64` values. /// Note that this is different from the implementation of [`ResultTypeInfo`] for `u64`. -impl<'a> SimpleArgTypeInfo<'a> for crate::protocol::Timestamp { +impl SimpleArgTypeInfo<'_> for crate::protocol::Timestamp { type ArgType = jlong; - fn convert_from(_env: &JNIEnv, foreign: jlong) -> SignalJniResult { - if foreign < 0 { + fn convert_from(_env: &mut JNIEnv, foreign: &jlong) -> SignalJniResult { + if *foreign < 0 { return Err(SignalJniError::IntegerOverflow(format!( "{} to Timestamp (u64)", foreign ))); } - Ok(Self::from_millis(foreign as u64)) + Ok(Self::from_millis(*foreign as u64)) } } @@ -197,24 +198,24 @@ impl<'a> SimpleArgTypeInfo<'a> for crate::protocol::Timestamp { /// /// Negative `long` values are *not* reinterpreted as large `u64` values. /// Note that this is different from the implementation of [`ResultTypeInfo`] for `u64`. -impl<'a> SimpleArgTypeInfo<'a> for crate::zkgroup::Timestamp { +impl SimpleArgTypeInfo<'_> for crate::zkgroup::Timestamp { type ArgType = jlong; - fn convert_from(_env: &JNIEnv, foreign: jlong) -> SignalJniResult { - if foreign < 0 { + fn convert_from(_env: &mut JNIEnv, foreign: &jlong) -> SignalJniResult { + if *foreign < 0 { return Err(SignalJniError::IntegerOverflow(format!( "{} to Timestamp (u64)", foreign ))); } - Ok(Self::from_seconds(foreign as u64)) + Ok(Self::from_seconds(*foreign as u64)) } } /// Supports all valid byte values `0..=255`. -impl<'a> SimpleArgTypeInfo<'a> for u8 { +impl SimpleArgTypeInfo<'_> for u8 { type ArgType = jint; - fn convert_from(_env: &JNIEnv, foreign: jint) -> SignalJniResult { - match u8::try_from(foreign) { + fn convert_from(_env: &mut JNIEnv, foreign: &jint) -> SignalJniResult { + match u8::try_from(*foreign) { Err(_) => Err(SignalJniError::IntegerOverflow(format!( "{} to u8", foreign @@ -226,14 +227,14 @@ impl<'a> SimpleArgTypeInfo<'a> for u8 { impl<'a> SimpleArgTypeInfo<'a> for String { type ArgType = JString<'a>; - fn convert_from(env: &JNIEnv, foreign: JString<'a>) -> SignalJniResult { + fn convert_from(env: &mut JNIEnv, foreign: &JString<'a>) -> SignalJniResult { Ok(env.get_string(foreign)?.into()) } } impl<'a> SimpleArgTypeInfo<'a> for Option { type ArgType = JString<'a>; - fn convert_from(env: &JNIEnv, foreign: JString<'a>) -> SignalJniResult { + fn convert_from(env: &mut JNIEnv<'a>, foreign: &JString<'a>) -> SignalJniResult { if foreign.is_null() { Ok(None) } else { @@ -244,7 +245,7 @@ impl<'a> SimpleArgTypeInfo<'a> for Option { impl<'a> SimpleArgTypeInfo<'a> for uuid::Uuid { type ArgType = JObject<'a>; - fn convert_from(env: &JNIEnv, foreign: JObject<'a>) -> SignalJniResult { + fn convert_from(env: &mut JNIEnv, foreign: &JObject<'a>) -> SignalJniResult { check_jobject_type(env, foreign, jni_class_name!(java.util.UUID))?; let args = jni_args!(() -> long); let msb: jlong = call_method_checked(env, foreign, "getMostSignificantBits", args)?; @@ -257,78 +258,78 @@ impl<'a> SimpleArgTypeInfo<'a> for uuid::Uuid { } } -impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> for &'storage [u8] { - type ArgType = jbyteArray; - type StoredType = AutoArray<'context, 'context, jbyte>; - fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - Ok(env.get_byte_array_elements(foreign, ReleaseMode::NoCopyBack)?) +impl<'storage, 'param: 'storage, 'context: 'param> ArgTypeInfo<'storage, 'param, 'context> + for &'storage [u8] +{ + type ArgType = JByteArray<'context>; + type StoredType = AutoElements<'context, 'context, 'param, jbyte>; + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult { + Ok(unsafe { env.get_array_elements(foreign, ReleaseMode::NoCopyBack)? }) } - fn load_from( - _env: &JNIEnv, - stored: &'storage mut Self::StoredType, - ) -> SignalJniResult<&'storage [u8]> { - Ok(unsafe { - std::slice::from_raw_parts(stored.as_ptr() as *const u8, stored.size()? as usize) - }) + fn load_from(stored: &'storage mut Self::StoredType) -> &'storage [u8] { + // Deref `stored` to the contained slice of [jbyte] ([i8]), then cast that to [u8]. + bytemuck::cast_slice(stored) } } -impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> for Option<&'storage [u8]> { - type ArgType = jbyteArray; - type StoredType = Option>; - fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult { +impl<'storage, 'param: 'storage, 'context: 'param> ArgTypeInfo<'storage, 'param, 'context> + for Option<&'storage [u8]> +{ + type ArgType = JByteArray<'context>; + type StoredType = Option>; + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult { if foreign.is_null() { Ok(None) } else { <&'storage [u8]>::borrow(env, foreign).map(Some) } } - fn load_from( - env: &JNIEnv, - stored: &'storage mut Self::StoredType, - ) -> SignalJniResult> { - stored - .as_mut() - .map(|s| <&'storage [u8]>::load_from(env, s)) - .transpose() + fn load_from(stored: &'storage mut Self::StoredType) -> Option<&'storage [u8]> { + stored.as_mut().map(ArgTypeInfo::load_from) } } -impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> for &'storage mut [u8] { - type ArgType = jbyteArray; - type StoredType = AutoArray<'context, 'context, jbyte>; - fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - Ok(env.get_byte_array_elements(foreign, ReleaseMode::CopyBack)?) +impl<'storage, 'param: 'storage, 'context: 'param> ArgTypeInfo<'storage, 'param, 'context> + for &'storage mut [u8] +{ + type ArgType = JByteArray<'context>; + type StoredType = AutoElements<'context, 'context, 'param, jbyte>; + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult { + Ok(unsafe { env.get_array_elements(foreign, ReleaseMode::CopyBack)? }) } - fn load_from( - _env: &JNIEnv, - stored: &'storage mut Self::StoredType, - ) -> SignalJniResult<&'storage mut [u8]> { - Ok(unsafe { - std::slice::from_raw_parts_mut(stored.as_ptr() as *mut u8, stored.size()? as usize) - }) + fn load_from(stored: &'storage mut Self::StoredType) -> &'storage mut [u8] { + // Deref `stored` to the contained slice of [jbyte] ([i8]), then cast that to [u8]. + bytemuck::cast_slice_mut(stored) } } macro_rules! store { ($name:ident) => { paste! { - impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> + impl<'storage, 'param: 'storage, 'context: 'param> ArgTypeInfo<'storage, 'param, 'context> for &'storage mut dyn $name { type ArgType = JObject<'context>; - type StoredType = []<'context>; + type StoredType = []<'storage>; fn borrow( - env: &'context JNIEnv, - store: Self::ArgType, + env: &mut JNIEnv<'context>, + store: &'param Self::ArgType, ) -> SignalJniResult { Self::StoredType::new(env, store) } fn load_from( - _env: &JNIEnv, stored: &'storage mut Self::StoredType, - ) -> SignalJniResult { - Ok(stored) + ) -> Self { + stored } } } @@ -343,43 +344,43 @@ store!(SignedPreKeyStore); store!(KyberPreKeyStore); store!(InputStream); -impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> +impl<'storage, 'param: 'storage, 'context: 'param> ArgTypeInfo<'storage, 'param, 'context> for &'storage mut dyn GrpcReplyListener { - type ArgType = JavaGrpcReplyListener<'context>; - type StoredType = JniGrpcReplyListener<'context>; + type ArgType = JavaGrpcReplyListener<'param>; + type StoredType = JniGrpcReplyListener<'storage>; - fn borrow(env: &'context JNIEnv, store: Self::ArgType) -> SignalJniResult { + fn borrow(env: &mut JNIEnv<'context>, store: &'param Self::ArgType) -> SignalJniResult { Self::StoredType::new(env, store) } - fn load_from(_env: &JNIEnv, stored: &'storage mut Self::StoredType) -> SignalJniResult { - Ok(stored) + fn load_from(stored: &'storage mut Self::StoredType) -> Self { + stored } } -impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> +impl<'storage, 'param: 'storage, 'context: 'param> ArgTypeInfo<'storage, 'param, 'context> for &'storage mut dyn QuicCallbackListener { - type ArgType = JavaQuicCallbackListener<'context>; - type StoredType = JniQuicCallbackListener<'context>; + type ArgType = JavaQuicCallbackListener<'param>; + type StoredType = JniQuicCallbackListener<'storage>; - fn borrow(env: &'context JNIEnv, store: Self::ArgType) -> SignalJniResult { + fn borrow(env: &mut JNIEnv<'context>, store: &'param Self::ArgType) -> SignalJniResult { Self::StoredType::new(env, store) } - fn load_from(_env: &JNIEnv, stored: &'storage mut Self::StoredType) -> SignalJniResult { - Ok(stored) + fn load_from(stored: &'storage mut Self::StoredType) -> Self { + stored } } /// A translation from a Java interface where the implementing class wraps the Rust handle. impl<'a> SimpleArgTypeInfo<'a> for CiphertextMessageRef<'a> { type ArgType = JavaCiphertextMessage<'a>; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { + fn convert_from(env: &mut JNIEnv, foreign: &Self::ArgType) -> SignalJniResult { fn native_handle_from_message<'a, T: 'static>( - env: &JNIEnv, - foreign: JavaCiphertextMessage<'a>, + env: &mut JNIEnv, + foreign: &JavaCiphertextMessage<'a>, class_name: &'static str, make_result: fn(&'a T) -> CiphertextMessageRef<'a>, ) -> SignalJniResult>> { @@ -439,7 +440,7 @@ impl<'a> SimpleArgTypeInfo<'a> for CiphertextMessageRef<'a> { impl<'a> SimpleArgTypeInfo<'a> for crate::grpc::GrpcHeaders { type ArgType = JavaMap<'a>; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { + fn convert_from(env: &mut JNIEnv, foreign: &JavaMap<'a>) -> SignalJniResult { if foreign.is_null() { return Err(SignalJniError::NullHandle); } @@ -447,13 +448,13 @@ impl<'a> SimpleArgTypeInfo<'a> for crate::grpc::GrpcHeaders { let mut headers = HashMap::new(); let jmap = env.get_map(foreign)?; - let mut jmap_iter = jmap.iter()?; - while let Some((key, value)) = jmap_iter.next() { - let header_key: String = env.get_string(key.into())?.into(); - let values = env.get_list(value)?; - let mut values_iter = values.iter()?; - while let Some(value) = values_iter.next() { - let header_value: String = env.get_string(value.into())?.into(); + let mut jmap_iter = jmap.iter(env)?; + while let Ok(Some((key, value))) = jmap_iter.next(env) { + let header_key: String = env.get_string(&key.into())?.into(); + let values = env.get_list(&value)?; + let mut values_iter = values.iter(env)?; + while let Ok(Some(value)) = values_iter.next(env) { + let header_value: String = env.get_string(&value.into())?.into(); headers .entry(header_key.clone()) .and_modify(|l: &mut Vec| l.push(header_value.clone())) @@ -465,33 +466,28 @@ impl<'a> SimpleArgTypeInfo<'a> for crate::grpc::GrpcHeaders { } } -impl ResultTypeInfo for signal_grpc::GrpcReply { - type ResultType = jobject; +impl <'a> ResultTypeInfo<'a> for signal_grpc::GrpcReply { + type ResultType = JByteArray<'a>; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { let message = env.byte_array_from_slice(&self.message)?; let args = jni_args!(( self.statuscode => int, message => [byte], ) -> void); + let class_name = env.find_class(jni_class_name!(org.signal.libsignal.grpc.SignalRpcReply))?; let jobj = env.new_object( - env.find_class(jni_class_name!(org.signal.libsignal.grpc.SignalRpcReply))?, + class_name, args.sig, &args.args, )?; - Ok(jobj.into_inner()) - } - - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) + Ok(jobj.into()) } } impl<'a> SimpleArgTypeInfo<'a> for crate::quic::QuicHeaders { type ArgType = JavaMap<'a>; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { + fn convert_from(env: &mut JNIEnv, foreign: &JavaMap<'a>) -> SignalJniResult { if foreign.is_null() { return Err(SignalJniError::NullHandle); } @@ -499,10 +495,10 @@ impl<'a> SimpleArgTypeInfo<'a> for crate::quic::QuicHeaders { let mut headers = HashMap::new(); let jmap = env.get_map(foreign)?; - let mut jmap_iter = jmap.iter()?; - while let Some((key, value)) = jmap_iter.next() { - let header_key: String = env.get_string(key.into())?.into(); - let header_value: String = env.get_string(value.into())?.into(); + let mut jmap_iter = jmap.iter(env)?; + while let Ok(Some((key, value))) = jmap_iter.next(env) { + let header_key: String = env.get_string(&key.into())?.into(); + let header_value: String = env.get_string(&value.into())?.into(); headers.insert(header_key.clone(), header_value.clone()); } @@ -511,267 +507,194 @@ impl<'a> SimpleArgTypeInfo<'a> for crate::quic::QuicHeaders { } #[cfg(not(target_os = "android"))] -impl ResultTypeInfo for crate::cds2::Cds2Metrics { - type ResultType = jobject; +impl<'a> ResultTypeInfo<'a> for crate::cds2::Cds2Metrics { + type ResultType = JObject<'a>; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { let map_args = jni_args!(() -> void); - let jobj = env.new_object( - env.find_class(jni_class_name!(java.util.HashMap))?, - map_args.sig, - &map_args.args, - )?; + let jclass = env.find_class(jni_class_name!(java.util.HashMap))?; + let jobj = env.new_object(jclass, map_args.sig, &map_args.args)?; // Fully-qualified so that we don't need to conditionalize the `use`. - let jmap = jni::objects::JMap::from_env(env, jobj)?; + let jmap = jni::objects::JMap::from_env(env, &jobj)?; let long_class = env.find_class(jni_class_name!(java.lang.Long))?; for (k, v) in self.0 { + let k = k.convert_into(env)?; let args = jni_args!((v => long) -> void); - jmap.put( - String::convert_into_jobject(&k.convert_into(env)), - env.new_object(long_class, args.sig, &args.args)?, - )?; + let v = env.new_object(&long_class, args.sig, &args.args)?; + jmap.put(env, &k, &v)?; } - Ok(jmap.into_inner()) - } - - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) + Ok(jobj) } } -impl ResultTypeInfo for bool { +impl ResultTypeInfo<'_> for bool { type ResultType = jboolean; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { Ok(if self { JNI_TRUE } else { JNI_FALSE }) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } /// Supports all valid byte values `0..=255`. -impl ResultTypeInfo for u8 { +impl ResultTypeInfo<'_> for u8 { type ResultType = jint; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { Ok(self as jint) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } /// Reinterprets the bits of the `u32` as a Java `int`. /// /// Note that this is different from the implementation of [`ArgTypeInfo`] for `u32`. -impl ResultTypeInfo for u32 { +impl ResultTypeInfo<'_> for u32 { type ResultType = jint; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { // Note that we don't check bounds here. Ok(self as jint) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } /// Reinterprets the bits of the `u32` as a Java `int`. Returns `-1` for `None`. /// /// Note that this is different from the implementation of [`ArgTypeInfo`] for `Option`. -impl ResultTypeInfo for Option { +impl ResultTypeInfo<'_> for Option { type ResultType = jint; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { // Note that we don't check bounds here. Ok(self.unwrap_or(u32::MAX) as jint) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } /// Reinterprets the bits of the `u64` as a Java `long`. -impl ResultTypeInfo for u64 { +impl ResultTypeInfo<'_> for u64 { type ResultType = jlong; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { // Note that we don't check bounds here. Ok(self as jlong) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } /// Reinterprets the bits of the timestamp's `u64` as a Java `long`. /// /// Note that this is different from the implementation of [`ArgTypeInfo`] for `Timestamp`. -impl ResultTypeInfo for crate::protocol::Timestamp { +impl ResultTypeInfo<'_> for crate::protocol::Timestamp { type ResultType = jlong; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { // Note that we don't check bounds here. Ok(self.as_millis() as jlong) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } /// Reinterprets the bits of the timestamp's `u64` as a Java `long`. /// /// Note that this is different from the implementation of [`ArgTypeInfo`] for `Timestamp`. -impl ResultTypeInfo for crate::zkgroup::Timestamp { +impl ResultTypeInfo<'_> for crate::zkgroup::Timestamp { type ResultType = jlong; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { // Note that we don't check bounds here. Ok(self.as_seconds() as jlong) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JObject { - JObject::null() - } } -impl ResultTypeInfo for String { - type ResultType = jstring; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for String { + type ResultType = JString<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { self.deref().convert_into(env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for Option { - type ResultType = jstring; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for Option { + type ResultType = JString<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { self.as_deref().convert_into(env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for &str { - type ResultType = jstring; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - Ok(env.new_string(self)?.into_inner()) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) +impl<'a> ResultTypeInfo<'a> for &str { + type ResultType = JString<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { + Ok(env.new_string(self)?) } } -impl ResultTypeInfo for Option<&str> { - type ResultType = jstring; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for Option<&str> { + type ResultType = JString<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { match self { Some(s) => s.convert_into(env), - None => Ok(std::ptr::null_mut()), + None => Ok(JString::default()), } } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for &[u8] { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for &[u8] { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { Ok(env.byte_array_from_slice(self)?) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for Option<&[u8]> { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for Option<&[u8]> { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { match self { Some(s) => s.convert_into(env), - None => Ok(std::ptr::null_mut()), + None => Ok(JByteArray::default()), } } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for Vec { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for Vec { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { self.deref().convert_into(env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for Option> { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for Option> { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { self.as_deref().convert_into(env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl<'storage, 'context: 'storage, const LEN: usize> ArgTypeInfo<'storage, 'context> - for &'storage [u8; LEN] +impl<'storage, 'param: 'storage, 'context: 'param, const LEN: usize> + ArgTypeInfo<'storage, 'param, 'context> for &'storage [u8; LEN] { - type ArgType = jbyteArray; - type StoredType = AutoArray<'context, 'context, jbyte>; - fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - Ok(env.get_byte_array_elements(foreign, ReleaseMode::NoCopyBack)?) + type ArgType = JByteArray<'context>; + type StoredType = AutoElements<'context, 'context, 'param, jbyte>; + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult { + let elements = unsafe { env.get_array_elements(foreign, ReleaseMode::NoCopyBack)? }; + if elements.len() != LEN { + return Err(SignalJniError::IncorrectArrayLength { + expected: LEN, + actual: elements.len(), + }); + } + Ok(elements) } - fn load_from( - _env: &JNIEnv, - stored: &'storage mut Self::StoredType, - ) -> SignalJniResult<&'storage [u8; LEN]> { - let slice = unsafe { - std::slice::from_raw_parts(stored.as_ptr() as *const u8, stored.size()? as usize) - }; - slice + fn load_from(stored: &'storage mut Self::StoredType) -> &'storage [u8; LEN] { + // Deref `stored` to the contained slice of [jbyte] ([i8]), then cast that to [u8], + // then convert the fixed-sized array [u8; LEN] + bytemuck::cast_slice(stored) .try_into() - .map_err(|_| SignalJniError::IncorrectArrayLength { - expected: LEN, - actual: slice.len(), - }) + .expect("checked in construction") } } -impl ResultTypeInfo for [u8; LEN] { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a, const LEN: usize> ResultTypeInfo<'a> for [u8; LEN] { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { self.as_ref().convert_into(env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for uuid::Uuid { - type ResultType = jobject; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for uuid::Uuid { + type ResultType = JObject<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { let uuid_class = env.find_class(jni_class_name!(java.util.UUID))?; let uuid_bytes: [u8; 16] = *self.as_bytes(); let (msb, lsb) = uuid_bytes.split_at(8); @@ -779,222 +702,73 @@ impl ResultTypeInfo for uuid::Uuid { jlong::from_be_bytes(msb.try_into().expect("correct length")) => long, jlong::from_be_bytes(lsb.try_into().expect("correct length")) => long, ) -> void); - Ok(*env.new_object(uuid_class, args.sig, &args.args)?) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) + Ok(env.new_object(uuid_class, args.sig, &args.args)?) } } /// A translation to a Java interface where the implementing class wraps the Rust handle. -impl ResultTypeInfo for CiphertextMessage { - type ResultType = JavaReturnCiphertextMessage; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - let obj = match self { - CiphertextMessage::SignalMessage(m) => jobject_from_native_handle( - env, - jni_class_name!(org.signal.libsignal.protocol.message.SignalMessage), - m.convert_into(env)?, - ), - CiphertextMessage::PreKeySignalMessage(m) => jobject_from_native_handle( - env, - jni_class_name!(org.signal.libsignal.protocol.message.PreKeySignalMessage), - m.convert_into(env)?, - ), - CiphertextMessage::SenderKeyMessage(m) => jobject_from_native_handle( - env, - jni_class_name!(org.signal.libsignal.protocol.message.SenderKeyMessage), - m.convert_into(env)?, - ), - CiphertextMessage::PlaintextContent(m) => jobject_from_native_handle( - env, - jni_class_name!(org.signal.libsignal.protocol.message.PlaintextContent), - m.convert_into(env)?, - ), - }; - - Ok(obj?.into_inner()) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -#[cfg(feature = "signal-media")] -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } -} - -impl ResultTypeInfo for Result { - type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { - T::convert_into(self?, env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) +impl<'a> ResultTypeInfo<'a> for CiphertextMessage { + type ResultType = JavaCiphertextMessage<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { + match self { + CiphertextMessage::SignalMessage(m) => { + let message = m.convert_into(env)?; + jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.message.SignalMessage), + message, + ) + } + CiphertextMessage::PreKeySignalMessage(m) => { + let message = m.convert_into(env)?; + jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.message.PreKeySignalMessage), + message, + ) + } + CiphertextMessage::SenderKeyMessage(m) => { + let message = m.convert_into(env)?; + jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.message.SenderKeyMessage), + message, + ) + } + CiphertextMessage::PlaintextContent(m) => { + let message = m.convert_into(env)?; + jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.message.PlaintextContent), + message, + ) + } + } } } -impl ResultTypeInfo for SignalJniResult { +impl<'a, T: ResultTypeInfo<'a>, E> ResultTypeInfo<'a> for Result +where + SignalJniError: From, +{ type ResultType = T::ResultType; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { T::convert_into(self?, env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - ::convert_into_jobject(signal_jni_result) - } } /// Used when returning an optional buffer, since the conversion to a Java array might also fail. -impl ResultTypeInfo for Option> { - type ResultType = as ResultTypeInfo>::ResultType; - fn convert_into(self, env: &jni::JNIEnv) -> SignalJniResult { - self.transpose()?.convert_into(env) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - as ResultTypeInfo>::convert_into_jobject(signal_jni_result) +impl<'a> ResultTypeInfo<'a> for Option>> { + type ResultType = JByteArray<'a>; + fn convert_into(self, _env: &mut JNIEnv<'a>) -> SignalJniResult { + Ok(self.transpose()?.unwrap_or_default()) } } -impl ResultTypeInfo for Option { - type ResultType = jobject; - fn convert_into(self, _env: &jni::JNIEnv) -> SignalJniResult { - Ok(self.unwrap_or(std::ptr::null_mut())) - } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) +impl<'a> ResultTypeInfo<'a> for Option> { + type ResultType = JObject<'a>; + fn convert_into(self, _env: &mut JNIEnv<'a>) -> SignalJniResult { + Ok(self.unwrap_or_default()) } } @@ -1004,17 +778,17 @@ impl ResultTypeInfo for Option { /// will be kept alive, it can't (safely) have references to anything with a non-static lifetime. pub trait BridgeHandle: 'static {} -impl<'a, T: BridgeHandle> SimpleArgTypeInfo<'a> for &T { +impl SimpleArgTypeInfo<'_> for &T { type ArgType = ObjectHandle; - fn convert_from(_env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - Ok(unsafe { native_handle_cast(foreign) }?) + fn convert_from(_env: &mut JNIEnv, foreign: &Self::ArgType) -> SignalJniResult { + Ok(unsafe { native_handle_cast(*foreign) }?) } } -impl<'a, T: BridgeHandle> SimpleArgTypeInfo<'a> for Option<&T> { +impl SimpleArgTypeInfo<'_> for Option<&T> { type ArgType = ObjectHandle; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - if foreign == 0 { + fn convert_from(env: &mut JNIEnv, foreign: &Self::ArgType) -> SignalJniResult { + if *foreign == 0 { Ok(None) } else { <&T>::convert_from(env, foreign).map(Some) @@ -1022,23 +796,24 @@ impl<'a, T: BridgeHandle> SimpleArgTypeInfo<'a> for Option<&T> { } } -impl<'a, T: BridgeHandle> SimpleArgTypeInfo<'a> for &mut T { +impl SimpleArgTypeInfo<'_> for &mut T { type ArgType = ObjectHandle; - fn convert_from(_env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - unsafe { native_handle_cast(foreign) } + fn convert_from(_env: &mut JNIEnv, foreign: &Self::ArgType) -> SignalJniResult { + unsafe { native_handle_cast(*foreign) } } } -impl<'storage, 'context: 'storage, T: BridgeHandle> ArgTypeInfo<'storage, 'context> - for &'storage [&'storage T] +impl<'storage, 'param: 'storage, 'context: 'param, T: BridgeHandle> + ArgTypeInfo<'storage, 'param, 'context> for &'storage [&'storage T] { - type ArgType = jlongArray; + type ArgType = JLongArray<'context>; type StoredType = Vec<&'storage T>; - fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - let array = env.get_long_array_elements(foreign, ReleaseMode::NoCopyBack)?; - let len = array.size()? as usize; - let slice = unsafe { std::slice::from_raw_parts(array.as_ptr(), len) }; - slice + fn borrow( + env: &mut JNIEnv<'context>, + foreign: &'param Self::ArgType, + ) -> SignalJniResult { + let array = unsafe { env.get_array_elements(foreign, ReleaseMode::NoCopyBack)? }; + array .iter() .map(|&raw_handle| unsafe { (raw_handle as *const T) @@ -1047,78 +822,60 @@ impl<'storage, 'context: 'storage, T: BridgeHandle> ArgTypeInfo<'storage, 'conte }) .collect() } - fn load_from( - _env: &JNIEnv, - stored: &'storage mut Self::StoredType, - ) -> SignalJniResult<&'storage [&'storage T]> { - Ok(&*stored) + fn load_from(stored: &'storage mut Self::StoredType) -> &'storage [&'storage T] { + &*stored } } -impl ResultTypeInfo for T { +impl ResultTypeInfo<'_> for T { type ResultType = ObjectHandle; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { Ok(Box::into_raw(Box::new(self)) as ObjectHandle) } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JavaObject { - JavaObject::null() - } } -impl ResultTypeInfo for Option { +impl ResultTypeInfo<'_> for Option { type ResultType = ObjectHandle; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, env: &mut JNIEnv) -> SignalJniResult { match self { Some(obj) => obj.convert_into(env), None => Ok(0), } } - fn convert_into_jobject(_signal_jni_result: &SignalJniResult) -> JavaObject { - JavaObject::null() - } } -impl ResultTypeInfo for ServiceId { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for ServiceId { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { Ok(env.byte_array_from_slice(&self.service_id_fixed_width_binary())?) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl ResultTypeInfo for Aci { - type ResultType = jbyteArray; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { +impl<'a> ResultTypeInfo<'a> for Aci { + type ResultType = JByteArray<'a>; + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { ServiceId::from(self).convert_into(env) } - fn convert_into_jobject(signal_jni_result: &SignalJniResult) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } -impl SimpleArgTypeInfo<'_> for Serialized +impl<'a, T> SimpleArgTypeInfo<'a> for Serialized where - T: FixedLengthBincodeSerializable + for<'a> serde::Deserialize<'a>, + T: FixedLengthBincodeSerializable + for<'x> serde::Deserialize<'x>, { - type ArgType = jbyteArray; - - fn convert_from(env: &jni_crate::JNIEnv, foreign: Self::ArgType) -> SignalJniResult { - let borrowed_array = env.get_byte_array_elements(foreign, ReleaseMode::NoCopyBack)?; - let len = borrowed_array.size()? as usize; - assert!( - len == T::Array::LEN, + type ArgType = JByteArray<'a>; + + fn convert_from(env: &mut JNIEnv<'a>, foreign: &Self::ArgType) -> SignalJniResult { + // Ideally we would deserialize directly to &T::Array. However, trying to require that + // T::Array: ArgTypeInfo is pretty much impossible with the lifetimes SimpleArgTypeInfo + // provides; we'd have to drop back to ArgTypeInfo for Serialized. + let mut borrowed_array = <&[u8]>::borrow(env, foreign)?; + let bytes = <&[u8]>::load_from(&mut borrowed_array); + assert_eq!( + bytes.len(), + T::Array::LEN, "{} should have been validated on creation", std::any::type_name::() ); - // Convert from i8 to u8. - let bytes = - unsafe { std::slice::from_raw_parts(borrowed_array.as_ptr() as *const u8, len) }; let result: T = bincode::deserialize(bytes).unwrap_or_else(|_| { panic!( "{} should have been validated on creation", @@ -1129,9 +886,9 @@ where } } -impl SimpleArgTypeInfo<'_> for ServiceId { - type ArgType = jbyteArray; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { +impl<'a> SimpleArgTypeInfo<'a> for ServiceId { + type ArgType = JByteArray<'a>; + fn convert_from(env: &mut JNIEnv, foreign: &Self::ArgType) -> SignalJniResult { env.convert_byte_array(foreign) .ok() .and_then(|vec| vec.try_into().ok()) @@ -1146,40 +903,34 @@ impl SimpleArgTypeInfo<'_> for ServiceId { } } -impl SimpleArgTypeInfo<'_> for Aci { - type ArgType = jbyteArray; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { +impl<'a> SimpleArgTypeInfo<'a> for Aci { + type ArgType = JByteArray<'a>; + fn convert_from(env: &mut JNIEnv<'a>, foreign: &Self::ArgType) -> SignalJniResult { ServiceId::convert_from(env, foreign)? .try_into() .map_err(|_| SignalProtocolError::InvalidArgument("not an ACI".to_string()).into()) } } -impl SimpleArgTypeInfo<'_> for Pni { - type ArgType = jbyteArray; - fn convert_from(env: &JNIEnv, foreign: Self::ArgType) -> SignalJniResult { +impl<'a> SimpleArgTypeInfo<'a> for Pni { + type ArgType = JByteArray<'a>; + fn convert_from(env: &mut JNIEnv<'a>, foreign: &Self::ArgType) -> SignalJniResult { ServiceId::convert_from(env, foreign)? .try_into() .map_err(|_| SignalProtocolError::InvalidArgument("not a PNI".to_string()).into()) } } -impl ResultTypeInfo for Serialized +impl<'a, T> ResultTypeInfo<'a> for Serialized where T: FixedLengthBincodeSerializable + serde::Serialize, { - type ResultType = jbyteArray; + type ResultType = JByteArray<'a>; - fn convert_into(self, env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, env: &mut JNIEnv<'a>) -> SignalJniResult { let result = bincode::serialize(self.deref()).expect("can always serialize a value"); result.convert_into(env) } - - fn convert_into_jobject( - signal_jni_result: &SignalJniResult, - ) -> jni_crate::objects::JObject { - Vec::::convert_into_jobject(signal_jni_result) - } } /// Implementation of [`bridge_handle`](crate::support::bridge_handle) for JNI. @@ -1201,52 +952,22 @@ macro_rules! jni_bridge_handle { macro_rules! trivial { ($typ:ty) => { - impl<'a> SimpleArgTypeInfo<'a> for $typ { - type ArgType = Self; - fn convert_from(_env: &JNIEnv, foreign: Self) -> SignalJniResult { - Ok(foreign) - } - } - impl ResultTypeInfo for $typ { - type ResultType = Self; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { - Ok(self) - } - fn convert_into_jobject( - _signal_jni_result: &SignalJniResult, - ) -> JObject { - JObject::null() - } - } - }; -} - -macro_rules! trivial_jobject { - ($typ:ty) => { - impl<'a> SimpleArgTypeInfo<'a> for $typ { + impl SimpleArgTypeInfo<'_> for $typ { type ArgType = Self; - fn convert_from(_env: &JNIEnv, foreign: Self) -> SignalJniResult { - Ok(foreign) + fn convert_from(_env: &mut JNIEnv, foreign: &Self) -> SignalJniResult { + Ok(*foreign) } } - impl ResultTypeInfo for $typ { + impl ResultTypeInfo<'_> for $typ { type ResultType = Self; - fn convert_into(self, _env: &JNIEnv) -> SignalJniResult { + fn convert_into(self, _env: &mut JNIEnv) -> SignalJniResult { Ok(self) } - fn convert_into_jobject( - signal_jni_result: &SignalJniResult, - ) -> JObject { - signal_jni_result - .as_ref() - .map_or(JObject::null(), |&jobj| JObject::from(jobj)) - } } }; } trivial!(i32); -trivial_jobject!(jbyteArray); trivial!(()); /// Syntactically translates `bridge_fn` argument types to JNI types for `cbindgen` and @@ -1256,6 +977,8 @@ trivial!(()); /// will need to be added here directly even if they already implement [`ArgTypeInfo`]. The default /// behavior for references is to assume they're opaque handles to Rust values; the default /// behavior for `&mut dyn Foo` is to assume there's a type called `jni::JavaFoo`. +/// +/// The `'local` lifetime represents the lifetime of the JNI context. macro_rules! jni_arg_type { (u8) => { // Note: not a jbyte. It's better to preserve the signedness here. @@ -1271,52 +994,52 @@ macro_rules! jni_arg_type { jni::jlong }; (String) => { - jni::JString + jni::JString<'local> }; (Option) => { - jni::JString + jni::JString<'local> }; (&[u8]) => { - jni::jbyteArray + jni::JByteArray<'local> }; (Option<&[u8]>) => { - jni::jbyteArray + jni::JByteArray<'local> }; (&mut [u8]) => { - jni::jbyteArray + jni::JByteArray<'local> }; (&[u8; $len:expr]) => { - jni::jbyteArray + jni::JByteArray<'local> }; (ServiceId) => { - jni::jbyteArray + jni::JByteArray<'local> }; (Aci) => { - jni::jbyteArray + jni::JByteArray<'local> }; (Pni) => { - jni::jbyteArray + jni::JByteArray<'local> }; (Timestamp) => { jni::jlong }; (Uuid) => { - jni::JavaUUID + jni::JavaUUID<'local> }; (GrpcHeaders) => { - jni::JavaMap + jni::JavaMap<'local> }; (QuicHeaders) => { - jni::JavaMap + jni::JavaMap<'local> }; (jni::CiphertextMessageRef) => { - jni::JavaCiphertextMessage + jni::JavaCiphertextMessage<'local> }; (& [& $typ:ty]) => { - jni::jlongArray + jni::JLongArray<'local> }; (&mut dyn $typ:ty) => { - paste!(jni::[]) + paste!(jni::[]<'local>) }; (& $typ:ty) => { jni::ObjectHandle @@ -1328,7 +1051,7 @@ macro_rules! jni_arg_type { jni::ObjectHandle }; (Serialized<$typ:ident>) => { - jni::jbyteArray + jni::JByteArray<'local> }; } @@ -1338,11 +1061,14 @@ macro_rules! jni_arg_type { /// This is a syntactic transformation (because that's how Rust macros work), so new result types /// will need to be added here directly even if they already implement [`ResultTypeInfo`]. The /// default behavior is to assume we're returning an opaque handle to a Rust value. +/// +/// The `'local` lifetime represents the lifetime of the JNI context. macro_rules! jni_result_type { // These rules only match a single token for a Result's success type. // We can't use `:ty` because we need the resulting tokens to be matched recursively rather than // treated as a single unit, and we can't match multiple tokens because Rust's macros match - // eagerly. Therefore, if you need to return a more complicated Result type, you'll have to add // another rule for its form. + // eagerly. Therefore, if you need to return a more complicated Result type, you'll have to add + // another rule for its form. (Result<$typ:tt $(, $_:ty)?>) => { jni_result_type!($typ) }; @@ -1378,31 +1104,40 @@ macro_rules! jni_result_type { jni::jlong }; (&str) => { - jni::jstring + jni::JString<'local> }; (String) => { - jni::jstring + jni::JString<'local> }; (Uuid) => { - jni::JavaReturnUUID + jni::JavaUUID<'local> }; (Timestamp) => { jni::jlong }; (&[u8]) => { - jni::jbyteArray + jni::JByteArray<'local> }; (Vec) => { - jni::jbyteArray + jni::JByteArray<'local> }; (GrpcReply) => { - jni::jbyteArray + jni::JByteArray<'local> }; (Cds2Metrics) => { - jni::JavaReturnMap + jni::JavaMap<'local> }; ([u8; $len:expr]) => { - jni::jbyteArray + jni::JByteArray<'local> + }; + (ServiceId) => { + jni::JByteArray<'local> + }; + (Aci) => { + jni::JByteArray<'local> + }; + (Pni) => { + jni::JByteArray<'local> }; (ServiceId) => { jni::jbyteArray @@ -1420,10 +1155,10 @@ macro_rules! jni_result_type { jni_result_type!($typ<$($args),+>) }; (CiphertextMessage) => { - jni::JavaReturnCiphertextMessage + jni::JavaCiphertextMessage<'local> }; (Serialized<$typ:ident>) => { - jni::jbyteArray + jni::JByteArray<'local> }; ( $handle:ty ) => { jni::ObjectHandle diff --git a/rust/bridge/shared/src/jni/error.rs b/rust/bridge/shared/src/jni/error.rs index 98f35553f3..fce97eb031 100644 --- a/rust/bridge/shared/src/jni/error.rs +++ b/rust/bridge/shared/src/jni/error.rs @@ -236,7 +236,7 @@ pub struct ThrownException { impl ThrownException { /// Gets the wrapped exception as a live object with a lifetime. - pub fn as_obj(&self) -> JThrowable<'_> { + pub fn as_obj(&self) -> &JThrowable<'static> { self.exception_ref.as_obj().into() } @@ -249,7 +249,7 @@ impl ThrownException { }) } - pub fn class_name(&self, env: &JNIEnv) -> Result { + pub fn class_name(&self, env: &mut JNIEnv) -> Result { let class_type = env.get_object_class(self.exception_ref.as_obj())?; let class_name: JObject = call_method_checked( env, @@ -258,23 +258,23 @@ impl ThrownException { jni_args!(() -> java.lang.String), )?; - Ok(env.get_string(JString::from(class_name))?.into()) + Ok(env.get_string(&JString::from(class_name))?.into()) } - pub fn message(&self, env: &JNIEnv) -> Result { + pub fn message(&self, env: &mut JNIEnv) -> Result { let message: JObject = call_method_checked( env, self.exception_ref.as_obj(), "getMessage", jni_args!(() -> java.lang.String), )?; - Ok(env.get_string(JString::from(message))?.into()) + Ok(env.get_string(&JString::from(message))?.into()) } } impl fmt::Display for ThrownException { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let env = &self.jvm.attach_current_thread().map_err(|_| fmt::Error)?; + let env = &mut self.jvm.attach_current_thread().map_err(|_| fmt::Error)?; let exn_type = self.class_name(env); let exn_type = exn_type.as_deref().unwrap_or(""); @@ -289,12 +289,12 @@ impl fmt::Display for ThrownException { impl fmt::Debug for ThrownException { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let env = &self.jvm.attach_current_thread().map_err(|_| fmt::Error)?; + let env = &mut self.jvm.attach_current_thread().map_err(|_| fmt::Error)?; let exn_type = self.class_name(env); let exn_type = exn_type.as_deref().unwrap_or(""); - let obj_addr = *self.exception_ref.as_obj(); + let obj_addr = **self.exception_ref.as_obj(); if let Ok(message) = self.message(env) { write!(f, "exception {} ({:p}) \"{}\"", exn_type, obj_addr, message) diff --git a/rust/bridge/shared/src/jni/grpc.rs b/rust/bridge/shared/src/jni/grpc.rs index 9bf7e9780c..d19f5a452d 100644 --- a/rust/bridge/shared/src/jni/grpc.rs +++ b/rust/bridge/shared/src/jni/grpc.rs @@ -4,44 +4,51 @@ // use super::*; +use std::cell::RefCell; + use async_trait::async_trait; pub type JavaGrpcReplyListener<'a> = JObject<'a>; pub struct JniGrpcReplyListener<'a> { - env: &'a JNIEnv<'a>, - listener: JObject<'a>, + env: RefCell>, + listener: &'a JObject<'a>, } impl<'a> JniGrpcReplyListener<'a> { - pub fn new(env: &'a JNIEnv, listener: JObject<'a>) -> Result { + pub fn new<'context: 'a>(env: &mut JNIEnv<'context>, listener: &'a JObject<'a>) -> Result { check_jobject_type( env, - listener, + &listener, jni_class_name!(org.signal.libsignal.grpc.GrpcReplyListener), )?; - Ok(Self { env, listener }) + Ok(Self { env: EnvHandle::new(env).into(), listener }) } } impl<'a> JniGrpcReplyListener<'a> { fn do_on_reply(&mut self, reply: GrpcReply) -> Result<(), SignalJniError> { - let callback_args = jni_args!(( - reply.convert_into(self.env)? => org.signal.libsignal.grpc.SignalRpcReply, - ) -> void); - call_method_checked(self.env, self.listener, "onReply", callback_args)?; + self.env.borrow_mut().with_local_frame(8, |env| { + let jni_reply = reply.convert_into(env)?; + let callback_args = jni_args!(( + jni_reply => org.signal.libsignal.grpc.SignalRpcReply, + ) -> void); + call_method_checked(env, self.listener, "onReply", callback_args)?; - Ok(()) + Ok(()) + }) } fn do_on_error(&mut self, error: String) -> Result<(), SignalJniError> { - let message = self.env.new_string(error.to_string())?; - let callback_args = jni_args!(( - message => java.lang.String, - ) -> void); - call_method_checked(self.env, self.listener, "onError", callback_args)?; + self.env.borrow_mut().with_local_frame(8, |env| { + let message = env.new_string(error.to_string())?; + let callback_args = jni_args!(( + message => java.lang.String, + ) -> void); + call_method_checked(env, self.listener, "onError", callback_args)?; - Ok(()) + Ok(()) + }) } } diff --git a/rust/bridge/shared/src/jni/io.rs b/rust/bridge/shared/src/jni/io.rs index 7e37c58588..22f8ab8c1c 100644 --- a/rust/bridge/shared/src/jni/io.rs +++ b/rust/bridge/shared/src/jni/io.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::cell::RefCell; use std::io; use async_trait::async_trait; @@ -15,55 +16,64 @@ use crate::io::{InputStream, InputStreamRead}; pub type JavaInputStream<'a> = JObject<'a>; pub struct JniInputStream<'a> { - env: &'a JNIEnv<'a>, - stream: JObject<'a>, + env: RefCell>, + stream: &'a JObject<'a>, } impl<'a> JniInputStream<'a> { - pub fn new(env: &'a JNIEnv, stream: JObject<'a>) -> SignalJniResult { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + stream: &'a JObject<'a>, + ) -> SignalJniResult { check_jobject_type(env, stream, jni_class_name!(java.io.InputStream))?; - Ok(Self { env, stream }) + Ok(Self { + env: EnvHandle::new(env).into(), + stream, + }) } fn do_read(&self, buf: &mut [u8]) -> SignalJniResult { - let java_buf = self.env.new_byte_array(buf.len() as i32)?; - let amount_read: jint = call_method_checked( - self.env, - self.stream, - "read", - jni_args!((java_buf => [byte]) -> int), - )?; - let amount_read = match amount_read { - -1 => 0, - _ => u32::convert_from(self.env, amount_read)? as usize, - }; - self.env - .get_byte_array_region(java_buf, 0, cast_slice_mut(&mut buf[..amount_read]))?; - Ok(amount_read) + self.env.borrow_mut().with_local_frame(8, |env| { + let java_buf = env.new_byte_array(buf.len() as i32)?; + let amount_read: jint = call_method_checked( + env, + self.stream, + "read", + jni_args!((java_buf => [byte]) -> int), + )?; + let amount_read = match amount_read { + -1 => 0, + _ => u32::convert_from(env, &amount_read)? as usize, + }; + env.get_byte_array_region(java_buf, 0, cast_slice_mut(&mut buf[..amount_read]))?; + Ok(amount_read) + }) } fn do_skip(&self, amount: u64) -> SignalJniResult<()> { - let java_amount = amount.try_into().map_err(|_| { - SignalJniError::Io(io::Error::new( - io::ErrorKind::UnexpectedEof, - "InputStream::skip more than i64::MAX not supported", - )) - })?; + self.env.borrow_mut().with_local_frame(8, |env| { + let java_amount = amount.try_into().map_err(|_| { + SignalJniError::Io(io::Error::new( + io::ErrorKind::UnexpectedEof, + "InputStream::skip more than i64::MAX not supported", + )) + })?; - let amount_skipped: jlong = call_method_checked( - self.env, - self.stream, - "skip", - jni_args!((java_amount => long) -> long), - )?; + let amount_skipped: jlong = call_method_checked( + env, + self.stream, + "skip", + jni_args!((java_amount => long) -> long), + )?; - if amount_skipped != java_amount { - return Err(SignalJniError::Io(io::Error::new( - io::ErrorKind::UnexpectedEof, - "InputStream skipped less than requested", - ))); - } - Ok(()) + if amount_skipped != java_amount { + return Err(SignalJniError::Io(io::Error::new( + io::ErrorKind::UnexpectedEof, + "InputStream skipped less than requested", + ))); + } + Ok(()) + }) } } diff --git a/rust/bridge/shared/src/jni/mod.rs b/rust/bridge/shared/src/jni/mod.rs index f888ffd082..b7d6270fbb 100644 --- a/rust/bridge/shared/src/jni/mod.rs +++ b/rust/bridge/shared/src/jni/mod.rs @@ -3,10 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // -extern crate jni_crate as jni; - -use jni::objects::{JThrowable, JValue}; -use jni::sys::jobject; +use jni::objects::{JThrowable, JValue, JValueOwned}; use attest::hsm_enclave::Error as HsmEnclaveError; use attest::sgx_session::Error as SgxError; @@ -21,8 +18,10 @@ use std::convert::{TryFrom, TryInto}; use std::error::Error; use std::fmt::Display; -pub(crate) use jni::objects::{AutoArray, JClass, JObject, JString, ReleaseMode}; -pub(crate) use jni::sys::{jboolean, jbyteArray, jint, jlong, jlongArray, jstring}; +pub(crate) use jni::objects::{ + AutoElements, JByteArray, JClass, JLongArray, JObject, JString, ReleaseMode, +}; +pub(crate) use jni::sys::{jboolean, jint, jlong}; pub(crate) use jni::JNIEnv; #[macro_use] @@ -55,18 +54,19 @@ pub type ObjectHandle = jlong; pub type JavaObject<'a> = JObject<'a>; pub type JavaUUID<'a> = JObject<'a>; -pub type JavaMap<'a> = JObject<'a>; -pub type JavaReturnUUID = jobject; pub type JavaCiphertextMessage<'a> = JObject<'a>; -pub type JavaReturnCiphertextMessage = jobject; -pub type JavaReturnMap = jobject; +pub type JavaMap<'a> = JObject<'a>; /// Translates errors into Java exceptions. /// /// Exceptions thrown in callbacks will be rethrown; all other errors will be mapped to an /// appropriate Java exception class and thrown. -fn throw_error(env: &JNIEnv, error: SignalJniError) { - fn try_throw(env: &JNIEnv, throwable: Result, error: SignalJniError) { +fn throw_error(env: &mut JNIEnv, error: SignalJniError) { + fn try_throw( + env: &mut JNIEnv, + throwable: Result, + error: SignalJniError, + ) { match throwable { Err(failure) => log::error!("failed to create exception for {}: {}", error, failure), Ok(throwable) => { @@ -456,56 +456,25 @@ fn throw_error(env: &JNIEnv, error: SignalJniError) { } } -/// Provides a dummy value to return when an exception is thrown. -pub trait JniDummyValue { - fn dummy_value() -> Self; -} - -impl JniDummyValue for ObjectHandle { - fn dummy_value() -> Self { - 0 - } -} - -impl JniDummyValue for jint { - fn dummy_value() -> Self { - 0 - } -} - -impl JniDummyValue for jobject { - fn dummy_value() -> Self { - std::ptr::null_mut() - } -} - -impl JniDummyValue for jboolean { - fn dummy_value() -> Self { - 0 - } -} - -impl JniDummyValue for () { - fn dummy_value() -> Self {} -} - #[inline(always)] -pub fn run_ffi_safe Result + std::panic::UnwindSafe, R>( - env: &JNIEnv, - f: F, -) -> R +pub fn run_ffi_safe<'local, F, R>(env: &mut JNIEnv<'local>, f: F) -> R where - R: JniDummyValue, + F: for<'a> FnOnce(&'a mut JNIEnv<'local>) -> Result + std::panic::UnwindSafe, + R: Default, { - match std::panic::catch_unwind(f) { + // This AssertUnwindSafe is not technically safe. + // If we get a panic downstream, it is entirely possible the Java environment won't be usable anymore. + // But if that's the case, we've got bigger problems! + // So if we want to catch panics, we have to allow this. + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(env))) { Ok(Ok(r)) => r, Ok(Err(e)) => { throw_error(env, e); - R::dummy_value() + R::default() } Err(r) => { throw_error(env, SignalJniError::UnexpectedPanic(r)); - R::dummy_value() + R::default() } } } @@ -525,50 +494,23 @@ pub unsafe fn native_handle_cast( Ok(&mut *(handle as *mut T)) } -/// Calls a passed in function with a local frame of capacity that's passed in. Basically just -/// the jni with_local_frame except with the result type changed to use SignalJniError instead. -pub fn with_local_frame<'a, F>(env: &'a JNIEnv, capacity: i32, f: F) -> SignalJniResult> -where - F: FnOnce() -> SignalJniResult>, -{ - env.push_local_frame(capacity)?; - let res = f(); - match res { - Ok(obj) => Ok(env.pop_local_frame(obj)?), - Err(e) => { - env.pop_local_frame(JObject::null())?; - Err(e) - } - } -} - -/// Calls a passed in function with a local frame of capacity that's passed in. Basically just -/// the jni with_local_frame except with the result type changed to use SignalJniError instead. -pub fn with_local_frame_no_jobject_result( - env: &JNIEnv, - capacity: i32, - f: F, -) -> SignalJniResult -where - F: FnOnce() -> SignalJniResult, -{ - env.push_local_frame(capacity)?; - let res = f(); - env.pop_local_frame(JObject::null())?; - res -} - /// Calls a method and translates any thrown exceptions to /// [`SignalProtocolError::ApplicationCallbackError`]. /// /// Wraps [`JNIEnv::call_method`]; all arguments are the same. /// The result must have the correct type, or [`SignalJniError::UnexpectedJniResultType`] will be /// returned instead. -pub fn call_method_checked<'a, O: Into>, R: TryFrom>, const LEN: usize>( - env: &JNIEnv<'a>, +pub fn call_method_checked< + 'input, + 'output, + O: AsRef>, + R: TryFrom>, + const LEN: usize, +>( + env: &mut JNIEnv<'output>, obj: O, fn_name: &'static str, - args: JniArgs<'a, R, LEN>, + args: JniArgs, ) -> Result { // Note that we are *not* unwrapping the result yet! // We need to check for exceptions *first*. @@ -577,9 +519,10 @@ pub fn call_method_checked<'a, O: Into>, R: TryFrom>, con let throwable = env.exception_occurred()?; if **throwable == *JObject::null() { let result = result?; + let type_name = result.type_name(); result .try_into() - .map_err(|_| SignalJniError::UnexpectedJniResultType(fn_name, result.type_name())) + .map_err(|_| SignalJniError::UnexpectedJniResultType(fn_name, type_name)) } else { env.exception_clear()?; @@ -595,7 +538,7 @@ pub fn call_method_checked<'a, O: Into>, R: TryFrom>, con /// /// Assumes there's a corresponding constructor that takes a single `long` to represent the address. pub fn jobject_from_native_handle<'a>( - env: &'a JNIEnv, + env: &mut JNIEnv<'a>, class_name: &str, boxed_handle: ObjectHandle, ) -> Result, SignalJniError> { @@ -610,20 +553,21 @@ pub fn jobject_from_native_handle<'a>( /// /// A convenience wrapper around `jobject_from_native_handle` for SignalProtocolAddress. fn protocol_address_to_jobject<'a>( - env: &'a JNIEnv, + env: &mut JNIEnv<'a>, address: &ProtocolAddress, ) -> Result, SignalJniError> { + let handle = address.clone().convert_into(env)?; jobject_from_native_handle( env, jni_class_name!(org.signal.libsignal.protocol.SignalProtocolAddress), - address.clone().convert_into(env)?, + handle, ) } /// Verifies that a Java object is a non-`null` instance of the given class. pub fn check_jobject_type( - env: &JNIEnv, - obj: JObject, + env: &mut JNIEnv, + obj: &JObject, class_name: &'static str, ) -> Result<(), SignalJniError> { if obj.is_null() { @@ -644,13 +588,18 @@ pub fn check_jobject_type( /// The method is assumed to return a type with a `long nativeHandle()` method, which in turn must /// produce a boxed Rust value. pub fn get_object_with_native_handle( - env: &JNIEnv, - store_obj: JObject, - callback_args: JniArgs, + env: &mut JNIEnv, + store_obj: &JObject, + callback_args: JniArgs, LEN>, callback_fn: &'static str, ) -> Result, SignalJniError> { - with_local_frame_no_jobject_result(env, 64, || -> SignalJniResult> { - let obj = call_method_checked(env, store_obj, callback_fn, callback_args)?; + env.with_local_frame(64, |env| -> SignalJniResult> { + let obj = call_method_checked( + env, + store_obj, + callback_fn, + callback_args.for_nested_frame(), + )?; if obj.is_null() { return Ok(None); } @@ -671,21 +620,27 @@ pub fn get_object_with_native_handle( /// /// The method is assumed to return a type with a `byte[] serialize()` method. pub fn get_object_with_serialization( - env: &JNIEnv, - store_obj: JObject, - callback_args: JniArgs, + env: &mut JNIEnv, + store_obj: &JObject, + callback_args: JniArgs, LEN>, callback_fn: &'static str, ) -> Result>, SignalJniError> { - with_local_frame_no_jobject_result(env, 64, || -> SignalJniResult>> { - let obj = call_method_checked(env, store_obj, callback_fn, callback_args)?; + env.with_local_frame(64, |env| -> SignalJniResult>> { + let obj = call_method_checked( + env, + store_obj, + callback_fn, + callback_args.for_nested_frame(), + )?; if obj.is_null() { return Ok(None); } - let bytes = call_method_checked(env, obj, "serialize", jni_args!(() -> [byte]))?; + let bytes: JByteArray = + call_method_checked(env, obj, "serialize", jni_args!(() -> [byte]))?.into(); - Ok(Some(env.convert_byte_array(*bytes)?)) + Ok(Some(env.convert_byte_array(bytes)?)) }) } @@ -742,3 +697,29 @@ macro_rules! jni_bridge_destroy { } }; } + +/// A wrapper around a cloned [`JNIEnv`] that forces scoped use. +/// +/// This sidesteps the safety issues with [`JNIEnv::unsafe_clone`] as long as an environment from an +/// outer frame is not used within an inner frame. (Really, this same condition makes `unsafe_clone` +/// safe as well, but using `EnvHandle` is a good reminder since it *only* allows scoped access.) +struct EnvHandle<'a> { + env: JNIEnv<'a>, +} + +impl<'a> EnvHandle<'a> { + fn new(env: &JNIEnv<'a>) -> Self { + Self { + env: unsafe { env.unsafe_clone() }, + } + } + + /// See [`JNIEnv::with_local_frame`]. + fn with_local_frame(&mut self, capacity: i32, f: F) -> Result + where + F: FnOnce(&mut JNIEnv<'_>) -> Result, + E: From, + { + self.env.with_local_frame(capacity, f) + } +} diff --git a/rust/bridge/shared/src/jni/quic.rs b/rust/bridge/shared/src/jni/quic.rs index 682684c0a2..47b3a3ce26 100644 --- a/rust/bridge/shared/src/jni/quic.rs +++ b/rust/bridge/shared/src/jni/quic.rs @@ -4,35 +4,39 @@ // use super::*; +use std::cell::RefCell; + use async_trait::async_trait; pub type JavaQuicCallbackListener<'a> = JObject<'a>; pub struct JniQuicCallbackListener<'a> { - env: &'a JNIEnv<'a>, - listener: JObject<'a>, + env: RefCell>, + listener: &'a JObject<'a>, } impl<'a> JniQuicCallbackListener<'a> { - pub fn new(env: &'a JNIEnv, listener: JObject<'a>) -> Result { + pub fn new<'context: 'a>(env: &mut JNIEnv<'context>, listener: &'a JObject<'a>) -> Result { check_jobject_type( env, listener, jni_class_name!(org.signal.libsignal.quic.QuicCallbackListener), )?; - Ok(Self { env, listener }) + Ok(Self { env: EnvHandle::new(env).into(), listener }) } } impl<'a> JniQuicCallbackListener<'a> { fn do_on_data(&mut self, data: Vec) -> Result<(), SignalJniError> { - let bytes = self.env.byte_array_from_slice(&data)?; - let callback_args = jni_args!(( - bytes => [byte], - ) -> void); - call_method_checked(self.env, self.listener, "onData", callback_args)?; - - Ok(()) + self.env.borrow_mut().with_local_frame(8, |env| { + let bytes = env.byte_array_from_slice(&data)?; + let callback_args = jni_args!(( + bytes => [byte], + ) -> void); + call_method_checked(env, self.listener, "onData", callback_args)?; + + Ok(()) + }) } } diff --git a/rust/bridge/shared/src/jni/storage.rs b/rust/bridge/shared/src/jni/storage.rs index 5b6185c188..5ca91ef268 100644 --- a/rust/bridge/shared/src/jni/storage.rs +++ b/rust/bridge/shared/src/jni/storage.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::cell::RefCell; + use super::*; use async_trait::async_trait; use uuid::Uuid; @@ -15,49 +17,59 @@ pub type JavaSessionStore<'a> = JObject<'a>; pub type JavaSenderKeyStore<'a> = JObject<'a>; pub struct JniIdentityKeyStore<'a> { - env: &'a JNIEnv<'a>, - store: JObject<'a>, + env: RefCell>, + store: &'a JObject<'a>, } impl<'a> JniIdentityKeyStore<'a> { - pub fn new(env: &'a JNIEnv, store: JObject<'a>) -> Result { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + store: &'a JObject<'a>, + ) -> Result { check_jobject_type( env, store, jni_class_name!(org.signal.libsignal.protocol.state.IdentityKeyStore), )?; - Ok(Self { env, store }) + Ok(Self { + env: EnvHandle::new(env).into(), + store, + }) } } impl<'a> JniIdentityKeyStore<'a> { fn do_get_identity_key_pair(&self) -> Result { - let callback_args = jni_args!(() -> org.signal.libsignal.protocol.IdentityKeyPair); - let bits = get_object_with_serialization( - self.env, - self.store, - callback_args, - "getIdentityKeyPair", - )?; - - match bits { - None => Err(SignalProtocolError::InvalidState( - "get_identity_key_pair", - "no local identity key".to_string(), - ) - .into()), - Some(k) => Ok(IdentityKeyPair::try_from(k.as_ref())?), - } + self.env.borrow_mut().with_local_frame(8, |env| { + let callback_args = jni_args!(() -> org.signal.libsignal.protocol.IdentityKeyPair); + let bits = get_object_with_serialization( + env, + self.store, + callback_args, + "getIdentityKeyPair", + )?; + + match bits { + None => Err(SignalProtocolError::InvalidState( + "get_identity_key_pair", + "no local identity key".to_string(), + ) + .into()), + Some(k) => Ok(IdentityKeyPair::try_from(k.as_ref())?), + } + }) } fn do_get_local_registration_id(&self) -> Result { - let i: jint = call_method_checked( - self.env, - self.store, - "getLocalRegistrationId", - jni_args!(() -> int), - )?; - u32::convert_from(self.env, i) + self.env.borrow_mut().with_local_frame(8, |env| { + let i: jint = call_method_checked( + env, + self.store, + "getLocalRegistrationId", + jni_args!(() -> int), + )?; + u32::convert_from(env, &i) + }) } fn do_save_identity( @@ -65,19 +77,22 @@ impl<'a> JniIdentityKeyStore<'a> { address: &ProtocolAddress, identity: &IdentityKey, ) -> Result { - let address_jobject = protocol_address_to_jobject(self.env, address)?; - let key_jobject = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.IdentityKey), - identity.public_key().convert_into(self.env)?, - )?; - let callback_args = jni_args!(( + self.env.borrow_mut().with_local_frame(8, |env| { + let address_jobject = protocol_address_to_jobject(env, address)?; + let key_handle = identity.public_key().convert_into(env)?; + let key_jobject = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.IdentityKey), + key_handle, + )?; + let callback_args = jni_args!(( address_jobject => org.signal.libsignal.protocol.SignalProtocolAddress, key_jobject => org.signal.libsignal.protocol.IdentityKey ) -> boolean); - let result: jboolean = - call_method_checked(self.env, self.store, "saveIdentity", callback_args)?; - Ok(result != 0) + let result: jboolean = + call_method_checked(env, self.store, "saveIdentity", callback_args)?; + Ok(result != 0) + }) } fn do_is_trusted_identity( @@ -86,68 +101,64 @@ impl<'a> JniIdentityKeyStore<'a> { identity: &IdentityKey, direction: Direction, ) -> Result { - let address_jobject = protocol_address_to_jobject(self.env, address)?; - let key_jobject = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.IdentityKey), - identity.public_key().convert_into(self.env)?, - )?; - - let direction_class = self.env.find_class( - jni_class_name!(org.signal.libsignal.protocol.state.IdentityKeyStore::Direction), - )?; - let field_name = match direction { - Direction::Sending => "SENDING", - Direction::Receiving => "RECEIVING", - }; - - let field_value: JObject = self - .env - .get_static_field( - direction_class, - field_name, - jni_signature!(org.signal.libsignal.protocol.state.IdentityKeyStore::Direction), - )? - .try_into() - .expect("already checked type"); - - let callback_args = jni_args!(( + self.env.borrow_mut().with_local_frame(8, |env| { + let address_jobject = protocol_address_to_jobject(env, address)?; + let key_handle = identity.public_key().convert_into(env)?; + let key_jobject = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.IdentityKey), + key_handle, + )?; + + let direction_class = env.find_class( + jni_class_name!(org.signal.libsignal.protocol.state.IdentityKeyStore::Direction), + )?; + let field_name = match direction { + Direction::Sending => "SENDING", + Direction::Receiving => "RECEIVING", + }; + + let field_value: JObject = env + .get_static_field( + direction_class, + field_name, + jni_signature!(org.signal.libsignal.protocol.state.IdentityKeyStore::Direction), + )? + .try_into() + .expect("already checked type"); + + let callback_args = jni_args!(( address_jobject => org.signal.libsignal.protocol.SignalProtocolAddress, key_jobject => org.signal.libsignal.protocol.IdentityKey, field_value => org.signal.libsignal.protocol.state.IdentityKeyStore::Direction, ) -> boolean); - let result: jboolean = - call_method_checked(self.env, self.store, "isTrustedIdentity", callback_args)?; + let result: jboolean = + call_method_checked(env, self.store, "isTrustedIdentity", callback_args)?; - Ok(result != 0) + Ok(result != 0) + }) } fn do_get_identity( &self, address: &ProtocolAddress, ) -> Result, SignalJniError> { - with_local_frame_no_jobject_result( - self.env, - 64, - || -> SignalJniResult> { - let address_jobject = protocol_address_to_jobject(self.env, address)?; + self.env + .borrow_mut() + .with_local_frame(8, |env| -> SignalJniResult> { + let address_jobject = protocol_address_to_jobject(env, address)?; let callback_args = jni_args!(( address_jobject => org.signal.libsignal.protocol.SignalProtocolAddress, ) -> org.signal.libsignal.protocol.IdentityKey); - let bits = get_object_with_serialization( - self.env, - self.store, - callback_args, - "getIdentity", - )?; + let bits = + get_object_with_serialization(env, self.store, callback_args, "getIdentity")?; match bits { None => Ok(None), Some(k) => Ok(Some(IdentityKey::decode(&k)?)), } - }, - ) + }) } } @@ -187,32 +198,40 @@ impl<'a> IdentityKeyStore for JniIdentityKeyStore<'a> { } pub struct JniPreKeyStore<'a> { - env: &'a JNIEnv<'a>, - store: JObject<'a>, + env: RefCell>, + store: &'a JObject<'a>, } impl<'a> JniPreKeyStore<'a> { - pub fn new(env: &'a JNIEnv, store: JObject<'a>) -> Result { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + store: &'a JObject<'a>, + ) -> Result { check_jobject_type( env, store, jni_class_name!(org.signal.libsignal.protocol.state.PreKeyStore), )?; - Ok(Self { env, store }) + Ok(Self { + env: EnvHandle::new(env).into(), + store, + }) } } impl<'a> JniPreKeyStore<'a> { fn do_get_pre_key(&self, prekey_id: u32) -> Result { - let callback_args = jni_args!(( - prekey_id.convert_into(self.env)? => int + self.env.borrow_mut().with_local_frame(8, |env| { + let callback_args = jni_args!(( + prekey_id.convert_into(env)? => int ) -> org.signal.libsignal.protocol.state.PreKeyRecord); - let pk: Option = - get_object_with_native_handle(self.env, self.store, callback_args, "loadPreKey")?; - match pk { - Some(pk) => Ok(pk), - None => Err(SignalJniError::Signal(SignalProtocolError::InvalidPreKeyId)), - } + let pk: Option = + get_object_with_native_handle(env, self.store, callback_args, "loadPreKey")?; + match pk { + Some(pk) => Ok(pk), + None => Err(SignalJniError::Signal(SignalProtocolError::InvalidPreKeyId)), + } + }) } fn do_save_pre_key( @@ -220,27 +239,33 @@ impl<'a> JniPreKeyStore<'a> { prekey_id: u32, record: &PreKeyRecord, ) -> Result<(), SignalJniError> { - let jobject_record = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.state.PreKeyRecord), - record.clone().convert_into(self.env)?, - )?; - let callback_args = jni_args!(( - prekey_id.convert_into(self.env)? => int, + self.env.borrow_mut().with_local_frame(8, |env| { + let record_handle = record.clone().convert_into(env)?; + let jobject_record = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.state.PreKeyRecord), + record_handle, + )?; + let callback_args = jni_args!(( + prekey_id.convert_into(env)? => int, jobject_record => org.signal.libsignal.protocol.state.PreKeyRecord ) -> void); - call_method_checked(self.env, self.store, "storePreKey", callback_args)?; - Ok(()) + call_method_checked(env, self.store, "storePreKey", callback_args)?; + Ok(()) + }) } fn do_remove_pre_key(&mut self, prekey_id: u32) -> Result<(), SignalJniError> { - call_method_checked( - self.env, - self.store, - "removePreKey", - jni_args!((prekey_id.convert_into(self.env)? => int) -> void), - )?; - Ok(()) + self.env.borrow_mut().with_local_frame(8, |env| { + let java_id = prekey_id.convert_into(env)?; + call_method_checked( + env, + self.store, + "removePreKey", + jni_args!((java_id => int) -> void), + )?; + Ok(()) + }) } } @@ -264,34 +289,42 @@ impl<'a> PreKeyStore for JniPreKeyStore<'a> { } pub struct JniSignedPreKeyStore<'a> { - env: &'a JNIEnv<'a>, - store: JObject<'a>, + env: RefCell>, + store: &'a JObject<'a>, } impl<'a> JniSignedPreKeyStore<'a> { - pub fn new(env: &'a JNIEnv, store: JObject<'a>) -> Result { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + store: &'a JObject<'a>, + ) -> Result { check_jobject_type( env, store, jni_class_name!(org.signal.libsignal.protocol.state.SignedPreKeyStore), )?; - Ok(Self { env, store }) + Ok(Self { + env: EnvHandle::new(env).into(), + store, + }) } } impl<'a> JniSignedPreKeyStore<'a> { fn do_get_signed_pre_key(&self, prekey_id: u32) -> Result { - let callback_args = jni_args!(( - prekey_id.convert_into(self.env)? => int + self.env.borrow_mut().with_local_frame(8, |env| { + let callback_args = jni_args!(( + prekey_id.convert_into(env)? => int ) -> org.signal.libsignal.protocol.state.SignedPreKeyRecord); - let spk: Option = - get_object_with_native_handle(self.env, self.store, callback_args, "loadSignedPreKey")?; - match spk { - Some(spk) => Ok(spk), - None => Err(SignalJniError::Signal( - SignalProtocolError::InvalidSignedPreKeyId, - )), - } + let spk: Option = + get_object_with_native_handle(env, self.store, callback_args, "loadSignedPreKey")?; + match spk { + Some(spk) => Ok(spk), + None => Err(SignalJniError::Signal( + SignalProtocolError::InvalidSignedPreKeyId, + )), + } + }) } fn do_save_signed_pre_key( @@ -299,17 +332,20 @@ impl<'a> JniSignedPreKeyStore<'a> { prekey_id: u32, record: &SignedPreKeyRecord, ) -> Result<(), SignalJniError> { - let jobject_record = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.state.SignedPreKeyRecord), - record.clone().convert_into(self.env)?, - )?; - let callback_args = jni_args!(( - prekey_id.convert_into(self.env)? => int, + self.env.borrow_mut().with_local_frame(8, |env| { + let record_handle = record.clone().convert_into(env)?; + let jobject_record = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.state.SignedPreKeyRecord), + record_handle, + )?; + let callback_args = jni_args!(( + prekey_id.convert_into(env)? => int, jobject_record => org.signal.libsignal.protocol.state.SignedPreKeyRecord ) -> void); - call_method_checked(self.env, self.store, "storeSignedPreKey", callback_args)?; - Ok(()) + call_method_checked(env, self.store, "storeSignedPreKey", callback_args)?; + Ok(()) + }) } } @@ -332,34 +368,42 @@ impl<'a> SignedPreKeyStore for JniSignedPreKeyStore<'a> { } pub struct JniKyberPreKeyStore<'a> { - env: &'a JNIEnv<'a>, - store: JObject<'a>, + env: RefCell>, + store: &'a JObject<'a>, } impl<'a> JniKyberPreKeyStore<'a> { - pub fn new(env: &'a JNIEnv, store: JObject<'a>) -> Result { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + store: &'a JObject<'a>, + ) -> Result { check_jobject_type( env, store, jni_class_name!(org.signal.libsignal.protocol.state.KyberPreKeyStore), )?; - Ok(Self { env, store }) + Ok(Self { + env: EnvHandle::new(env).into(), + store, + }) } } impl<'a> JniKyberPreKeyStore<'a> { fn do_get_kyber_pre_key(&self, prekey_id: u32) -> Result { - let callback_args = jni_args!(( - prekey_id.convert_into(self.env)? => int + self.env.borrow_mut().with_local_frame(8, |env| { + let callback_args = jni_args!(( + prekey_id.convert_into(env)? => int ) -> org.signal.libsignal.protocol.state.KyberPreKeyRecord); - let kpk: Option = - get_object_with_native_handle(self.env, self.store, callback_args, "loadKyberPreKey")?; - match kpk { - Some(kpk) => Ok(kpk), - None => Err(SignalJniError::Signal( - SignalProtocolError::InvalidKyberPreKeyId, - )), - } + let kpk: Option = + get_object_with_native_handle(env, self.store, callback_args, "loadKyberPreKey")?; + match kpk { + Some(kpk) => Ok(kpk), + None => Err(SignalJniError::Signal( + SignalProtocolError::InvalidKyberPreKeyId, + )), + } + }) } fn do_save_kyber_pre_key( @@ -367,27 +411,33 @@ impl<'a> JniKyberPreKeyStore<'a> { prekey_id: u32, record: &KyberPreKeyRecord, ) -> Result<(), SignalJniError> { - let jobject_record = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.state.KyberPreKeyRecord), - record.clone().convert_into(self.env)?, - )?; - let callback_args = jni_args!(( - prekey_id.convert_into(self.env)? => int, + self.env.borrow_mut().with_local_frame(8, |env| { + let record_handle = record.clone().convert_into(env)?; + let jobject_record = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.state.KyberPreKeyRecord), + record_handle, + )?; + let callback_args = jni_args!(( + prekey_id.convert_into(env)? => int, jobject_record => org.signal.libsignal.protocol.state.KyberPreKeyRecord ) -> void); - call_method_checked(self.env, self.store, "storeKyberPreKey", callback_args)?; - Ok(()) + call_method_checked(env, self.store, "storeKyberPreKey", callback_args)?; + Ok(()) + }) } fn do_mark_kyber_pre_key_used(&mut self, prekey_id: u32) -> Result<(), SignalJniError> { - call_method_checked( - self.env, - self.store, - "markKyberPreKeyUsed", - jni_args!((prekey_id.convert_into(self.env)? => int) -> void), - )?; - Ok(()) + self.env.borrow_mut().with_local_frame(8, |env| { + let java_id = prekey_id.convert_into(env)?; + call_method_checked( + env, + self.store, + "markKyberPreKeyUsed", + jni_args!((java_id => int) -> void), + )?; + Ok(()) + }) } } @@ -417,18 +467,24 @@ impl<'a> KyberPreKeyStore for JniKyberPreKeyStore<'a> { } pub struct JniSessionStore<'a> { - env: &'a JNIEnv<'a>, - store: JObject<'a>, + env: RefCell>, + store: &'a JObject<'a>, } impl<'a> JniSessionStore<'a> { - pub fn new(env: &'a JNIEnv, store: JObject<'a>) -> Result { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + store: &'a JObject<'a>, + ) -> Result { check_jobject_type( env, store, jni_class_name!(org.signal.libsignal.protocol.state.SessionStore), )?; - Ok(Self { env, store }) + Ok(Self { + env: EnvHandle::new(env).into(), + store, + }) } } @@ -437,12 +493,14 @@ impl<'a> JniSessionStore<'a> { &self, address: &ProtocolAddress, ) -> Result, SignalJniError> { - let address_jobject = protocol_address_to_jobject(self.env, address)?; + self.env.borrow_mut().with_local_frame(8, |env| { + let address_jobject = protocol_address_to_jobject(env, address)?; - let callback_args = jni_args!(( + let callback_args = jni_args!(( address_jobject => org.signal.libsignal.protocol.SignalProtocolAddress ) -> org.signal.libsignal.protocol.state.SessionRecord); - get_object_with_native_handle(self.env, self.store, callback_args, "loadSession") + get_object_with_native_handle(env, self.store, callback_args, "loadSession") + }) } fn do_store_session( @@ -450,19 +508,22 @@ impl<'a> JniSessionStore<'a> { address: &ProtocolAddress, record: &SessionRecord, ) -> Result<(), SignalJniError> { - let address_jobject = protocol_address_to_jobject(self.env, address)?; - let session_jobject = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.state.SessionRecord), - record.clone().convert_into(self.env)?, - )?; - - let callback_args = jni_args!(( + self.env.borrow_mut().with_local_frame(8, |env| { + let address_jobject = protocol_address_to_jobject(env, address)?; + let record_handle = record.clone().convert_into(env)?; + let session_jobject = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.state.SessionRecord), + record_handle, + )?; + + let callback_args = jni_args!(( address_jobject => org.signal.libsignal.protocol.SignalProtocolAddress, session_jobject => org.signal.libsignal.protocol.state.SessionRecord, ) -> void); - call_method_checked(self.env, self.store, "storeSession", callback_args)?; - Ok(()) + call_method_checked(env, self.store, "storeSession", callback_args)?; + Ok(()) + }) } } @@ -485,18 +546,24 @@ impl<'a> SessionStore for JniSessionStore<'a> { } pub struct JniSenderKeyStore<'a> { - env: &'a JNIEnv<'a>, - store: JObject<'a>, + env: RefCell>, + store: &'a JObject<'a>, } impl<'a> JniSenderKeyStore<'a> { - pub fn new(env: &'a JNIEnv, store: JObject<'a>) -> Result { + pub fn new<'context: 'a>( + env: &mut JNIEnv<'context>, + store: &'a JObject<'a>, + ) -> Result { check_jobject_type( env, store, jni_class_name!(org.signal.libsignal.protocol.groups.state.SenderKeyStore), )?; - Ok(Self { env, store }) + Ok(Self { + env: EnvHandle::new(env).into(), + store, + }) } } @@ -507,22 +574,25 @@ impl<'a> JniSenderKeyStore<'a> { distribution_id: Uuid, record: &SenderKeyRecord, ) -> Result<(), SignalJniError> { - let sender_jobject = protocol_address_to_jobject(self.env, sender)?; - let distribution_id_jobject = distribution_id.convert_into(self.env)?; - let sender_key_record_jobject = jobject_from_native_handle( - self.env, - jni_class_name!(org.signal.libsignal.protocol.groups.state.SenderKeyRecord), - record.clone().convert_into(self.env)?, - )?; - - let callback_args = jni_args!(( + self.env.borrow_mut().with_local_frame(8, |env| { + let sender_jobject = protocol_address_to_jobject(env, sender)?; + let distribution_id_jobject = distribution_id.convert_into(env)?; + let record_handle = record.clone().convert_into(env)?; + let sender_key_record_jobject = jobject_from_native_handle( + env, + jni_class_name!(org.signal.libsignal.protocol.groups.state.SenderKeyRecord), + record_handle, + )?; + + let callback_args = jni_args!(( sender_jobject => org.signal.libsignal.protocol.SignalProtocolAddress, distribution_id_jobject => java.util.UUID, sender_key_record_jobject => org.signal.libsignal.protocol.groups.state.SenderKeyRecord, ) -> void); - call_method_checked(self.env, self.store, "storeSenderKey", callback_args)?; + call_method_checked(env, self.store, "storeSenderKey", callback_args)?; - Ok(()) + Ok(()) + }) } fn do_load_sender_key( @@ -530,13 +600,15 @@ impl<'a> JniSenderKeyStore<'a> { sender: &ProtocolAddress, distribution_id: Uuid, ) -> Result, SignalJniError> { - let sender_jobject = protocol_address_to_jobject(self.env, sender)?; - let distribution_id_jobject = distribution_id.convert_into(self.env)?; - let callback_args = jni_args!(( + self.env.borrow_mut().with_local_frame(8, |env| { + let sender_jobject = protocol_address_to_jobject(env, sender)?; + let distribution_id_jobject = distribution_id.convert_into(env)?; + let callback_args = jni_args!(( sender_jobject => org.signal.libsignal.protocol.SignalProtocolAddress, distribution_id_jobject => java.util.UUID, ) -> org.signal.libsignal.protocol.groups.state.SenderKeyRecord); - get_object_with_native_handle(self.env, self.store, callback_args, "loadSenderKey") + get_object_with_native_handle(env, self.store, callback_args, "loadSenderKey") + }) } } diff --git a/rust/bridge/shared/src/node/convert.rs b/rust/bridge/shared/src/node/convert.rs index b0e1ab3a50..79887fe022 100644 --- a/rust/bridge/shared/src/node/convert.rs +++ b/rust/bridge/shared/src/node/convert.rs @@ -409,7 +409,7 @@ impl<'a> AssumedImmutableBuffer<'a> { /// potentially optimizing out that checksum, though.) /// /// [napi]: https://nodejs.org/api/n-api.html#n_api_napi_get_buffer_info - fn new<'b>(cx: &mut impl Context<'b>, handle: Handle<'a, JsBuffer>) -> Self { + fn new<'b>(cx: &impl Context<'b>, handle: Handle<'a, JsBuffer>) -> Self { let buf = handle.as_slice(cx); let extended_lifetime_buffer = if buf.is_empty() { &[] @@ -822,7 +822,8 @@ pub(crate) const NATIVE_HANDLE_PROPERTY: &str = "_nativeHandle"; /// /// Since this trait is used as a marker and implemented through a macro, /// operations that might differ across types have been factored into the [`BridgeHandleStrategy`] -/// trait. The [`bridge_handle`] macro will automatically choose the correct strategy. +/// trait. The [`bridge_handle`](crate::support::bridge_handle) macro will automatically choose the +/// correct strategy. pub trait BridgeHandle: Send + Sync + Sized + 'static { /// Factors out operations that differ between mutable and immutable bridge handles. type Strategy: BridgeHandleStrategy; diff --git a/rust/bridge/shared/src/protocol.rs b/rust/bridge/shared/src/protocol.rs index fb54f2ed4b..a9dc8b37d3 100644 --- a/rust/bridge/shared/src/protocol.rs +++ b/rust/bridge/shared/src/protocol.rs @@ -8,6 +8,7 @@ use libsignal_protocol::error::Result; use libsignal_protocol::*; use static_assertions::const_assert_eq; use std::convert::TryFrom; +use std::time::{Duration, SystemTime}; use uuid::Uuid; // Will be unused when building for Node only. @@ -60,6 +61,10 @@ impl Timestamp { pub(crate) fn as_millis(self) -> u64 { self.0 } + + fn as_millis_from_unix_epoch(self) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_millis(self.as_millis()) + } } impl From for Timestamp { @@ -939,11 +944,6 @@ fn SessionRecord_NewFresh() -> SessionRecord { SessionRecord::new_fresh() } -#[bridge_fn(ffi = false, node = false)] -fn SessionRecord_FromSingleSessionState(session_state: &[u8]) -> Result { - SessionRecord::from_single_session_state(session_state) -} - // For historical reasons Android assumes this function will return zero if there is no session state #[bridge_fn(ffi = false, node = false)] fn SessionRecord_GetSessionVersion(s: &SessionRecord) -> Result { @@ -959,13 +959,16 @@ fn SessionRecord_ArchiveCurrentState(session_record: &mut SessionRecord) -> Resu session_record.archive_current_state() } +#[bridge_fn] +fn SessionRecord_HasUsableSenderChain(s: &SessionRecord, now: Timestamp) -> Result { + s.has_usable_sender_chain(now.as_millis_from_unix_epoch()) +} + #[bridge_fn] fn SessionRecord_CurrentRatchetKeyMatches(s: &SessionRecord, key: &PublicKey) -> Result { s.current_ratchet_key_matches(key) } -bridge_get!(SessionRecord::has_current_session_state as HasCurrentState -> bool, jni = false); - bridge_deserialize!(SessionRecord::deserialize); bridge_get!(SessionRecord::serialize as Serialize -> Vec); bridge_get!(SessionRecord::alice_base_key -> &[u8], ffi = false, node = false); @@ -981,7 +984,6 @@ bridge_get!( ); bridge_get!(SessionRecord::local_registration_id -> u32); bridge_get!(SessionRecord::remote_registration_id -> u32); -bridge_get!(SessionRecord::has_sender_chain as HasSenderChain -> bool, ffi = false, node = false); bridge_get!(SealedSenderDecryptionResult::sender_uuid -> String, ffi = false, jni = false); bridge_get!(SealedSenderDecryptionResult::sender_e164 -> Option, ffi = false, jni = false); @@ -1085,6 +1087,7 @@ async fn SessionBuilder_ProcessPreKeyBundle( protocol_address: &ProtocolAddress, session_store: &mut dyn SessionStore, identity_key_store: &mut dyn IdentityKeyStore, + now: Timestamp, ) -> Result<()> { let mut csprng = rand::rngs::OsRng; process_prekey_bundle( @@ -1092,6 +1095,7 @@ async fn SessionBuilder_ProcessPreKeyBundle( session_store, identity_key_store, bundle, + now.as_millis_from_unix_epoch(), &mut csprng, ) .await @@ -1103,8 +1107,16 @@ async fn SessionCipher_EncryptMessage( protocol_address: &ProtocolAddress, session_store: &mut dyn SessionStore, identity_key_store: &mut dyn IdentityKeyStore, + now: Timestamp, ) -> Result { - message_encrypt(ptext, protocol_address, session_store, identity_key_store).await + message_encrypt( + ptext, + protocol_address, + session_store, + identity_key_store, + now.as_millis_from_unix_epoch(), + ) + .await } #[bridge_fn(ffi = "decrypt_message")] diff --git a/rust/bridge/shared/src/support/mod.rs b/rust/bridge/shared/src/support/mod.rs index 25adfd76b9..0b66290ca8 100644 --- a/rust/bridge/shared/src/support/mod.rs +++ b/rust/bridge/shared/src/support/mod.rs @@ -75,6 +75,8 @@ pub fn describe_panic(any: &Box) -> String { /// arguments are of the form `Wrapper`. /// /// [`JsBox`]: https://docs.rs/neon/0.7.1-napi/neon/types/struct.JsBox.html +/// [`node::AsyncArgTypeInfo`]: crate::node::AsyncArgTypeInfo +#[macro_export] macro_rules! bridge_handle { ($typ:ty $(, clone = $_clone:tt)? $(, mut = $_mut:tt)? $(, ffi = $ffi_name:ident)? $(, jni = $jni_name:ident)? $(, node = $node_name:ident)?) => { #[cfg(feature = "ffi")] @@ -86,6 +88,10 @@ macro_rules! bridge_handle { }; } +// Allow referring to the macro by path in doc comments. +#[cfg(doc)] +pub use bridge_handle; + /// Convenience syntax to expose a deserialization method to the bridges. /// /// Example: diff --git a/rust/bridge/shared/src/zkgroup.rs b/rust/bridge/shared/src/zkgroup.rs index 5d263870cc..fa018e43d6 100644 --- a/rust/bridge/shared/src/zkgroup.rs +++ b/rust/bridge/shared/src/zkgroup.rs @@ -35,11 +35,11 @@ fn validate_serialization<'a, T: Deserialize<'a>>( } } -/// Exposes a ZKGroup serializable type to the bridges via [`FixedLengthSerializable`]. +/// Exposes a ZKGroup serializable type to the bridges via [`FixedLengthBincodeSerializable`]. /// /// `fixed_length_serializable!(FooBar)` generates -/// - `impl FixedLengthSerializable for FooBar`, using `[u8; FOO_BAR_LEN]` as the associated array -/// type. +/// - `impl FixedLengthBincodeSerializable for FooBar`, using `[u8; FOO_BAR_LEN]` as the associated +/// array type. /// - `#[bridge_fn] fn FooBar_CheckValidContents`, which checks that the type can be deserialized. macro_rules! fixed_length_serializable { ($typ:ident) => { diff --git a/rust/crypto/Cargo.toml b/rust/crypto/Cargo.toml index 84e4783086..c84e156afc 100644 --- a/rust/crypto/Cargo.toml +++ b/rust/crypto/Cargo.toml @@ -11,26 +11,24 @@ edition = "2018" license = "AGPL-3.0-only" [dependencies] -aes = { version = "0.7.4", features = ["ctr"] } -block-modes = "0.8" +aes = { version = "0.8.3", features = ["zeroize"] } +cbc = { version = "0.1.2", features = ["std", "zeroize"] } +ctr = { version = "0.9.2", features = ["zeroize"] } subtle = "2.3" -generic-array = "0.14" -ghash = "0.4.2" +ghash = { version = "0.5.0", features = ["zeroize"] } hmac = { version = "0.12", features = ["reset"] } -sha-1 = "0.10" +sha1 = "0.10" sha2 = "0.10" thiserror = "1.0.38" displaydoc = "0.2" [dev-dependencies] +criterion = "0.5" +hex = "0.4" +hex-literal = "0.4.1" rand = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -hex = "0.4" -criterion = "0.5" - -[features] -armv8 = ["aes/armv8", "ghash/armv8"] [[bench]] name = "aes_gcm" diff --git a/rust/crypto/src/aes_cbc.rs b/rust/crypto/src/aes_cbc.rs index bdd3849384..cb25b4b846 100644 --- a/rust/crypto/src/aes_cbc.rs +++ b/rust/crypto/src/aes_cbc.rs @@ -3,12 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use aes::cipher::block_padding::Pkcs7; +use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use aes::Aes256; use std::result::Result; -use block_modes::block_padding::Pkcs7; -use block_modes::{BlockMode, Cbc}; - #[derive(Debug, displaydoc::Display, thiserror::Error)] pub enum EncryptionError { /// The key or IV is the wrong length. @@ -28,9 +27,9 @@ pub fn aes_256_cbc_encrypt( key: &[u8], iv: &[u8], ) -> Result, EncryptionError> { - Cbc::::new_from_slices(key, iv) - .map_err(|_| EncryptionError::BadKeyOrIv) - .map(|mode| mode.encrypt_vec(ptext)) + Ok(cbc::Encryptor::::new_from_slices(key, iv) + .map_err(|_| EncryptionError::BadKeyOrIv)? + .encrypt_padded_vec_mut::(ptext)) } pub fn aes_256_cbc_decrypt( @@ -44,23 +43,23 @@ pub fn aes_256_cbc_decrypt( )); } - Cbc::::new_from_slices(key, iv) + cbc::Decryptor::::new_from_slices(key, iv) .map_err(|_| DecryptionError::BadKeyOrIv)? - .decrypt_vec(ctext) + .decrypt_padded_vec_mut::(ctext) .map_err(|_| DecryptionError::BadCiphertext("failed to decrypt")) } #[cfg(test)] mod test { use super::*; + use hex_literal::hex; #[test] fn aes_cbc_test() { - let key = hex::decode("4e22eb16d964779994222e82192ce9f747da72dc4abe49dfdeeb71d0ffe3796e") - .expect("valid hex"); - let iv = hex::decode("6f8a557ddc0a140c878063a6d5f31d3d").expect("valid hex"); + let key = hex!("4e22eb16d964779994222e82192ce9f747da72dc4abe49dfdeeb71d0ffe3796e"); + let iv = hex!("6f8a557ddc0a140c878063a6d5f31d3d"); - let ptext = hex::decode("30736294a124482a4159").expect("valid hex"); + let ptext = hex!("30736294a124482a4159"); let ctext = aes_256_cbc_encrypt(&ptext, &key, &iv).expect("valid key and IV"); assert_eq!( @@ -76,7 +75,7 @@ mod test { assert!(aes_256_cbc_decrypt(&ctext, &key, &ctext).is_err()); // bitflip the IV to cause a change in the recovered text - let bad_iv = hex::decode("ef8a557ddc0a140c878063a6d5f31d3d").expect("valid hex"); + let bad_iv = hex!("ef8a557ddc0a140c878063a6d5f31d3d"); let recovered = aes_256_cbc_decrypt(&ctext, &key, &bad_iv).expect("still valid"); assert_eq!(hex::encode(recovered), "b0736294a124482a4159"); } diff --git a/rust/crypto/src/aes_ctr.rs b/rust/crypto/src/aes_ctr.rs index c35d5fbcd9..622dda1a04 100644 --- a/rust/crypto/src/aes_ctr.rs +++ b/rust/crypto/src/aes_ctr.rs @@ -4,25 +4,29 @@ // use crate::error::{Error, Result}; -use aes::cipher::{FromBlockCipher, StreamCipher, StreamCipherSeek}; -use aes::{Aes256, NewBlockCipher}; +use aes::cipher::typenum::Unsigned; +use aes::cipher::{InnerIvInit, KeyInit, StreamCipher, StreamCipherSeek}; +use aes::Aes256; -/// A wrapper around [`aes::Aes256Ctr`] that uses a smaller nonce and supports an initial counter. -pub struct Aes256Ctr32(aes::Aes256Ctr); +/// A wrapper around [`ctr::Ctr32BE`] that uses a smaller nonce and supports an initial counter. +pub struct Aes256Ctr32(ctr::Ctr32BE); impl Aes256Ctr32 { - pub const NONCE_SIZE: usize = aes::BLOCK_SIZE - 4; + pub const NONCE_SIZE: usize = ::BlockSize::USIZE - 4; pub fn new(aes256: Aes256, nonce: &[u8], init_ctr: u32) -> Result { if nonce.len() != Self::NONCE_SIZE { return Err(Error::InvalidNonceSize); } - let mut nonce_block = [0u8; aes::BLOCK_SIZE]; + let mut nonce_block = [0u8; ::BlockSize::USIZE]; nonce_block[0..Self::NONCE_SIZE].copy_from_slice(nonce); - let mut ctr = aes::Aes256Ctr::from_block_cipher(aes256, &nonce_block.into()); - ctr.seek((aes::BLOCK_SIZE as u64) * (init_ctr as u64)); + let mut ctr = + ctr::Ctr32BE::from_core(ctr::CtrCore::inner_iv_init(aes256, &nonce_block.into())); + ctr.seek( + (::BlockSize::USIZE as u64) * (init_ctr as u64), + ); Ok(Self(ctr)) } diff --git a/rust/crypto/src/aes_gcm.rs b/rust/crypto/src/aes_gcm.rs index a49b359ba1..c3ebc668b8 100644 --- a/rust/crypto/src/aes_gcm.rs +++ b/rust/crypto/src/aes_gcm.rs @@ -4,9 +4,10 @@ // use crate::{Aes256Ctr32, Error, Result}; -use aes::{Aes256, BlockEncrypt, NewBlockCipher}; -use generic_array::GenericArray; -use ghash::universal_hash::{NewUniversalHash, UniversalHash}; +use aes::cipher::generic_array::GenericArray; +use aes::cipher::{BlockEncrypt, KeyInit}; +use aes::Aes256; +use ghash::universal_hash::UniversalHash; use ghash::GHash; use subtle::ConstantTimeEq; @@ -50,7 +51,10 @@ impl GcmGhash { self.msg_len += taking; if self.msg_buf_offset == TAG_SIZE { - self.ghash.update(&self.msg_buf.into()); + self.ghash + .update(std::slice::from_ref(ghash::Block::from_slice( + &self.msg_buf, + ))); self.msg_buf_offset = 0; return self.update(&msg[taking..]); } else { @@ -65,9 +69,16 @@ impl GcmGhash { let leftover = msg.len() - 16 * full_blocks; assert!(leftover < TAG_SIZE); if full_blocks > 0 { - for block in msg[..full_blocks * 16].chunks_exact(16) { - self.ghash.update(block.into()); - } + // Transmute [u8] to [[u8; 16]], like slice::as_chunks. + // Then transmute [[u8; 16]] to [GenericArray], per repr(transparent). + let blocks = unsafe { + std::slice::from_raw_parts(msg[..16 * full_blocks].as_ptr().cast(), full_blocks) + }; + assert_eq!( + std::mem::size_of_val(blocks) + leftover, + std::mem::size_of_val(msg) + ); + self.ghash.update(blocks); } self.msg_buf[0..leftover].copy_from_slice(&msg[full_blocks * 16..]); @@ -87,8 +98,8 @@ impl GcmGhash { final_block[..8].copy_from_slice(&(8 * self.ad_len as u64).to_be_bytes()); final_block[8..].copy_from_slice(&(8 * self.msg_len as u64).to_be_bytes()); - self.ghash.update(&final_block.into()); - let mut hash = self.ghash.finalize().into_bytes(); + self.ghash.update(&[final_block.into()]); + let mut hash = self.ghash.finalize(); for (i, b) in hash.iter_mut().enumerate() { *b ^= self.ghash_pad[i]; diff --git a/rust/crypto/tests/aes_ctr.rs b/rust/crypto/tests/aes_ctr.rs index 1628678552..f50da3dfd0 100644 --- a/rust/crypto/tests/aes_ctr.rs +++ b/rust/crypto/tests/aes_ctr.rs @@ -3,16 +3,16 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use hex_literal::hex; use rand::Rng; #[test] fn aes_ctr_smoke_test() -> Result<(), signal_crypto::Error> { - let key = hex::decode("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4") - .expect("Valid hex"); - let nonce = hex::decode("F0F1F2F3F4F5F6F7F8F9FAFB").expect("Valid hex"); + let key = hex!("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4"); + let nonce = hex!("F0F1F2F3F4F5F6F7F8F9FAFB"); let init_ctr = 0xFCFDFEFF; - let input = hex::decode("6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710").expect("Valid hex"); - let output = hex::decode("601EC313775789A5B7A7F504BBF3D228F443E3CA4D62B59ACA84E990CACAF5C52B0930DAA23DE94CE87017BA2D84988DDFC9C58DB67AADA613C2DD08457941A6").expect("Valid hex"); + let input = hex!("6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710"); + let output = hex!("601EC313775789A5B7A7F504BBF3D228F443E3CA4D62B59ACA84E990CACAF5C52B0930DAA23DE94CE87017BA2D84988DDFC9C58DB67AADA613C2DD08457941A6"); let mut aes_ctr = signal_crypto::Aes256Ctr32::from_key(&key, &nonce, init_ctr)?; @@ -26,18 +26,17 @@ fn aes_ctr_smoke_test() -> Result<(), signal_crypto::Error> { #[test] fn aes_ctr_long_test() -> Result<(), signal_crypto::Error> { - let key = hex::decode("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4") - .expect("Valid hex"); - let nonce = hex::decode("FFFFFFFFFFFFFFFFFFFFFFFF").expect("Valid hex"); + let key = hex!("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4"); + let nonce = hex!("FFFFFFFFFFFFFFFFFFFFFFFF"); let init_ctr = 0x00000000; - let output = hex::decode("FD4C14729F5004BA49D832AD7BE87C18F4FAFB58962B9A43C3BE41713DED93DBF854AC4CA26285B7F76E04B8F8D4E7D9F7548F9B465C8F713C106E9F63F54305331A4983A2F4B718DE29FA794DA12EEE808642FAEFF8271A0EA28E3CC80EEB65A8EB61F69D8BA97F6BF9054453F55EFB8F9422081F1620FE44ACF99E81122F73D3F921D5E3391654E9947904984375B725FDFBA895C5CDE3D225D7BE3A213C3965178A7DC1E3B552EC7B2FFD9C77EBCC243C4500DFDFBE3B7554AA427C01305BEC48D71AF27C5911D1E649C620D22CF5F3A5AEB9468651DA796F369522FAF91EFABF0FEBD33FCA41C9534606A4EA0199B904B243BA9CB8F37A792DF02EFAB8F0E2E0CF1D579DABA042CFE4C9430AD4EDA786052FCF15E7ACFA2736AAB4590F73675FA1805FE23892C63E0CD01D006935A6E3F8E105A754803D00D9857E49636AB034164156856D58A244EAD475300D93B31E44B5BE3BBF6994EDB895804B4F1BAD43ECFE08B4E130148B669FE620E4F73034FC3E748237870BEC3B1F517684654D1D6BC074DDF7B759A2405F78ED84D1006D25AF9BBC12D6C632F5D543DA0CBE9EA866B2C92126009C27AD59394B76337DE246B50895317E2E345DF3629A5F6227F64522866E7A39121CCC552E3DABC989DCE066DEA355F788C5D92ADA099917A297CFEFA867CE37656FAC6A50798C10B394D5BA54F85CF0F7EF1EEDDFCA1E53E93F1349888CC745190C196F84ECF0721287CC592D406F0A6CC5A55294BF7AA3B35F6CEFC61CAB794B12444312B5E50EC0712E221CC95E9E26E9C3D000881E792AFCB58641B1A94613D64EC72F3DB9AB65BA07A4F05B7E9EE7B335D86A06FCBDB8CBD695AEEF53964A965FFE4C6D7B4E580AB139F8422A702E09EACBEA5D512C31A955B3D60310BE2BBDD73484BAE6612791A19DA3C7B0FD1487E72131A8F9CB801790CE8A6E1E378662CEDCD5EE82BD390576ACFE5334ECD9D907273AEFE67058916388210638E5E60F20EE92389B3533FD6AFFD33095B23D169F0913657F033B8D5C4EA517F167C1D53E031787BBE6D5B577245FFF8151CD8FDCC5D6C32DF70FB8043D42F896CD513B4C85CFF292676CF13B6A1931E87727A561711A3105D9F3519B90C9429B5CD3EDAAE3EE334826A3FD74D6175B5589DB392F956A67C5E67BE59656F1CB37E52C636B2692A60C2044327472FA9AF651AFBCF55D8398A31D343074931A72D54833B29EF21FCB6EF419BB56313513E46C65D833677DBB0F2813E9CE5EF70676102CA0D3C14BBDD659A7498FA08CD359D428A803AEFCC660E9FC704E9BACC5F1D27F2528D46B3FCAA2D47DFA28BF4C").expect("Valid hex"); + let output = hex!("FD4C14729F5004BA49D832AD7BE87C18F4FAFB58962B9A43C3BE41713DED93DBF854AC4CA26285B7F76E04B8F8D4E7D9F7548F9B465C8F713C106E9F63F54305331A4983A2F4B718DE29FA794DA12EEE808642FAEFF8271A0EA28E3CC80EEB65A8EB61F69D8BA97F6BF9054453F55EFB8F9422081F1620FE44ACF99E81122F73D3F921D5E3391654E9947904984375B725FDFBA895C5CDE3D225D7BE3A213C3965178A7DC1E3B552EC7B2FFD9C77EBCC243C4500DFDFBE3B7554AA427C01305BEC48D71AF27C5911D1E649C620D22CF5F3A5AEB9468651DA796F369522FAF91EFABF0FEBD33FCA41C9534606A4EA0199B904B243BA9CB8F37A792DF02EFAB8F0E2E0CF1D579DABA042CFE4C9430AD4EDA786052FCF15E7ACFA2736AAB4590F73675FA1805FE23892C63E0CD01D006935A6E3F8E105A754803D00D9857E49636AB034164156856D58A244EAD475300D93B31E44B5BE3BBF6994EDB895804B4F1BAD43ECFE08B4E130148B669FE620E4F73034FC3E748237870BEC3B1F517684654D1D6BC074DDF7B759A2405F78ED84D1006D25AF9BBC12D6C632F5D543DA0CBE9EA866B2C92126009C27AD59394B76337DE246B50895317E2E345DF3629A5F6227F64522866E7A39121CCC552E3DABC989DCE066DEA355F788C5D92ADA099917A297CFEFA867CE37656FAC6A50798C10B394D5BA54F85CF0F7EF1EEDDFCA1E53E93F1349888CC745190C196F84ECF0721287CC592D406F0A6CC5A55294BF7AA3B35F6CEFC61CAB794B12444312B5E50EC0712E221CC95E9E26E9C3D000881E792AFCB58641B1A94613D64EC72F3DB9AB65BA07A4F05B7E9EE7B335D86A06FCBDB8CBD695AEEF53964A965FFE4C6D7B4E580AB139F8422A702E09EACBEA5D512C31A955B3D60310BE2BBDD73484BAE6612791A19DA3C7B0FD1487E72131A8F9CB801790CE8A6E1E378662CEDCD5EE82BD390576ACFE5334ECD9D907273AEFE67058916388210638E5E60F20EE92389B3533FD6AFFD33095B23D169F0913657F033B8D5C4EA517F167C1D53E031787BBE6D5B577245FFF8151CD8FDCC5D6C32DF70FB8043D42F896CD513B4C85CFF292676CF13B6A1931E87727A561711A3105D9F3519B90C9429B5CD3EDAAE3EE334826A3FD74D6175B5589DB392F956A67C5E67BE59656F1CB37E52C636B2692A60C2044327472FA9AF651AFBCF55D8398A31D343074931A72D54833B29EF21FCB6EF419BB56313513E46C65D833677DBB0F2813E9CE5EF70676102CA0D3C14BBDD659A7498FA08CD359D428A803AEFCC660E9FC704E9BACC5F1D27F2528D46B3FCAA2D47DFA28BF4C"); let mut aes_ctr = signal_crypto::Aes256Ctr32::from_key(&key, &nonce, init_ctr)?; let mut buf = vec![0u8; output.len()]; aes_ctr.process(&mut buf)?; - assert_eq!(hex::encode(buf), hex::encode(&output)); + assert_eq!(hex::encode(buf), hex::encode(output)); let mut rng = rand::rngs::OsRng; @@ -59,7 +58,7 @@ fn aes_ctr_long_test() -> Result<(), signal_crypto::Error> { processed += this_time; } - assert_eq!(hex::encode(buf), hex::encode(&output)); + assert_eq!(hex::encode(buf), hex::encode(output)); } Ok(()) diff --git a/rust/crypto/tests/aes_gcm.rs b/rust/crypto/tests/aes_gcm.rs index 831ff1d945..b286ff8740 100644 --- a/rust/crypto/tests/aes_gcm.rs +++ b/rust/crypto/tests/aes_gcm.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use hex_literal::hex; use rand::Rng; use serde::Deserialize; use std::collections::HashMap; @@ -149,16 +150,15 @@ fn aes_gcm_wycheproof_kats() -> Result<(), signal_crypto::Error> { #[test] fn aes_gcm_smoke_test() -> Result<(), signal_crypto::Error> { - let key = hex::decode("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") - .expect("Valid hex"); - let nonce = hex::decode("cafebabefacedbaddecaf888").expect("Valid hex"); - let input = hex::decode("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39").expect("Valid hex"); - let ad = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").expect("Valid hex"); - let output = hex::decode("522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f66276fc6ece0f4e1768cddf8853bb2d551b").expect("Valid hex"); + let key = hex!("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308"); + let nonce = hex!("cafebabefacedbaddecaf888"); + let input = hex!("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"); + let ad = hex!("feedfacedeadbeeffeedfacedeadbeefabaddad2"); + let output = hex!("522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f66276fc6ece0f4e1768cddf8853bb2d551b"); let mut aes_gcm = signal_crypto::Aes256GcmEncryption::new(&key, &nonce, &ad)?; - let mut buf = input; + let mut buf = input.to_vec(); aes_gcm.encrypt(&mut buf)?; let tag = aes_gcm.compute_tag()?; diff --git a/rust/media/src/sanitize.rs b/rust/media/src/sanitize.rs index ab3a7bbc46..261c3a7d15 100644 --- a/rust/media/src/sanitize.rs +++ b/rust/media/src/sanitize.rs @@ -46,8 +46,8 @@ pub struct ParseErrorReport { /// Sanitize an MP4 input. /// -/// The input must implement [`AsyncRead`] + [`AsyncSkip`], where `AsyncSkip` represents a subset of the [`AsyncSeek`] -/// trait; an input stream which can be skipped forward, but not necessarily seeked to arbitrary positions. +/// The input must implement [`AsyncRead`] + [`AsyncSkip`], where `AsyncSkip` represents the +/// ability to skip forward, but not necessarily seek to arbitrary positions. /// /// # Errors /// diff --git a/rust/pin/Cargo.toml b/rust/pin/Cargo.toml index e56b3ba7de..0213174f14 100644 --- a/rust/pin/Cargo.toml +++ b/rust/pin/Cargo.toml @@ -12,7 +12,7 @@ description = "A library for operating on signal pins" license = "AGPL-3.0-only" [dependencies] -argon2 = "0.5.0" +argon2 = { version = "0.5.0", features = ["zeroize"] } displaydoc = "0.2" hkdf = "0.12" rand_core = { version = "0.6", features = ["getrandom"] } @@ -21,7 +21,7 @@ static_assertions = "1.1" [dev-dependencies] hex-literal = "0.4.1" -hmac = "0.12" +hmac = { version = "0.12", features = ["reset"] } criterion = "0.5" diff --git a/rust/protocol/Cargo.toml b/rust/protocol/Cargo.toml index 9afaa9a826..3ab8a5cb97 100644 --- a/rust/protocol/Cargo.toml +++ b/rust/protocol/Cargo.toml @@ -12,22 +12,21 @@ license = "AGPL-3.0-only" [dependencies] signal-crypto = { path = "../crypto" } -aes = { version = "0.7.4", features = ["ctr"] } -aes-gcm-siv = "0.10.1" +aes = { version = "0.8.3", features = ["zeroize"] } +aes-gcm-siv = "0.11.1" arrayref = "0.3.6" async-trait = "0.1.41" -block-modes = "0.8" -curve25519-dalek = { version = "4.0.0" } -generic-array = "0.14.5" +ctr = { version = "0.9.2", features = ["zeroize"] } +curve25519-dalek = { version = "4.0.0", features = ["digest"] } hkdf = "0.12" hmac = "0.12" -typenum = "1.12.0" itertools = "0.10.5" prost = "0.9" rand = "0.8" sha2 = "0.10" +static_assertions = "1.1" subtle = "2.3" -x25519-dalek = { version = "2.0.0-rc.3", features = ["static_secrets"] } +x25519-dalek = { version = "2.0.0", features = ["static_secrets"] } hex = "0.4" log = "0.4" num_enum = "0.6.1" @@ -37,9 +36,6 @@ thiserror = "1.0.30" pqcrypto-kyber = { version = "0.7.6", default-features = false, features = ["std"] } pqcrypto-traits = "0.3.4" -[features] -armv8 = ["aes/armv8", "aes-gcm-siv/armv8"] - [dev-dependencies] criterion = "0.5" hex-literal = "0.4.1" diff --git a/rust/protocol/benches/sealed_sender.rs b/rust/protocol/benches/sealed_sender.rs index ee1bbb6d70..d4a53ea25b 100644 --- a/rust/protocol/benches/sealed_sender.rs +++ b/rust/protocol/benches/sealed_sender.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::time::SystemTime; + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use futures_util::FutureExt; use libsignal_protocol::*; @@ -34,6 +36,7 @@ pub fn v1(c: &mut Criterion) { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .now_or_never() @@ -77,20 +80,15 @@ pub fn v1(c: &mut Criterion) { .expect("valid"); let mut encrypt_it = || { - sealed_sender_encrypt_from_usmc( - &bob_address, - &usmc, - &mut alice_store.identity_store, - &mut rng, - ) - .now_or_never() - .expect("sync") - .expect("valid") + sealed_sender_encrypt_from_usmc(&bob_address, &usmc, &alice_store.identity_store, &mut rng) + .now_or_never() + .expect("sync") + .expect("valid") }; let encrypted = encrypt_it(); let mut decrypt_it = || { - sealed_sender_decrypt_to_usmc(&encrypted, &mut bob_store.identity_store) + sealed_sender_decrypt_to_usmc(&encrypted, &bob_store.identity_store) .now_or_never() .expect("sync") .expect("valid") @@ -122,6 +120,7 @@ pub fn v2(c: &mut Criterion) { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .now_or_never() @@ -172,7 +171,7 @@ pub fn v2(c: &mut Criterion) { .load_existing_sessions(&[&bob_address]) .expect("present"), &usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .now_or_never() @@ -188,7 +187,7 @@ pub fn v2(c: &mut Criterion) { .expect("at least one destination"); let mut decrypt_it = || { - sealed_sender_decrypt_to_usmc(&incoming, &mut bob_store.identity_store) + sealed_sender_decrypt_to_usmc(&incoming, &bob_store.identity_store) .now_or_never() .expect("sync") .expect("valid") @@ -198,6 +197,43 @@ pub fn v2(c: &mut Criterion) { c.bench_function("v2/encrypt", |b| b.iter(&mut encrypt_it)); c.bench_function("v2/decrypt", |b| b.iter(&mut decrypt_it)); + { + // Test new derivation, while we're still using the old one by default. + let mut encrypt_it = || { + sealed_sender_multi_recipient_encrypt_using_new_ephemeral_key_derivation( + &[&bob_address], + &alice_store + .session_store + .load_existing_sessions(&[&bob_address]) + .expect("present"), + &usmc, + &alice_store.identity_store, + &mut rng, + ) + .now_or_never() + .expect("sync") + .expect("valid") + }; + let outgoing = encrypt_it(); + + let incoming = sealed_sender_multi_recipient_fan_out(&outgoing) + .expect("valid") + .into_iter() + .next() + .expect("at least one destination"); + + let mut decrypt_it = || { + sealed_sender_decrypt_to_usmc(&incoming, &bob_store.identity_store) + .now_or_never() + .expect("sync") + .expect("valid") + }; + assert_eq!(message, decrypt_it().contents().expect("valid")); + + c.bench_function("v2/encrypt/new-derivation", |b| b.iter(&mut encrypt_it)); + c.bench_function("v2/decrypt/new-derivation", |b| b.iter(&mut decrypt_it)); + } + // Fill out additional recipients. let mut recipients = vec![bob_address.clone()]; while recipients.len() < 10 { @@ -215,6 +251,7 @@ pub fn v2(c: &mut Criterion) { &mut alice_store.session_store, &mut alice_store.identity_store, &next_pre_key_bundle, + SystemTime::now(), &mut rng, ) .now_or_never() @@ -239,7 +276,7 @@ pub fn v2(c: &mut Criterion) { .load_existing_sessions(&recipients) .expect("present"), &usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .now_or_never() @@ -266,7 +303,7 @@ pub fn v2(c: &mut Criterion) { .load_existing_sessions(&recipients) .expect("present"), &usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .now_or_never() diff --git a/rust/protocol/benches/session.rs b/rust/protocol/benches/session.rs index 1158246477..0848b5039d 100644 --- a/rust/protocol/benches/session.rs +++ b/rust/protocol/benches/session.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::time::SystemTime; + use criterion::{criterion_group, criterion_main, Criterion}; use futures_util::FutureExt; use libsignal_protocol::*; @@ -133,6 +135,7 @@ pub fn session_encrypt_result(c: &mut Criterion) -> Result<(), SignalProtocolErr &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut OsRng, ) .now_or_never() diff --git a/rust/protocol/cross-version-testing/.gitignore b/rust/protocol/cross-version-testing/.gitignore new file mode 100644 index 0000000000..fa8d85ac52 --- /dev/null +++ b/rust/protocol/cross-version-testing/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target diff --git a/rust/protocol/cross-version-testing/Cargo.toml b/rust/protocol/cross-version-testing/Cargo.toml new file mode 100644 index 0000000000..5cd69f7735 --- /dev/null +++ b/rust/protocol/cross-version-testing/Cargo.toml @@ -0,0 +1,24 @@ +# +# Copyright (C) 2023 Signal Messenger, LLC. +# SPDX-License-Identifier: AGPL-3.0-only +# + +[package] +name = "libsignal-protocol-cross-version-testing" +version = "0.1.0" +edition = "2021" + +[dependencies] +env_logger = "0.10.0" +futures-util = "0.3.7" +log = "0.4" +rand = "0.8" +rand_v7 = { package = "rand", version = "0.7" } + +libsignal-protocol-current = { path = "..", package = "libsignal-protocol" } +libsignal-protocol-v12 = { git = "https://github.com/signalapp/libsignal", tag = "v0.12.3", package = "libsignal-protocol" } +libsignal-protocol-v21 = { git = "https://github.com/signalapp/libsignal", tag = "v0.21.1", package = "libsignal-protocol" } + +# Prevent this crate from being included in the top-level workspace +[workspace] +members = ["."] diff --git a/rust/protocol/cross-version-testing/src/current.rs b/rust/protocol/cross-version-testing/src/current.rs new file mode 100644 index 0000000000..231f6f0741 --- /dev/null +++ b/rust/protocol/cross-version-testing/src/current.rs @@ -0,0 +1,163 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + +use libsignal_protocol_current::*; + +use std::time::SystemTime; + +use futures_util::FutureExt; +use rand::{thread_rng, Rng}; + +fn address(id: &str) -> ProtocolAddress { + ProtocolAddress::new(id.into(), 1.into()) +} + +pub struct LibSignalProtocolCurrent(InMemSignalProtocolStore); + +impl LibSignalProtocolCurrent { + pub fn new() -> Self { + let mut csprng = thread_rng(); + let identity_key = IdentityKeyPair::generate(&mut csprng); + // Valid registration IDs fit in 14 bits. + let registration_id: u8 = csprng.gen(); + + Self( + InMemSignalProtocolStore::new(identity_key, registration_id as u32) + .expect("can initialize"), + ) + } +} + +impl super::LibSignalProtocolStore for LibSignalProtocolCurrent { + fn version(&self) -> &'static str { + "current" + } + + fn create_pre_key_bundle(&mut self) -> PreKeyBundle { + let mut csprng = thread_rng(); + let pre_key_pair = KeyPair::generate(&mut csprng); + let signed_pre_key_pair = KeyPair::generate(&mut csprng); + + let signed_pre_key_public = signed_pre_key_pair.public_key.serialize(); + let signed_pre_key_signature = self + .0 + .get_identity_key_pair() + .now_or_never() + .expect("synchronous") + .expect("can fetch identity key") + .private_key() + .calculate_signature(&signed_pre_key_public, &mut csprng) + .expect("can calculate signatures"); + + let device_id: u32 = csprng.gen(); + let pre_key_id: u32 = csprng.gen(); + let signed_pre_key_id: u32 = csprng.gen(); + + let pre_key_bundle = PreKeyBundle::new( + self.0 + .get_local_registration_id() + .now_or_never() + .expect("synchronous") + .expect("can fetch registration id"), + device_id.into(), + Some((pre_key_id.into(), pre_key_pair.public_key)), + signed_pre_key_id.into(), + signed_pre_key_pair.public_key, + signed_pre_key_signature.to_vec(), + *self + .0 + .get_identity_key_pair() + .now_or_never() + .expect("synchronous") + .expect("can fetch identity key") + .identity_key(), + ) + .expect("can create pre-key bundles"); + + self.0 + .save_pre_key( + pre_key_id.into(), + &PreKeyRecord::new(pre_key_id.into(), &pre_key_pair), + ) + .now_or_never() + .expect("synchronous") + .expect("can save pre-keys"); + + let timestamp = csprng.gen(); + + self.0 + .save_signed_pre_key( + signed_pre_key_id.into(), + &SignedPreKeyRecord::new( + signed_pre_key_id.into(), + timestamp, + &signed_pre_key_pair, + &signed_pre_key_signature, + ), + ) + .now_or_never() + .expect("synchronous") + .expect("can save pre-keys"); + + pre_key_bundle + } + + fn process_pre_key_bundle(&mut self, remote: &str, pre_key_bundle: PreKeyBundle) { + process_prekey_bundle( + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &pre_key_bundle, + SystemTime::now(), + &mut thread_rng(), + ) + .now_or_never() + .expect("synchronous") + .expect("can process pre-key bundles") + } + + fn encrypt(&mut self, remote: &str, msg: &[u8]) -> (Vec, CiphertextMessageType) { + let encrypted = message_encrypt( + msg, + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + SystemTime::now(), + ) + .now_or_never() + .expect("synchronous") + .expect("can encrypt messages"); + (encrypted.serialize().to_vec(), encrypted.message_type()) + } + + fn decrypt(&mut self, remote: &str, msg: &[u8], msg_type: CiphertextMessageType) -> Vec { + match msg_type { + CiphertextMessageType::Whisper => message_decrypt_signal( + &SignalMessage::try_from(msg).expect("valid"), + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &mut thread_rng(), + ) + .now_or_never() + .expect("synchronous") + .expect("can decrypt messages"), + CiphertextMessageType::PreKey => message_decrypt_prekey( + &PreKeySignalMessage::try_from(msg).expect("valid"), + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &mut self.0.pre_key_store, + &mut self.0.signed_pre_key_store, + &mut self.0.kyber_pre_key_store, + &mut thread_rng(), + ) + .now_or_never() + .expect("synchronous") + .expect("can decrypt messages"), + _ => panic!("unexpected 1:1 message type"), + } + } +} diff --git a/rust/protocol/cross-version-testing/src/lib.rs b/rust/protocol/cross-version-testing/src/lib.rs new file mode 100644 index 0000000000..96b575444b --- /dev/null +++ b/rust/protocol/cross-version-testing/src/lib.rs @@ -0,0 +1,25 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + +#![allow(clippy::new_without_default)] + +pub use libsignal_protocol_current::{CiphertextMessageType, PreKeyBundle}; + +pub trait LibSignalProtocolStore { + fn version(&self) -> &'static str; + fn create_pre_key_bundle(&mut self) -> PreKeyBundle; + fn process_pre_key_bundle(&mut self, remote: &str, pre_key_bundle: PreKeyBundle); + fn encrypt(&mut self, remote: &str, msg: &[u8]) -> (Vec, CiphertextMessageType); + fn decrypt(&mut self, remote: &str, msg: &[u8], msg_type: CiphertextMessageType) -> Vec; +} + +mod current; +pub use current::LibSignalProtocolCurrent; + +mod v21; +pub use v21::LibSignalProtocolV21; + +mod v12; +pub use v12::LibSignalProtocolV12; diff --git a/rust/protocol/cross-version-testing/src/v12.rs b/rust/protocol/cross-version-testing/src/v12.rs new file mode 100644 index 0000000000..a3203c5463 --- /dev/null +++ b/rust/protocol/cross-version-testing/src/v12.rs @@ -0,0 +1,227 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + +use futures_util::FutureExt; +use libsignal_protocol_v12::*; + +use rand_v7::{thread_rng, Rng}; + +fn address(id: &str) -> ProtocolAddress { + ProtocolAddress::new(id.into(), 1) +} + +pub struct LibSignalProtocolV12(InMemSignalProtocolStore); + +impl LibSignalProtocolV12 { + pub fn new() -> Self { + let mut csprng = thread_rng(); + let identity_key = IdentityKeyPair::generate(&mut csprng); + // Valid registration IDs fit in 14 bits. + let registration_id: u8 = csprng.gen(); + + Self( + InMemSignalProtocolStore::new(identity_key, registration_id as u32) + .expect("can initialize"), + ) + } +} + +impl super::LibSignalProtocolStore for LibSignalProtocolV12 { + fn version(&self) -> &'static str { + "v12" + } + + fn create_pre_key_bundle(&mut self) -> super::PreKeyBundle { + let mut csprng = thread_rng(); + let pre_key_pair = KeyPair::generate(&mut csprng); + let signed_pre_key_pair = KeyPair::generate(&mut csprng); + + let signed_pre_key_public = signed_pre_key_pair.public_key.serialize(); + let signed_pre_key_signature = self + .0 + .get_identity_key_pair(None) + .now_or_never() + .expect("synchronous") + .expect("can fetch identity key") + .private_key() + .calculate_signature(&signed_pre_key_public, &mut csprng) + .expect("can calculate signatures"); + + let device_id: u32 = csprng.gen(); + let pre_key_id: u32 = csprng.gen(); + let signed_pre_key_id: u32 = csprng.gen(); + + let pre_key_bundle = super::PreKeyBundle::new( + self.0 + .get_local_registration_id(None) + .now_or_never() + .expect("synchronous") + .expect("can fetch registration id"), + device_id.into(), + Some(( + pre_key_id.into(), + pre_key_pair.public_key.serialize()[..] + .try_into() + .expect("compatible key serialization format"), + )), + signed_pre_key_id.into(), + signed_pre_key_pair.public_key.serialize()[..] + .try_into() + .expect("compatible key serialization format"), + signed_pre_key_signature.to_vec(), + self.0 + .get_identity_key_pair(None) + .now_or_never() + .expect("synchronous") + .expect("can fetch identity key") + .identity_key() + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + ) + .expect("can create pre-key bundles"); + + self.0 + .save_pre_key( + pre_key_id, + &PreKeyRecord::new(pre_key_id, &pre_key_pair), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can save pre-keys"); + + let timestamp = csprng.gen(); + + self.0 + .save_signed_pre_key( + signed_pre_key_id, + &SignedPreKeyRecord::new( + signed_pre_key_id, + timestamp, + &signed_pre_key_pair, + &signed_pre_key_signature, + ), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can save pre-keys"); + + pre_key_bundle + } + + fn process_pre_key_bundle(&mut self, remote: &str, pre_key_bundle: super::PreKeyBundle) { + let pre_key_bundle = PreKeyBundle::new( + pre_key_bundle + .registration_id() + .expect("has registration ID"), + DeviceId::from(pre_key_bundle.device_id().expect("has device ID")), + pre_key_bundle + .pre_key_id() + .expect("can ask about one-time pre-keys") + .map(|pre_key_id| { + ( + u32::from(pre_key_id), + pre_key_bundle + .pre_key_public() + .expect("can ask about one-time pre-keys") + .expect("has one-time pre-key") + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + ) + }), + u32::from( + pre_key_bundle + .signed_pre_key_id() + .expect("has signed pre-key ID"), + ), + pre_key_bundle + .signed_pre_key_public() + .expect("has signed pre-key") + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + pre_key_bundle + .signed_pre_key_signature() + .expect("has signature") + .to_vec(), + pre_key_bundle + .identity_key() + .expect("has identity key") + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + ) + .expect("can create pre-key bundles"); + process_prekey_bundle( + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &pre_key_bundle, + &mut thread_rng(), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can process pre-key bundles") + } + + fn encrypt(&mut self, remote: &str, msg: &[u8]) -> (Vec, super::CiphertextMessageType) { + let encrypted = message_encrypt( + msg, + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can encrypt messages"); + let message_type = match encrypted.message_type() { + CiphertextMessageType::Whisper => super::CiphertextMessageType::Whisper, + CiphertextMessageType::PreKey => super::CiphertextMessageType::PreKey, + CiphertextMessageType::SenderKey => super::CiphertextMessageType::SenderKey, + CiphertextMessageType::Plaintext => super::CiphertextMessageType::Plaintext, + }; + (encrypted.serialize().to_vec(), message_type) + } + + fn decrypt( + &mut self, + remote: &str, + msg: &[u8], + msg_type: super::CiphertextMessageType, + ) -> Vec { + match msg_type { + super::CiphertextMessageType::Whisper => message_decrypt_signal( + &SignalMessage::try_from(msg).expect("valid"), + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &mut thread_rng(), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can decrypt messages"), + super::CiphertextMessageType::PreKey => message_decrypt_prekey( + &PreKeySignalMessage::try_from(msg).expect("valid"), + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &mut self.0.pre_key_store, + &mut self.0.signed_pre_key_store, + &mut thread_rng(), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can decrypt messages"), + _ => panic!("unexpected 1:1 message type"), + } + } +} diff --git a/rust/protocol/cross-version-testing/src/v21.rs b/rust/protocol/cross-version-testing/src/v21.rs new file mode 100644 index 0000000000..31089008a3 --- /dev/null +++ b/rust/protocol/cross-version-testing/src/v21.rs @@ -0,0 +1,229 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + +use futures_util::FutureExt; +use libsignal_protocol_v21::*; + +use rand_v7::{thread_rng, Rng}; + +fn address(id: &str) -> ProtocolAddress { + ProtocolAddress::new(id.into(), 1.into()) +} + +pub struct LibSignalProtocolV21(InMemSignalProtocolStore); + +impl LibSignalProtocolV21 { + pub fn new() -> Self { + let mut csprng = thread_rng(); + let identity_key = IdentityKeyPair::generate(&mut csprng); + // Valid registration IDs fit in 14 bits. + let registration_id: u8 = csprng.gen(); + + Self( + InMemSignalProtocolStore::new(identity_key, registration_id as u32) + .expect("can initialize"), + ) + } +} + +impl super::LibSignalProtocolStore for LibSignalProtocolV21 { + fn version(&self) -> &'static str { + "v21" + } + + fn create_pre_key_bundle(&mut self) -> super::PreKeyBundle { + let mut csprng = thread_rng(); + let pre_key_pair = KeyPair::generate(&mut csprng); + let signed_pre_key_pair = KeyPair::generate(&mut csprng); + + let signed_pre_key_public = signed_pre_key_pair.public_key.serialize(); + let signed_pre_key_signature = self + .0 + .get_identity_key_pair(None) + .now_or_never() + .expect("synchronous") + .expect("can fetch identity key") + .private_key() + .calculate_signature(&signed_pre_key_public, &mut csprng) + .expect("can calculate signatures"); + + let device_id: u32 = csprng.gen(); + let pre_key_id: u32 = csprng.gen(); + let signed_pre_key_id: u32 = csprng.gen(); + + let pre_key_bundle = super::PreKeyBundle::new( + self.0 + .get_local_registration_id(None) + .now_or_never() + .expect("synchronous") + .expect("can fetch registration id"), + device_id.into(), + Some(( + pre_key_id.into(), + pre_key_pair.public_key.serialize()[..] + .try_into() + .expect("compatible key serialization format"), + )), + signed_pre_key_id.into(), + signed_pre_key_pair.public_key.serialize()[..] + .try_into() + .expect("compatible key serialization format"), + signed_pre_key_signature.to_vec(), + self.0 + .get_identity_key_pair(None) + .now_or_never() + .expect("synchronous") + .expect("can fetch identity key") + .identity_key() + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + ) + .expect("can create pre-key bundles"); + + self.0 + .save_pre_key( + pre_key_id.into(), + &PreKeyRecord::new(pre_key_id.into(), &pre_key_pair), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can save pre-keys"); + + let timestamp = csprng.gen(); + + self.0 + .save_signed_pre_key( + signed_pre_key_id.into(), + &SignedPreKeyRecord::new( + signed_pre_key_id.into(), + timestamp, + &signed_pre_key_pair, + &signed_pre_key_signature, + ), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can save pre-keys"); + + pre_key_bundle + } + + fn process_pre_key_bundle(&mut self, remote: &str, pre_key_bundle: super::PreKeyBundle) { + let pre_key_bundle = PreKeyBundle::new( + pre_key_bundle + .registration_id() + .expect("has registration ID"), + DeviceId::from(u32::from( + pre_key_bundle.device_id().expect("has device ID"), + )), + pre_key_bundle + .pre_key_id() + .expect("can ask about one-time pre-keys") + .map(|pre_key_id| { + ( + PreKeyId::from(u32::from(pre_key_id)), + pre_key_bundle + .pre_key_public() + .expect("can ask about one-time pre-keys") + .expect("has one-time pre-key") + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + ) + }), + SignedPreKeyId::from(u32::from( + pre_key_bundle + .signed_pre_key_id() + .expect("has signed pre-key ID"), + )), + pre_key_bundle + .signed_pre_key_public() + .expect("has signed pre-key") + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + pre_key_bundle + .signed_pre_key_signature() + .expect("has signature") + .to_vec(), + pre_key_bundle + .identity_key() + .expect("has identity key") + .serialize()[..] + .try_into() + .expect("compatible key serialization format"), + ) + .expect("can create pre-key bundles"); + process_prekey_bundle( + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &pre_key_bundle, + &mut thread_rng(), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can process pre-key bundles") + } + + fn encrypt(&mut self, remote: &str, msg: &[u8]) -> (Vec, super::CiphertextMessageType) { + let encrypted = message_encrypt( + msg, + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can encrypt messages"); + let message_type = match encrypted.message_type() { + CiphertextMessageType::Whisper => super::CiphertextMessageType::Whisper, + CiphertextMessageType::PreKey => super::CiphertextMessageType::PreKey, + CiphertextMessageType::SenderKey => super::CiphertextMessageType::SenderKey, + CiphertextMessageType::Plaintext => super::CiphertextMessageType::Plaintext, + }; + (encrypted.serialize().to_vec(), message_type) + } + + fn decrypt( + &mut self, + remote: &str, + msg: &[u8], + msg_type: super::CiphertextMessageType, + ) -> Vec { + match msg_type { + super::CiphertextMessageType::Whisper => message_decrypt_signal( + &SignalMessage::try_from(msg).expect("valid"), + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &mut thread_rng(), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can decrypt messages"), + super::CiphertextMessageType::PreKey => message_decrypt_prekey( + &PreKeySignalMessage::try_from(msg).expect("valid"), + &address(remote), + &mut self.0.session_store, + &mut self.0.identity_store, + &mut self.0.pre_key_store, + &mut self.0.signed_pre_key_store, + &mut thread_rng(), + None, + ) + .now_or_never() + .expect("synchronous") + .expect("can decrypt messages"), + _ => panic!("unexpected 1:1 message type"), + } + } +} diff --git a/rust/protocol/cross-version-testing/tests/session.rs b/rust/protocol/cross-version-testing/tests/session.rs new file mode 100644 index 0000000000..086517979e --- /dev/null +++ b/rust/protocol/cross-version-testing/tests/session.rs @@ -0,0 +1,168 @@ +// +// Copyright 2023 Signal Messenger, LLC. +// SPDX-License-Identifier: AGPL-3.0-only +// + +use libsignal_protocol_cross_version_testing::*; + +// Use this function to debug tests +#[allow(dead_code)] +fn init_logger() { + let _ = env_logger::builder() + .filter_level(log::LevelFilter::max()) + .is_test(true) + .try_init(); +} + +fn try_all_combinations( + f: fn(&mut dyn LibSignalProtocolStore, &mut dyn LibSignalProtocolStore), + make_previous: &[fn() -> Box], +) { + let run = |alice_store: &mut dyn LibSignalProtocolStore, + bob_store: &mut dyn LibSignalProtocolStore| { + log::info!( + "alice: {}, bob: {}", + alice_store.version(), + bob_store.version() + ); + f(alice_store, bob_store) + }; + + // Current<->Current, to test that the test is correct. + run( + &mut LibSignalProtocolCurrent::new(), + &mut LibSignalProtocolCurrent::new(), + ); + + // Current<->Previous + for bob_store_maker in make_previous { + let mut alice_store = LibSignalProtocolCurrent::new(); + let mut bob_store = bob_store_maker(); + run(&mut alice_store, &mut *bob_store); + } + + // Previous<->Current + for alice_store_maker in make_previous { + let mut alice_store = alice_store_maker(); + let mut bob_store = LibSignalProtocolCurrent::new(); + run(&mut *alice_store, &mut bob_store); + } +} + +#[test] +fn test_basic_prekey() { + try_all_combinations( + run, + &[ + || Box::new(LibSignalProtocolV21::new()), + || Box::new(LibSignalProtocolV12::new()), + ], + ); + + fn run( + alice_store: &mut dyn LibSignalProtocolStore, + bob_store: &mut dyn LibSignalProtocolStore, + ) { + let alice_name = "alice"; + let bob_name = "bob"; + + let bob_pre_key_bundle = bob_store.create_pre_key_bundle(); + alice_store.process_pre_key_bundle(bob_name, bob_pre_key_bundle); + + let original_message = "L'homme est condamné à être libre".as_bytes(); + let (outgoing_message, outgoing_message_type) = + alice_store.encrypt(bob_name, original_message); + assert_eq!(outgoing_message_type, CiphertextMessageType::PreKey); + + let ptext = bob_store.decrypt(alice_name, &outgoing_message, outgoing_message_type); + assert_eq!(&ptext, original_message); + + let bobs_response = "Who watches the watchers?".as_bytes(); + let (bob_outgoing, bob_outgoing_type) = bob_store.encrypt(alice_name, bobs_response); + assert_eq!(bob_outgoing_type, CiphertextMessageType::Whisper); + + let alice_decrypts = alice_store.decrypt(bob_name, &bob_outgoing, bob_outgoing_type); + assert_eq!(&alice_decrypts, bobs_response); + + run_interaction(alice_store, alice_name, bob_store, bob_name); + } +} + +fn run_interaction( + alice_store: &mut dyn LibSignalProtocolStore, + alice_name: &str, + bob_store: &mut dyn LibSignalProtocolStore, + bob_name: &str, +) { + let alice_ptext = b"It's rabbit season"; + + let (alice_message, alice_message_type) = alice_store.encrypt(bob_name, alice_ptext); + assert_eq!(alice_message_type, CiphertextMessageType::Whisper); + assert_eq!( + &bob_store.decrypt(alice_name, &alice_message, alice_message_type), + alice_ptext + ); + + let bob_ptext = b"It's duck season"; + + let (bob_message, bob_message_type) = bob_store.encrypt(alice_name, bob_ptext); + assert_eq!(bob_message_type, CiphertextMessageType::Whisper); + assert_eq!( + &alice_store.decrypt(bob_name, &bob_message, bob_message_type), + bob_ptext + ); + + for i in 0..10 { + let alice_ptext = format!("A->B message {}", i); + let (alice_message, alice_message_type) = + alice_store.encrypt(bob_name, alice_ptext.as_bytes()); + assert_eq!(alice_message_type, CiphertextMessageType::Whisper); + assert_eq!( + &bob_store.decrypt(alice_name, &alice_message, alice_message_type), + alice_ptext.as_bytes() + ); + } + + for i in 0..10 { + let bob_ptext = format!("B->A message {}", i); + let (bob_message, bob_message_type) = bob_store.encrypt(alice_name, bob_ptext.as_bytes()); + assert_eq!(bob_message_type, CiphertextMessageType::Whisper); + assert_eq!( + &alice_store.decrypt(bob_name, &bob_message, bob_message_type), + bob_ptext.as_bytes() + ); + } + + let mut alice_ooo_messages = vec![]; + + for i in 0..10 { + let alice_ptext = format!("A->B OOO message {}", i); + let (alice_message, _) = alice_store.encrypt(bob_name, alice_ptext.as_bytes()); + alice_ooo_messages.push((alice_ptext, alice_message)); + } + + for i in 0..10 { + let alice_ptext = format!("A->B post-OOO message {}", i); + let (alice_message, _) = alice_store.encrypt(bob_name, alice_ptext.as_bytes()); + assert_eq!( + &bob_store.decrypt(alice_name, &alice_message, CiphertextMessageType::Whisper), + alice_ptext.as_bytes() + ); + } + + for i in 0..10 { + let bob_ptext = format!("B->A message post-OOO {}", i); + let (bob_message, _) = bob_store.encrypt(alice_name, bob_ptext.as_bytes()); + assert_eq!( + &alice_store.decrypt(bob_name, &bob_message, CiphertextMessageType::Whisper), + bob_ptext.as_bytes() + ); + } + + for (ptext, ctext) in alice_ooo_messages { + assert_eq!( + &bob_store.decrypt(alice_name, &ctext, CiphertextMessageType::Whisper), + ptext.as_bytes() + ); + } +} diff --git a/rust/protocol/fuzz/Cargo.toml b/rust/protocol/fuzz/Cargo.toml index 70fb9024e1..2ff926463f 100644 --- a/rust/protocol/fuzz/Cargo.toml +++ b/rust/protocol/fuzz/Cargo.toml @@ -31,4 +31,3 @@ doc = false [patch.crates-io] # Use our fork of curve25519-dalek for zkgroup support. curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.0.0' } -x25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.0.0' } diff --git a/rust/protocol/fuzz/fuzz_targets/interaction.rs b/rust/protocol/fuzz/fuzz_targets/interaction.rs index 7c001e879a..d054b493cf 100644 --- a/rust/protocol/fuzz/fuzz_targets/interaction.rs +++ b/rust/protocol/fuzz/fuzz_targets/interaction.rs @@ -6,6 +6,7 @@ #![no_main] use std::convert::TryFrom; +use std::time::SystemTime; use futures_util::FutureExt; use libfuzzer_sys::fuzz_target; @@ -44,7 +45,7 @@ impl Participant { them.store .save_signed_pre_key( - signed_pre_key_id.into(), + signed_pre_key_id, &SignedPreKeyRecord::new( signed_pre_key_id, /*timestamp*/ 42, @@ -74,8 +75,8 @@ impl Participant { let their_pre_key_bundle = PreKeyBundle::new( them.store.get_local_registration_id().await.unwrap(), 1.into(), // device id - pre_key_info.into(), - signed_pre_key_id.into(), + pre_key_info, + signed_pre_key_id, their_signed_pre_key_pair.public_key, their_signed_pre_key_signature.into_vec(), *them @@ -92,6 +93,7 @@ impl Participant { &mut self.store.session_store, &mut self.store.identity_store, &their_pre_key_bundle, + SystemTime::UNIX_EPOCH, rng, ) .await @@ -100,13 +102,13 @@ impl Participant { async fn send_message(&mut self, them: &mut Self, rng: &mut (impl Rng + CryptoRng)) { info!("{}: sending message", self.name); - if self + if !self .store .load_session(&them.address) .await .unwrap() - .map(|session| !session.has_current_session_state()) - .unwrap_or(true) + .and_then(|session| session.has_usable_sender_chain(SystemTime::UNIX_EPOCH).ok()) + .unwrap_or(false) { self.process_pre_key(them, rng.gen_bool(0.75), rng).await; } @@ -120,6 +122,7 @@ impl Participant { &them.address, &mut self.store.session_store, &mut self.store.identity_store, + SystemTime::UNIX_EPOCH, ) .await .unwrap(); diff --git a/rust/protocol/src/consts.rs b/rust/protocol/src/consts.rs index 6b0b4dbab5..0f1291c85a 100644 --- a/rust/protocol/src/consts.rs +++ b/rust/protocol/src/consts.rs @@ -3,8 +3,12 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::time::Duration; + pub const MAX_FORWARD_JUMPS: usize = 25_000; pub const MAX_MESSAGE_KEYS: usize = 2000; pub const MAX_RECEIVER_CHAINS: usize = 5; pub const ARCHIVED_STATES_MAX_LENGTH: usize = 40; pub const MAX_SENDER_KEY_STATES: usize = 5; + +pub const MAX_UNACKNOWLEDGED_SESSION_AGE: Duration = Duration::from_secs(60 * 60 * 24 * 30); diff --git a/rust/protocol/src/crypto.rs b/rust/protocol/src/crypto.rs index c3ca9d3b7b..70cc4b7124 100644 --- a/rust/protocol/src/crypto.rs +++ b/rust/protocol/src/crypto.rs @@ -6,8 +6,8 @@ use std::convert::TryInto; use std::result::Result; -use aes::cipher::{NewCipher, StreamCipher}; -use aes::Aes256Ctr; +use aes::cipher::{KeyIvInit, StreamCipher}; +use aes::Aes256; use hmac::{Hmac, Mac}; use sha2::Sha256; use subtle::ConstantTimeEq; @@ -32,7 +32,7 @@ fn aes_256_ctr_encrypt(ptext: &[u8], key: &[u8]) -> Result, EncryptionEr let key: [u8; 32] = key.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?; let zero_nonce = [0u8; 16]; - let mut cipher = Aes256Ctr::new(key[..].into(), zero_nonce[..].into()); + let mut cipher = ctr::Ctr32BE::::new(key[..].into(), zero_nonce[..].into()); let mut ctext = ptext.to_vec(); cipher.apply_keystream(&mut ctext); @@ -83,11 +83,11 @@ pub(crate) fn aes256_ctr_hmacsha256_decrypt( #[cfg(test)] mod test { use super::*; + use hex_literal::hex; #[test] fn aes_ctr_test() { - let key = hex::decode("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4") - .expect("valid hex"); + let key = hex!("603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4"); let ptext = [0u8; 35]; let ctext = aes_256_ctr_encrypt(&ptext, &key).expect("valid key"); diff --git a/rust/protocol/src/curve.rs b/rust/protocol/src/curve.rs index 33e683742a..9c1d9b0ddd 100644 --- a/rust/protocol/src/curve.rs +++ b/rust/protocol/src/curve.rs @@ -12,6 +12,7 @@ use std::convert::TryFrom; use std::fmt; use arrayref::array_ref; +use curve25519_dalek::scalar; use rand::{CryptoRng, Rng}; use subtle::ConstantTimeEq; @@ -220,10 +221,8 @@ impl PrivateKey { } else { let mut key = [0u8; curve25519::PRIVATE_KEY_LENGTH]; key.copy_from_slice(&value[..curve25519::PRIVATE_KEY_LENGTH]); - // Clamp: - key[0] &= 0xF8; - key[31] &= 0x7F; - key[31] |= 0x40; + // Clamping is not necessary but is kept for backward compatibility + key = scalar::clamp_integer(key); Ok(Self { key: PrivateKeyData::DjbPrivateKey(key), }) diff --git a/rust/protocol/src/curve/curve25519.rs b/rust/protocol/src/curve/curve25519.rs index af97ff3de5..3f39472f15 100644 --- a/rust/protocol/src/curve/curve25519.rs +++ b/rust/protocol/src/curve/curve25519.rs @@ -49,7 +49,7 @@ impl PrivateKey { /// Calculates an XEdDSA signature using the X25519 private key directly. /// - /// Refer to https://signal.org/docs/specifications/xeddsa/#curve25519 for more details. + /// Refer to for more details. /// /// Note that this implementation varies slightly from that paper in that the sign bit is not /// fixed to 0, but rather passed back in the most significant bit of the signature which would diff --git a/rust/protocol/src/fingerprint.rs b/rust/protocol/src/fingerprint.rs index 07c96ac816..31bccdb00b 100644 --- a/rust/protocol/src/fingerprint.rs +++ b/rust/protocol/src/fingerprint.rs @@ -213,10 +213,12 @@ impl Fingerprint { #[cfg(test)] mod test { use super::*; + use hex_literal::hex; - const ALICE_IDENTITY: &str = - "0506863bc66d02b40d27b8d49ca7c09e9239236f9d7d25d6fcca5ce13c7064d868"; - const BOB_IDENTITY: &str = "05f781b6fb32fed9ba1cf2de978d4d5da28dc34046ae814402b5c0dbd96fda907b"; + const ALICE_IDENTITY: &[u8] = + &hex!("0506863bc66d02b40d27b8d49ca7c09e9239236f9d7d25d6fcca5ce13c7064d868"); + const BOB_IDENTITY: &[u8] = + &hex!("05f781b6fb32fed9ba1cf2de978d4d5da28dc34046ae814402b5c0dbd96fda907b"); const DISPLAYABLE_FINGERPRINT_V1: &str = "300354477692869396892869876765458257569162576843440918079131"; @@ -248,8 +250,8 @@ mod test { fn fingerprint_test_v1() -> Result<()> { // testVectorsVersion1 in Java - let a_key = IdentityKey::decode(&hex::decode(ALICE_IDENTITY).expect("valid hex"))?; - let b_key = IdentityKey::decode(&hex::decode(BOB_IDENTITY).expect("valid hex"))?; + let a_key = IdentityKey::decode(ALICE_IDENTITY)?; + let b_key = IdentityKey::decode(BOB_IDENTITY)?; let version = 1; let iterations = 5200; @@ -300,8 +302,8 @@ mod test { fn fingerprint_test_v2() -> Result<()> { // testVectorsVersion2 in Java - let a_key = IdentityKey::decode(&hex::decode(ALICE_IDENTITY).expect("valid hex"))?; - let b_key = IdentityKey::decode(&hex::decode(BOB_IDENTITY).expect("valid hex"))?; + let a_key = IdentityKey::decode(ALICE_IDENTITY)?; + let b_key = IdentityKey::decode(BOB_IDENTITY)?; let version = 2; let iterations = 5200; @@ -505,8 +507,8 @@ mod test { #[test] fn fingerprint_mismatching_versions() -> Result<()> { - let a_key = IdentityKey::decode(&hex::decode(ALICE_IDENTITY).expect("valid hex"))?; - let b_key = IdentityKey::decode(&hex::decode(BOB_IDENTITY).expect("valid hex"))?; + let a_key = IdentityKey::decode(ALICE_IDENTITY)?; + let b_key = IdentityKey::decode(BOB_IDENTITY)?; let iterations = 5200; diff --git a/rust/protocol/src/incremental_mac.rs b/rust/protocol/src/incremental_mac.rs index 1e92eb487e..b9e47f24a1 100644 --- a/rust/protocol/src/incremental_mac.rs +++ b/rust/protocol/src/incremental_mac.rs @@ -3,10 +3,10 @@ // SPDX-License-Identifier: AGPL-3.0-only // -use generic_array::{ArrayLength, GenericArray}; +use aes::cipher::Unsigned; +use hmac::digest::generic_array::{ArrayLength, GenericArray}; use hmac::Mac; use sha2::digest::{FixedOutput, MacError, Output}; -use typenum::Unsigned; #[derive(Clone)] pub struct Incremental { @@ -22,24 +22,27 @@ pub struct Validating { expected: Vec>, } -const MINIMUM_INCREMENTAL_CHUNK_SIZE: usize = 8 * 1024; -const MAXIMUM_INCREMENTAL_DIGEST_BYTES: usize = 1024; +const MINIMUM_CHUNK_SIZE: usize = 64 * 1024; +const MAXIMUM_CHUNK_SIZE: usize = 2 * 1024 * 1024; +const TARGET_TOTAL_DIGEST_SIZE: usize = 8 * 1024; -#[allow(clippy::manual_clamp)] -pub fn calculate_chunk_size(data_size: usize) -> usize +pub const fn calculate_chunk_size(data_size: usize) -> usize where D: FixedOutput, D::OutputSize: ArrayLength, { - if data_size == 0 { - return MINIMUM_INCREMENTAL_CHUNK_SIZE; + assert!( + 0 == TARGET_TOTAL_DIGEST_SIZE % D::OutputSize::USIZE, + "Target digest size should be a multiple of digest size" + ); + let target_chunk_count = TARGET_TOTAL_DIGEST_SIZE / D::OutputSize::USIZE; + if data_size < target_chunk_count * MINIMUM_CHUNK_SIZE { + return MINIMUM_CHUNK_SIZE; } - let max_chunks = MAXIMUM_INCREMENTAL_DIGEST_BYTES / D::OutputSize::USIZE; - let chunk_size = (data_size + max_chunks - 1) / max_chunks; - std::cmp::min( - data_size, - std::cmp::max(chunk_size, MINIMUM_INCREMENTAL_CHUNK_SIZE), - ) + if data_size < target_chunk_count * MAXIMUM_CHUNK_SIZE { + return (data_size + target_chunk_count - 1) / target_chunk_count; + } + MAXIMUM_CHUNK_SIZE } impl Incremental { @@ -94,17 +97,22 @@ impl Incremental { None } } + + fn pending_bytes_size(&self) -> usize { + self.chunk_size - self.unused_length + } } impl Validating { - pub fn update(&mut self, bytes: &[u8]) -> Result<(), MacError> { - let mut result = Ok(()); + pub fn update(&mut self, bytes: &[u8]) -> Result { + let mut result = Ok(0); let macs = self.incremental.update(bytes); - // for mac in self.incremental.update(bytes) { + let mut whole_chunks = 0; for mac in macs { match self.expected.last() { Some(expected) if expected == &mac => { + whole_chunks += 1; self.expected.pop(); } _ => { @@ -112,13 +120,15 @@ impl Validating { } } } - result + let validated_bytes = whole_chunks * self.incremental.chunk_size; + result.map(|_| validated_bytes) } - pub fn finalize(self) -> Result<(), MacError> { + pub fn finalize(self) -> Result { + let pending_bytes_size = self.incremental.pending_bytes_size(); let mac = self.incremental.finalize(); match &self.expected[..] { - [expected] if expected == &mac => Ok(()), + [expected] if expected == &mac => Ok(pending_bytes_size), _ => Err(MacError), } } @@ -126,6 +136,7 @@ impl Validating { #[cfg(test)] mod test { + use hex_literal::hex; use hmac::Hmac; use proptest::prelude::*; use rand::distributions::Uniform; @@ -137,8 +148,8 @@ mod test { use super::*; - const TEST_HMAC_KEY_HEX: &str = - "a83481457efecc69ad1342e21d9c0297f71debbf5c9304b4c1b2e433c1a78f98"; + const TEST_HMAC_KEY: &[u8] = + &hex!("a83481457efecc69ad1342e21d9c0297f71debbf5c9304b4c1b2e433c1a78f98"); const TEST_CHUNK_SIZE: usize = 32; @@ -148,18 +159,20 @@ mod test { Incremental::new(hmac, chunk_size) } - fn test_key() -> Vec { - hex::decode(TEST_HMAC_KEY_HEX).expect("Should be able to decode the key from a hex string") + #[test] + #[should_panic] + fn chunk_size_zero() { + new_incremental(&[], 0); } #[test] fn simple_test() { - let key = test_key(); + let key = TEST_HMAC_KEY; let input = "this is a simple test input string which is longer than the chunk"; let bytes = input.as_bytes(); - let expected = hmac_sha256(&key, bytes); - let mut incremental = new_incremental(&key, TEST_CHUNK_SIZE); + let expected = hmac_sha256(key, bytes); + let mut incremental = new_incremental(key, TEST_CHUNK_SIZE); let _ = incremental.update(bytes).collect::>(); let digest = incremental.finalize(); let actual: [u8; 32] = digest.into(); @@ -168,11 +181,11 @@ mod test { #[test] fn final_result_should_be_equal_to_non_incremental_hmac() { - let key = test_key(); + let key = TEST_HMAC_KEY; proptest!(|(input in ".{0,100}")| { let bytes = input.as_bytes(); - let expected = hmac_sha256(&key, bytes); - let mut incremental = new_incremental(&key, TEST_CHUNK_SIZE); + let expected = hmac_sha256(key, bytes); + let mut incremental = new_incremental(key, TEST_CHUNK_SIZE); let _ = incremental.update(bytes).collect::>(); let actual: [u8; 32] = incremental.finalize().into(); assert_eq!(actual, expected); @@ -181,11 +194,11 @@ mod test { #[test] fn incremental_macs_are_valid() { - let key = test_key(); + let key = TEST_HMAC_KEY; proptest!(|(input in ".{50,100}")| { let bytes = input.as_bytes(); - let mut incremental = new_incremental(&key, TEST_CHUNK_SIZE); + let mut incremental = new_incremental(key, TEST_CHUNK_SIZE); // Manually breaking the input in buffer-sized chunks and calculating the HMACs on the // ever-increasing input prefix. @@ -193,7 +206,7 @@ mod test { .chunks(incremental.chunk_size) .scan(Vec::new(), |acc, chunk| { acc.extend(chunk.iter()); - Some(hmac_sha256(&key, acc).to_vec()) + Some(hmac_sha256(key, acc).to_vec()) }) .collect(); @@ -215,11 +228,11 @@ mod test { #[test] fn validating_simple_test() { - let key = test_key(); + let key = TEST_HMAC_KEY; let input = "this is a simple test input string"; let bytes = input.as_bytes(); - let mut incremental = new_incremental(&key, TEST_CHUNK_SIZE); + let mut incremental = new_incremental(key, TEST_CHUNK_SIZE); let mut expected_macs: Vec<_> = incremental.update(bytes).collect(); expected_macs.push(incremental.finalize()); @@ -228,7 +241,7 @@ mod test { { let mut validating = - new_incremental(&key, TEST_CHUNK_SIZE).validating(expected_bytes.clone()); + new_incremental(key, TEST_CHUNK_SIZE).validating(expected_bytes.clone()); validating .update(bytes) .expect("update: validation should succeed"); @@ -243,7 +256,7 @@ mod test { .first_mut() .expect("there must be at least one mac")[0] ^= 0xff; let mut validating = - new_incremental(&key, TEST_CHUNK_SIZE).validating(failing_first_update); + new_incremental(key, TEST_CHUNK_SIZE).validating(failing_first_update); validating.update(bytes).expect_err("MacError"); } @@ -252,16 +265,14 @@ mod test { failing_finalize .last_mut() .expect("there must be at least one mac")[0] ^= 0xff; - let mut validating = - new_incremental(&key, TEST_CHUNK_SIZE).validating(failing_finalize); + let mut validating = new_incremental(key, TEST_CHUNK_SIZE).validating(failing_finalize); validating.update(bytes).expect("update should succeed"); validating.finalize().expect_err("MacError"); } { let missing_last_mac = &expected_bytes[0..expected_bytes.len() - 1]; - let mut validating = - new_incremental(&key, TEST_CHUNK_SIZE).validating(missing_last_mac); + let mut validating = new_incremental(key, TEST_CHUNK_SIZE).validating(missing_last_mac); validating.update(bytes).expect("update should succeed"); validating.finalize().expect_err("MacError"); } @@ -269,20 +280,57 @@ mod test { { let missing_first_mac: Vec<_> = expected_bytes.clone().into_iter().skip(1).collect(); let mut validating = - new_incremental(&key, TEST_CHUNK_SIZE).validating(missing_first_mac); + new_incremental(key, TEST_CHUNK_SIZE).validating(missing_first_mac); validating.update(bytes).expect_err("MacError"); } // To make clippy happy and allow extending the test in the future std::hint::black_box(expected_bytes); } + #[test] + fn validating_returns_right_size() { + let key = TEST_HMAC_KEY; + let input = "this is a simple test input string"; + + let bytes = input.as_bytes(); + let mut incremental = new_incremental(key, TEST_CHUNK_SIZE); + let mut expected_macs: Vec<_> = incremental.update(bytes).collect(); + expected_macs.push(incremental.finalize()); + + let expected_bytes: Vec<[u8; 32]> = + expected_macs.into_iter().map(|mac| mac.into()).collect(); + + let mut validating = new_incremental(key, TEST_CHUNK_SIZE).validating(expected_bytes); + + // Splitting input into chunks of 16 will give us one full incremental chunk + 3 bytes + // authenticated by call to finalize. + let input_chunks = bytes.chunks(16).collect::>(); + assert_eq!(3, input_chunks.len()); + let expected_remainder = bytes.len() - TEST_CHUNK_SIZE; + + for (expected_size, input) in std::iter::zip([0, TEST_CHUNK_SIZE, 0], input_chunks) { + assert_eq!( + expected_size, + validating + .update(input) + .expect("update: validation should succeed") + ); + } + assert_eq!( + expected_remainder, + validating + .finalize() + .expect("finalize: validation should succeed") + ); + } + #[test] fn produce_and_validate() { - let key = test_key(); + let key = TEST_HMAC_KEY; proptest!(|(input in ".{0,100}")| { let bytes = input.as_bytes(); - let mut incremental = new_incremental(&key, TEST_CHUNK_SIZE); + let mut incremental = new_incremental(key, TEST_CHUNK_SIZE); let input_chunks = bytes.random_chunks(incremental.chunk_size*2); let mut produced: Vec<[u8; 32]> = input_chunks.clone() @@ -291,7 +339,7 @@ mod test { .collect(); produced.push(incremental.finalize().into()); - let mut validating = new_incremental(&key, TEST_CHUNK_SIZE).validating(produced); + let mut validating = new_incremental(key, TEST_CHUNK_SIZE).validating(produced); for chunk in input_chunks.clone() { validating.update(chunk).expect("update: validation should succeed"); } @@ -299,45 +347,67 @@ mod test { }); } + const KIBIBYTES: usize = 1024; + const MEBIBYTES: usize = 1024 * KIBIBYTES; + const GIBIBYTES: usize = 1024 * MEBIBYTES; + #[test] - fn chunk_sizes() { + fn chunk_sizes_sha256() { for (data_size, expected) in [ - (1024, 1024), - (10 * 1024, MINIMUM_INCREMENTAL_CHUNK_SIZE), - (100 * 1024, MINIMUM_INCREMENTAL_CHUNK_SIZE), - (1024 * 1024, 65_536), - (10 * 1024 * 1024, 10 * 65_536), - (100 * 1024 * 1024, 100 * 65_536), + (0, MINIMUM_CHUNK_SIZE), + (KIBIBYTES, MINIMUM_CHUNK_SIZE), + (10 * KIBIBYTES, MINIMUM_CHUNK_SIZE), + (100 * KIBIBYTES, MINIMUM_CHUNK_SIZE), + (MEBIBYTES, MINIMUM_CHUNK_SIZE), + (10 * MEBIBYTES, MINIMUM_CHUNK_SIZE), + (20 * MEBIBYTES, 80 * KIBIBYTES), + (100 * MEBIBYTES, 400 * KIBIBYTES), + (200 * MEBIBYTES, 800 * KIBIBYTES), + (256 * MEBIBYTES, MEBIBYTES), + (512 * MEBIBYTES, 2 * MEBIBYTES), + (GIBIBYTES, 2 * MEBIBYTES), + (2 * GIBIBYTES, 2 * MEBIBYTES), ] { - let actual = calculate_chunk_size::(data_size); + let actual = calculate_chunk_size::(data_size); assert_eq!(actual, expected); } } #[test] - fn total_digest_size_is_never_too_big() { - proptest!(|(data_size in 256_usize..1_000_000_000)| { - let chunk_size = calculate_chunk_size::(data_size); - let num_increments = std::cmp::max(1, (data_size + chunk_size - 1) / chunk_size); - let total_digest_size = num_increments * ::OutputSize::USIZE; - assert!(total_digest_size <= MAXIMUM_INCREMENTAL_DIGEST_BYTES) - }) + fn chunk_sizes_sha512() { + for (data_size, expected) in [ + (0, MINIMUM_CHUNK_SIZE), + (KIBIBYTES, MINIMUM_CHUNK_SIZE), + (10 * KIBIBYTES, MINIMUM_CHUNK_SIZE), + (100 * KIBIBYTES, MINIMUM_CHUNK_SIZE), + (MEBIBYTES, MINIMUM_CHUNK_SIZE), + (10 * MEBIBYTES, 80 * KIBIBYTES), + (20 * MEBIBYTES, 160 * KIBIBYTES), + (100 * MEBIBYTES, 800 * KIBIBYTES), + (200 * MEBIBYTES, 1600 * KIBIBYTES), + (256 * MEBIBYTES, 2 * MEBIBYTES), + (512 * MEBIBYTES, 2 * MEBIBYTES), + (GIBIBYTES, 2 * MEBIBYTES), + ] { + let actual = calculate_chunk_size::(data_size); + assert_eq!(actual, expected); + } } #[test] - fn chunk_size_is_never_larger_than_data_size() { - proptest!(|(data_size in 256_usize..1_000_000_000)| { + fn total_digest_size_is_never_too_big() { + fn total_digest_size(data_size: usize) -> usize { let chunk_size = calculate_chunk_size::(data_size); - assert!(chunk_size <= data_size) - }) - } - - #[test] - fn chunk_size_for_empty_input() { - assert_eq!( - MINIMUM_INCREMENTAL_CHUNK_SIZE, - calculate_chunk_size::(0) - ); + let num_chunks = std::cmp::max(1, (data_size + chunk_size - 1) / chunk_size); + num_chunks * ::OutputSize::USIZE + } + let config = ProptestConfig::with_cases(10_000); + proptest!(config, |(data_size in 256..256*MEBIBYTES)| { + assert!(total_digest_size(data_size) <= 8*KIBIBYTES) + }); + proptest!(|(data_size_mib in 256_usize..2048)| { + assert!(total_digest_size(data_size_mib*MEBIBYTES) <= 32*KIBIBYTES) + }); } #[derive(Clone)] diff --git a/rust/protocol/src/lib.rs b/rust/protocol/src/lib.rs index 3c014be414..10cdb1df2d 100644 --- a/rust/protocol/src/lib.rs +++ b/rust/protocol/src/lib.rs @@ -68,6 +68,7 @@ pub use ratchet::{ pub use sealed_sender::{ sealed_sender_decrypt, sealed_sender_decrypt_to_usmc, sealed_sender_encrypt, sealed_sender_encrypt_from_usmc, sealed_sender_multi_recipient_encrypt, + sealed_sender_multi_recipient_encrypt_using_new_ephemeral_key_derivation, sealed_sender_multi_recipient_fan_out, ContentHint, SealedSenderDecryptionResult, SenderCertificate, ServerCertificate, UnidentifiedSenderMessageContent, }; diff --git a/rust/protocol/src/proto/storage.proto b/rust/protocol/src/proto/storage.proto index 18904d7332..f080febc8e 100644 --- a/rust/protocol/src/proto/storage.proto +++ b/rust/protocol/src/proto/storage.proto @@ -33,6 +33,7 @@ message SessionStructure { optional uint32 pre_key_id = 1; int32 signed_pre_key_id = 3; bytes base_key = 2; + uint64 timestamp = 4; } message PendingKyberPreKey { diff --git a/rust/protocol/src/protocol.rs b/rust/protocol/src/protocol.rs index 3845dc65e3..2f16a9a52e 100644 --- a/rust/protocol/src/protocol.rs +++ b/rust/protocol/src/protocol.rs @@ -87,8 +87,7 @@ impl SignalMessage { previous_counter: Some(previous_counter), ciphertext: Some(Vec::::from(ciphertext)), }; - let mut serialized = Vec::new(); - serialized.reserve(1 + message.encoded_len() + Self::MAC_LENGTH); + let mut serialized = Vec::with_capacity(1 + message.encoded_len() + Self::MAC_LENGTH); serialized.push(((message_version & 0xF) << 4) | CIPHERTEXT_MESSAGE_CURRENT_VERSION); message .encode(&mut serialized) @@ -287,8 +286,7 @@ impl PreKeySignalMessage { identity_key: Some(identity_key.serialize().into_vec()), message: Some(Vec::from(message.as_ref())), }; - let mut serialized = Vec::new(); - serialized.reserve(1 + proto_message.encoded_len()); + let mut serialized = Vec::with_capacity(1 + proto_message.encoded_len()); serialized.push(((message_version & 0xF) << 4) | CIPHERTEXT_MESSAGE_CURRENT_VERSION); proto_message .encode(&mut serialized) @@ -464,8 +462,7 @@ impl SenderKeyMessage { ciphertext: Some(ciphertext.to_vec()), }; let proto_message_len = proto_message.encoded_len(); - let mut serialized = Vec::new(); - serialized.reserve(1 + proto_message_len + Self::SIGNATURE_LEN); + let mut serialized = Vec::with_capacity(1 + proto_message_len + Self::SIGNATURE_LEN); serialized.push(((message_version & 0xF) << 4) | SENDERKEY_MESSAGE_CURRENT_VERSION); proto_message .encode(&mut serialized) @@ -603,8 +600,7 @@ impl SenderKeyDistributionMessage { chain_key: Some(chain_key.clone()), signing_key: Some(signing_key.serialize().to_vec()), }; - let mut serialized = Vec::new(); - serialized.reserve(1 + proto_message.encoded_len()); + let mut serialized = Vec::with_capacity(1 + proto_message.encoded_len()); serialized.push(((message_version & 0xF) << 4) | SENDERKEY_MESSAGE_CURRENT_VERSION); proto_message .encode(&mut serialized) diff --git a/rust/protocol/src/ratchet.rs b/rust/protocol/src/ratchet.rs index 92f3a76e06..f3c4ff77cf 100644 --- a/rust/protocol/src/ratchet.rs +++ b/rust/protocol/src/ratchet.rs @@ -98,6 +98,7 @@ pub(crate) fn initialize_alice_session( local_identity, parameters.their_identity_key(), &sending_chain_root_key, + ¶meters.our_base_key_pair().public_key, ) .with_receiver_chain(parameters.their_ratchet_key(), &chain_key) .with_sender_chain(&sending_ratchet_key, &sending_chain_chain_key); @@ -169,6 +170,7 @@ pub(crate) fn initialize_bob_session( local_identity, parameters.their_identity_key(), &root_key, + parameters.their_base_key(), ) .with_sender_chain(parameters.our_ratchet_key_pair(), &chain_key); diff --git a/rust/protocol/src/sealed_sender.rs b/rust/protocol/src/sealed_sender.rs index cce5fe192f..946d0b7f39 100644 --- a/rust/protocol/src/sealed_sender.rs +++ b/rust/protocol/src/sealed_sender.rs @@ -12,8 +12,8 @@ use crate::{ use crate::{crypto, curve, proto, session_cipher}; -use aes_gcm_siv::aead::{AeadInPlace, NewAead}; -use aes_gcm_siv::Aes256GcmSiv; +use aes_gcm_siv::aead::generic_array::typenum::Unsigned; +use aes_gcm_siv::{AeadInPlace, Aes256GcmSiv, KeyInit}; use arrayref::array_ref; use curve25519_dalek::scalar::Scalar; use prost::Message; @@ -23,6 +23,7 @@ use subtle::ConstantTimeEq; use proto::sealed_sender::unidentified_sender_message::message::Type as ProtoMessageType; use std::convert::{TryFrom, TryInto}; +use std::time::SystemTime; #[derive(Debug, Clone)] pub struct ServerCertificate { @@ -779,9 +780,10 @@ pub async fn sealed_sender_encrypt( ptext: &[u8], session_store: &mut dyn SessionStore, identity_store: &mut dyn IdentityKeyStore, + now: SystemTime, rng: &mut R, ) -> Result> { - let message = message_encrypt(ptext, destination, session_store, identity_store).await?; + let message = message_encrypt(ptext, destination, session_store, identity_store, now).await?; let usmc = UnidentifiedSenderMessageContent::new( message.message_type(), sender_cert.clone(), @@ -845,7 +847,7 @@ pub async fn sealed_sender_encrypt( pub async fn sealed_sender_encrypt_from_usmc( destination: &ProtocolAddress, usmc: &UnidentifiedSenderMessageContent, - identity_store: &mut dyn IdentityKeyStore, + identity_store: &dyn IdentityKeyStore, rng: &mut R, ) -> Result> { let our_identity = identity_store.get_identity_key_pair().await?; @@ -900,34 +902,61 @@ mod sealed_sender_v2 { use super::*; // Static byte strings used as part of a MAC in HKDF. - const LABEL_R: &[u8] = b"Sealed Sender v2: r"; + const LABEL_R: &[u8] = b"Sealed Sender v2: r (2023-08)"; + const LABEL_R_LEGACY: &[u8] = b"Sealed Sender v2: r"; const LABEL_K: &[u8] = b"Sealed Sender v2: K"; const LABEL_DH: &[u8] = b"Sealed Sender v2: DH"; const LABEL_DH_S: &[u8] = b"Sealed Sender v2: DH-sender"; pub const MESSAGE_KEY_LEN: usize = 32; + pub const CIPHER_KEY_LEN: usize = + ::KeySize::USIZE; pub const AUTH_TAG_LEN: usize = 16; + // Change this to false after all clients have receive support. + pub const USE_LEGACY_EPHEMERAL_KEY_DERIVATION_FOR_ENCRYPT: bool = true; + /// An asymmetric and a symmetric cipher key. pub(super) struct DerivedKeys { - /// Asymmetric key pair. - pub(super) e: KeyPair, - /// Symmetric key used to instantiate [`Aes256GcmSiv::new_from_slice`]. - pub(super) k: [u8; MESSAGE_KEY_LEN], + kdf: hkdf::Hkdf, } impl DerivedKeys { - /// Derive a set of ephemeral keys from a slice of random bytes `m`. - pub(super) fn calculate(m: &[u8]) -> DerivedKeys { - let kdf = hkdf::Hkdf::::new(None, m); + /// Initialize from a slice of random bytes `m`. + pub(super) fn new(m: &[u8]) -> DerivedKeys { + Self { + kdf: hkdf::Hkdf::::new(None, m), + } + } + + /// Derive the ephemeral asymmetric keys. + pub(super) fn derive_e(&self) -> KeyPair { + let mut r = [0; 32]; + self.kdf + .expand(LABEL_R, &mut r) + .expect("valid output length"); + let e = PrivateKey::try_from(&r[..]).expect("valid PrivateKey"); + KeyPair::try_from(e).expect("can derive public key") + } + + /// Derive the ephemeral asymmetric keys using the legacy implementation. + pub(super) fn derive_e_legacy(&self) -> KeyPair { let mut r = [0; 64]; - kdf.expand(LABEL_R, &mut r).expect("valid output length"); - let mut k = [0; MESSAGE_KEY_LEN]; - kdf.expand(LABEL_K, &mut k).expect("valid output length"); + self.kdf + .expand(LABEL_R_LEGACY, &mut r) + .expect("valid output length"); let e_raw = Scalar::from_bytes_mod_order_wide(&r); let e = PrivateKey::try_from(&e_raw.as_bytes()[..]).expect("valid PrivateKey"); - let e = KeyPair::try_from(e).expect("can derive public key"); - DerivedKeys { e, k } + KeyPair::try_from(e).expect("can derive public key") + } + + /// Derive the symmetric cipher key. + pub(super) fn derive_k(&self) -> [u8; CIPHER_KEY_LEN] { + let mut k = [0; CIPHER_KEY_LEN]; + self.kdf + .expand(LABEL_K, &mut k) + .expect("valid output length"); + k } } @@ -1017,29 +1046,25 @@ mod sealed_sender_v2 { // Generate random bytes used for our multi-recipient encoding scheme. let m: [u8; MESSAGE_KEY_LEN] = rand::thread_rng().gen(); // Derive an ephemeral key pair from those random bytes. - let ephemeral_keys = DerivedKeys::calculate(&m); - let ephemeral_public_key = ephemeral_keys.e.public_key; + let ephemeral_keys = DerivedKeys::new(&m); + let e = ephemeral_keys.derive_e(); // Encrypt the ephemeral key pair. - let sender_c_0: [u8; MESSAGE_KEY_LEN] = apply_agreement_xor( - &ephemeral_keys.e, - recipient_identity.public_key(), - Direction::Sending, - &m, - )?; + let sender_c_0: [u8; MESSAGE_KEY_LEN] = + apply_agreement_xor(&e, recipient_identity.public_key(), Direction::Sending, &m)?; // Compute an authentication tag for the encrypted key pair. let sender_at_0 = compute_authentication_tag( &sender_identity, recipient_identity.identity_key(), Direction::Sending, - &ephemeral_public_key, + &e.public_key, &sender_c_0, )?; // The message recipient calculates the original random bytes and authenticates the result. let recv_m = apply_agreement_xor( &recipient_identity.into(), - &ephemeral_public_key, + &e.public_key, Direction::Receiving, &sender_c_0, )?; @@ -1049,7 +1074,7 @@ mod sealed_sender_v2 { &recipient_identity, sender_identity.identity_key(), Direction::Receiving, - &ephemeral_public_key, + &e.public_key, &sender_c_0, )?; assert_eq!(&recv_at_0, &sender_at_0); @@ -1076,7 +1101,7 @@ mod sealed_sender_v2 { /// 1. it requires a [`SessionRecord`] to exist already for the recipient, i.e. that a Double /// Ratchet message chain has previously been established in the [`SessionStore`] via /// [`process_prekey_bundle`][crate::process_prekey_bundle] after an initial -/// [`PreKeySignalMessage`][crate::PreKeySignalMessage] is received. +/// [`PreKeySignalMessage`] is received. /// 2. it ferries a lot of additional information in its encoding which makes the resulting message /// bulkier than the message produced by [Sealed Sender v1]. For sending, this will generally /// still be more compact than sending the same message N times, but on the receiver side the @@ -1109,7 +1134,7 @@ mod sealed_sender_v2 { ///```text /// ENCRYPT(message, R_i): /// M = Random(32) -/// r = KDF(label_r, M, len=64) +/// r = KDF(label_r, M, len=32) /// K = KDF(label_K, M, len=32) /// E = DeriveKeyPair(r) /// for i in num_recipients: @@ -1120,7 +1145,7 @@ mod sealed_sender_v2 { /// /// DECRYPT(E.public, C, AT, ciphertext): /// M = KDF(label_DH, DH(E, R) || E.public || R.public, len=32) xor C -/// r = KDF(label_r, M, len=64) +/// r = KDF(label_r, M, len=32) /// K = KDF(label_K, M, len=32) /// E' = DeriveKeyPair(r) /// if E.public != E'.public: @@ -1210,8 +1235,52 @@ pub async fn sealed_sender_multi_recipient_encrypt( destinations: &[&ProtocolAddress], destination_sessions: &[&SessionRecord], usmc: &UnidentifiedSenderMessageContent, - identity_store: &mut dyn IdentityKeyStore, + identity_store: &dyn IdentityKeyStore, + rng: &mut R, +) -> Result> { + sealed_sender_multi_recipient_encrypt_impl( + destinations, + destination_sessions, + usmc, + identity_store, + rng, + sealed_sender_v2::USE_LEGACY_EPHEMERAL_KEY_DERIVATION_FOR_ENCRYPT, + ) + .await +} + +/// For testing only. +pub async fn sealed_sender_multi_recipient_encrypt_using_new_ephemeral_key_derivation< + R: Rng + CryptoRng, +>( + destinations: &[&ProtocolAddress], + destination_sessions: &[&SessionRecord], + usmc: &UnidentifiedSenderMessageContent, + identity_store: &dyn IdentityKeyStore, + rng: &mut R, +) -> Result> { + // When this is flipped, we should use this function to test the legacy encryption instead. + static_assertions::const_assert!( + sealed_sender_v2::USE_LEGACY_EPHEMERAL_KEY_DERIVATION_FOR_ENCRYPT, + ); + sealed_sender_multi_recipient_encrypt_impl( + destinations, + destination_sessions, + usmc, + identity_store, + rng, + false, + ) + .await +} + +async fn sealed_sender_multi_recipient_encrypt_impl( + destinations: &[&ProtocolAddress], + destination_sessions: &[&SessionRecord], + usmc: &UnidentifiedSenderMessageContent, + identity_store: &dyn IdentityKeyStore, rng: &mut R, + should_use_legacy_ephemeral_key_derivation: bool, ) -> Result> { if destinations.len() != destination_sessions.len() { return Err(SignalProtocolError::InvalidArgument( @@ -1220,21 +1289,24 @@ pub async fn sealed_sender_multi_recipient_encrypt( } let m: [u8; sealed_sender_v2::MESSAGE_KEY_LEN] = rng.gen(); - let keys = sealed_sender_v2::DerivedKeys::calculate(&m); - let e_pub = &keys.e.public_key; + let keys = sealed_sender_v2::DerivedKeys::new(&m); + let e = if should_use_legacy_ephemeral_key_derivation { + keys.derive_e_legacy() + } else { + keys.derive_e() + }; + let e_pub = &e.public_key; let ciphertext = { let mut ciphertext = usmc.serialized()?.to_vec(); - let symmetric_authentication_tag = Aes256GcmSiv::new_from_slice(&keys.k) - .and_then(|aes_gcm_siv| { - aes_gcm_siv.encrypt_in_place_detached( - // There's no nonce because the key is already one-use. - &aes_gcm_siv::Nonce::default(), - // And there's no associated data. - &[], - &mut ciphertext, - ) - }) + let symmetric_authentication_tag = Aes256GcmSiv::new(&keys.derive_k().into()) + .encrypt_in_place_detached( + // There's no nonce because the key is already one-use. + &aes_gcm_siv::Nonce::default(), + // And there's no associated data. + &[], + &mut ciphertext, + ) .expect("AES-GCM-SIV encryption should not fail with a just-computed key"); // AES-GCM-SIV expects the authentication tag to be at the end of the ciphertext // when decrypting. @@ -1314,7 +1386,7 @@ pub async fn sealed_sender_multi_recipient_encrypt( ) } else { let c_i = sealed_sender_v2::apply_agreement_xor( - &keys.e, + &e, their_identity.public_key(), Direction::Sending, &m, @@ -1411,7 +1483,7 @@ pub fn sealed_sender_multi_recipient_fan_out(data: &[u8]) -> Result> /// before decrypting the underlying message. pub async fn sealed_sender_decrypt_to_usmc( ciphertext: &[u8], - identity_store: &mut dyn IdentityKeyStore, + identity_store: &dyn IdentityKeyStore, ) -> Result { let our_identity = identity_store.get_identity_key_pair().await?; @@ -1504,24 +1576,33 @@ pub async fn sealed_sender_decrypt_to_usmc( &encrypted_message_key, )?; - let keys = sealed_sender_v2::DerivedKeys::calculate(&m); - if !bool::from(keys.e.public_key.ct_eq(&ephemeral_public)) { + let keys = sealed_sender_v2::DerivedKeys::new(&m); + // It is okay that this is not constant time; the only information revealed is whether + // the sender is using the new or old derivation for the ephemeral key, combined with + // which key the receiver tried first. + let mut derive_first_key: fn(_) -> _ = sealed_sender_v2::DerivedKeys::derive_e; + let mut derive_second_key: fn(_) -> _ = sealed_sender_v2::DerivedKeys::derive_e_legacy; + if sealed_sender_v2::USE_LEGACY_EPHEMERAL_KEY_DERIVATION_FOR_ENCRYPT { + std::mem::swap(&mut derive_first_key, &mut derive_second_key); + } + + if !bool::from(derive_first_key(&keys).public_key.ct_eq(&ephemeral_public)) + && !bool::from(derive_second_key(&keys).public_key.ct_eq(&ephemeral_public)) + { return Err(SignalProtocolError::InvalidSealedSenderMessage( "derived ephemeral key did not match key provided in message".to_string(), )); } let mut message_bytes = encrypted_message.into_vec(); - Aes256GcmSiv::new_from_slice(&keys.k) - .and_then(|aes_gcm_siv| { - aes_gcm_siv.decrypt_in_place( - // There's no nonce because the key is already one-use. - &aes_gcm_siv::Nonce::default(), - // And there's no associated data. - &[], - &mut message_bytes, - ) - }) + Aes256GcmSiv::new(&keys.derive_k().into()) + .decrypt_in_place( + // There's no nonce because the key is already one-use. + &aes_gcm_siv::Nonce::default(), + // And there's no associated data. + &[], + &mut message_bytes, + ) .map_err(|err| { SignalProtocolError::InvalidSealedSenderMessage(format!( "failed to decrypt inner message: {}", @@ -1593,7 +1674,7 @@ pub async fn sealed_sender_decrypt( identity_store: &mut dyn IdentityKeyStore, session_store: &mut dyn SessionStore, pre_key_store: &mut dyn PreKeyStore, - signed_pre_key_store: &mut dyn SignedPreKeyStore, + signed_pre_key_store: &dyn SignedPreKeyStore, kyber_pre_key_store: &mut dyn KyberPreKeyStore, ) -> Result { let usmc = sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?; diff --git a/rust/protocol/src/session.rs b/rust/protocol/src/session.rs index 323287c11b..7c55b25aba 100644 --- a/rust/protocol/src/session.rs +++ b/rust/protocol/src/session.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::time::SystemTime; + use crate::{ kem, Direction, IdentityKeyStore, KeyPair, KyberPreKeyId, KyberPreKeyStore, PreKeyBundle, PreKeyId, PreKeySignalMessage, PreKeyStore, ProtocolAddress, Result, SessionRecord, @@ -34,9 +36,9 @@ pub async fn process_prekey( remote_address: &ProtocolAddress, session_record: &mut SessionRecord, identity_store: &mut dyn IdentityKeyStore, - pre_key_store: &mut dyn PreKeyStore, - signed_prekey_store: &mut dyn SignedPreKeyStore, - kyber_prekey_store: &mut dyn KyberPreKeyStore, + pre_key_store: &dyn PreKeyStore, + signed_prekey_store: &dyn SignedPreKeyStore, + kyber_prekey_store: &dyn KyberPreKeyStore, ) -> Result { let their_identity_key = message.identity_key(); @@ -71,10 +73,10 @@ async fn process_prekey_impl( message: &PreKeySignalMessage, remote_address: &ProtocolAddress, session_record: &mut SessionRecord, - signed_prekey_store: &mut dyn SignedPreKeyStore, - kyber_prekey_store: &mut dyn KyberPreKeyStore, - pre_key_store: &mut dyn PreKeyStore, - identity_store: &mut dyn IdentityKeyStore, + signed_prekey_store: &dyn SignedPreKeyStore, + kyber_prekey_store: &dyn KyberPreKeyStore, + pre_key_store: &dyn PreKeyStore, + identity_store: &dyn IdentityKeyStore, ) -> Result { if session_record.has_session_state( message.message_version() as u32, @@ -130,7 +132,6 @@ async fn process_prekey_impl( new_session.set_local_registration_id(identity_store.get_local_registration_id().await?); new_session.set_remote_registration_id(message.registration_id()); - new_session.set_alice_base_key(&message.base_key().serialize()); session_record.promote_state(new_session); @@ -146,6 +147,7 @@ pub async fn process_prekey_bundle( session_store: &mut dyn SessionStore, identity_store: &mut dyn IdentityKeyStore, bundle: &PreKeyBundle, + now: SystemTime, mut csprng: &mut R, ) -> Result<()> { let their_identity_key = bundle.identity_key()?; @@ -216,6 +218,7 @@ pub async fn process_prekey_bundle( their_one_time_prekey_id, bundle.signed_pre_key_id()?, &our_base_key_pair.public_key, + now, ); if let Some(kyber_pre_key_id) = bundle.kyber_pre_key_id()? { @@ -224,7 +227,6 @@ pub async fn process_prekey_bundle( session.set_local_registration_id(identity_store.get_local_registration_id().await?); session.set_remote_registration_id(bundle.registration_id()?); - session.set_alice_base_key(&our_base_key_pair.public_key.serialize()); identity_store .save_identity(remote_address, their_identity_key) diff --git a/rust/protocol/src/session_cipher.rs b/rust/protocol/src/session_cipher.rs index 9f53859f24..81c8e5933a 100644 --- a/rust/protocol/src/session_cipher.rs +++ b/rust/protocol/src/session_cipher.rs @@ -3,9 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0-only // +use std::time::SystemTime; + use rand::{CryptoRng, Rng}; -use crate::consts::MAX_FORWARD_JUMPS; +use crate::consts::{MAX_FORWARD_JUMPS, MAX_UNACKNOWLEDGED_SESSION_AGE}; use crate::ratchet::{ChainKey, MessageKeys}; use crate::state::{InvalidSessionError, SessionState}; use crate::{ @@ -19,6 +21,7 @@ pub async fn message_encrypt( remote_address: &ProtocolAddress, session_store: &mut dyn SessionStore, identity_store: &mut dyn IdentityKeyStore, + now: SystemTime, ) -> Result { let mut session_record = session_store .load_session(remote_address) @@ -52,6 +55,19 @@ pub async fn message_encrypt( })?; let message = if let Some(items) = session_state.unacknowledged_pre_key_message_items()? { + if items.timestamp() + MAX_UNACKNOWLEDGED_SESSION_AGE < now { + log::warn!( + "stale unacknowledged session for {} (created at {})", + remote_address, + items + .timestamp() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + ); + return Err(SignalProtocolError::SessionNotFound(remote_address.clone())); + } + let local_registration_id = session_state.local_registration_id(); log::info!( @@ -139,7 +155,7 @@ pub async fn message_decrypt( session_store: &mut dyn SessionStore, identity_store: &mut dyn IdentityKeyStore, pre_key_store: &mut dyn PreKeyStore, - signed_pre_key_store: &mut dyn SignedPreKeyStore, + signed_pre_key_store: &dyn SignedPreKeyStore, kyber_pre_key_store: &mut dyn KyberPreKeyStore, csprng: &mut R, ) -> Result> { @@ -174,7 +190,7 @@ pub async fn message_decrypt_prekey( session_store: &mut dyn SessionStore, identity_store: &mut dyn IdentityKeyStore, pre_key_store: &mut dyn PreKeyStore, - signed_pre_key_store: &mut dyn SignedPreKeyStore, + signed_pre_key_store: &dyn SignedPreKeyStore, kyber_pre_key_store: &mut dyn KyberPreKeyStore, csprng: &mut R, ) -> Result> { @@ -547,12 +563,13 @@ fn decrypt_message_with_state( remote_address: &ProtocolAddress, csprng: &mut R, ) -> Result> { - if !state.has_sender_chain()? { - return Err(SignalProtocolError::InvalidMessage( + // Check for a completely empty or invalid session state before we do anything else. + let _ = state.root_key().map_err(|_| { + SignalProtocolError::InvalidMessage( original_message_type, "No session available to decrypt", - )); - } + ) + })?; let ciphertext_version = ciphertext.message_version() as u32; if ciphertext_version != state.session_version()? { diff --git a/rust/protocol/src/state/session.rs b/rust/protocol/src/state/session.rs index 84b4e439cf..c733c54f46 100644 --- a/rust/protocol/src/state/session.rs +++ b/rust/protocol/src/state/session.rs @@ -5,6 +5,7 @@ use std::convert::TryInto; use std::result::Result; +use std::time::{Duration, SystemTime}; use prost::Message; use subtle::ConstantTimeEq; @@ -39,6 +40,7 @@ pub(crate) struct UnacknowledgedPreKeyMessageItems<'a> { base_key: PublicKey, kyber_pre_key_id: Option, kyber_ciphertext: Option<&'a [u8]>, + timestamp: SystemTime, } impl<'a> UnacknowledgedPreKeyMessageItems<'a> { @@ -47,6 +49,7 @@ impl<'a> UnacknowledgedPreKeyMessageItems<'a> { signed_pre_key_id: SignedPreKeyId, base_key: PublicKey, pending_kyber_pre_key: Option<&'a session_structure::PendingKyberPreKey>, + timestamp: SystemTime, ) -> Self { let (kyber_pre_key_id, kyber_ciphertext) = pending_kyber_pre_key .map(|pending| (pending.pre_key_id.into(), pending.ciphertext.as_slice())) @@ -57,6 +60,7 @@ impl<'a> UnacknowledgedPreKeyMessageItems<'a> { base_key, kyber_pre_key_id, kyber_ciphertext, + timestamp, } } @@ -79,6 +83,10 @@ impl<'a> UnacknowledgedPreKeyMessageItems<'a> { pub(crate) fn kyber_ciphertext(&self) -> Option<&'a [u8]> { self.kyber_ciphertext } + + pub(crate) fn timestamp(&self) -> SystemTime { + self.timestamp + } } #[derive(Clone, Debug)] @@ -96,12 +104,13 @@ impl SessionState { our_identity: &IdentityKey, their_identity: &IdentityKey, root_key: &RootKey, + alice_base_key: &PublicKey, ) -> Self { Self { session: SessionStructure { session_version: version as u32, - local_identity_public: our_identity.public_key().serialize().to_vec(), - remote_identity_public: their_identity.serialize().to_vec(), + local_identity_public: our_identity.public_key().serialize().into_vec(), + remote_identity_public: their_identity.serialize().into_vec(), root_key: root_key.key().to_vec(), previous_counter: 0, sender_chain: None, @@ -110,7 +119,7 @@ impl SessionState { pending_kyber_pre_key: None, remote_registration_id: 0, local_registration_id: 0, - alice_base_key: vec![], + alice_base_key: alice_base_key.serialize().into_vec(), }, } } @@ -120,11 +129,6 @@ impl SessionState { &self.session.alice_base_key } - pub(crate) fn set_alice_base_key(&mut self, key: &[u8]) { - // Should we check the length? - self.session.alice_base_key = key.to_vec(); - } - pub(crate) fn session_version(&self) -> Result { match self.session.session_version { 0 => Ok(2), @@ -208,8 +212,18 @@ impl SessionState { } } - pub fn has_sender_chain(&self) -> Result { - Ok(self.session.sender_chain.is_some()) + pub fn has_usable_sender_chain(&self, now: SystemTime) -> Result { + if self.session.sender_chain.is_none() { + return Ok(false); + } + if let Some(pending_pre_key) = &self.session.pending_pre_key { + let creation_timestamp = + SystemTime::UNIX_EPOCH + Duration::from_secs(pending_pre_key.timestamp); + if creation_timestamp + consts::MAX_UNACKNOWLEDGED_SESSION_AGE < now { + return Ok(false); + } + } + Ok(true) } pub(crate) fn all_receiver_chain_logging_info(&self) -> Vec<(Vec, Option)> { @@ -449,12 +463,17 @@ impl SessionState { pre_key_id: Option, signed_ec_pre_key_id: SignedPreKeyId, base_key: &PublicKey, + now: SystemTime, ) { let signed_ec_pre_key_id: u32 = signed_ec_pre_key_id.into(); let pending = session_structure::PendingPreKey { pre_key_id: pre_key_id.map(PreKeyId::into), signed_pre_key_id: signed_ec_pre_key_id as i32, base_key: base_key.serialize().to_vec(), + timestamp: now + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(), }; self.session.pending_pre_key = Some(pending); } @@ -472,7 +491,7 @@ impl SessionState { &mut self, signed_kyber_pre_key_id: KyberPreKeyId, ) { - let mut pending = self + let pending = self .session .pending_kyber_pre_key .as_mut() @@ -490,6 +509,7 @@ impl SessionState { PublicKey::deserialize(&pending_pre_key.base_key) .map_err(|_| InvalidSessionError("invalid pending PreKey message base key"))?, self.session.pending_kyber_pre_key.as_ref(), + SystemTime::UNIX_EPOCH + Duration::from_secs(pending_pre_key.timestamp), ))) } else { Ok(None) @@ -573,17 +593,6 @@ impl SessionRecord { }) } - pub fn from_single_session_state(bytes: &[u8]) -> Result { - let session = SessionState::from_session_structure( - SessionStructure::decode(bytes) - .map_err(|_| InvalidSessionError("failed to decode session state protobuf"))?, - ); - Ok(Self { - current_session: Some(session), - previous_sessions: Vec::new(), - }) - } - pub(crate) fn has_session_state( &self, version: u32, @@ -611,10 +620,6 @@ impl SessionRecord { Ok(false) } - pub fn has_current_session_state(&self) -> bool { - self.current_session.is_some() - } - pub(crate) fn session_state(&self) -> Option<&SessionState> { self.current_session.as_ref() } @@ -734,9 +739,9 @@ impl SessionRecord { .remote_identity_key_bytes()?) } - pub fn has_sender_chain(&self) -> Result { + pub fn has_usable_sender_chain(&self, now: SystemTime) -> Result { match &self.current_session { - Some(session) => Ok(session.has_sender_chain()?), + Some(session) => Ok(session.has_usable_sender_chain(now)?), None => Ok(false), } } diff --git a/rust/protocol/tests/groups.rs b/rust/protocol/tests/groups.rs index 3b1342953e..ebb0d39f5d 100644 --- a/rust/protocol/tests/groups.rs +++ b/rust/protocol/tests/groups.rs @@ -11,6 +11,7 @@ use rand::rngs::OsRng; use rand::seq::SliceRandom; use rand::Rng; use std::convert::TryFrom; +use std::time::SystemTime; use support::*; use uuid::Uuid; @@ -174,6 +175,7 @@ fn group_sealed_sender() -> Result<(), SignalProtocolError> { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -183,6 +185,7 @@ fn group_sealed_sender() -> Result<(), SignalProtocolError> { &mut alice_store.session_store, &mut alice_store.identity_store, &carol_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -258,7 +261,7 @@ fn group_sealed_sender() -> Result<(), SignalProtocolError> { .session_store .load_existing_sessions(&recipients)?, &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut csprng, ) .await?; @@ -266,8 +269,7 @@ fn group_sealed_sender() -> Result<(), SignalProtocolError> { let [bob_ctext, carol_ctext] = <[_; 2]>::try_from(sealed_sender_multi_recipient_fan_out(&alice_ctext)?).unwrap(); - let bob_usmc = - sealed_sender_decrypt_to_usmc(&bob_ctext, &mut bob_store.identity_store).await?; + let bob_usmc = sealed_sender_decrypt_to_usmc(&bob_ctext, &bob_store.identity_store).await?; assert_eq!(bob_usmc.sender()?.sender_uuid()?, alice_uuid); assert_eq!(bob_usmc.sender()?.sender_e164()?, Some(alice_e164.as_ref())); @@ -284,7 +286,7 @@ fn group_sealed_sender() -> Result<(), SignalProtocolError> { ); let carol_usmc = - sealed_sender_decrypt_to_usmc(&carol_ctext, &mut carol_store.identity_store).await?; + sealed_sender_decrypt_to_usmc(&carol_ctext, &carol_store.identity_store).await?; assert_eq!(carol_usmc.serialized()?, bob_usmc.serialized()?); diff --git a/rust/protocol/tests/ratchet.rs b/rust/protocol/tests/ratchet.rs index f033f0ad8f..ecc69457da 100644 --- a/rust/protocol/tests/ratchet.rs +++ b/rust/protocol/tests/ratchet.rs @@ -2,44 +2,38 @@ // Copyright 2020 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // -mod support; +use hex_literal::hex; use libsignal_protocol::*; + +mod support; use support::*; #[test] fn test_ratcheting_session_as_bob() -> Result<(), SignalProtocolError> { let bob_ephemeral_public = - hex::decode("052cb49776b8770205745a3a6e24f579cdb4ba7a89041005928ebbadc9c05ad458") - .expect("valid hex"); + hex!("052cb49776b8770205745a3a6e24f579cdb4ba7a89041005928ebbadc9c05ad458"); let bob_ephemeral_private = - hex::decode("a1cab48f7c893fafa9880a28c3b4999d28d6329562d27a4ea4e22e9ff1bdd65a") - .expect("valid hex"); + hex!("a1cab48f7c893fafa9880a28c3b4999d28d6329562d27a4ea4e22e9ff1bdd65a"); let bob_identity_public = - hex::decode("05f1f43874f6966956c2dd473f8fa15adeb71d1cb991b2341692324cefb1c5e626") - .expect("valid hex"); + hex!("05f1f43874f6966956c2dd473f8fa15adeb71d1cb991b2341692324cefb1c5e626"); let bob_identity_private = - hex::decode("4875cc69ddf8ea0719ec947d61081135868d5fd801f02c0225e516df2156605e") - .expect("valid hex"); + hex!("4875cc69ddf8ea0719ec947d61081135868d5fd801f02c0225e516df2156605e"); let alice_base_public = - hex::decode("05472d1fb1a9862c3af6beaca8920277e2b26f4a79213ec7c906aeb35e03cf8950") - .expect("valid hex"); + hex!("05472d1fb1a9862c3af6beaca8920277e2b26f4a79213ec7c906aeb35e03cf8950"); let alice_identity_public = - hex::decode("05b4a8455660ada65b401007f615e654041746432e3339c6875149bceefcb42b4a") - .expect("valid hex"); + hex!("05b4a8455660ada65b401007f615e654041746432e3339c6875149bceefcb42b4a"); let bob_signed_prekey_public = - hex::decode("05ac248a8f263be6863576eb0362e28c828f0107a3379d34bab1586bf8c770cd67") - .expect("valid hex"); + hex!("05ac248a8f263be6863576eb0362e28c828f0107a3379d34bab1586bf8c770cd67"); let bob_signed_prekey_private = - hex::decode("583900131fb727998b7803fe6ac22cc591f342e4e42a8c8d5d78194209b8d253") - .expect("valid hex"); + hex!("583900131fb727998b7803fe6ac22cc591f342e4e42a8c8d5d78194209b8d253"); let expected_sender_chain = "9797caca53c989bbe229a40ca7727010eb2604fc14945d77958a0aeda088b44d"; @@ -98,32 +92,25 @@ fn test_ratcheting_session_as_bob() -> Result<(), SignalProtocolError> { #[test] fn test_ratcheting_session_as_alice() -> Result<(), SignalProtocolError> { let bob_ephemeral_public = - hex::decode("052cb49776b8770205745a3a6e24f579cdb4ba7a89041005928ebbadc9c05ad458") - .expect("valid hex"); + hex!("052cb49776b8770205745a3a6e24f579cdb4ba7a89041005928ebbadc9c05ad458"); let bob_identity_public = - hex::decode("05f1f43874f6966956c2dd473f8fa15adeb71d1cb991b2341692324cefb1c5e626") - .expect("valid hex"); + hex!("05f1f43874f6966956c2dd473f8fa15adeb71d1cb991b2341692324cefb1c5e626"); let alice_base_public = - hex::decode("05472d1fb1a9862c3af6beaca8920277e2b26f4a79213ec7c906aeb35e03cf8950") - .expect("valid hex"); + hex!("05472d1fb1a9862c3af6beaca8920277e2b26f4a79213ec7c906aeb35e03cf8950"); let alice_base_private = - hex::decode("11ae7c64d1e61cd596b76a0db5012673391cae66edbfcf073b4da80516a47449") - .expect("valid hex"); + hex!("11ae7c64d1e61cd596b76a0db5012673391cae66edbfcf073b4da80516a47449"); let bob_signed_prekey_public = - hex::decode("05ac248a8f263be6863576eb0362e28c828f0107a3379d34bab1586bf8c770cd67") - .expect("valid hex"); + hex!("05ac248a8f263be6863576eb0362e28c828f0107a3379d34bab1586bf8c770cd67"); let alice_identity_public = - hex::decode("05b4a8455660ada65b401007f615e654041746432e3339c6875149bceefcb42b4a") - .expect("valid hex"); + hex!("05b4a8455660ada65b401007f615e654041746432e3339c6875149bceefcb42b4a"); let alice_identity_private = - hex::decode("9040f0d4e09cf38f6dc7c13779c908c015a1da4fa78737a080eb0a6f4f5f8f58") - .expect("valid hex"); + hex!("9040f0d4e09cf38f6dc7c13779c908c015a1da4fa78737a080eb0a6f4f5f8f58"); // This differs from the Java test and needs investigation let expected_receiver_chain = diff --git a/rust/protocol/tests/sealed_sender.rs b/rust/protocol/tests/sealed_sender.rs index 9956585d91..7b89b02100 100644 --- a/rust/protocol/tests/sealed_sender.rs +++ b/rust/protocol/tests/sealed_sender.rs @@ -10,6 +10,7 @@ use futures_util::FutureExt; use libsignal_protocol::*; use rand::rngs::OsRng; use std::convert::TryFrom; +use std::time::SystemTime; use uuid::Uuid; #[test] @@ -160,6 +161,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .await?; @@ -190,6 +192,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &alice_ptext, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), &mut rng, ) .await?; @@ -204,7 +207,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &mut bob_store.identity_store, &mut bob_store.session_store, &mut bob_store.pre_key_store, - &mut bob_store.signed_pre_key_store, + &bob_store.signed_pre_key_store, &mut bob_store.kyber_pre_key_store, ) .await?; @@ -222,6 +225,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &alice_ptext, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), &mut rng, ) .await?; @@ -236,7 +240,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &mut bob_store.identity_store, &mut bob_store.session_store, &mut bob_store.pre_key_store, - &mut bob_store.signed_pre_key_store, + &bob_store.signed_pre_key_store, &mut bob_store.kyber_pre_key_store, ) .await; @@ -259,6 +263,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &alice_ptext, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), &mut rng, ) .await?; @@ -275,7 +280,7 @@ fn test_sealed_sender() -> Result<(), SignalProtocolError> { &mut bob_store.identity_store, &mut bob_store.session_store, &mut bob_store.pre_key_store, - &mut bob_store.signed_pre_key_store, + &bob_store.signed_pre_key_store, &mut bob_store.kyber_pre_key_store, ) .await; @@ -327,6 +332,7 @@ fn test_sender_key_in_sealed_sender() -> Result<(), SignalProtocolError> { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .await?; @@ -384,13 +390,13 @@ fn test_sender_key_in_sealed_sender() -> Result<(), SignalProtocolError> { let alice_ctext = sealed_sender_encrypt_from_usmc( &bob_uuid_address, &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await?; let bob_usmc = - sealed_sender_decrypt_to_usmc(&alice_ctext, &mut bob_store.identity_store).await?; + sealed_sender_decrypt_to_usmc(&alice_ctext, &bob_store.identity_store).await?; assert!(matches!( bob_usmc.msg_type()?, @@ -439,6 +445,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .await?; @@ -468,6 +475,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &bob_uuid_address, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), ) .await?; @@ -486,7 +494,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { .session_store .load_existing_sessions(&recipients)?, &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await?; @@ -504,7 +512,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &mut bob_store.identity_store, &mut bob_store.session_store, &mut bob_store.pre_key_store, - &mut bob_store.signed_pre_key_store, + &bob_store.signed_pre_key_store, &mut bob_store.kyber_pre_key_store, ) .await?; @@ -520,6 +528,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &bob_uuid_address, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), ) .await?; @@ -538,7 +547,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { .session_store .load_existing_sessions(&recipients)?, &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await?; @@ -556,7 +565,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &mut bob_store.identity_store, &mut bob_store.session_store, &mut bob_store.pre_key_store, - &mut bob_store.signed_pre_key_store, + &bob_store.signed_pre_key_store, &mut bob_store.kyber_pre_key_store, ) .await; @@ -578,6 +587,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &bob_uuid_address, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), ) .await?; @@ -596,7 +606,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { .session_store .load_existing_sessions(&recipients)?, &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await?; @@ -616,7 +626,7 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { &mut bob_store.identity_store, &mut bob_store.session_store, &mut bob_store.pre_key_store, - &mut bob_store.signed_pre_key_store, + &bob_store.signed_pre_key_store, &mut bob_store.kyber_pre_key_store, ) .await; @@ -637,6 +647,117 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> { .expect("sync") } +#[test] +fn test_sealed_sender_multi_recipient_new_derivation() -> Result<(), SignalProtocolError> { + async { + let mut rng = OsRng; + + let alice_device_id: DeviceId = 23.into(); + let bob_device_id: DeviceId = 42.into(); + + let alice_e164 = "+14151111111".to_owned(); + let bob_e164 = "+14151114444".to_owned(); + + let alice_uuid = "9d0652a3-dcc3-4d11-975f-74d61598733f".to_string(); + let bob_uuid = "796abedb-ca4e-4f18-8803-1fde5b921f9f".to_string(); + + let bob_uuid_address = ProtocolAddress::new(bob_uuid.clone(), bob_device_id); + + let mut alice_store = support::test_in_memory_protocol_store()?; + let mut bob_store = support::test_in_memory_protocol_store()?; + + let alice_pubkey = *alice_store.get_identity_key_pair().await?.public_key(); + + let bob_pre_key_bundle = create_pre_key_bundle(&mut bob_store, &mut rng).await?; + + process_prekey_bundle( + &bob_uuid_address, + &mut alice_store.session_store, + &mut alice_store.identity_store, + &bob_pre_key_bundle, + SystemTime::now(), + &mut rng, + ) + .await?; + + let trust_root = KeyPair::generate(&mut rng); + let server_key = KeyPair::generate(&mut rng); + + let server_cert = + ServerCertificate::new(1, server_key.public_key, &trust_root.private_key, &mut rng)?; + + let expires = 1605722925; + + let sender_cert = SenderCertificate::new( + alice_uuid.clone(), + Some(alice_e164.clone()), + alice_pubkey, + alice_device_id, + expires, + server_cert, + &server_key.private_key, + &mut rng, + )?; + + let alice_ptext = vec![1, 2, 3, 23, 99]; + let alice_message = message_encrypt( + &alice_ptext, + &bob_uuid_address, + &mut alice_store.session_store, + &mut alice_store.identity_store, + SystemTime::now(), + ) + .await?; + + let alice_usmc = UnidentifiedSenderMessageContent::new( + alice_message.message_type(), + sender_cert.clone(), + alice_message.serialize().to_vec(), + ContentHint::Default, + None, + )?; + + let recipients = [&bob_uuid_address]; + let alice_ctext = sealed_sender_multi_recipient_encrypt_using_new_ephemeral_key_derivation( + &recipients, + &alice_store + .session_store + .load_existing_sessions(&recipients)?, + &alice_usmc, + &alice_store.identity_store, + &mut rng, + ) + .await?; + + let [bob_ctext] = <[_; 1]>::try_from(sealed_sender_multi_recipient_fan_out(&alice_ctext)?) + .expect("only one recipient"); + + let bob_ptext = sealed_sender_decrypt( + &bob_ctext, + &trust_root.public_key, + expires - 1, + Some(bob_e164.clone()), + bob_uuid.clone(), + bob_device_id, + &mut bob_store.identity_store, + &mut bob_store.session_store, + &mut bob_store.pre_key_store, + &bob_store.signed_pre_key_store, + &mut bob_store.kyber_pre_key_store, + ) + .await?; + + assert_eq!(bob_ptext.message, alice_ptext); + assert_eq!(bob_ptext.sender_uuid, alice_uuid); + assert_eq!(bob_ptext.sender_e164, Some(alice_e164)); + assert_eq!(bob_ptext.device_id, alice_device_id); + + Ok(()) + } + .now_or_never() + .expect("sync") +} + #[test] fn test_sealed_sender_multi_recipient_encrypt_with_archived_session( ) -> Result<(), SignalProtocolError> { @@ -665,6 +786,7 @@ fn test_sealed_sender_multi_recipient_encrypt_with_archived_session( &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .await?; @@ -694,6 +816,7 @@ fn test_sealed_sender_multi_recipient_encrypt_with_archived_session( &bob_uuid_address, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), ) .await?; @@ -716,7 +839,7 @@ fn test_sealed_sender_multi_recipient_encrypt_with_archived_session( &recipients, &[&session], &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await @@ -768,6 +891,7 @@ fn test_sealed_sender_multi_recipient_encrypt_with_bad_registration_id( &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut rng, ) .await?; @@ -797,6 +921,7 @@ fn test_sealed_sender_multi_recipient_encrypt_with_bad_registration_id( &bob_uuid_address, &mut alice_store.session_store, &mut alice_store.identity_store, + SystemTime::now(), ) .await?; @@ -815,7 +940,7 @@ fn test_sealed_sender_multi_recipient_encrypt_with_bad_registration_id( .session_store .load_existing_sessions(&recipients)?, &alice_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await @@ -861,6 +986,7 @@ fn test_decryption_error_in_sealed_sender() -> Result<(), SignalProtocolError> { &mut bob_store.session_store, &mut bob_store.identity_store, &alice_pre_key_bundle, + SystemTime::now(), &mut rng, ) .await?; @@ -872,6 +998,7 @@ fn test_decryption_error_in_sealed_sender() -> Result<(), SignalProtocolError> { &alice_uuid_address, &mut bob_store.session_store, &mut bob_store.identity_store, + SystemTime::now(), ) .await?; @@ -881,7 +1008,7 @@ fn test_decryption_error_in_sealed_sender() -> Result<(), SignalProtocolError> { &mut alice_store.session_store, &mut alice_store.identity_store, &mut alice_store.pre_key_store, - &mut alice_store.signed_pre_key_store, + &alice_store.signed_pre_key_store, &mut alice_store.kyber_pre_key_store, &mut rng, ) @@ -894,6 +1021,7 @@ fn test_decryption_error_in_sealed_sender() -> Result<(), SignalProtocolError> { &alice_uuid_address, &mut bob_store.session_store, &mut bob_store.identity_store, + SystemTime::now(), ) .await?; @@ -942,13 +1070,13 @@ fn test_decryption_error_in_sealed_sender() -> Result<(), SignalProtocolError> { let alice_ctext = sealed_sender_encrypt_from_usmc( &bob_uuid_address, &error_message_usmc, - &mut alice_store.identity_store, + &alice_store.identity_store, &mut rng, ) .await?; let bob_usmc = - sealed_sender_decrypt_to_usmc(&alice_ctext, &mut bob_store.identity_store).await?; + sealed_sender_decrypt_to_usmc(&alice_ctext, &bob_store.identity_store).await?; assert!(matches!( bob_usmc.msg_type()?, diff --git a/rust/protocol/tests/session.rs b/rust/protocol/tests/session.rs index 95877d212b..1efe338e8c 100644 --- a/rust/protocol/tests/session.rs +++ b/rust/protocol/tests/session.rs @@ -8,6 +8,7 @@ use futures_util::FutureExt; use libsignal_protocol::*; use rand::rngs::OsRng; use std::convert::TryFrom; +use std::time::{Duration, SystemTime}; use support::*; type TestResult = Result<(), SignalProtocolError>; @@ -65,6 +66,7 @@ fn test_basic_prekey() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -148,6 +150,7 @@ fn test_basic_prekey() -> TestResult { &mut alter_alice_store.session_store, &mut alter_alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -204,6 +207,7 @@ fn test_basic_prekey() -> TestResult { &mut alter_alice_store.session_store, &mut alter_alice_store.identity_store, &bad_bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await @@ -252,6 +256,7 @@ fn test_chain_jump_over_limit() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -317,6 +322,7 @@ fn test_chain_jump_over_limit_with_self() -> TestResult { &mut a1_store.session_store, &mut a1_store.identity_store, &a2_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -374,7 +380,7 @@ fn test_bad_signed_pre_key_signature() -> TestResult { let bad_bundle = good_bundle .clone() - .modify(|mut content| content.ec_pre_key_signature = Some(bad_signature)) + .modify(|content| content.ec_pre_key_signature = Some(bad_signature)) .expect("can recreate the bundle"); assert!(process_prekey_bundle( @@ -382,6 +388,7 @@ fn test_bad_signed_pre_key_signature() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bad_bundle, + SystemTime::now(), &mut csprng, ) .await @@ -394,6 +401,7 @@ fn test_bad_signed_pre_key_signature() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &good_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -446,6 +454,7 @@ fn test_repeat_bundle_message() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -581,6 +590,7 @@ fn test_bad_message_bundle() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -678,6 +688,7 @@ fn test_optional_one_time_prekey() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -848,6 +859,7 @@ fn test_basic_simultaneous_initiate() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -857,6 +869,7 @@ fn test_basic_simultaneous_initiate() -> TestResult { &mut bob_store.session_store, &mut bob_store.identity_store, &alice_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1020,6 +1033,7 @@ fn test_simultaneous_initiate_with_lossage() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1029,6 +1043,7 @@ fn test_simultaneous_initiate_with_lossage() -> TestResult { &mut bob_store.session_store, &mut bob_store.identity_store, &alice_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1172,6 +1187,7 @@ fn test_simultaneous_initiate_lost_message() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1181,6 +1197,7 @@ fn test_simultaneous_initiate_lost_message() -> TestResult { &mut bob_store.session_store, &mut bob_store.identity_store, &alice_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1330,6 +1347,7 @@ fn test_simultaneous_initiate_repeated_messages() -> TestResult { &mut alice_store_builder.store.session_store, &mut alice_store_builder.store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1339,6 +1357,7 @@ fn test_simultaneous_initiate_repeated_messages() -> TestResult { &mut bob_store_builder.store.session_store, &mut bob_store_builder.store.identity_store, &alice_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1584,6 +1603,7 @@ fn test_simultaneous_initiate_lost_message_repeated_messages() -> TestResult { &mut alice_store_builder.store.session_store, &mut alice_store_builder.store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1608,6 +1628,7 @@ fn test_simultaneous_initiate_lost_message_repeated_messages() -> TestResult { &mut alice_store_builder.store.session_store, &mut alice_store_builder.store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1617,6 +1638,7 @@ fn test_simultaneous_initiate_lost_message_repeated_messages() -> TestResult { &mut bob_store_builder.store.session_store, &mut bob_store_builder.store.identity_store, &alice_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1892,6 +1914,7 @@ fn test_zero_is_a_valid_prekey_id() -> TestResult { &mut alice_store.session_store, &mut alice_store.identity_store, &bob_pre_key_bundle, + SystemTime::now(), &mut csprng, ) .await?; @@ -1936,6 +1959,94 @@ fn test_zero_is_a_valid_prekey_id() -> TestResult { .expect("sync") } +#[test] +fn test_unacknowledged_sessions_eventually_expire() -> TestResult { + async { + const WELL_PAST_EXPIRATION: Duration = Duration::from_secs(60 * 60 * 24 * 90); + + let mut csprng = OsRng; + let bob_address = ProtocolAddress::new("+14151111112".to_owned(), 1.into()); + + let mut alice_store = TestStoreBuilder::new().store; + let bob_store_builder = TestStoreBuilder::new() + .with_pre_key(0.into()) + .with_signed_pre_key(0.into()) + .with_kyber_pre_key(0.into()); + + let bob_pre_key_bundle = bob_store_builder.make_bundle_with_latest_keys(1.into()); + + process_prekey_bundle( + &bob_address, + &mut alice_store.session_store, + &mut alice_store.identity_store, + &bob_pre_key_bundle, + SystemTime::UNIX_EPOCH, + &mut csprng, + ) + .await?; + + let initial_session = alice_store + .session_store + .load_session(&bob_address) + .await + .expect("session can be loaded") + .expect("session exists"); + assert!(initial_session + .has_usable_sender_chain(SystemTime::UNIX_EPOCH) + .expect("can check for a sender chain")); + assert!(!initial_session + .has_usable_sender_chain(SystemTime::UNIX_EPOCH + WELL_PAST_EXPIRATION) + .expect("can check for a sender chain")); + + let original_message = "L'homme est condamné à être libre"; + let outgoing_message = message_encrypt( + original_message.as_bytes(), + &bob_address, + &mut alice_store.session_store, + &mut alice_store.identity_store, + SystemTime::UNIX_EPOCH + Duration::from_secs(1), + ) + .await?; + + assert_eq!( + outgoing_message.message_type(), + CiphertextMessageType::PreKey + ); + + let updated_session = alice_store + .session_store + .load_session(&bob_address) + .await + .expect("session can be loaded") + .expect("session exists"); + assert!(updated_session + .has_usable_sender_chain(SystemTime::UNIX_EPOCH) + .expect("can check for a sender chain")); + assert!(!updated_session + .has_usable_sender_chain(SystemTime::UNIX_EPOCH + WELL_PAST_EXPIRATION) + .expect("can check for a sender chain")); + + let error = message_encrypt( + original_message.as_bytes(), + &bob_address, + &mut alice_store.session_store, + &mut alice_store.identity_store, + SystemTime::UNIX_EPOCH + WELL_PAST_EXPIRATION, + ) + .await + .unwrap_err(); + assert!( + matches!(&error, SignalProtocolError::SessionNotFound(addr) if addr == &bob_address), + "{:?}", + error + ); + + Ok(()) + } + .now_or_never() + .expect("sync") +} + #[allow(clippy::needless_range_loop)] fn run_session_interaction(alice_session: SessionRecord, bob_session: SessionRecord) -> TestResult { async { diff --git a/rust/protocol/tests/support/mod.rs b/rust/protocol/tests/support/mod.rs index f5fe67ab97..4949bc3a81 100644 --- a/rust/protocol/tests/support/mod.rs +++ b/rust/protocol/tests/support/mod.rs @@ -13,6 +13,7 @@ use rand::rngs::OsRng; use rand::{CryptoRng, Rng}; use std::ops::RangeFrom; +use std::time::SystemTime; // Deliberately not reusing the constants from `protocol`. pub(crate) const PRE_KYBER_MESSAGE_VERSION: u32 = 3; @@ -37,6 +38,7 @@ pub async fn encrypt( remote_address, &mut store.session_store, &mut store.identity_store, + SystemTime::now(), ) .await } @@ -53,7 +55,7 @@ pub async fn decrypt( &mut store.session_store, &mut store.identity_store, &mut store.pre_key_store, - &mut store.signed_pre_key_store, + &store.signed_pre_key_store, &mut store.kyber_pre_key_store, &mut csprng, ) diff --git a/rust/quic/src/client.rs b/rust/quic/src/client.rs index 29e5b3ed06..72fed2ed12 100644 --- a/rust/quic/src/client.rs +++ b/rust/quic/src/client.rs @@ -100,7 +100,7 @@ impl<'r> QuicCallbackListener for PwaveQuicCallbackListener<'r> { let cloned_buf = self.buf.clone(); self.len = vec![]; self.buf = vec![]; - self.wrapped.on_data(cloned_buf); + let _ = self.wrapped.on_data(cloned_buf); } } diff --git a/rust/zkcredential/Cargo.toml b/rust/zkcredential/Cargo.toml index d6bb0b4588..45b2b0f91d 100644 --- a/rust/zkcredential/Cargo.toml +++ b/rust/zkcredential/Cargo.toml @@ -21,6 +21,7 @@ serde = { version = "1.0.106", features = ["derive"] } [dev-dependencies] bincode = "1.2.1" hex = "0.4" +hex-literal = "0.4.1" # Many of zkcredential's tests live in the zkgroup crate, # since zkcredential was designed to work with existing verifiably encrypted attribute types. diff --git a/rust/zkcredential/src/credentials.rs b/rust/zkcredential/src/credentials.rs index 6584388f92..fd0cbafe53 100644 --- a/rust/zkcredential/src/credentials.rs +++ b/rust/zkcredential/src/credentials.rs @@ -220,53 +220,32 @@ impl SystemParams { pub fn get_hardcoded() -> SystemParams { *SYSTEM_PARAMS } - - #[cfg(test)] - const SYSTEM_HARDCODED: &[u8] = &[ - 0x58, 0x9c, 0x87, 0x18, 0xe8, 0x26, 0x3a, 0x53, 0xa7, 0x89, 0x32, 0xb6, 0x21, 0x2a, 0x46, - 0xe7, 0xfd, 0x52, 0xde, 0x3a, 0xd1, 0x57, 0xb5, 0xbb, 0x27, 0x7d, 0xba, 0x49, 0x4c, 0xfd, - 0x34, 0x71, 0xd4, 0xcc, 0x5f, 0x90, 0x68, 0x59, 0x52, 0x91, 0x7b, 0x33, 0x36, 0x6e, 0xfc, - 0xce, 0x5, 0x12, 0xa1, 0xf8, 0xd7, 0xf, 0x97, 0x47, 0x58, 0x26, 0x6c, 0xb0, 0x4f, 0xc4, - 0x24, 0x34, 0x6d, 0x37, 0xb2, 0xf, 0x49, 0xcb, 0x2a, 0x8, 0x1c, 0x94, 0xb1, 0x77, 0x1f, - 0xd8, 0xc1, 0x72, 0xae, 0x21, 0x78, 0x5c, 0x61, 0xea, 0x2c, 0x7e, 0x31, 0x94, 0x7c, 0xe3, - 0x51, 0xe7, 0xb5, 0xff, 0x7, 0x2, 0x8c, 0x53, 0x29, 0xbe, 0xb8, 0x7b, 0x31, 0x7f, 0xfc, - 0xd9, 0x81, 0xe4, 0x40, 0x81, 0x9d, 0x91, 0x13, 0x6c, 0x98, 0x8d, 0x6d, 0x9f, 0xbe, 0xa4, - 0xa8, 0x7e, 0x55, 0xed, 0x24, 0xa5, 0x99, 0x3a, 0xa0, 0x2f, 0x68, 0x8a, 0xb1, 0xd3, 0xbd, - 0x19, 0x5, 0x6f, 0x94, 0xc8, 0xa4, 0x4b, 0x8f, 0xad, 0xdf, 0xa3, 0xc9, 0xc7, 0x9c, 0x95, - 0xad, 0x44, 0x31, 0x1a, 0x7b, 0xf0, 0xe, 0x5e, 0x86, 0x2e, 0xc2, 0xc3, 0x99, 0xf0, 0xd6, - 0x89, 0xdf, 0xb8, 0xc2, 0xdc, 0xd, 0x7c, 0xab, 0xa3, 0x2a, 0xfc, 0xf5, 0x8c, 0xf0, 0xd8, - 0x5f, 0x78, 0x19, 0x5a, 0xb, 0x5a, 0xb7, 0x32, 0xf5, 0x65, 0x59, 0x54, 0x92, 0xcf, 0xd9, - 0x82, 0x32, 0x1d, 0x1f, 0x9b, 0xe4, 0xb2, 0x1f, 0xe6, 0xa0, 0x21, 0x43, 0x6, 0x2, 0x3d, - 0x6a, 0x5, 0xd0, 0xd2, 0x3f, 0x67, 0xdd, 0xc1, 0xc0, 0x40, 0xe, 0x5e, 0xa, 0x5e, 0x92, - 0xd1, 0x75, 0x95, 0x13, 0x1b, 0x7a, 0x9, 0x5e, 0x74, 0xb, 0x88, 0x4b, 0x8c, 0x9b, 0xb0, - 0x22, 0x6a, 0x39, 0xcf, 0xd0, 0x27, 0xc7, 0x69, 0xc4, 0xf4, 0x67, 0x7c, 0x51, 0xf2, 0x1b, - 0x24, 0xda, 0x81, 0xfb, 0x2b, 0xd1, 0x35, 0x6a, 0x9d, 0x6, 0x50, 0xf6, 0xa6, 0x3f, 0xcc, - 0x90, 0xd9, 0x3b, 0xd7, 0x4a, 0x95, 0x4b, 0xa6, 0xf7, 0x5f, 0xe, 0x9f, 0xca, 0x47, 0xa6, - 0xd2, 0x17, 0x34, 0xbc, 0xe7, 0xb2, 0x8f, 0x6, 0xb7, 0x6e, 0xf2, 0xc4, 0x4d, 0x20, 0xa0, - 0x70, 0x26, 0x53, 0x4e, 0x58, 0x6e, 0xb8, 0xe1, 0x3, 0x88, 0x74, 0xa9, 0x3e, 0x44, 0xde, - 0x36, 0x2c, 0xe7, 0xbc, 0x8, 0x44, 0xbf, 0xfc, 0x88, 0xe3, 0x90, 0xc6, 0x25, 0x19, 0xe2, - 0x81, 0xaa, 0x6f, 0xd5, 0x3f, 0xf9, 0xdd, 0xd1, 0xd9, 0xba, 0x30, 0x3c, 0xf7, 0x0, 0x4, - 0x27, 0x8e, 0xa2, 0xae, 0x66, 0xce, 0x5, 0xa2, 0x74, 0x9d, 0x29, 0xeb, 0xa5, 0x6f, 0x3e, - 0xfe, 0x99, 0xe4, 0x29, 0x2, 0x82, 0x5c, 0x47, 0x3d, 0xfc, 0x3c, 0x15, 0x4c, 0x37, 0x62, - 0xd2, 0xe7, 0x6b, 0xd1, 0x3, 0xf6, 0x29, 0xd2, 0x50, 0xb2, 0xd9, 0xd5, 0xc2, 0x43, 0xa4, - 0xcf, 0x8f, 0x3b, 0xe2, 0x1a, 0x84, 0xf1, 0x53, 0xf4, 0x4e, 0x27, 0x33, 0xa1, 0x5, 0xcf, - 0x78, 0xa, 0x20, 0xf0, 0x3d, 0x84, 0xfe, 0x1e, 0xbb, 0xeb, 0xe, - ]; } -#[test] -fn test_system() { - let params = SystemParams::generate(); - let serialized = bincode::serialize(¶ms).expect("can serialize"); - println!("PARAMS = {:#x?}", serialized); - assert!(serialized == SystemParams::SYSTEM_HARDCODED); -} +#[cfg(test)] +mod tests { + use hex_literal::hex; + + use super::*; + + impl SystemParams { + const SYSTEM_HARDCODED: &'static [u8] = &hex!("589c8718e8263a53a78932b6212a46e7fd52de3ad157b5bb277dba494cfd3471d4cc5f90685952917b33366efcce0512a1f8d70f974758266cb04fc424346d37b20f49cb2a081c94b1771fd8c172ae21785c61ea2c7e31947ce351e7b5ff07028c5329beb87b317ffcd981e440819d91136c988d6d9fbea4a87e55ed24a5993aa02f688ab1d3bd19056f94c8a44b8faddfa3c9c79c95ad44311a7bf00e5e862ec2c399f0d689dfb8c2dc0d7caba32afcf58cf0d85f78195a0b5ab732f565595492cfd982321d1f9be4b21fe6a0214306023d6a05d0d23f67ddc1c0400e5e0a5e92d17595131b7a095e740b884b8c9bb0226a39cfd027c769c4f4677c51f21b24da81fb2bd1356a9d0650f6a63fcc90d93bd74a954ba6f75f0e9fca47a6d21734bce7b28f06b76ef2c44d20a07026534e586eb8e1038874a93e44de362ce7bc0844bffc88e390c62519e281aa6fd53ff9ddd1d9ba303cf70004278ea2ae66ce05a2749d29eba56f3efe99e42902825c473dfc3c154c3762d2e76bd103f629d250b2d9d5c243a4cf8f3be21a84f153f44e2733a105cf780a20f03d84fe1ebbeb0e"); + } + + #[test] + fn test_system() { + let params = SystemParams::generate(); + let serialized = bincode::serialize(¶ms).expect("can serialize"); + println!("PARAMS = {:#x?}", serialized); + assert!(serialized == SystemParams::SYSTEM_HARDCODED); + } -#[test] -fn round_trip_key_pair() { - let key_pair = CredentialKeyPair::generate([0x42; RANDOMNESS_LEN]); - let serialized = bincode::serialize(&key_pair).unwrap(); - let deserialized: CredentialKeyPair = bincode::deserialize(&serialized).unwrap(); - assert_eq!(&key_pair.public_key.C_W, &deserialized.public_key.C_W); - assert_eq!(&key_pair.private_key.w, &deserialized.private_key.w); + #[test] + fn round_trip_key_pair() { + let key_pair = CredentialKeyPair::generate([0x42; RANDOMNESS_LEN]); + let serialized = bincode::serialize(&key_pair).unwrap(); + let deserialized: CredentialKeyPair = bincode::deserialize(&serialized).unwrap(); + assert_eq!(&key_pair.public_key.C_W, &deserialized.public_key.C_W); + assert_eq!(&key_pair.private_key.w, &deserialized.private_key.w); + } } diff --git a/rust/zkcredential/src/issuance.rs b/rust/zkcredential/src/issuance.rs index b07b180975..3204f37f1d 100644 --- a/rust/zkcredential/src/issuance.rs +++ b/rust/zkcredential/src/issuance.rs @@ -42,7 +42,7 @@ pub struct IssuanceProof { /// calling [`issue`](Self::issue) and the client by calling [`verify`](Self::verify). pub struct IssuanceProofBuilder<'a> { public_attrs: ShoHmacSha256, - // Directly accessed by BlindIssuanceProofBuilder. + /// Directly accessed by [`blind::BlindedIssuanceProofBuilder`]. attr_points: Vec, authenticated_message: &'a [u8], } @@ -136,7 +136,7 @@ impl<'a> IssuanceProofBuilder<'a> { /// Generates a [`poksho::PointArgs`] to be used in the final proof. /// - /// `total_attr_count` is passed in for [blind issuance](blind::BlindIssuanceProofBuilder), in + /// `total_attr_count` is passed in for [blind issuance](blind::BlindedIssuanceProofBuilder), in /// which case the caller may provide additional attributes. fn prepare_scalar_args( &self, @@ -168,7 +168,7 @@ impl<'a> IssuanceProofBuilder<'a> { /// Generates a [`poksho::PointArgs`] to be used in the final proof. /// /// The `credential` argument may be `None` when used for [blind - /// issuance](blind::BlindIssuanceProofBuilder), in which case the caller is responsible for + /// issuance](blind::BlindedIssuanceProofBuilder), in which case the caller is responsible for /// adding its own points representing the credential. fn prepare_point_args( &self, diff --git a/rust/zkgroup/Cargo.toml b/rust/zkgroup/Cargo.toml index a8a3b9f25e..5a9e315a7f 100644 --- a/rust/zkgroup/Cargo.toml +++ b/rust/zkgroup/Cargo.toml @@ -21,8 +21,8 @@ bincode = "1.2.1" serde = { version = "1.0.106", features = ["derive"] } sha2 = "0.10.0" hex = "0.4.0" -aead = "0.4.3" -aes-gcm-siv = "0.10.1" +hex-literal = "0.4.1" +aes-gcm-siv = "0.11.1" displaydoc = "0.2" lazy_static = "1.4.0" subtle = "2.3" diff --git a/rust/zkgroup/src/api/groups/group_params.rs b/rust/zkgroup/src/api/groups/group_params.rs index f5794b5b46..b1f6a56af0 100644 --- a/rust/zkgroup/src/api/groups/group_params.rs +++ b/rust/zkgroup/src/api/groups/group_params.rs @@ -10,9 +10,9 @@ use crate::common::errors::*; use crate::common::sho::*; use crate::common::simple_types::*; use crate::{api, crypto}; -use aead::generic_array::GenericArray; -use aead::{Aead, NewAead}; -use aes_gcm_siv::Aes256GcmSiv; +use aes_gcm_siv::aead::generic_array::GenericArray; +use aes_gcm_siv::aead::Aead; +use aes_gcm_siv::{Aes256GcmSiv, KeyInit}; use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Serialize, Deserialize, Default)] @@ -275,17 +275,17 @@ mod tests { 0x00, 0x00, 0x00, 0x00, ]; - let key_vec = vec![ + let key = [ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let nonce_vec = vec![ + let nonce = [ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let ciphertext_vec = vec![ + let ciphertext = [ 0x4a, 0x6a, 0x9d, 0xb4, 0xc8, 0xc6, 0x54, 0x92, 0x01, 0xb9, 0xed, 0xb5, 0x30, 0x06, 0xcb, 0xa8, 0x21, 0xec, 0x9c, 0xf8, 0x50, 0x94, 0x8a, 0x7c, 0x86, 0xc6, 0x8a, 0xc7, 0x53, 0x9d, 0x02, 0x7f, 0xe8, 0x19, 0xe6, 0x3a, 0xbc, 0xd0, 0x20, 0xb0, 0x06, 0xa9, @@ -293,12 +293,12 @@ mod tests { ]; let calc_ciphertext = - group_secret_params.encrypt_blob_aesgcmsiv(&key_vec, &nonce_vec, &plaintext_vec); + group_secret_params.encrypt_blob_aesgcmsiv(&key, &nonce, &plaintext_vec); - assert!(calc_ciphertext[..ciphertext_vec.len()] == ciphertext_vec[..]); + assert!(calc_ciphertext[..ciphertext.len()] == ciphertext[..]); let calc_plaintext = group_secret_params - .decrypt_blob_aesgcmsiv(&key_vec, &nonce_vec, &calc_ciphertext) + .decrypt_blob_aesgcmsiv(&key, &nonce, &calc_ciphertext) .unwrap(); assert!(calc_plaintext[..] == plaintext_vec[..]); } @@ -315,17 +315,17 @@ mod tests { 0x3a, 0x98, 0xe1, 0x08, ]; - let key_vec = vec![ + let key = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let nonce_vec = vec![ + let nonce = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let ciphertext_vec = vec![ + let ciphertext = [ 0xf3, 0xf8, 0x0f, 0x2c, 0xf0, 0xcb, 0x2d, 0xd9, 0xc5, 0x98, 0x4f, 0xcd, 0xa9, 0x08, 0x45, 0x6c, 0xc5, 0x37, 0x70, 0x3b, 0x5b, 0xa7, 0x03, 0x24, 0xa6, 0x79, 0x3a, 0x7b, 0xf2, 0x18, 0xd3, 0xea, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -333,12 +333,12 @@ mod tests { ]; let calc_ciphertext = - group_secret_params.encrypt_blob_aesgcmsiv(&key_vec, &nonce_vec, &plaintext_vec); + group_secret_params.encrypt_blob_aesgcmsiv(&key, &nonce, &plaintext_vec); - assert!(calc_ciphertext[..ciphertext_vec.len()] == ciphertext_vec[..]); + assert!(calc_ciphertext[..ciphertext.len()] == ciphertext[..]); let calc_plaintext = group_secret_params - .decrypt_blob_aesgcmsiv(&key_vec, &nonce_vec, &calc_ciphertext) + .decrypt_blob_aesgcmsiv(&key, &nonce, &calc_ciphertext) .unwrap(); assert!(calc_plaintext[..] == plaintext_vec[..]); } diff --git a/rust/zkgroup/src/crypto/credentials.rs b/rust/zkgroup/src/crypto/credentials.rs index 586e2efd1a..f0012cb2e3 100644 --- a/rust/zkgroup/src/crypto/credentials.rs +++ b/rust/zkgroup/src/crypto/credentials.rs @@ -22,6 +22,7 @@ use crate::{ NUM_AUTH_CRED_ATTRIBUTES, NUM_PROFILE_KEY_CRED_ATTRIBUTES, NUM_RECEIPT_CRED_ATTRIBUTES, }; +use hex_literal::hex; use lazy_static::lazy_static; lazy_static! { @@ -145,7 +146,7 @@ pub struct AuthCredentialWithPni { pub(crate) V: RistrettoPoint, } -/// Unused, kept only because ServerSecretParams contains a KeyPair. +/// Unused, kept only because ServerSecretParams contains a `KeyPair`. #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProfileKeyCredential { pub(crate) t: Scalar, @@ -177,7 +178,7 @@ pub struct BlindedExpiringProfileKeyCredential { pub(crate) S2: RistrettoPoint, } -/// Unused, kept only because ServerSecretParams contains a KeyPair. +/// Unused, kept only because ServerSecretParams contains a `KeyPair`. #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PniCredential { pub(crate) t: Scalar, @@ -304,45 +305,7 @@ impl SystemParams { *SYSTEM_PARAMS } - const SYSTEM_HARDCODED: &'static [u8] = &[ - 0x9a, 0xe7, 0xc8, 0xe5, 0xed, 0x77, 0x9b, 0x11, 0x4a, 0xe7, 0x70, 0x8a, 0xa2, 0xf7, 0x94, - 0x67, 0xa, 0xdd, 0xa3, 0x24, 0x98, 0x7b, 0x65, 0x99, 0x13, 0x12, 0x2c, 0x35, 0x50, 0x5b, - 0x10, 0x5e, 0x6c, 0xa3, 0x10, 0x25, 0xd2, 0xd7, 0x6b, 0xe7, 0xfd, 0x34, 0x94, 0x4f, 0x98, - 0xf7, 0xfa, 0xe, 0x37, 0xba, 0xbb, 0x2c, 0x8b, 0x98, 0xbb, 0xbd, 0xbd, 0x3d, 0xd1, 0xbf, - 0x13, 0xc, 0xca, 0x2c, 0x8a, 0x9a, 0x3b, 0xdf, 0xaa, 0xa2, 0xb6, 0xb3, 0x22, 0xd4, 0x6b, - 0x93, 0xec, 0xa7, 0xb0, 0xd5, 0x1c, 0x86, 0xa3, 0xc8, 0x39, 0xe1, 0x14, 0x66, 0x35, 0x82, - 0x58, 0xa6, 0xc1, 0xc, 0x57, 0x7f, 0xc2, 0xbf, 0xfd, 0x34, 0xcd, 0x99, 0x16, 0x4c, 0x9a, - 0x6c, 0xd2, 0x9f, 0xab, 0x55, 0xd9, 0x1f, 0xf9, 0x26, 0x93, 0x22, 0xec, 0x34, 0x58, 0x60, - 0x3c, 0xc9, 0x6a, 0xd, 0x47, 0xf7, 0x4, 0x5, 0x82, 0x88, 0xf6, 0x2e, 0xe0, 0xac, 0xed, - 0xb8, 0xaa, 0x23, 0x24, 0x21, 0x21, 0xd9, 0x89, 0x65, 0xa9, 0xbb, 0x29, 0x91, 0x25, 0xc, - 0x11, 0x75, 0x80, 0x95, 0xec, 0xe0, 0xfd, 0x2b, 0x33, 0x28, 0x52, 0x86, 0xfe, 0x1f, 0xcb, - 0x5, 0x61, 0x3, 0xb6, 0x8, 0x17, 0x44, 0xb9, 0x75, 0xf5, 0x50, 0xd0, 0x85, 0x21, 0x56, - 0x8d, 0xd3, 0xd8, 0x61, 0x8f, 0x25, 0xc1, 0x40, 0x37, 0x5a, 0xf, 0x40, 0x24, 0xc3, 0xaa, - 0x23, 0xbd, 0xff, 0xfb, 0x27, 0xfb, 0xd9, 0x82, 0x20, 0x8d, 0x3e, 0xcd, 0x1f, 0xd3, 0xbc, - 0xb7, 0xac, 0xc, 0x3a, 0x14, 0xb1, 0x9, 0x80, 0x4f, 0xc7, 0x48, 0xd7, 0xfa, 0x45, 0x6c, - 0xff, 0xb4, 0x93, 0x4f, 0x98, 0xb, 0x6e, 0x9, 0xa2, 0x48, 0xa6, 0xf, 0x44, 0xa6, 0x15, 0xa, - 0xe6, 0xc1, 0x3d, 0x7e, 0x3c, 0x6, 0x26, 0x1d, 0x7e, 0x4e, 0xed, 0x37, 0xf3, 0x9f, 0x60, - 0xb0, 0x4d, 0xd9, 0xd6, 0x7, 0xfd, 0x35, 0x70, 0x12, 0x27, 0x4d, 0x3c, 0x63, 0xdb, 0xb3, - 0x8e, 0x73, 0x78, 0x59, 0x9c, 0x9e, 0x97, 0xdf, 0xbb, 0x28, 0x84, 0x26, 0x94, 0x89, 0x1d, - 0x5f, 0xd, 0xdc, 0x72, 0x99, 0x19, 0xb7, 0x98, 0xb4, 0x13, 0x15, 0x3, 0x40, 0x8c, 0xc5, - 0x7a, 0x9c, 0x53, 0x2f, 0x44, 0x27, 0x63, 0x2c, 0x88, 0xf5, 0x4c, 0xea, 0x53, 0x86, 0x1a, - 0x5b, 0xc4, 0x4c, 0x61, 0xcc, 0x60, 0x37, 0xdc, 0x31, 0xc2, 0xe8, 0xd4, 0x47, 0x4f, 0xb5, - 0x19, 0x58, 0x7a, 0x44, 0x86, 0x93, 0x18, 0x2a, 0xd9, 0xd6, 0xd8, 0x6b, 0x53, 0x59, 0x57, - 0x85, 0x8f, 0x54, 0x7b, 0x93, 0x40, 0x12, 0x7d, 0xa7, 0x5f, 0x80, 0x74, 0xca, 0xee, 0x94, - 0x4a, 0xc3, 0x6c, 0xa, 0xc6, 0x62, 0xd3, 0x8c, 0x9b, 0x3c, 0xcc, 0xe0, 0x3a, 0x9, 0x3f, - 0xcd, 0x96, 0x44, 0x4, 0x73, 0x98, 0xb8, 0x6b, 0x6e, 0x83, 0x37, 0x2f, 0xf1, 0x4f, 0xb8, - 0xbb, 0xd, 0xea, 0x65, 0x53, 0x12, 0x52, 0xac, 0x70, 0xd5, 0x8a, 0x4a, 0x8, 0x10, 0xd6, - 0x82, 0xa0, 0xe7, 0x9, 0xc9, 0x22, 0x7b, 0x30, 0xef, 0x6c, 0x8e, 0x17, 0xc5, 0x91, 0x5d, - 0x52, 0x72, 0x21, 0xbb, 0x0, 0xda, 0x81, 0x75, 0xcd, 0x64, 0x89, 0xaa, 0x8a, 0xa4, 0x92, - 0xa5, 0x0, 0xf9, 0xab, 0xee, 0x56, 0x90, 0xb9, 0xdf, 0xca, 0x88, 0x55, 0xdc, 0xb, 0xd0, - 0x2a, 0x7f, 0x27, 0x7a, 0xdd, 0x24, 0xf, 0x63, 0x9a, 0xc1, 0x68, 0x1, 0xe8, 0x15, 0x74, - 0xaf, 0xb4, 0x68, 0x3e, 0xdf, 0xf6, 0x3b, 0x9a, 0x1, 0xe9, 0x3d, 0xbd, 0x86, 0x7a, 0x4, - 0xb6, 0x16, 0xc7, 0x6, 0xc8, 0xc, 0x75, 0x6c, 0x11, 0xa3, 0x1, 0x6b, 0xbf, 0xb6, 0x9, 0x77, - 0xf4, 0x64, 0x8b, 0x5f, 0x23, 0x95, 0xa4, 0xb4, 0x28, 0xb7, 0x21, 0x19, 0x40, 0x81, 0x3e, - 0x3a, 0xfd, 0xe2, 0xb8, 0x7a, 0xa9, 0xc2, 0xc3, 0x7b, 0xf7, 0x16, 0xe2, 0x57, 0x8f, 0x95, - 0x65, 0x6d, 0xf1, 0x2c, 0x2f, 0xb6, 0xf5, 0xd0, 0x63, 0x1f, 0x6f, 0x71, 0xe2, 0xc3, 0x19, - 0x3f, 0x6d, - ]; + const SYSTEM_HARDCODED: &'static [u8] = &hex!("9ae7c8e5ed779b114ae7708aa2f794670adda324987b659913122c35505b105e6ca31025d2d76be7fd34944f98f7fa0e37babb2c8b98bbbdbd3dd1bf130cca2c8a9a3bdfaaa2b6b322d46b93eca7b0d51c86a3c839e11466358258a6c10c577fc2bffd34cd99164c9a6cd29fab55d91ff9269322ec3458603cc96a0d47f704058288f62ee0acedb8aa23242121d98965a9bb2991250c11758095ece0fd2b33285286fe1fcb056103b6081744b975f550d08521568dd3d8618f25c140375a0f4024c3aa23bdfffb27fbd982208d3ecd1fd3bcb7ac0c3a14b109804fc748d7fa456cffb4934f980b6e09a248a60f44a6150ae6c13d7e3c06261d7e4eed37f39f60b04dd9d607fd357012274d3c63dbb38e7378599c9e97dfbb28842694891d5f0ddc729919b798b4131503408cc57a9c532f4427632c88f54cea53861a5bc44c61cc6037dc31c2e8d4474fb519587a448693182ad9d6d86b535957858f547b9340127da75f8074caee944ac36c0ac662d38c9b3ccce03a093fcd9644047398b86b6e83372ff14fb8bb0dea65531252ac70d58a4a0810d682a0e709c9227b30ef6c8e17c5915d527221bb00da8175cd6489aa8aa492a500f9abee5690b9dfca8855dc0bd02a7f277add240f639ac16801e81574afb4683edff63b9a01e93dbd867a04b616c706c80c756c11a3016bbfb60977f4648b5f2395a4b428b7211940813e3afde2b87aa9c2c37bf716e2578f95656df12c2fb6f5d0631f6f71e2c3193f6d"); } impl KeyPair { diff --git a/rust/zkgroup/tests/integration_tests.rs b/rust/zkgroup/tests/integration_tests.rs index 9c7c55ab3b..6ae61e03ef 100644 --- a/rust/zkgroup/tests/integration_tests.rs +++ b/rust/zkgroup/tests/integration_tests.rs @@ -4,313 +4,46 @@ // use curve25519_dalek::ristretto::RistrettoPoint; +use hex_literal::hex; use sha2::Sha256; use zkgroup::SECONDS_PER_DAY; +/// Simple wrapper around `assert_eq` that prints the hex-encoded values on +/// failure. +macro_rules! assert_hex_eq { + ($lhs:expr, $rhs: expr) => { + assert_eq!( + &$lhs, + &$rhs, + "{} = {}, {} = {}", + stringify!($lhs), + hex::encode(&$lhs), + stringify!($rhs), + hex::encode(&$rhs), + ); + }; +} + #[test] fn test_lizard() { let p = RistrettoPoint::lizard_encode::(&zkgroup::common::constants::TEST_ARRAY_16); let data_out = p.lizard_decode::(); - assert!(data_out.unwrap() == zkgroup::common::constants::TEST_ARRAY_16); + assert_hex_eq!(data_out.unwrap(), zkgroup::common::constants::TEST_ARRAY_16); } -pub const AUTH_CREDENTIAL_PRESENTATION_V1: &[u8] = &[ - 0x00, 0x0c, 0xde, 0x97, 0x97, 0x37, 0xed, 0x30, 0xbb, 0xeb, 0x16, 0x36, 0x2e, 0x4e, 0x07, 0x69, - 0x45, 0xce, 0x02, 0x06, 0x9f, 0x72, 0x7b, 0x0e, 0xd4, 0xc3, 0xc3, 0x3c, 0x01, 0x1e, 0x82, 0x54, - 0x6e, 0x1c, 0xdf, 0x08, 0x1f, 0xbd, 0xf3, 0x7c, 0x03, 0xa8, 0x51, 0xad, 0x06, 0x0b, 0xdc, 0xbf, - 0x63, 0x78, 0xcb, 0x4c, 0xb1, 0x6d, 0xc3, 0x15, 0x4d, 0x08, 0xde, 0x54, 0x39, 0xb5, 0x32, 0x32, - 0x03, 0x72, 0x9d, 0x18, 0x41, 0xb5, 0x17, 0x03, 0x3a, 0xf2, 0xfd, 0x17, 0x7d, 0x30, 0x49, 0x1c, - 0x13, 0x8a, 0xe7, 0x23, 0x65, 0x57, 0x34, 0xf6, 0xe5, 0xcc, 0x01, 0xc0, 0x06, 0x96, 0xf4, 0xe9, - 0x20, 0x96, 0xd8, 0xc3, 0x3d, 0xf2, 0x6b, 0xa2, 0xa8, 0x20, 0xd4, 0x2e, 0x97, 0x35, 0xd3, 0x0f, - 0x8e, 0xee, 0xf9, 0x6d, 0x39, 0x90, 0x79, 0x07, 0x3c, 0x09, 0x9f, 0x70, 0x35, 0x52, 0x3b, 0xfe, - 0x71, 0x66, 0x38, 0x65, 0x93, 0x19, 0xd3, 0xc3, 0x6a, 0xd3, 0x4c, 0x00, 0xef, 0x88, 0x50, 0xf6, - 0x63, 0xc4, 0xd9, 0x30, 0x30, 0x23, 0x50, 0x74, 0x31, 0x2a, 0x88, 0x78, 0xb6, 0xa5, 0xc5, 0xdf, - 0x4f, 0xbc, 0x7d, 0x32, 0x93, 0x52, 0x78, 0xbf, 0xa5, 0x99, 0x6b, 0x44, 0xab, 0x75, 0xd6, 0xf0, - 0x6f, 0x4c, 0x30, 0xb9, 0x86, 0x40, 0xad, 0x5d, 0xe7, 0x47, 0x42, 0x65, 0x6c, 0x89, 0x77, 0x56, - 0x7d, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xe6, 0x9f, 0x82, 0xad, 0x2d, 0xcb, - 0x49, 0x09, 0x65, 0x0a, 0xc6, 0xb2, 0x57, 0x38, 0x41, 0xaf, 0x56, 0x8f, 0xef, 0x82, 0x2b, 0x32, - 0xb4, 0x5f, 0x62, 0x5a, 0x76, 0x46, 0x91, 0xa7, 0x04, 0xd1, 0x1b, 0x6f, 0x38, 0x52, 0x61, 0x46, - 0x81, 0x17, 0xea, 0xd5, 0x7f, 0xa6, 0x23, 0x33, 0x8e, 0x21, 0xc6, 0x6e, 0xd8, 0x46, 0xab, 0x65, - 0x80, 0x9f, 0xca, 0xc1, 0x58, 0x06, 0x6d, 0x8e, 0x0e, 0x44, 0x40, 0x77, 0xb9, 0x95, 0x40, 0xd8, - 0x86, 0xe7, 0xdc, 0x09, 0x55, 0x5d, 0xd6, 0xfa, 0xea, 0x2c, 0xd3, 0x69, 0x7f, 0x1e, 0x08, 0x9f, - 0x82, 0xd5, 0x4e, 0x5d, 0x0f, 0xe4, 0xa1, 0x85, 0x00, 0x8b, 0x5c, 0xbc, 0x39, 0x79, 0x39, 0x1a, - 0xd7, 0x16, 0x86, 0xbc, 0x03, 0xbe, 0x7b, 0x00, 0xea, 0x7e, 0x42, 0xc0, 0x8d, 0x9f, 0x1d, 0x75, - 0xc3, 0xa5, 0x6c, 0x27, 0xae, 0x24, 0x67, 0xb8, 0x06, 0x36, 0xc0, 0xb5, 0x34, 0x3e, 0xda, 0x7c, - 0xd5, 0x78, 0xba, 0x88, 0xdd, 0xb7, 0xa0, 0x76, 0x65, 0x68, 0x47, 0x7f, 0xed, 0x63, 0xcf, 0x53, - 0x18, 0x62, 0x12, 0x2c, 0x6c, 0x15, 0xb4, 0xa7, 0x07, 0x97, 0x3d, 0x41, 0x78, 0x2c, 0xfc, 0x0e, - 0xf4, 0xfe, 0x6c, 0x31, 0x15, 0x98, 0x8a, 0x2e, 0x33, 0x90, 0x15, 0x93, 0x8d, 0x2d, 0xf0, 0xa5, - 0xd3, 0x02, 0x37, 0xa2, 0x59, 0x2c, 0xc1, 0x0c, 0x05, 0xa9, 0xe4, 0xef, 0x6b, 0x69, 0x5b, 0xca, - 0x99, 0x73, 0x6b, 0x1a, 0x49, 0xea, 0x39, 0x60, 0x6a, 0x38, 0x1e, 0xcf, 0xb0, 0x5e, 0xfe, 0x60, - 0xd2, 0x8b, 0x54, 0x82, 0x3e, 0xc5, 0xa3, 0x68, 0x0c, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0x40, 0xe2, 0x01, 0x00, -]; - -pub const AUTH_CREDENTIAL_PRESENTATION_V2_RESULT: [u8; - zkgroup::AUTH_CREDENTIAL_PRESENTATION_V2_LEN] = [ - 0x01, 0x32, 0x2f, 0x91, 0x00, 0xde, 0x07, 0x34, 0x55, 0x0a, 0x81, 0xdc, 0x81, 0x72, 0x4a, 0x81, - 0xdb, 0xd3, 0xb1, 0xb4, 0x3d, 0xbc, 0x1d, 0x55, 0x2d, 0x53, 0x45, 0x59, 0x11, 0xc2, 0x77, 0x2f, - 0x34, 0xa6, 0x35, 0x6c, 0xa1, 0x7c, 0x6d, 0x34, 0xd8, 0x58, 0x39, 0x14, 0x56, 0xaf, 0x55, 0xd0, - 0xef, 0x84, 0x1f, 0xbe, 0x1f, 0xa8, 0xc4, 0xee, 0x81, 0x0f, 0x21, 0xe0, 0xbb, 0x9f, 0x4a, 0xce, - 0x4c, 0x5c, 0x48, 0xc7, 0x2e, 0xbb, 0xeb, 0x2c, 0xcd, 0xa5, 0xf7, 0xaa, 0x49, 0xae, 0xe6, 0xbc, - 0x00, 0x51, 0xcd, 0xde, 0x16, 0x6e, 0x0f, 0x8c, 0x5f, 0x1f, 0xeb, 0xd5, 0x3a, 0x44, 0x37, 0xc5, - 0x70, 0xee, 0x1a, 0xa2, 0x23, 0xf5, 0xeb, 0x93, 0x7d, 0xb9, 0x8f, 0x34, 0xe3, 0x65, 0x3d, 0x85, - 0xec, 0x16, 0x3f, 0x39, 0x84, 0x72, 0x22, 0xa2, 0xde, 0xc4, 0x23, 0x5e, 0xa4, 0x1c, 0x47, 0xbb, - 0x62, 0x02, 0x8a, 0xae, 0x30, 0x94, 0x58, 0x57, 0xee, 0x77, 0x66, 0x30, 0x79, 0xbc, 0xc4, 0x92, - 0x3d, 0x14, 0xa4, 0x3a, 0xd4, 0xf6, 0xbc, 0x33, 0x71, 0x50, 0x46, 0xf7, 0xbd, 0xe5, 0x27, 0x15, - 0x37, 0x5c, 0xa9, 0xf8, 0x9b, 0xe0, 0xe6, 0x30, 0xd4, 0xbd, 0xaa, 0x21, 0x11, 0x56, 0xd0, 0x30, - 0x67, 0x23, 0xf5, 0x43, 0xb0, 0x6f, 0x5e, 0x99, 0x84, 0x47, 0xb9, 0x62, 0xc8, 0xe9, 0x72, 0x9b, - 0x4c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xd0, 0xea, 0xe8, 0xe4, 0x31, 0x1a, - 0x6a, 0xe3, 0xd2, 0x97, 0x0e, 0xf1, 0x98, 0xc3, 0x98, 0x11, 0x04, 0x62, 0xbe, 0x47, 0xdd, 0x2f, - 0x26, 0xe6, 0x55, 0x92, 0x09, 0xef, 0x6c, 0xc2, 0x00, 0x01, 0xa0, 0x5a, 0x0b, 0x31, 0x9a, 0x17, - 0x2d, 0xbe, 0xb2, 0x29, 0x3c, 0xc1, 0xe0, 0xe1, 0x91, 0xce, 0xfb, 0x23, 0xe2, 0x4c, 0xf0, 0xd6, - 0xb4, 0xb5, 0x37, 0x3a, 0x30, 0x04, 0x4b, 0xe1, 0x0c, 0xb0, 0x33, 0x67, 0x4d, 0x63, 0x1e, 0x17, - 0xdf, 0xce, 0x09, 0x39, 0x8f, 0x23, 0x4e, 0x9d, 0x62, 0xe1, 0x18, 0xa6, 0x07, 0x7c, 0xae, 0xa0, - 0xef, 0x8b, 0xf6, 0x7d, 0x7d, 0x72, 0x3d, 0xb7, 0x0f, 0xec, 0xf2, 0x09, 0x8f, 0xa0, 0x41, 0x31, - 0x7b, 0x7b, 0xe9, 0xfd, 0xbb, 0x68, 0xb0, 0xf2, 0x5f, 0x5c, 0x47, 0x9d, 0x68, 0xbd, 0x91, 0x7f, - 0xc6, 0xf1, 0x87, 0xc5, 0xbf, 0x7a, 0x58, 0x91, 0x02, 0x31, 0x92, 0x1f, 0xc4, 0x35, 0x65, 0x23, - 0x24, 0x66, 0x32, 0x5c, 0x03, 0x92, 0x12, 0x36, 0x2b, 0x6d, 0x12, 0x03, 0xcc, 0xae, 0xdf, 0x83, - 0x1d, 0xc7, 0xf9, 0x06, 0x0d, 0xca, 0xaf, 0xfa, 0x02, 0x62, 0x40, 0x42, 0x17, 0x1f, 0x5f, 0x0e, - 0x78, 0x0b, 0x9f, 0x74, 0xcf, 0xa8, 0x8a, 0x14, 0x7f, 0x3f, 0x1c, 0x08, 0x2f, 0x9c, 0xa8, 0x63, - 0x8a, 0xf1, 0x78, 0x8e, 0x78, 0x99, 0xcb, 0xae, 0x0c, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0x40, 0xe2, 0x01, 0x00, -]; - -pub const AUTH_CREDENTIAL_PRESENTATION_V3_RESULT: &[u8] = &[ - 0x02, 0xec, 0x23, 0x74, 0x62, 0x4e, 0xe8, 0xde, 0x07, 0x39, 0x3f, 0x4c, 0x4f, 0x62, 0x5a, 0xfe, - 0x17, 0x93, 0xa3, 0xfe, 0x0c, 0xfc, 0xf1, 0x9a, 0x44, 0x7e, 0xe9, 0x36, 0x67, 0xe5, 0x2d, 0xc7, - 0x76, 0x38, 0x00, 0x38, 0x2c, 0x6e, 0xe4, 0x1e, 0x49, 0xbb, 0x60, 0xc4, 0x0c, 0xbd, 0x76, 0x65, - 0x7e, 0x1f, 0x6c, 0x73, 0x7f, 0x50, 0x2d, 0x6f, 0x47, 0xab, 0xe1, 0x6b, 0xd4, 0xef, 0xab, 0x1f, - 0x71, 0x94, 0x8d, 0x76, 0x34, 0x77, 0x1c, 0xd0, 0x45, 0x73, 0xa7, 0x5f, 0x3c, 0x8e, 0x77, 0xe7, - 0x0c, 0x55, 0xf5, 0x55, 0x07, 0x53, 0xad, 0x07, 0x7c, 0xfe, 0x5b, 0xb3, 0xed, 0xee, 0x0b, 0x0e, - 0x2a, 0xb8, 0x08, 0x72, 0x85, 0x65, 0x3d, 0xf8, 0x41, 0x5b, 0x9f, 0xea, 0x2f, 0x54, 0x10, 0xc4, - 0x09, 0x40, 0x59, 0xa2, 0x21, 0x7e, 0x28, 0x08, 0x65, 0xbf, 0xeb, 0xa6, 0x60, 0x53, 0x8d, 0xa2, - 0x07, 0x86, 0x34, 0x6d, 0xa3, 0x34, 0xc6, 0x7b, 0xfc, 0x4d, 0x70, 0x6f, 0x72, 0x51, 0x17, 0xa7, - 0x5e, 0x60, 0xc6, 0xfa, 0x24, 0x2e, 0x27, 0x35, 0xfc, 0x23, 0x61, 0xd1, 0x29, 0xb7, 0xab, 0x79, - 0x3a, 0x10, 0x0a, 0x6a, 0x49, 0x17, 0x95, 0x9f, 0x0f, 0x87, 0xef, 0x75, 0xef, 0x35, 0x35, 0x0e, - 0x6d, 0xa9, 0xd7, 0xd6, 0x38, 0xcf, 0x8c, 0xf9, 0xf1, 0x06, 0xc9, 0x47, 0x34, 0xa3, 0x2b, 0x85, - 0x33, 0x74, 0x41, 0xd2, 0x2a, 0x99, 0xcf, 0x08, 0xc2, 0x4d, 0x11, 0xf4, 0xe7, 0xbe, 0xdd, 0xbf, - 0x7f, 0xc9, 0x1a, 0x10, 0x14, 0x52, 0x15, 0xb9, 0x50, 0xa2, 0xb7, 0x8e, 0x7f, 0xbb, 0xc7, 0x70, - 0x7f, 0xaa, 0x0d, 0xe2, 0x54, 0x12, 0x5c, 0xba, 0xc9, 0x8f, 0x02, 0x14, 0x67, 0xf5, 0x40, 0x15, - 0x1c, 0x57, 0x73, 0x66, 0x80, 0x0f, 0xee, 0x1f, 0xc6, 0xea, 0x47, 0x30, 0xee, 0x58, 0xcd, 0x74, - 0x60, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0xf8, 0x7a, 0x5e, 0xa8, 0x7d, 0x8b, - 0xb0, 0x05, 0x16, 0x62, 0x33, 0x94, 0xff, 0x9b, 0x82, 0xce, 0xd6, 0x94, 0x77, 0x36, 0x0c, 0x21, - 0xe0, 0x0e, 0x44, 0xda, 0x18, 0x71, 0x29, 0xd8, 0x0d, 0x41, 0x4d, 0xa3, 0xf3, 0xd2, 0x6d, 0x2d, - 0x22, 0xaf, 0x93, 0xd6, 0x59, 0xc4, 0x81, 0x6c, 0x75, 0x04, 0x6e, 0x59, 0x7a, 0xb6, 0x14, 0xf0, - 0x9e, 0xdd, 0xa5, 0x81, 0xb0, 0x81, 0x62, 0x07, 0x0d, 0xa8, 0xc1, 0x23, 0x4c, 0x65, 0x26, 0x84, - 0x96, 0xdb, 0x7b, 0xa8, 0xaa, 0x3b, 0x81, 0xe6, 0x73, 0x36, 0xf5, 0x31, 0x74, 0x92, 0x65, 0x73, - 0xba, 0x5a, 0x00, 0x7b, 0xba, 0xa2, 0xa2, 0x3b, 0x01, 0xd0, 0x9f, 0xa4, 0x19, 0x98, 0xbd, 0xfe, - 0xb4, 0x9e, 0x4e, 0x7d, 0x07, 0x7b, 0x81, 0x69, 0x34, 0x21, 0xc0, 0x5a, 0xf8, 0x1c, 0x53, 0xa5, - 0x86, 0x81, 0x03, 0x5b, 0x72, 0xf9, 0xb6, 0xeb, 0x04, 0xe3, 0x66, 0xda, 0x64, 0xca, 0x95, 0xf5, - 0x27, 0x7e, 0x1a, 0xf3, 0xaa, 0x2f, 0x91, 0x5a, 0xcc, 0xd1, 0x20, 0xca, 0xd6, 0x75, 0x78, 0xf7, - 0xa4, 0xd0, 0x75, 0xbd, 0x3c, 0xc9, 0x1c, 0x07, 0x09, 0x5d, 0xb5, 0x4b, 0x02, 0x03, 0x95, 0x54, - 0x19, 0x67, 0x77, 0x23, 0xad, 0x3e, 0xe7, 0xff, 0x95, 0xf3, 0x85, 0x85, 0x58, 0xae, 0xb0, 0x40, - 0x42, 0x7c, 0xb8, 0x1a, 0xb0, 0x2b, 0xe7, 0xe6, 0x0c, 0xea, 0x45, 0xdc, 0x2f, 0x4a, 0xd1, 0x2e, - 0x70, 0xd3, 0x2f, 0x67, 0x74, 0x12, 0x28, 0xfd, 0x57, 0x89, 0xb7, 0x11, 0x0b, 0x4a, 0x31, 0x69, - 0xc1, 0x3a, 0xd6, 0x6e, 0x4c, 0x9a, 0xa3, 0xcd, 0x08, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0xde, 0x74, 0x9a, 0x0f, 0xef, 0x17, 0xb0, - 0x6b, 0xbc, 0xe7, 0x4c, 0xa5, 0xd0, 0xf7, 0xa0, 0xc4, 0x5f, 0x44, 0x3a, 0x19, 0x01, 0xf1, 0xe3, - 0xe0, 0x16, 0xd3, 0x54, 0x8e, 0x50, 0xa2, 0xfa, 0x19, 0xce, 0x6b, 0x27, 0xac, 0x46, 0x7e, 0xd9, - 0xc9, 0xf5, 0x01, 0x8b, 0x2a, 0x44, 0x56, 0xb6, 0xc2, 0xb1, 0xa9, 0x14, 0x54, 0x42, 0x2f, 0xdd, - 0x47, 0x3c, 0x96, 0x36, 0xa8, 0x45, 0x9e, 0x1c, 0x17, 0x00, 0x60, 0xc7, 0x7b, 0x02, 0x00, 0x00, - 0x00, -]; - -pub const AUTH_CREDENTIAL_PRESENTATION_V3_RESULT_WITH_PNI_AS_ACI: &[u8] = &[ - 0x02, 0xec, 0x23, 0x74, 0x62, 0x4e, 0xe8, 0xde, 0x07, 0x39, 0x3f, 0x4c, 0x4f, 0x62, 0x5a, 0xfe, - 0x17, 0x93, 0xa3, 0xfe, 0x0c, 0xfc, 0xf1, 0x9a, 0x44, 0x7e, 0xe9, 0x36, 0x67, 0xe5, 0x2d, 0xc7, - 0x76, 0x38, 0x00, 0x38, 0x2c, 0x6e, 0xe4, 0x1e, 0x49, 0xbb, 0x60, 0xc4, 0x0c, 0xbd, 0x76, 0x65, - 0x7e, 0x1f, 0x6c, 0x73, 0x7f, 0x50, 0x2d, 0x6f, 0x47, 0xab, 0xe1, 0x6b, 0xd4, 0xef, 0xab, 0x1f, - 0x71, 0x94, 0x8d, 0x76, 0x34, 0x77, 0x1c, 0xd0, 0x45, 0x73, 0xa7, 0x5f, 0x3c, 0x8e, 0x77, 0xe7, - 0x0c, 0x55, 0xf5, 0x55, 0x07, 0x53, 0xad, 0x07, 0x7c, 0xfe, 0x5b, 0xb3, 0xed, 0xee, 0x0b, 0x0e, - 0x2a, 0xb8, 0x08, 0x72, 0x85, 0x65, 0x3d, 0xf8, 0x41, 0x5b, 0x9f, 0xea, 0x2f, 0x54, 0x10, 0xc4, - 0x09, 0x40, 0x59, 0xa2, 0x21, 0x7e, 0x28, 0x08, 0x65, 0xbf, 0xeb, 0xa6, 0x60, 0x53, 0x8d, 0xa2, - 0x07, 0xf8, 0x87, 0x9e, 0x4a, 0xfc, 0x64, 0xbb, 0x83, 0x90, 0xb8, 0xdd, 0xc4, 0x67, 0x61, 0x86, - 0x87, 0x0e, 0x5a, 0x34, 0x5a, 0x85, 0x32, 0x5a, 0xb1, 0xdf, 0xd9, 0x8b, 0xe0, 0x19, 0x53, 0x1a, - 0x16, 0x10, 0x0a, 0x6a, 0x49, 0x17, 0x95, 0x9f, 0x0f, 0x87, 0xef, 0x75, 0xef, 0x35, 0x35, 0x0e, - 0x6d, 0xa9, 0xd7, 0xd6, 0x38, 0xcf, 0x8c, 0xf9, 0xf1, 0x06, 0xc9, 0x47, 0x34, 0xa3, 0x2b, 0x85, - 0x33, 0x74, 0x41, 0xd2, 0x2a, 0x99, 0xcf, 0x08, 0xc2, 0x4d, 0x11, 0xf4, 0xe7, 0xbe, 0xdd, 0xbf, - 0x7f, 0xc9, 0x1a, 0x10, 0x14, 0x52, 0x15, 0xb9, 0x50, 0xa2, 0xb7, 0x8e, 0x7f, 0xbb, 0xc7, 0x70, - 0x7f, 0x8e, 0xeb, 0xb4, 0x75, 0x78, 0xae, 0x1c, 0x9d, 0xa0, 0xaf, 0x2f, 0x74, 0xeb, 0x26, 0x75, - 0xc8, 0xae, 0xc0, 0x4c, 0x6d, 0xf8, 0x0b, 0x56, 0x6e, 0xd3, 0xce, 0xd4, 0x5c, 0xd9, 0x52, 0x04, - 0x53, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xaf, 0xf5, 0x41, 0xb1, 0xef, 0x79, - 0xa2, 0x82, 0xca, 0x4e, 0x55, 0x3e, 0xd1, 0xe2, 0xcc, 0x3e, 0x1c, 0x95, 0x51, 0x0c, 0x6d, 0x3b, - 0x90, 0x37, 0xa5, 0xa6, 0x72, 0x41, 0x1a, 0x35, 0x0e, 0x82, 0x5c, 0xb0, 0xfd, 0xbf, 0x4c, 0xc5, - 0x17, 0xdd, 0xaa, 0x48, 0x2b, 0x4b, 0x24, 0x67, 0xec, 0x91, 0x36, 0x25, 0x44, 0x15, 0x3b, 0x83, - 0x19, 0x1a, 0xba, 0x0b, 0x41, 0x3c, 0xa0, 0xb0, 0x00, 0x34, 0xa1, 0xa9, 0x49, 0xd1, 0x07, 0xb3, - 0xe7, 0x41, 0x56, 0xd5, 0xd9, 0xb1, 0x96, 0x48, 0x0e, 0xe3, 0x7f, 0x0d, 0xf1, 0xb4, 0x2a, 0x13, - 0x6f, 0xfe, 0xa8, 0x96, 0x81, 0x39, 0xa0, 0xaa, 0x01, 0x59, 0x5f, 0xc8, 0xe2, 0x02, 0x31, 0xf3, - 0x58, 0x78, 0x06, 0xc9, 0xc4, 0x71, 0x3c, 0x87, 0xac, 0xc8, 0xfe, 0xf6, 0x0d, 0x3f, 0x1a, 0x84, - 0x9d, 0x18, 0x4d, 0xd4, 0x95, 0x58, 0x8b, 0xf3, 0x03, 0xb1, 0xfc, 0xe7, 0x5e, 0x4c, 0x7a, 0x17, - 0x7c, 0x64, 0x4b, 0x62, 0xd2, 0xad, 0xbc, 0x04, 0x2c, 0x77, 0x98, 0xcf, 0xd6, 0xc4, 0x39, 0x21, - 0x54, 0x16, 0x49, 0xd2, 0x3a, 0x90, 0xe8, 0x7d, 0x0f, 0x14, 0x30, 0x30, 0x50, 0xfc, 0xc1, 0x5d, - 0x97, 0x5a, 0xef, 0x8d, 0x56, 0xbf, 0xab, 0xcf, 0x55, 0xb0, 0x5e, 0x93, 0x44, 0x91, 0x6b, 0xb6, - 0xae, 0xb4, 0x35, 0x18, 0x98, 0xdf, 0x15, 0x89, 0x03, 0xbe, 0x4f, 0xef, 0xc9, 0x33, 0xc6, 0xa2, - 0x03, 0x2e, 0x34, 0x3a, 0x59, 0xf1, 0xce, 0x1e, 0x1f, 0x4d, 0xe8, 0xf3, 0x81, 0xa5, 0x9f, 0x35, - 0xf9, 0x8c, 0x3c, 0x22, 0x49, 0x36, 0xfe, 0x54, 0x04, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0xfe, 0x74, 0x40, 0x90, 0x60, 0x61, 0x56, - 0x79, 0xfc, 0x11, 0x54, 0x73, 0x68, 0x3d, 0x63, 0xab, 0xd9, 0xce, 0xd4, 0x6c, 0x7f, 0x2a, 0xd7, - 0x36, 0x04, 0x6d, 0xe5, 0xa2, 0xc7, 0xd2, 0x52, 0x2f, 0x12, 0x28, 0x95, 0x59, 0x70, 0x49, 0xcf, - 0xd7, 0xcc, 0x5b, 0xeb, 0x6d, 0xc7, 0x2a, 0xa9, 0x90, 0xae, 0x9a, 0x62, 0xec, 0x8e, 0x25, 0x6a, - 0x1c, 0xbf, 0x5f, 0x3f, 0x28, 0x42, 0x33, 0xbb, 0x07, 0x00, 0x60, 0xc7, 0x7b, 0x02, 0x00, 0x00, - 0x00, -]; - -pub const PROFILE_KEY_CREDENTIAL_PRESENTATION_V1: &[u8] = &[ - 0x00, 0xc4, 0xd1, 0x9b, 0xca, 0x1a, 0xe8, 0x44, 0x58, 0x51, 0x68, 0x86, 0x9d, 0xa4, 0x13, 0x3e, - 0x0e, 0x0b, 0xb5, 0x9f, 0x2c, 0xe1, 0x7b, 0x7a, 0xc6, 0x5b, 0xff, 0x5d, 0xa9, 0x61, 0x0e, 0xca, - 0x10, 0x34, 0x29, 0xd8, 0x02, 0x2a, 0x94, 0xba, 0xe2, 0xb5, 0xb1, 0x05, 0x7b, 0x55, 0x95, 0xb8, - 0xad, 0x70, 0xbf, 0xc2, 0xd0, 0xe1, 0xad, 0x66, 0x2c, 0xb7, 0x5e, 0x6b, 0xae, 0x07, 0x82, 0xbe, - 0x6f, 0x00, 0xe3, 0xdb, 0x79, 0x3b, 0xc2, 0x85, 0x61, 0xf0, 0x19, 0x6c, 0x2e, 0x74, 0xda, 0x6f, - 0x30, 0x3f, 0xa8, 0xbc, 0xb7, 0x0c, 0x94, 0x09, 0x66, 0x71, 0xb7, 0x3f, 0x7b, 0x3a, 0x95, 0xfb, - 0x00, 0x22, 0x00, 0xd5, 0xb9, 0x18, 0x0f, 0xa0, 0xef, 0x7d, 0x30, 0x14, 0xd0, 0x13, 0x44, 0x14, - 0x5b, 0x4d, 0x38, 0x48, 0x0d, 0x72, 0xff, 0x25, 0xc2, 0x42, 0x94, 0xe3, 0x05, 0xe5, 0x70, 0x50, - 0x72, 0xe0, 0xd3, 0x2c, 0xc4, 0xe8, 0x4f, 0x5c, 0xaf, 0x31, 0x48, 0x60, 0x89, 0xa4, 0xb9, 0x34, - 0xc8, 0x0c, 0x92, 0xeb, 0xa4, 0x34, 0x72, 0xff, 0x23, 0xa5, 0xaf, 0x93, 0xc3, 0x97, 0x53, 0x5d, - 0x33, 0x80, 0x1f, 0x0e, 0x6f, 0xc6, 0xeb, 0x2e, 0xe0, 0xd1, 0x17, 0xf0, 0x3b, 0xb4, 0xfd, 0x38, - 0xa8, 0xb9, 0xc8, 0x8d, 0x94, 0x70, 0x81, 0x31, 0xf3, 0x87, 0x42, 0xca, 0x80, 0x4a, 0x3c, 0xfc, - 0x4f, 0x94, 0x76, 0xbc, 0x2d, 0x03, 0xf5, 0x3d, 0x17, 0x00, 0x1c, 0x36, 0x47, 0x8a, 0xfb, 0xe9, - 0xcc, 0x53, 0x5a, 0x22, 0x4b, 0x2d, 0xf6, 0xb2, 0xb0, 0x8b, 0xef, 0x06, 0xcb, 0xc7, 0xd4, 0xdc, - 0x42, 0xcc, 0xfc, 0x34, 0x59, 0xf7, 0xac, 0x5c, 0x44, 0x19, 0xae, 0x9f, 0x3c, 0x8a, 0x16, 0x1d, - 0x55, 0x4d, 0x04, 0x77, 0x78, 0x94, 0x32, 0x16, 0x24, 0x08, 0x58, 0xda, 0x3b, 0x11, 0x01, 0x98, - 0x4c, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x01, 0xee, 0xa6, 0xb2, 0xad, 0xad, - 0x14, 0xd7, 0x1a, 0xb8, 0xb8, 0xe4, 0x11, 0xbe, 0xf3, 0xc5, 0x96, 0xe9, 0x54, 0xb7, 0x0e, 0x40, - 0x31, 0x57, 0x0c, 0xb1, 0xab, 0xd7, 0xe9, 0x32, 0x08, 0x32, 0x41, 0xf1, 0xca, 0xca, 0x31, 0x16, - 0x70, 0x8f, 0xa4, 0x31, 0x9f, 0xbb, 0xdf, 0xe3, 0x51, 0x37, 0x6c, 0x23, 0x64, 0x4a, 0xe0, 0x9a, - 0x42, 0xf0, 0x15, 0x5d, 0xb4, 0x99, 0x6c, 0x9d, 0x0c, 0x7f, 0xfc, 0x85, 0x21, 0xc1, 0x91, 0x4c, - 0x0e, 0x1a, 0x20, 0xae, 0x51, 0xe6, 0x5d, 0xf6, 0x4d, 0xd5, 0xe6, 0xe5, 0x98, 0x5b, 0x3d, 0x9d, - 0x31, 0x73, 0x20, 0x46, 0xd2, 0xd7, 0x7f, 0x9c, 0x08, 0xaa, 0xcc, 0xf0, 0x56, 0xb8, 0x40, 0x26, - 0x07, 0x39, 0x76, 0xee, 0xc6, 0x16, 0x4c, 0xbd, 0xae, 0xe5, 0xd9, 0xe7, 0x6e, 0x49, 0x7f, 0x0c, - 0x29, 0x0a, 0xf6, 0x81, 0xca, 0xbd, 0x5c, 0x51, 0x01, 0x28, 0x2a, 0xbb, 0x26, 0xc3, 0x68, 0x0d, - 0x60, 0x87, 0xce, 0x05, 0x33, 0x10, 0xfe, 0x8a, 0x94, 0xf5, 0x9d, 0x8a, 0xe2, 0x3c, 0xaa, 0xc5, - 0xfc, 0x0e, 0xd0, 0xc3, 0x79, 0x88, 0x8a, 0xbf, 0x02, 0x8a, 0x6f, 0x29, 0xf8, 0x9d, 0x4f, 0xe2, - 0xac, 0xc1, 0x70, 0x63, 0x41, 0xb2, 0x24, 0x5b, 0xa1, 0x88, 0x5b, 0xca, 0x57, 0xe1, 0xe2, 0x7c, - 0xcf, 0x7e, 0xd7, 0x93, 0x71, 0x50, 0x09, 0x65, 0x00, 0x9f, 0x96, 0x0c, 0x2b, 0xa0, 0x0f, 0xad, - 0x3e, 0x93, 0x38, 0x3b, 0x87, 0xce, 0x11, 0x9c, 0xac, 0x0b, 0x33, 0x60, 0xeb, 0x99, 0x28, 0x4c, - 0xe7, 0x8e, 0x2c, 0xbe, 0xd6, 0x80, 0xf7, 0x96, 0x03, 0x73, 0xe0, 0xab, 0x75, 0xc1, 0x90, 0x25, - 0x41, 0x60, 0xc2, 0x35, 0x36, 0x14, 0x10, 0x94, 0x89, 0xe6, 0x53, 0xc9, 0xb2, 0xe1, 0xc9, 0x3f, - 0x92, 0xc7, 0xc5, 0xad, 0x58, 0x3d, 0x98, 0x7a, 0x04, 0xbd, 0x35, 0x41, 0xb2, 0x44, 0x85, 0xc3, - 0x3e, 0xa4, 0x9b, 0xac, 0x43, 0xc8, 0x7c, 0x4a, 0xb3, 0xef, 0xde, 0x2e, 0x2d, 0x7e, 0xc1, 0x0a, - 0x40, 0xbe, 0x54, 0x41, 0x99, 0xf9, 0x25, 0xb2, 0x0b, 0x2c, 0x55, 0x54, 0x2b, 0xc5, 0x64, 0x10, - 0x57, 0x1e, 0x41, 0xcd, 0x8e, 0x02, 0x86, 0xf6, 0x09, 0xa6, 0x67, 0x68, 0xb5, 0x06, 0x1c, 0xcb, - 0x47, 0x77, 0xaf, 0x32, 0x30, 0x99, 0x28, 0xdd, 0x09, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0x48, 0xc0, 0x3a, 0xb4, 0xaf, 0xbf, 0x6b, - 0x8f, 0xb0, 0xe1, 0x26, 0xc0, 0x37, 0xa0, 0xad, 0x40, 0x94, 0x60, 0x0d, 0xd0, 0xe0, 0x63, 0x4d, - 0x76, 0xf8, 0x8c, 0x21, 0x08, 0x7f, 0x3c, 0xfb, 0x48, 0x5a, 0x89, 0xbc, 0x1e, 0x3a, 0xbc, 0x4c, - 0x95, 0x04, 0x1d, 0x1d, 0x17, 0x0e, 0xcc, 0xf0, 0x29, 0x33, 0xec, 0x53, 0x93, 0xd4, 0xbe, 0x1d, - 0xc5, 0x73, 0xf8, 0x3c, 0x33, 0xd3, 0xb9, 0xa7, 0x46, -]; - -pub const PROFILE_KEY_CREDENTIAL_PRESENTATION_V2_RESULT: [u8; - zkgroup::PROFILE_KEY_CREDENTIAL_PRESENTATION_V2_LEN] = [ - 0x01, 0xe0, 0xf4, 0x9c, 0xef, 0x4f, 0x25, 0xc3, 0x1d, 0x1b, 0xfd, 0xc4, 0xa3, 0x28, 0xfd, 0x50, - 0x8d, 0x22, 0x22, 0xb6, 0xde, 0xce, 0xe2, 0xa2, 0x53, 0xcf, 0x71, 0xe8, 0x82, 0x1e, 0x97, 0xcc, - 0x3f, 0x86, 0x82, 0x4f, 0x79, 0xb1, 0x88, 0x4b, 0x43, 0xc6, 0x7f, 0x85, 0x47, 0x17, 0xb1, 0xa4, - 0x7f, 0x56, 0xc8, 0xff, 0x50, 0xa1, 0xc0, 0x7f, 0xdd, 0xbf, 0x4f, 0x6e, 0x85, 0x70, 0x27, 0xd5, - 0x48, 0x58, 0x3b, 0x54, 0x07, 0x9d, 0xd6, 0x1d, 0x54, 0xcd, 0xd3, 0x9c, 0xd4, 0xac, 0xae, 0x5f, - 0x8b, 0x3b, 0xbf, 0xa2, 0xbb, 0x6b, 0x35, 0x02, 0xb6, 0x9b, 0x36, 0xda, 0x77, 0xad, 0xdd, 0xdc, - 0x14, 0x5e, 0xf2, 0x54, 0xa1, 0x6f, 0x2b, 0xae, 0xc1, 0xe3, 0xd7, 0xe8, 0xdc, 0x80, 0x73, 0x0b, - 0xc6, 0x08, 0xfc, 0xd0, 0xe4, 0xd8, 0xcf, 0xef, 0x33, 0x30, 0xa4, 0x96, 0x38, 0x0c, 0x7a, 0xc6, - 0x48, 0x68, 0x6b, 0x9c, 0x5b, 0x91, 0x4d, 0x0a, 0x77, 0xee, 0x84, 0x84, 0x8a, 0xa9, 0x70, 0xb2, - 0x40, 0x44, 0x50, 0x17, 0x9b, 0x40, 0x22, 0xee, 0xf0, 0x03, 0x38, 0x7f, 0x6b, 0xdb, 0xcb, 0xa3, - 0x03, 0x44, 0xca, 0xdf, 0xd5, 0xe3, 0xf1, 0x67, 0x7c, 0xaa, 0x2c, 0x78, 0x5f, 0x4f, 0xef, 0xe0, - 0x42, 0xa1, 0xb2, 0xad, 0xf4, 0xf4, 0xb8, 0xfa, 0x60, 0x23, 0xe4, 0x1d, 0x70, 0x4b, 0xda, 0x90, - 0x1d, 0x3a, 0x69, 0x79, 0x04, 0x77, 0x0a, 0xc4, 0x6e, 0x0e, 0x30, 0x4c, 0xf1, 0x9f, 0x91, 0xce, - 0x9a, 0xb0, 0xed, 0x1c, 0xca, 0xd8, 0xa6, 0xfe, 0xbd, 0x72, 0x31, 0x34, 0x55, 0xf1, 0x39, 0xb9, - 0x22, 0x2e, 0x9a, 0x30, 0xa2, 0x26, 0x5c, 0x6c, 0xd2, 0x2e, 0xe5, 0xb9, 0x07, 0xfc, 0x95, 0x96, - 0x74, 0x17, 0xa0, 0xd8, 0xca, 0x33, 0x8a, 0x5e, 0xe4, 0xd5, 0x1b, 0xba, 0x78, 0x03, 0x9c, 0x31, - 0x4e, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x9d, 0x54, 0x77, 0x2b, 0x81, 0x37, - 0xe5, 0x70, 0x15, 0x7c, 0x06, 0x8a, 0x5c, 0xfe, 0xbb, 0x46, 0x4b, 0x6c, 0x11, 0x33, 0xc7, 0x2d, - 0x9a, 0xbf, 0xda, 0x72, 0xdb, 0x42, 0x1c, 0xd0, 0x05, 0x61, 0xac, 0x4e, 0xec, 0xb9, 0x43, 0x13, - 0xc6, 0x91, 0x20, 0x13, 0xe3, 0x2c, 0x32, 0x2e, 0xa3, 0x67, 0x43, 0xb0, 0x18, 0x14, 0xfe, 0x91, - 0x9c, 0xa8, 0x4b, 0x9a, 0xea, 0x9c, 0x78, 0xb1, 0x0b, 0xa0, 0x21, 0x50, 0x6f, 0x7a, 0xd8, 0xc6, - 0x62, 0x5e, 0x87, 0xe0, 0x7c, 0xe3, 0x2b, 0x55, 0x90, 0x36, 0xaf, 0x6b, 0x67, 0xe2, 0xc0, 0x38, - 0x3a, 0x64, 0x3c, 0xb9, 0x3c, 0xdc, 0x2b, 0x98, 0x00, 0xe9, 0x05, 0x88, 0xa1, 0x8f, 0xcc, 0x44, - 0x9c, 0xd4, 0x66, 0xc2, 0x8c, 0x6d, 0xb7, 0x35, 0x07, 0xd8, 0x28, 0x2d, 0xd0, 0x08, 0x08, 0xb5, - 0x92, 0x7f, 0xee, 0x33, 0x36, 0xed, 0x0a, 0x22, 0x02, 0xdf, 0xb1, 0xe1, 0x76, 0xfe, 0xce, 0x6a, - 0x41, 0x04, 0xca, 0xa2, 0xa8, 0x66, 0xc4, 0x75, 0x20, 0x99, 0x67, 0x63, 0x8e, 0xa2, 0xf1, 0x46, - 0x68, 0x47, 0xda, 0x73, 0x01, 0xa7, 0x7b, 0x90, 0x07, 0xdf, 0xb3, 0x32, 0xa3, 0x0e, 0x9b, 0xbf, - 0xae, 0x8a, 0x83, 0x98, 0x16, 0x5e, 0xc9, 0xdd, 0x47, 0x78, 0x21, 0x4e, 0x0d, 0x6e, 0xd3, 0x5a, - 0x34, 0x07, 0x1b, 0xdf, 0x3b, 0x3b, 0x19, 0x51, 0x0f, 0xf2, 0xa6, 0x17, 0xbc, 0x53, 0xeb, 0x0e, - 0x6b, 0x0d, 0xdc, 0x50, 0x1d, 0xb0, 0x27, 0xbb, 0x47, 0xe4, 0xf4, 0x12, 0x7d, 0x7a, 0x01, 0x04, - 0x94, 0x5f, 0x3d, 0x3d, 0xc7, 0xec, 0x17, 0x41, 0x03, 0x8b, 0x9b, 0x80, 0xe2, 0xc7, 0xf1, 0x31, - 0xc5, 0x19, 0xee, 0x26, 0xff, 0xcb, 0x7c, 0xb9, 0xd3, 0x55, 0x6c, 0xd3, 0x5a, 0x12, 0xbe, 0xf1, - 0xd4, 0xb3, 0x76, 0xfc, 0x51, 0x31, 0x97, 0xba, 0x00, 0xce, 0x8f, 0x01, 0x2a, 0x0b, 0x37, 0x41, - 0x64, 0x22, 0x2b, 0xa7, 0x9a, 0x39, 0xe7, 0x4e, 0x15, 0x08, 0x13, 0x47, 0x4c, 0xa6, 0xf8, 0x7b, - 0xa7, 0x05, 0xc0, 0xf0, 0x6e, 0x7b, 0x70, 0x68, 0x03, 0x9c, 0x5e, 0xdd, 0x9d, 0xd1, 0xa5, 0xab, - 0x67, 0x93, 0xac, 0x21, 0x19, 0x89, 0x90, 0x76, 0x86, 0xb4, 0x56, 0x50, 0x22, 0x11, 0x87, 0xd4, - 0xd5, 0x9a, 0xe4, 0x92, 0x67, 0x9f, 0x3b, 0x43, 0x08, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0x48, 0xc0, 0x3a, 0xb4, 0xaf, 0xbf, 0x6b, - 0x8f, 0xb0, 0xe1, 0x26, 0xc0, 0x37, 0xa0, 0xad, 0x40, 0x94, 0x60, 0x0d, 0xd0, 0xe0, 0x63, 0x4d, - 0x76, 0xf8, 0x8c, 0x21, 0x08, 0x7f, 0x3c, 0xfb, 0x48, 0x5a, 0x89, 0xbc, 0x1e, 0x3a, 0xbc, 0x4c, - 0x95, 0x04, 0x1d, 0x1d, 0x17, 0x0e, 0xcc, 0xf0, 0x29, 0x33, 0xec, 0x53, 0x93, 0xd4, 0xbe, 0x1d, - 0xc5, 0x73, 0xf8, 0x3c, 0x33, 0xd3, 0xb9, 0xa7, 0x46, -]; - -pub const PROFILE_KEY_CREDENTIAL_PRESENTATION_V3_RESULT: &[u8] = &[ - 0x02, 0xfc, 0x58, 0xa4, 0xf2, 0xc9, 0xbd, 0x73, 0x62, 0x38, 0xab, 0xfc, 0x28, 0x89, 0x0c, 0x8b, - 0x23, 0x63, 0xd0, 0x84, 0xbe, 0xe4, 0x30, 0x69, 0x2f, 0x05, 0xee, 0x55, 0x9b, 0xd3, 0x7d, 0xea, - 0x33, 0x78, 0x94, 0x9e, 0x72, 0xb2, 0x71, 0xfe, 0x0d, 0x81, 0x5b, 0x6d, 0x90, 0x80, 0x35, 0x10, - 0x6c, 0xd6, 0x70, 0xb4, 0x58, 0x92, 0xdf, 0x40, 0x78, 0x0c, 0x62, 0xc3, 0x7f, 0xae, 0x10, 0x6c, - 0x41, 0xbe, 0x38, 0x37, 0x1f, 0xe0, 0x42, 0xa4, 0xd4, 0xf6, 0x97, 0xdb, 0x11, 0x29, 0x72, 0xd7, - 0x92, 0x04, 0xb3, 0xd4, 0x8d, 0x12, 0x53, 0xd3, 0x23, 0x1c, 0x22, 0x92, 0x6e, 0x10, 0x7f, 0x66, - 0x1d, 0x40, 0x89, 0x7c, 0xb7, 0xfd, 0xb4, 0x77, 0x7c, 0x16, 0x80, 0xa5, 0x70, 0x08, 0x65, 0x5d, - 0xb7, 0x1e, 0xfa, 0xac, 0x1f, 0x69, 0xcd, 0x9d, 0xdf, 0x8c, 0xda, 0x33, 0xb2, 0x26, 0x66, 0x2d, - 0x7b, 0xa4, 0x43, 0x41, 0x62, 0x81, 0x50, 0x8f, 0xcd, 0xbb, 0x02, 0x6d, 0x63, 0xf8, 0x31, 0x68, - 0x47, 0x0a, 0x83, 0xe1, 0x28, 0x03, 0xa6, 0xd2, 0xee, 0x2c, 0x90, 0x73, 0x43, 0xf2, 0xf6, 0xb0, - 0x63, 0xfe, 0x6b, 0xf0, 0xf1, 0x7a, 0x03, 0x2f, 0xab, 0xe6, 0x1e, 0x77, 0xe9, 0x04, 0xdf, 0xe7, - 0xd3, 0x04, 0x21, 0x25, 0x72, 0x8c, 0x19, 0x84, 0xc8, 0x6a, 0x09, 0x4a, 0x0e, 0x39, 0x91, 0xba, - 0x55, 0x4c, 0x1e, 0xbf, 0x60, 0x4c, 0x14, 0xa8, 0xb1, 0x3c, 0x38, 0x4c, 0x5c, 0x01, 0x90, 0x96, - 0x56, 0xc1, 0x14, 0xb2, 0x4f, 0x9d, 0x36, 0x15, 0xd3, 0xb1, 0x4b, 0xde, 0x7c, 0xe9, 0xcf, 0x12, - 0x6a, 0xca, 0x3e, 0x07, 0x3e, 0x80, 0x4b, 0x20, 0x16, 0xf7, 0xc5, 0xaf, 0xfa, 0x15, 0x8a, 0x3a, - 0x68, 0xed, 0x90, 0x24, 0xc6, 0x88, 0x0e, 0xcb, 0x44, 0x1a, 0x34, 0x6e, 0x7e, 0x91, 0xae, 0xdd, - 0x62, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x70, 0xf2, 0x7f, 0xb3, 0xf4, 0xc5, - 0x8c, 0xb4, 0x0d, 0xfe, 0x58, 0xce, 0x1d, 0x12, 0x23, 0x12, 0x96, 0x94, 0x26, 0xab, 0xb0, 0xbb, - 0xb8, 0x20, 0xbf, 0xbc, 0x5f, 0xf6, 0x1d, 0x40, 0x0a, 0x41, 0x9d, 0x5d, 0xdb, 0x7c, 0x30, 0xc5, - 0x46, 0x42, 0x72, 0x73, 0xd4, 0xfc, 0xa3, 0x09, 0x6e, 0xe4, 0xdd, 0x2f, 0xd0, 0x3c, 0xcb, 0xbd, - 0x26, 0x30, 0x4f, 0xfc, 0xfe, 0x54, 0xfe, 0xf5, 0x0d, 0xb8, 0x53, 0x81, 0x77, 0xeb, 0xc6, 0x11, - 0x17, 0xa2, 0x22, 0x25, 0x3b, 0x4d, 0x41, 0x89, 0xf7, 0x95, 0xab, 0xbd, 0xe3, 0xb3, 0xd8, 0xa0, - 0xa7, 0x2d, 0x97, 0xb7, 0x75, 0x0e, 0x03, 0x94, 0x01, 0x0a, 0x01, 0xb4, 0x74, 0xc3, 0xe9, 0x42, - 0xef, 0x1e, 0xe8, 0x07, 0xe1, 0x74, 0x21, 0x68, 0x9c, 0x6c, 0xa7, 0x93, 0xc4, 0xf3, 0x0b, 0x09, - 0xc9, 0x89, 0xb8, 0xa9, 0x67, 0x9a, 0xee, 0x13, 0x0e, 0xb0, 0x34, 0xf6, 0x4a, 0x34, 0xdb, 0xca, - 0xf1, 0x26, 0x16, 0x97, 0x0d, 0x2c, 0x8d, 0x58, 0xca, 0x71, 0x5b, 0xf5, 0xc4, 0xd4, 0x24, 0x75, - 0xfa, 0x6a, 0x1b, 0x82, 0xba, 0x31, 0x57, 0x4e, 0x07, 0x25, 0x06, 0x65, 0x22, 0x53, 0xe8, 0x6c, - 0xd7, 0x83, 0xe3, 0x0e, 0x1c, 0x06, 0xd2, 0xe8, 0x61, 0xba, 0x86, 0x4a, 0x53, 0x73, 0x75, 0x94, - 0x72, 0xb3, 0x1c, 0x5b, 0x26, 0xa8, 0xe4, 0x6d, 0x06, 0x2b, 0x8b, 0x5d, 0xa2, 0xec, 0x0a, 0x3b, - 0xa4, 0x99, 0x64, 0x8e, 0x80, 0xf3, 0x07, 0x72, 0x8b, 0x78, 0x15, 0xaa, 0x60, 0xd1, 0x67, 0xa0, - 0xa9, 0xd0, 0x1c, 0x2d, 0x2c, 0xbf, 0xb0, 0xa6, 0x0d, 0xdc, 0x9d, 0xfc, 0x53, 0x43, 0x56, 0x4b, - 0x5f, 0x02, 0x1f, 0xd1, 0xad, 0xba, 0x6d, 0x2a, 0x38, 0x9e, 0x7c, 0x33, 0x1b, 0xff, 0xfe, 0xed, - 0x2a, 0x5d, 0x18, 0x87, 0x63, 0x43, 0x23, 0x84, 0x05, 0x74, 0xe4, 0x92, 0x55, 0xa6, 0x2d, 0x9e, - 0x00, 0xff, 0xc2, 0x1f, 0x56, 0xaf, 0xbb, 0x12, 0xfb, 0x96, 0x60, 0xe1, 0x85, 0xf9, 0x79, 0x22, - 0x3e, 0xc7, 0x14, 0xc0, 0x1e, 0x40, 0x3a, 0x3a, 0x0a, 0x32, 0x76, 0xd0, 0xef, 0x78, 0x18, 0x2f, - 0x12, 0xc0, 0x92, 0xf5, 0x23, 0x7b, 0xef, 0xe3, 0xf0, 0xaf, 0xea, 0x76, 0x93, 0x37, 0x07, 0x88, - 0xf8, 0x54, 0xec, 0x69, 0x7e, 0x44, 0xc9, 0xbd, 0x02, 0x76, 0x5d, 0xe9, 0xdf, 0x4c, 0xfa, 0x54, - 0x87, 0xf3, 0x60, 0xe2, 0x9e, 0x99, 0x34, 0x3e, 0x91, 0x81, 0x1b, 0xae, 0xc3, 0x31, 0xc4, 0x68, - 0x09, 0x85, 0xe6, 0x08, 0xca, 0x5d, 0x40, 0x8e, 0x21, 0x72, 0x5c, 0x6a, 0xa1, 0xb6, 0x1d, 0x5a, - 0x8b, 0x48, 0xd7, 0x5f, 0x4a, 0xaa, 0x9a, 0x3c, 0xbe, 0x88, 0xd3, 0xe0, 0xf1, 0xa5, 0x43, 0x19, - 0x08, 0x1f, 0x77, 0xc7, 0x2c, 0x8f, 0x52, 0x54, 0x74, 0x48, 0xc0, 0x3a, 0xb4, 0xaf, 0xbf, 0x6b, - 0x8f, 0xb0, 0xe1, 0x26, 0xc0, 0x37, 0xa0, 0xad, 0x40, 0x94, 0x60, 0x0d, 0xd0, 0xe0, 0x63, 0x4d, - 0x76, 0xf8, 0x8c, 0x21, 0x08, 0x7f, 0x3c, 0xfb, 0x48, 0x5a, 0x89, 0xbc, 0x1e, 0x3a, 0xbc, 0x4c, - 0x95, 0x04, 0x1d, 0x1d, 0x17, 0x0e, 0xcc, 0xf0, 0x29, 0x33, 0xec, 0x53, 0x93, 0xd4, 0xbe, 0x1d, - 0xc5, 0x73, 0xf8, 0x3c, 0x33, 0xd3, 0xb9, 0xa7, 0x46, 0x80, 0x69, 0x16, 0x00, 0x00, 0x00, 0x00, - 0x00, -]; + +const AUTH_CREDENTIAL_PRESENTATION_V1:&[u8] = &hex!( "000cde979737ed30bbeb16362e4e076945ce02069f727b0ed4c3c33c011e82546e1cdf081fbdf37c03a851ad060bdcbf6378cb4cb16dc3154d08de5439b5323203729d1841b517033af2fd177d30491c138ae723655734f6e5cc01c00696f4e92096d8c33df26ba2a820d42e9735d30f8eeef96d399079073c099f7035523bfe716638659319d3c36ad34c00ef8850f663c4d93030235074312a8878b6a5c5df4fbc7d32935278bfa5996b44ab75d6f06f4c30b98640ad5de74742656c8977567de000000000000000fde69f82ad2dcb4909650ac6b2573841af568fef822b32b45f625a764691a704d11b6f385261468117ead57fa623338e21c66ed846ab65809fcac158066d8e0e444077b99540d886e7dc09555dd6faea2cd3697f1e089f82d54e5d0fe4a185008b5cbc3979391ad71686bc03be7b00ea7e42c08d9f1d75c3a56c27ae2467b80636c0b5343eda7cd578ba88ddb7a0766568477fed63cf531862122c6c15b4a707973d41782cfc0ef4fe6c3115988a2e339015938d2df0a5d30237a2592cc10c05a9e4ef6b695bca99736b1a49ea39606a381ecfb05efe60d28b54823ec5a3680c765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547440e20100"); + +const AUTH_CREDENTIAL_PRESENTATION_V2_RESULT: [u8; zkgroup::AUTH_CREDENTIAL_PRESENTATION_V2_LEN] = hex!("01322f9100de0734550a81dc81724a81dbd3b1b43dbc1d552d53455911c2772f34a6356ca17c6d34d858391456af55d0ef841fbe1fa8c4ee810f21e0bb9f4ace4c5c48c72ebbeb2ccda5f7aa49aee6bc0051cdde166e0f8c5f1febd53a4437c570ee1aa223f5eb937db98f34e3653d85ec163f39847222a2dec4235ea41c47bb62028aae30945857ee77663079bcc4923d14a43ad4f6bc33715046f7bde52715375ca9f89be0e630d4bdaa211156d0306723f543b06f5e998447b962c8e9729b4cc00000000000000074d0eae8e4311a6ae3d2970ef198c398110462be47dd2f26e6559209ef6cc20001a05a0b319a172dbeb2293cc1e0e191cefb23e24cf0d6b4b5373a30044be10cb033674d631e17dfce09398f234e9d62e118a6077caea0ef8bf67d7d723db70fecf2098fa041317b7be9fdbb68b0f25f5c479d68bd917fc6f187c5bf7a58910231921fc43565232466325c039212362b6d1203ccaedf831dc7f9060dcaaffa02624042171f5f0e780b9f74cfa88a147f3f1c082f9ca8638af1788e7899cbae0c765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547440e20100"); + +const AUTH_CREDENTIAL_PRESENTATION_V3_RESULT: &[u8] = &hex!("02ec2374624ee8de07393f4c4f625afe1793a3fe0cfcf19a447ee93667e52dc7763800382c6ee41e49bb60c40cbd76657e1f6c737f502d6f47abe16bd4efab1f71948d7634771cd04573a75f3c8e77e70c55f5550753ad077cfe5bb3edee0b0e2ab8087285653df8415b9fea2f5410c4094059a2217e280865bfeba660538da20786346da334c67bfc4d706f725117a75e60c6fa242e2735fc2361d129b7ab793a100a6a4917959f0f87ef75ef35350e6da9d7d638cf8cf9f106c94734a32b85337441d22a99cf08c24d11f4e7beddbf7fc91a10145215b950a2b78e7fbbc7707faa0de254125cbac98f021467f540151c577366800fee1fc6ea4730ee58cd7460e0000000000000009ef87a5ea87d8bb00516623394ff9b82ced69477360c21e00e44da187129d80d414da3f3d26d2d22af93d659c4816c75046e597ab614f09edda581b08162070da8c1234c65268496db7ba8aa3b81e67336f53174926573ba5a007bbaa2a23b01d09fa41998bdfeb49e4e7d077b81693421c05af81c53a58681035b72f9b6eb04e366da64ca95f5277e1af3aa2f915accd120cad67578f7a4d075bd3cc91c07095db54b0203955419677723ad3ee7ff95f3858558aeb040427cb81ab02be7e60cea45dc2f4ad12e70d32f67741228fd5789b7110b4a3169c13ad66e4c9aa3cd08765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f525474de749a0fef17b06bbce74ca5d0f7a0c45f443a1901f1e3e016d3548e50a2fa19ce6b27ac467ed9c9f5018b2a4456b6c2b1a91454422fdd473c9636a8459e1c170060c77b02000000"); + +const AUTH_CREDENTIAL_PRESENTATION_V3_RESULT_WITH_PNI_AS_ACI: &[u8] = &hex!("02ec2374624ee8de07393f4c4f625afe1793a3fe0cfcf19a447ee93667e52dc7763800382c6ee41e49bb60c40cbd76657e1f6c737f502d6f47abe16bd4efab1f71948d7634771cd04573a75f3c8e77e70c55f5550753ad077cfe5bb3edee0b0e2ab8087285653df8415b9fea2f5410c4094059a2217e280865bfeba660538da207f8879e4afc64bb8390b8ddc4676186870e5a345a85325ab1dfd98be019531a16100a6a4917959f0f87ef75ef35350e6da9d7d638cf8cf9f106c94734a32b85337441d22a99cf08c24d11f4e7beddbf7fc91a10145215b950a2b78e7fbbc7707f8eebb47578ae1c9da0af2f74eb2675c8aec04c6df80b566ed3ced45cd9520453e000000000000000deaff541b1ef79a282ca4e553ed1e2cc3e1c95510c6d3b9037a5a672411a350e825cb0fdbf4cc517ddaa482b4b2467ec91362544153b83191aba0b413ca0b00034a1a949d107b3e74156d5d9b196480ee37f0df1b42a136ffea8968139a0aa01595fc8e20231f3587806c9c4713c87acc8fef60d3f1a849d184dd495588bf303b1fce75e4c7a177c644b62d2adbc042c7798cfd6c43921541649d23a90e87d0f14303050fcc15d975aef8d56bfabcf55b05e9344916bb6aeb4351898df158903be4fefc933c6a2032e343a59f1ce1e1f4de8f381a59f35f98c3c224936fe5404765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f525474fe74409060615679fc115473683d63abd9ced46c7f2ad736046de5a2c7d2522f122895597049cfd7cc5beb6dc72aa990ae9a62ec8e256a1cbf5f3f284233bb070060c77b02000000"); + +const PROFILE_KEY_CREDENTIAL_PRESENTATION_V1: &[u8] = &hex!("00c4d19bca1ae844585168869da4133e0e0bb59f2ce17b7ac65bff5da9610eca103429d8022a94bae2b5b1057b5595b8ad70bfc2d0e1ad662cb75e6bae0782be6f00e3db793bc28561f0196c2e74da6f303fa8bcb70c94096671b73f7b3a95fb002200d5b9180fa0ef7d3014d01344145b4d38480d72ff25c24294e305e5705072e0d32cc4e84f5caf31486089a4b934c80c92eba43472ff23a5af93c397535d33801f0e6fc6eb2ee0d117f03bb4fd38a8b9c88d94708131f38742ca804a3cfc4f9476bc2d03f53d17001c36478afbe9cc535a224b2df6b2b08bef06cbc7d4dc42ccfc3459f7ac5c4419ae9f3c8a161d554d047778943216240858da3b1101984c40010000000000007a01eea6b2adad14d71ab8b8e411bef3c596e954b70e4031570cb1abd7e932083241f1caca3116708fa4319fbbdfe351376c23644ae09a42f0155db4996c9d0c7ffc8521c1914c0e1a20ae51e65df64dd5e6e5985b3d9d31732046d2d77f9c08aaccf056b84026073976eec6164cbdaee5d9e76e497f0c290af681cabd5c5101282abb26c3680d6087ce053310fe8a94f59d8ae23caac5fc0ed0c379888abf028a6f29f89d4fe2acc1706341b2245ba1885bca57e1e27ccf7ed79371500965009f960c2ba00fad3e93383b87ce119cac0b3360eb99284ce78e2cbed680f7960373e0ab75c190254160c2353614109489e653c9b2e1c93f92c7c5ad583d987a04bd3541b24485c33ea49bac43c87c4ab3efde2e2d7ec10a40be544199f925b20b2c55542bc56410571e41cd8e0286f609a66768b5061ccb4777af32309928dd09765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547448c03ab4afbf6b8fb0e126c037a0ad4094600dd0e0634d76f88c21087f3cfb485a89bc1e3abc4c95041d1d170eccf02933ec5393d4be1dc573f83c33d3b9a746"); + +const PROFILE_KEY_CREDENTIAL_PRESENTATION_V2_RESULT: [u8; zkgroup::PROFILE_KEY_CREDENTIAL_PRESENTATION_V2_LEN] = hex!("01e0f49cef4f25c31d1bfdc4a328fd508d2222b6decee2a253cf71e8821e97cc3f86824f79b1884b43c67f854717b1a47f56c8ff50a1c07fddbf4f6e857027d548583b54079dd61d54cdd39cd4acae5f8b3bbfa2bb6b3502b69b36da77addddc145ef254a16f2baec1e3d7e8dc80730bc608fcd0e4d8cfef3330a496380c7ac648686b9c5b914d0a77ee84848aa970b2404450179b4022eef003387f6bdbcba30344cadfd5e3f1677caa2c785f4fefe042a1b2adf4f4b8fa6023e41d704bda901d3a697904770ac46e0e304cf19f91ce9ab0ed1ccad8a6febd72313455f139b9222e9a30a2265c6cd22ee5b907fc95967417a0d8ca338a5ee4d51bba78039c314e4001000000000000749d54772b8137e570157c068a5cfebb464b6c1133c72d9abfda72db421cd00561ac4eecb94313c6912013e32c322ea36743b01814fe919ca84b9aea9c78b10ba021506f7ad8c6625e87e07ce32b559036af6b67e2c0383a643cb93cdc2b9800e90588a18fcc449cd466c28c6db73507d8282dd00808b5927fee3336ed0a2202dfb1e176fece6a4104caa2a866c475209967638ea2f1466847da7301a77b9007dfb332a30e9bbfae8a8398165ec9dd4778214e0d6ed35a34071bdf3b3b19510ff2a617bc53eb0e6b0ddc501db027bb47e4f4127d7a0104945f3d3dc7ec1741038b9b80e2c7f131c519ee26ffcb7cb9d3556cd35a12bef1d4b376fc513197ba00ce8f012a0b374164222ba79a39e74e150813474ca6f87ba705c0f06e7b7068039c5edd9dd1a5ab6793ac211989907686b45650221187d4d59ae492679f3b4308765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547448c03ab4afbf6b8fb0e126c037a0ad4094600dd0e0634d76f88c21087f3cfb485a89bc1e3abc4c95041d1d170eccf02933ec5393d4be1dc573f83c33d3b9a746"); + +const PROFILE_KEY_CREDENTIAL_PRESENTATION_V3_RESULT: &[u8] = &hex!("02fc58a4f2c9bd736238abfc28890c8b2363d084bee430692f05ee559bd37dea3378949e72b271fe0d815b6d908035106cd670b45892df40780c62c37fae106c41be38371fe042a4d4f697db112972d79204b3d48d1253d3231c22926e107f661d40897cb7fdb4777c1680a57008655db71efaac1f69cd9ddf8cda33b226662d7ba443416281508fcdbb026d63f83168470a83e12803a6d2ee2c907343f2f6b063fe6bf0f17a032fabe61e77e904dfe7d3042125728c1984c86a094a0e3991ba554c1ebf604c14a8b13c384c5c01909656c114b24f9d3615d3b14bde7ce9cf126aca3e073e804b2016f7c5affa158a3a68ed9024c6880ecb441a346e7e91aedd6240010000000000002e70f27fb3f4c58cb40dfe58ce1d122312969426abb0bbb820bfbc5ff61d400a419d5ddb7c30c546427273d4fca3096ee4dd2fd03ccbbd26304ffcfe54fef50db8538177ebc61117a222253b4d4189f795abbde3b3d8a0a72d97b7750e0394010a01b474c3e942ef1ee807e17421689c6ca793c4f30b09c989b8a9679aee130eb034f64a34dbcaf12616970d2c8d58ca715bf5c4d42475fa6a1b82ba31574e072506652253e86cd783e30e1c06d2e861ba864a5373759472b31c5b26a8e46d062b8b5da2ec0a3ba499648e80f307728b7815aa60d167a0a9d01c2d2cbfb0a60ddc9dfc5343564b5f021fd1adba6d2a389e7c331bfffeed2a5d1887634323840574e49255a62d9e00ffc21f56afbb12fb9660e185f979223ec714c01e403a3a0a3276d0ef78182f12c092f5237befe3f0afea7693370788f854ec697e44c9bd02765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547448c03ab4afbf6b8fb0e126c037a0ad4094600dd0e0634d76f88c21087f3cfb485a89bc1e3abc4c95041d1d170eccf02933ec5393d4be1dc573f83c33d3b9a7468069160000000000"); #[test] fn test_integration_auth() { @@ -362,12 +95,15 @@ fn test_integration_auth() { let presentation_v2_bytes = &bincode::serialize(&presentation_v2).unwrap(); let presentation_any_bytes = &bincode::serialize(&presentation_any).unwrap(); - //for b in presentation_bytes.iter() { - // print!("0x{:02x}, ", b); - //} + assert_hex_eq!( + AUTH_CREDENTIAL_PRESENTATION_V2_RESULT[..], + presentation_v2_bytes[..] + ); - assert!(AUTH_CREDENTIAL_PRESENTATION_V2_RESULT[..] == presentation_v2_bytes[..]); - assert!(AUTH_CREDENTIAL_PRESENTATION_V2_RESULT[..] == presentation_any_bytes[..]); + assert_hex_eq!( + AUTH_CREDENTIAL_PRESENTATION_V2_RESULT[..], + presentation_any_bytes[..] + ); let presentation_v2_parsed = zkgroup::auth::AnyAuthCredentialPresentation::new(presentation_v2_bytes).unwrap(); @@ -492,12 +228,14 @@ fn test_integration_auth_with_pni() { let presentation_any_bytes = &bincode::serialize(&presentation_any).unwrap(); - // for b in presentation_bytes.iter() { - // print!("0x{:02x}, ", b); - // } - - assert!(AUTH_CREDENTIAL_PRESENTATION_V3_RESULT[..] == presentation_bytes[..]); - assert!(AUTH_CREDENTIAL_PRESENTATION_V3_RESULT[..] == presentation_any_bytes[..]); + assert_hex_eq!( + AUTH_CREDENTIAL_PRESENTATION_V3_RESULT[..], + presentation_bytes[..] + ); + assert_hex_eq!( + AUTH_CREDENTIAL_PRESENTATION_V3_RESULT[..], + presentation_any_bytes[..] + ); let presentation_parsed = bincode::deserialize::< zkgroup::auth::AuthCredentialWithPniPresentation, @@ -622,13 +360,14 @@ fn test_integration_auth_with_pni_using_pni_as_aci() { let presentation_any_bytes = &bincode::serialize(&presentation_any).unwrap(); - // for b in presentation_bytes.iter() { - // print!("0x{:02x}, ", b); - // } + assert_hex_eq!( + AUTH_CREDENTIAL_PRESENTATION_V3_RESULT_WITH_PNI_AS_ACI[..], + presentation_bytes[..] + ); - assert!(AUTH_CREDENTIAL_PRESENTATION_V3_RESULT_WITH_PNI_AS_ACI[..] == presentation_bytes[..]); - assert!( - AUTH_CREDENTIAL_PRESENTATION_V3_RESULT_WITH_PNI_AS_ACI[..] == presentation_any_bytes[..] + assert_hex_eq!( + AUTH_CREDENTIAL_PRESENTATION_V3_RESULT_WITH_PNI_AS_ACI[..], + presentation_any_bytes[..] ); let presentation_parsed = bincode::deserialize::< @@ -736,7 +475,7 @@ fn test_integration_expiring_profile() { .decrypt_profile_key(profile_key_ciphertext, aci) .unwrap(); - assert!(decrypted_profile_key.get_bytes() == profile_key.get_bytes()); + assert_hex_eq!(decrypted_profile_key.get_bytes(), profile_key.get_bytes()); // Create presentation let randomness = zkgroup::TEST_ARRAY_32_5; @@ -753,12 +492,15 @@ fn test_integration_expiring_profile() { presentation.into(); let presentation_any_bytes = &bincode::serialize(&presentation_any).unwrap(); - // for b in presentation_bytes.iter() { - // print!("0x{:02x}, ", b); - // } + assert_hex_eq!( + PROFILE_KEY_CREDENTIAL_PRESENTATION_V3_RESULT[..], + presentation_bytes[..] + ); - assert!(PROFILE_KEY_CREDENTIAL_PRESENTATION_V3_RESULT[..] == presentation_bytes[..]); - assert!(PROFILE_KEY_CREDENTIAL_PRESENTATION_V3_RESULT[..] == presentation_any_bytes[..]); + assert_hex_eq!( + PROFILE_KEY_CREDENTIAL_PRESENTATION_V3_RESULT[..], + presentation_any_bytes[..] + ); server_secret_params .verify_profile_key_credential_presentation( @@ -826,20 +568,15 @@ fn test_server_sigs() { let randomness = zkgroup::TEST_ARRAY_32_2; let message = zkgroup::TEST_ARRAY_32_1; let signature = server_secret_params.sign(randomness, &message); - //println!("signature = {:#x?}", &signature[..]); - for b in signature.iter() { - print!("0x{:02x}, ", b); - } - assert!( - signature[..] - == [ - 0x87, 0xd3, 0x54, 0x56, 0x4d, 0x35, 0xef, 0x91, 0xed, 0xba, 0x85, 0x1e, 0x08, 0x15, - 0x61, 0x2e, 0x86, 0x4c, 0x22, 0x7a, 0x04, 0x71, 0xd5, 0x0c, 0x27, 0x06, 0x98, 0x60, - 0x44, 0x06, 0xd0, 0x03, 0xa5, 0x54, 0x73, 0xf5, 0x76, 0xcf, 0x24, 0x1f, 0xc6, 0xb4, - 0x1c, 0x6b, 0x16, 0xe5, 0xe6, 0x3b, 0x33, 0x3c, 0x02, 0xfe, 0x4a, 0x33, 0x85, 0x80, - 0x22, 0xfd, 0xd7, 0xa4, 0xab, 0x36, 0x7b, 0x06, - ][..] + + const EXPECTED_SIGNATURE: &[u8] = &hex!("87d354564d35ef91edba851e0815612e864c227a0471d50c270698604406d003a55473f576cf241fc6b41c6b16e5e63b333c02fe4a33858022fdd7a4ab367b06"); + assert_eq!( + &signature[..], + EXPECTED_SIGNATURE, + "signature = {}", + hex::encode(signature) ); + server_public_params .verify_signature(&message, signature) .unwrap(); @@ -869,11 +606,8 @@ fn test_blob_encryption() { let calc_plaintext_vec = group_secret_params .decrypt_blob(&calc_ciphertext_vec) .unwrap(); - assert!(calc_plaintext_vec == plaintext_vec); - for b in calc_ciphertext_vec.iter() { - print!("0x{:02x}, ", b); - } - assert!(calc_ciphertext_vec == ciphertext_vec); + assert_hex_eq!(calc_plaintext_vec, plaintext_vec); + assert_hex_eq!(calc_ciphertext_vec, ciphertext_vec); } /// Check that older clients can still retrieve the UUID and profile key ciphertexts from a v2 diff --git a/swift/README.md b/swift/README.md index d7edd06c2d..5647f9e5de 100644 --- a/swift/README.md +++ b/swift/README.md @@ -61,8 +61,10 @@ When exposing new APIs to Swift, you will need to add the `--generate-ffi` flag # Catalyst Support -Rust targets for Mac Catalyst are still in tier 3 support, so we use the experimental `-Zbuild-std` flag to build the standard library. +Mac Catalyst is not supported by this repository, but we've done experiments with it in the past. Rust targets for Catalyst are still in tier 3 support, so we use the experimental `-Zbuild-std` flag to build the standard library. -In order to compile for these platforms you will need to: +In order to compile for Catalyst you will need to: * Install the standard library component with `rustup component add rust-src` -* If not using Cocoapods, add the `--build-std` flag to your `build_ffi.sh` invocation +* Add the `--build-std` flag to your `build_ffi.sh` invocation + +There may be other issues. For example, at one point the `cmake` crate had trouble compiling for Catalyst; you can try downgrading to version 0.1.48 if that's affecting you. diff --git a/swift/Sources/LibSignalClient/IncrementalMac.swift b/swift/Sources/LibSignalClient/IncrementalMac.swift index 19bf83e6cf..4d312313a9 100644 --- a/swift/Sources/LibSignalClient/IncrementalMac.swift +++ b/swift/Sources/LibSignalClient/IncrementalMac.swift @@ -74,23 +74,25 @@ public class ValidatingMacContext: NativeHandleOwner { return signal_validating_mac_destroy(handle) } - public func update(_ bytes: Bytes) throws { - let isValid = try bytes.withUnsafeBorrowedBuffer { bytesPtr in - return try invokeFnReturningBool { + public func update(_ bytes: Bytes) throws -> UInt32 { + let validBytesCount = try bytes.withUnsafeBorrowedBuffer { bytesPtr in + return try invokeFnReturningInteger { return signal_validating_mac_update($0, unsafeNativeHandle, bytesPtr, 0, UInt32(bytesPtr.length)) } } - guard isValid else { + if validBytesCount < 0 { throw SignalError.verificationFailed("Bad incremental MAC") } + return UInt32(validBytesCount) } - public func finalize() throws { - let isValid = try invokeFnReturningBool { + public func finalize() throws -> UInt32 { + let validBytesCount = try invokeFnReturningInteger { return signal_validating_mac_finalize($0, unsafeNativeHandle) } - guard isValid else { + if validBytesCount < 0 { throw SignalError.verificationFailed("Bad incremental MAC (finalize)") } + return UInt32(validBytesCount) } } diff --git a/swift/Sources/LibSignalClient/Protocol.swift b/swift/Sources/LibSignalClient/Protocol.swift index e4ee4bebc3..fd58b2c982 100644 --- a/swift/Sources/LibSignalClient/Protocol.swift +++ b/swift/Sources/LibSignalClient/Protocol.swift @@ -10,13 +10,14 @@ public func signalEncrypt(message: Bytes, for address: ProtocolAddress, sessionStore: SessionStore, identityStore: IdentityKeyStore, + now: Date = Date(), context: StoreContext) throws -> CiphertextMessage { return try address.withNativeHandle { addressHandle in try message.withUnsafeBorrowedBuffer { messageBuffer in try withSessionStore(sessionStore, context) { ffiSessionStore in try withIdentityKeyStore(identityStore, context) { ffiIdentityStore in try invokeFnReturningNativeHandle { - signal_encrypt_message($0, messageBuffer, addressHandle, ffiSessionStore, ffiIdentityStore) + signal_encrypt_message($0, messageBuffer, addressHandle, ffiSessionStore, ffiIdentityStore, UInt64(now.timeIntervalSince1970 * 1000)) } } } @@ -69,11 +70,12 @@ public func processPreKeyBundle(_ bundle: PreKeyBundle, for address: ProtocolAddress, sessionStore: SessionStore, identityStore: IdentityKeyStore, + now: Date = Date(), context: StoreContext) throws { return try withNativeHandles(bundle, address) { bundleHandle, addressHandle in try withSessionStore(sessionStore, context) { ffiSessionStore in try withIdentityKeyStore(identityStore, context) { ffiIdentityStore in - try checkError(signal_process_prekey_bundle(bundleHandle, addressHandle, ffiSessionStore, ffiIdentityStore)) + try checkError(signal_process_prekey_bundle(bundleHandle, addressHandle, ffiSessionStore, ffiIdentityStore, UInt64(now.timeIntervalSince1970 * 1000))) } } } diff --git a/swift/Sources/LibSignalClient/state/SessionRecord.swift b/swift/Sources/LibSignalClient/state/SessionRecord.swift index 9a1745ddca..0445fa9777 100644 --- a/swift/Sources/LibSignalClient/state/SessionRecord.swift +++ b/swift/Sources/LibSignalClient/state/SessionRecord.swift @@ -35,9 +35,13 @@ public class SessionRecord: ClonableHandleOwner { } public var hasCurrentState: Bool { + hasCurrentState(now: Date()) + } + + public func hasCurrentState(now: Date) -> Bool { var result = false self.withNativeHandle { nativeHandle in - failOnError(signal_session_record_has_current_state(&result, nativeHandle)) + failOnError(signal_session_record_has_usable_sender_chain(&result, nativeHandle, UInt64(now.timeIntervalSince1970 * 1000))) } return result } diff --git a/swift/Sources/SignalFfi/signal_ffi.h b/swift/Sources/SignalFfi/signal_ffi.h index 527c87b0af..2c3409cffa 100644 --- a/swift/Sources/SignalFfi/signal_ffi.h +++ b/swift/Sources/SignalFfi/signal_ffi.h @@ -174,7 +174,7 @@ typedef enum { } SignalErrorCode; /** - * A wrapper around [`aes::Aes256Ctr`] that uses a smaller nonce and supports an initial counter. + * A wrapper around [`ctr::Ctr32BE`] that uses a smaller nonce and supports an initial counter. */ typedef struct SignalAes256Ctr32 SignalAes256Ctr32; @@ -829,9 +829,9 @@ SignalFfiError *signal_ciphertext_message_from_plaintext_content(SignalCiphertex SignalFfiError *signal_session_record_archive_current_state(SignalSessionRecord *session_record); -SignalFfiError *signal_session_record_current_ratchet_key_matches(bool *out, const SignalSessionRecord *s, const SignalPublicKey *key); +SignalFfiError *signal_session_record_has_usable_sender_chain(bool *out, const SignalSessionRecord *s, uint64_t now); -SignalFfiError *signal_session_record_has_current_state(bool *out, const SignalSessionRecord *obj); +SignalFfiError *signal_session_record_current_ratchet_key_matches(bool *out, const SignalSessionRecord *s, const SignalPublicKey *key); SignalFfiError *signal_session_record_deserialize(SignalSessionRecord **out, SignalBorrowedBuffer data); @@ -841,9 +841,9 @@ SignalFfiError *signal_session_record_get_local_registration_id(uint32_t *out, c SignalFfiError *signal_session_record_get_remote_registration_id(uint32_t *out, const SignalSessionRecord *obj); -SignalFfiError *signal_process_prekey_bundle(const SignalPreKeyBundle *bundle, const SignalProtocolAddress *protocol_address, const SignalSessionStore *session_store, const SignalIdentityKeyStore *identity_key_store); +SignalFfiError *signal_process_prekey_bundle(const SignalPreKeyBundle *bundle, const SignalProtocolAddress *protocol_address, const SignalSessionStore *session_store, const SignalIdentityKeyStore *identity_key_store, uint64_t now); -SignalFfiError *signal_encrypt_message(SignalCiphertextMessage **out, SignalBorrowedBuffer ptext, const SignalProtocolAddress *protocol_address, const SignalSessionStore *session_store, const SignalIdentityKeyStore *identity_key_store); +SignalFfiError *signal_encrypt_message(SignalCiphertextMessage **out, SignalBorrowedBuffer ptext, const SignalProtocolAddress *protocol_address, const SignalSessionStore *session_store, const SignalIdentityKeyStore *identity_key_store, uint64_t now); SignalFfiError *signal_decrypt_message(SignalOwnedBuffer *out, const SignalMessage *message, const SignalProtocolAddress *protocol_address, const SignalSessionStore *session_store, const SignalIdentityKeyStore *identity_key_store); @@ -1133,9 +1133,9 @@ SignalFfiError *signal_validating_mac_destroy(SignalValidatingMac *p); SignalFfiError *signal_validating_mac_initialize(SignalValidatingMac **out, SignalBorrowedBuffer key, uint32_t chunk_size, SignalBorrowedBuffer digests); -SignalFfiError *signal_validating_mac_update(bool *out, SignalValidatingMac *mac, SignalBorrowedBuffer bytes, uint32_t offset, uint32_t length); +SignalFfiError *signal_validating_mac_update(int32_t *out, SignalValidatingMac *mac, SignalBorrowedBuffer bytes, uint32_t offset, uint32_t length); -SignalFfiError *signal_validating_mac_finalize(bool *out, SignalValidatingMac *mac); +SignalFfiError *signal_validating_mac_finalize(int32_t *out, SignalValidatingMac *mac); SignalFfiError *signal_username_hash(uint8_t (*out)[32], const char *username); diff --git a/swift/Tests/LibSignalClientTests/IncrementalMacTests.swift b/swift/Tests/LibSignalClientTests/IncrementalMacTests.swift index 7af434808c..568e4055ed 100644 --- a/swift/Tests/LibSignalClientTests/IncrementalMacTests.swift +++ b/swift/Tests/LibSignalClientTests/IncrementalMacTests.swift @@ -24,22 +24,37 @@ class IncrementalMacTests: TestCaseBase { func testIncrementalValidationSuccess() throws { let mac = try ValidatingMacContext(key: TEST_KEY, chunkSize: CHUNK_SIZE, expectingDigest: TEST_DIGEST) - for d in TEST_INPUT { - XCTAssertNoThrow { try mac.update(d) } + for d in TEST_INPUT { + _ = try mac.update(d) + } + _ = try mac.finalize() + } + + func testNoBytesCanBeConsumedWithoutValidation() throws { + var corruptInput = TEST_INPUT + corruptInput[0][1] ^= 0xff + + let mac = try ValidatingMacContext(key: TEST_KEY, chunkSize: CHUNK_SIZE, expectingDigest: TEST_DIGEST) + XCTAssertEqual(0, try mac.update(corruptInput[0])) + do { + _ = try mac.update(corruptInput[1]) + XCTFail("Should have failed") + } catch SignalError.verificationFailed { + } catch { + XCTFail("Unexpected error thrown") } - XCTAssertNoThrow { try mac.finalize() } } - func testIncrementalValidationFailure() throws { + func testIncrementalValidationFailureInFinalize() throws { var corruptInput = TEST_INPUT corruptInput[2][0] ^= 0xff let mac = try ValidatingMacContext(key: TEST_KEY, chunkSize: CHUNK_SIZE, expectingDigest: TEST_DIGEST) - for d in corruptInput { - try mac.update(d) - } + XCTAssertEqual(0, try mac.update(corruptInput[0])) + XCTAssertEqual(32, try mac.update(corruptInput[1])) + XCTAssertEqual(0, try mac.update(corruptInput[2])) do { - try mac.finalize() + _ = try mac.finalize() XCTFail("Should have failed") } catch SignalError.verificationFailed { } catch { diff --git a/swift/Tests/LibSignalClientTests/SessionTests.swift b/swift/Tests/LibSignalClientTests/SessionTests.swift index 3a4a8062a2..f1a205eb68 100644 --- a/swift/Tests/LibSignalClientTests/SessionTests.swift +++ b/swift/Tests/LibSignalClientTests/SessionTests.swift @@ -116,6 +116,68 @@ class SessionTests: TestCaseBase { } } + func testExpiresUnacknowledgedSessions() { + let bob_address = try! ProtocolAddress(name: "+14151111112", deviceId: 1) + + let alice_store = InMemorySignalProtocolStore() + let bob_store = InMemorySignalProtocolStore() + + let bob_pre_key = PrivateKey.generate() + let bob_signed_pre_key = PrivateKey.generate() + + let bob_signed_pre_key_public = bob_signed_pre_key.publicKey.serialize() + + let bob_identity_key = try! bob_store.identityKeyPair(context: NullContext()).identityKey + let bob_signed_pre_key_signature = try! bob_store.identityKeyPair(context: NullContext()).privateKey.generateSignature(message: bob_signed_pre_key_public) + + let prekey_id: UInt32 = 4570 + let signed_prekey_id: UInt32 = 3006 + + let bob_bundle = try! PreKeyBundle(registrationId: bob_store.localRegistrationId(context: NullContext()), + deviceId: 9, + prekeyId: prekey_id, + prekey: bob_pre_key.publicKey, + signedPrekeyId: signed_prekey_id, + signedPrekey: bob_signed_pre_key.publicKey, + signedPrekeySignature: bob_signed_pre_key_signature, + identity: bob_identity_key) + + // Alice processes the bundle: + try! processPreKeyBundle(bob_bundle, + for: bob_address, + sessionStore: alice_store, + identityStore: alice_store, + now: Date(timeIntervalSinceReferenceDate: 0), + context: NullContext()) + + let initial_session = try! alice_store.loadSession(for: bob_address, context: NullContext())! + XCTAssertTrue(initial_session.hasCurrentState(now: Date(timeIntervalSinceReferenceDate: 0))) + XCTAssertFalse(initial_session.hasCurrentState(now: Date(timeIntervalSinceReferenceDate: 60 * 60 * 24 * 90))) + + // Alice sends a message: + let ptext_a: [UInt8] = [8, 6, 7, 5, 3, 0, 9] + + let ctext_a = try! signalEncrypt(message: ptext_a, + for: bob_address, + sessionStore: alice_store, + identityStore: alice_store, + now: Date(timeIntervalSinceReferenceDate: 0), + context: NullContext()) + + XCTAssertEqual(ctext_a.messageType, .preKey) + + let updated_session = try! alice_store.loadSession(for: bob_address, context: NullContext())! + XCTAssertTrue(updated_session.hasCurrentState(now: Date(timeIntervalSinceReferenceDate: 0))) + XCTAssertFalse(updated_session.hasCurrentState(now: Date(timeIntervalSinceReferenceDate: 60 * 60 * 24 * 90))) + + XCTAssertThrowsError(try signalEncrypt(message: ptext_a, + for: bob_address, + sessionStore: alice_store, + identityStore: alice_store, + now: Date(timeIntervalSinceReferenceDate: 60 * 60 * 24 * 90), + context: NullContext())) + } + func testSealedSenderSession() throws { let alice_address = try! ProtocolAddress(name: "9d0652a3-dcc3-4d11-975f-74d61598733f", deviceId: 1) let bob_address = try! ProtocolAddress(name: "6838237D-02F6-4098-B110-698253D15961", deviceId: 1) diff --git a/swift/build_ffi.sh b/swift/build_ffi.sh index ff52f52722..603eacf37a 100755 --- a/swift/build_ffi.sh +++ b/swift/build_ffi.sh @@ -11,15 +11,19 @@ SCRIPT_DIR=$(dirname "$0") cd "${SCRIPT_DIR}"/.. . bin/build_helpers.sh -export IPHONEOS_DEPLOYMENT_TARGET=13 export CARGO_PROFILE_RELEASE_DEBUG=1 # enable line tables export CARGO_PROFILE_RELEASE_LTO=fat # use fat LTO to reduce binary size export CFLAGS="-DOPENSSL_SMALL ${CFLAGS:-}" # use small BoringSSL curve tables to reduce binary size +export RUSTFLAGS="--cfg aes_armv8 --cfg polyval_armv8 ${RUSTFLAGS:-}" # Enable ARMv8 cryptography acceleration when available # Work around cc crate bug with Catalyst targets export CFLAGS_aarch64_apple_ios_macabi="--target=arm64-apple-ios-macabi ${CFLAGS}" export CFLAGS_x86_64_apple_ios_macabi="--target=x86_64-apple-ios-macabi ${CFLAGS}" +if [[ "${CARGO_BUILD_TARGET:-}" =~ -ios(-sim|-macabi)?$ ]]; then + export IPHONEOS_DEPLOYMENT_TARGET=13 +fi + usage() { cat >&2 <