From db5027bc22a8bdf5a16fecc103e7744d2effbc25 Mon Sep 17 00:00:00 2001 From: Madhava Jay Date: Tue, 20 Jan 2026 15:37:52 +1000 Subject: [PATCH] Remove libsignal --- .github/workflows/release.yml | 2 +- .github/workflows/unified-release.yml | 2 +- .gitmodules | 3 - Cargo.lock | 1107 ++------------ Cargo.toml | 14 +- README.md | 6 +- bindings/python/Cargo.toml | 2 +- bindings/python/src/lib.rs | 13 +- cli/src/commands/file.rs | 9 +- cli/src/commands/key.rs | 6 +- cli/src/commands/mod.rs | 2 +- cli/tests/integration_test.rs | 5 +- docs/cli/tutorial.md | 5 +- docs/protocol/envelope.md | 21 +- licenses.sh | 598 ++++++++ licenses/all-licenses.csv | 16 + licenses/rust-licenses-bindings_python.json | 1244 ++++++++++++++++ licenses/rust-licenses-cli.json | 1280 +++++++++++++++++ licenses/rust-licenses-protocol.json | 1136 +++++++++++++++ ...censes-vendor_libsignal-protocol-syft.json | 0 lint.sh | 6 - protocol/Cargo.toml | 6 +- protocol/src/datasite/bytes.rs | 2 +- protocol/src/encryption/file_cipher.rs | 7 +- protocol/src/encryption/key_wrap.rs | 26 +- protocol/src/encryption/mod.rs | 65 +- protocol/src/encryption/pqxdh.rs | 210 --- protocol/src/encryption/tests.rs | 60 +- protocol/src/encryption/x3dh.rs | 210 +++ protocol/src/envelope.rs | 65 +- protocol/src/error.rs | 14 +- protocol/src/keys.rs | 261 ++-- protocol/src/serialization.rs | 221 +-- protocol/src/tests/envelope_tests.rs | 2 +- protocol/tests/envelope_signing_test.rs | 57 +- protocol/tests/file_encryption_test.rs | 13 +- protocol/tests/key_gen_test.rs | 151 +- protocol/tests/keys_fingerprint_test.rs | 22 +- protocol/tests/keys_serialization_test.rs | 61 +- protocol/tests/keys_storage_test.rs | 111 +- protocol/tests/pqxdh_keys_test.rs | 497 ------- protocol/tests/pqxdh_security_test.rs | 411 ------ protocol/tests/pqxdh_test.rs | 343 ----- protocol/tests/private_keys_test.rs | 136 +- protocol/tests/recovery_key_mnemonic_test.rs | 38 +- protocol/tests/storage_permissions_test.rs | 2 +- .../tests/syft_private_public_keys_test.rs | 431 +----- protocol/tests/x3dh_test.rs | 221 ++- vendor/libsignal-protocol-syft | 1 - 49 files changed, 5274 insertions(+), 3847 deletions(-) delete mode 100644 .gitmodules create mode 100755 licenses.sh create mode 100644 licenses/all-licenses.csv create mode 100644 licenses/rust-licenses-bindings_python.json create mode 100644 licenses/rust-licenses-cli.json create mode 100644 licenses/rust-licenses-protocol.json create mode 100644 licenses/rust-licenses-vendor_libsignal-protocol-syft.json delete mode 100644 protocol/src/encryption/pqxdh.rs create mode 100644 protocol/src/encryption/x3dh.rs delete mode 100644 protocol/tests/pqxdh_keys_test.rs delete mode 100644 protocol/tests/pqxdh_security_test.rs delete mode 100644 protocol/tests/pqxdh_test.rs delete mode 160000 vendor/libsignal-protocol-syft diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b7f972..85fea47 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -353,7 +353,7 @@ jobs: cat > "$NOTES_FILE" < "$NOTES_FILE" <(&self, py: Python<'py>) -> Py { - PyBytes::new(py, &self.inner.signal_identity_public_key.serialize()).into() + PyBytes::new(py, self.inner.identity_signing_public_key.as_bytes()).into() } /// Verify bundle signatures. @@ -261,7 +262,10 @@ pub fn compute_key_fingerprint(key_bytes: &[u8]) -> String { #[pyfunction] pub fn compute_identity_fingerprint(identity_key_bytes: &[u8]) -> PyResult { - let identity_key = libsignal_protocol::IdentityKey::decode(identity_key_bytes) + let identity_key_bytes: [u8; 32] = identity_key_bytes + .try_into() + .map_err(|_| PyValueError::new_err("invalid identity key bytes"))?; + let identity_key = VerifyingKey::from_bytes(&identity_key_bytes) .map_err(|_| PyValueError::new_err("invalid identity key bytes"))?; Ok(protocol::compute_identity_fingerprint(&identity_key)) } @@ -283,7 +287,10 @@ pub fn verify_envelope_signature( envelope: &PyParsedEnvelope, sender_identity_key: &[u8], ) -> PyResult<()> { - let identity_key = libsignal_protocol::IdentityKey::decode(sender_identity_key) + let identity_key_bytes: [u8; 32] = sender_identity_key + .try_into() + .map_err(|_| PyValueError::new_err("invalid sender identity key"))?; + let identity_key = VerifyingKey::from_bytes(&identity_key_bytes) .map_err(|_| PyValueError::new_err("invalid sender identity key"))?; protocol::envelope::verify_signature(&envelope.inner, &identity_key).map_err(to_py_err) } diff --git a/cli/src/commands/file.rs b/cli/src/commands/file.rs index 7f48e66..2d3b0eb 100644 --- a/cli/src/commands/file.rs +++ b/cli/src/commands/file.rs @@ -384,7 +384,7 @@ fn handle_file_inspect(context: &AppContext, args: FileInspectArgs) -> Result<() if let Some(parsed) = parse_optional_envelope(&bytes)? { println!( " envelope magic: {} (version {})", - std::str::from_utf8(MAGIC).unwrap_or("SYC1"), + std::str::from_utf8(MAGIC).unwrap_or("SYC2"), CURRENT_VERSION ); println!(" created_at: {}", parsed.prelude.created_at); @@ -394,7 +394,7 @@ fn handle_file_inspect(context: &AppContext, args: FileInspectArgs) -> Result<() ); report_sender_consistency(context, &parsed)?; match resolve_sender_bundle_for_decrypt(context, &parsed) { - Ok(bundle) => match verify_signature(&parsed, &bundle.signal_identity_public_key) { + Ok(bundle) => match verify_signature(&parsed, &bundle.identity_signing_public_key) { Ok(()) => println!(" signature: valid (sender bundle cached)"), Err(_) => println!(" signature: INVALID (signature mismatch)"), }, @@ -416,11 +416,10 @@ fn handle_file_inspect(context: &AppContext, args: FileInspectArgs) -> Result<() .as_deref() .unwrap_or(""); println!( - " - {} [{}] spk={} pqspk={}", + " - {} [{}] spk={}", identity, device, - recipient.spk_fingerprint.as_deref().unwrap_or(""), - recipient.pqspk_fingerprint.as_deref().unwrap_or("") + recipient.spk_fingerprint.as_deref().unwrap_or("") ); } println!( diff --git a/cli/src/commands/key.rs b/cli/src/commands/key.rs index 3de425f..ebbd692 100644 --- a/cli/src/commands/key.rs +++ b/cli/src/commands/key.rs @@ -14,7 +14,7 @@ use syft_crypto_protocol::datasite::crypto::parse_public_bundle; /// Identity and key-management subcommands. #[derive(Subcommand, Debug)] pub(crate) enum KeyCommand { - /// Generate a new identity, signed pre-key, and PQ pre-key set + /// Generate a new identity and signed pre-key set Generate(KeyGenerateArgs), /// Import an existing bundle into the local vault after verifying signatures @@ -184,7 +184,7 @@ fn handle_key_generate(context: &AppContext, args: KeyGenerateArgs) -> Result<() if args.dry_run { plan.info("dry-run: no files will be written"); } - plan.info("derive recovery key + PQXDH material via syft-crypto-protocol") + plan.info("derive recovery key + X3DH material via syft-crypto-protocol") .info("persist private JWKS into the vault and emit DID bundle artifacts"); if args.dry_run { @@ -256,7 +256,7 @@ fn handle_key_import(context: &AppContext, args: KeyImportArgs) -> Result<()> { } plan.bool("verification only", args.verify_only) .bool("force overwrite", args.force) - .info("verify libsignal signatures + identity metadata"); + .info("verify bundle signatures + identity metadata"); let bundle_body = fs::read_to_string(&bundle_path)?; let bundle_info = parse_public_bundle(&bundle_body)?; diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index dedba58..5c0e25d 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -16,7 +16,7 @@ pub(crate) use key::KeyCommand; pub(crate) use syft_crypto_protocol::datasite::context::resolve_identity; pub(crate) use vault::VaultCommand; -/// Syft Crypto (syc) CLI – manage Signal-compatible post-quantum keys and files. +/// Syft Crypto (syc) CLI – manage X3DH-style keys and files. #[derive(Parser, Debug)] #[command(name = "syc", version, about = "Syft Crypto CLI (syc)")] pub struct Cli { diff --git a/cli/tests/integration_test.rs b/cli/tests/integration_test.rs index 6a9fc68..2d69138 100644 --- a/cli/tests/integration_test.rs +++ b/cli/tests/integration_test.rs @@ -12,10 +12,9 @@ const CONFIG_JSON: &str = r#"{ const SAMPLE_MESSAGE: &str = r#"Hello Bob, -This is a placeholder message from Alice. Once the PQ encryption -plumbing is wired up, this text will be replaced with sealed bytes."#; +This is a sample message from Alice. It will be encrypted for recipients."#; -const SYC_MAGIC: &[u8; 4] = b"SYC1"; +const SYC_MAGIC: &[u8; 4] = b"SYC2"; #[test] fn simulate_workflow_matches_shell_script() -> Result<(), Box> { diff --git a/docs/cli/tutorial.md b/docs/cli/tutorial.md index 09e3532..3517a8c 100644 --- a/docs/cli/tutorial.md +++ b/docs/cli/tutorial.md @@ -65,8 +65,7 @@ If you ran `just init-sandbox`, these files already exist. cat > sandbox/alice/unencrypted/alice@example.org/shared/bob@example.org/files/message.txt <<'TEXT' Hello Bob, -This is a placeholder message from Alice. Once the PQ encryption -plumbing is wired up, this text will be replaced with sealed bytes. +This is a sample message from Alice. It will be encrypted for recipients. TEXT ``` @@ -163,7 +162,7 @@ syc \ --verbose ``` -You should see the `SYC1` magic, the sender (`alice@example.org`), the recipient list, and cipher statistics without the tool touching the payload bytes. +You should see the `SYC2` magic, the sender (`alice@example.org`), the recipient list, and cipher statistics without the tool touching the payload bytes. ## 8. Decrypt into Bob’s Shadow Tree diff --git a/docs/protocol/envelope.md b/docs/protocol/envelope.md index 9af6b9f..477b968 100644 --- a/docs/protocol/envelope.md +++ b/docs/protocol/envelope.md @@ -1,12 +1,12 @@ -# SYC1 Envelope Format +# SYC2 Envelope Format -This document describes the “SYC1” envelope implemented inside the CLI. The structure mirrors the Signal-compatible container we now produce with real fingerprints, signatures, and payloads. The JSON sample below still shows placeholder values for readability, but production builds populate those fields with the actual libsignal-derived data. +This document describes the “SYC2” envelope implemented inside the CLI. The JSON sample below still shows placeholder values for readability, but production builds populate those fields with the actual Syft crypto data. --- ## High-Level Goals -- Detect encrypted assets by peeking at a fixed header (`SYC1`) instead of relying on file extensions. +- Detect encrypted assets by peeking at a fixed header (`SYC2`) instead of relying on file extensions. - Carry signer and recipient metadata in a canonical JSON prelude so tooling (`syc file inspect`) can expose context without touching ciphertext. - Allow signature verification prior to decryption and support optional payload integrity checks. - Prevent the need for sidecar files which ensures atomicity when dealing with each file @@ -25,10 +25,10 @@ This document describes the “SYC1” envelope implemented inside the CLI. The CIPHERTEXT STREAM ``` -The header is binary. If you dump the file as UTF‑8 you may see odd characters such as `}` right after `SYC1`. That’s simply the little-endian representation of the prelude length (`0x7D` = 125) and signature length (`0x1D` = 29) landing on printable ASCII. Use `hexdump -C` to inspect the structure; you’ll see the magic (`53 59 43 31`), version byte, 4-byte prelude length, canonical JSON prelude (padded to 4 KiB), 2-byte signature length, the detached signature, and finally the ciphertext. +The header is binary. If you dump the file as UTF‑8 you may see odd characters such as `}` right after `SYC2`. That’s simply the little-endian representation of the prelude length (`0x7D` = 125) and signature length (`0x1D` = 29) landing on printable ASCII. Use `hexdump -C` to inspect the structure; you’ll see the magic (`53 59 43 32`), version byte, 4-byte prelude length, canonical JSON prelude (padded to 4 KiB), 2-byte signature length, the detached signature, and finally the ciphertext. 1. **Magic + Version** - - Magic: ASCII `SYC1`. + - Magic: ASCII `SYC2`. - Version: single byte (`1` for the current version). 2. **Prelude Length** - Unsigned 32-bit little-endian integer describing the canonical JSON prelude length (in bytes). @@ -41,7 +41,7 @@ The header is binary. If you dump the file as UTF‑8 you may see odd characters 5. **Signature** - Signature bytes. Currently this is a deterministic placeholder. 6. **Ciphertext** - - Remainder of the file: the encrypted payload emitted by the libsignal file layer. + - Remainder of the file: the encrypted payload emitted by the Syft file cipher. ![](./evelope-structure.png) @@ -63,7 +63,6 @@ The header is binary. If you dump the file as UTF‑8 you may see odd characters "identity": "bob@example.org", "device_label": "default", "spk_fingerprint": "sha256hex...", - "pqspk_fingerprint": "sha256hex...", "signed_prekey_id": 1, }, ], @@ -73,7 +72,7 @@ The header is binary. If you dump the file as UTF‑8 you may see odd characters "recipient_identity": "bob@example.org", "device_label": "default", "wrap_ephemeral_public": "base64url(x25519)", - "wrap_ciphertext": "base64url(kyber)", + "wrap_ciphertext": "base64url(wrapped_key)", }, ], "cipher": { @@ -90,11 +89,11 @@ The header is binary. If you dump the file as UTF‑8 you may see odd characters } ``` -Fields correspond to libsignal terminology: +Fields correspond to Syft terminology: - `sender.identity` / `sender.ik_fingerprint` describe the author (later derived from the Ed25519 identity key). -- Each entry in `recipients` mirrors a device binding: signed prekey fingerprint, PQ prekey fingerprint, and the signed prekey ID. -- `wrappings` contain PQXDH outputs: sender’s ephemeral public key and Kyber ciphertext for each target device. +- Each entry in `recipients` mirrors a device binding: signed prekey fingerprint and the signed prekey ID. +- `wrappings` contain X3DH outputs: sender’s ephemeral public key and the wrapped file key for each target device. - `cipher` summarises the Double Ratchet file-layer stats so `inspect` can report segment sizes without decryption. - `integrity` will eventually hold a base64url SHA-256 hash of the ciphertext for tamper detection. - `public_meta` carries small hints (e.g., `filename_hint`) that do not compromise confidentiality. diff --git a/licenses.sh b/licenses.sh new file mode 100755 index 0000000..35a61fc --- /dev/null +++ b/licenses.sh @@ -0,0 +1,598 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUT_DIR="${ROOT_DIR}/licenses" + +mkdir -p "${OUT_DIR}" +export OUT_DIR + +IGNORE_DIRS=("syftbox" "syftbox-sdk" "biovault" "beaver" "sbenv" "bioscript" "vendor") +IGNORE_DIRS_CSV="$(IFS=','; echo "${IGNORE_DIRS[*]}")" +export IGNORE_DIRS_CSV + +INSTALL_MISSING=false +SCAN_TARGETS="all" +FETCH_LICENSES=false + +usage() { + cat <<'EOF' +Usage: ./licenses.sh [--install] [--scan all|go|rust|go,rust] [--fetch] + +Options: + --install Install missing license tools for detected languages + --scan Restrict scans to a subset (default: all) + --fetch Fetch and cache license texts for Go modules (requires network) +EOF +} + +find_manifests() { + local pattern="$1" + local -a prune_expr=() + + for dir in "${IGNORE_DIRS[@]}"; do + prune_expr+=(-path "${ROOT_DIR}/${dir}" -o -path "${ROOT_DIR}/${dir}/*" -o) + done + + if (( ${#prune_expr[@]} > 0 )); then + prune_expr+=(-false) + find "${ROOT_DIR}" \( "${prune_expr[@]}" \) -prune -o -name "${pattern}" -print + else + find "${ROOT_DIR}" -name "${pattern}" -print + fi +} + +slugify_path() { + local path="$1" + local rel="${path#${ROOT_DIR}/}" + if [[ "${rel}" == "${path}" ]]; then + rel="${path}" + fi + if [[ -z "${rel}" ]]; then + rel="root" + fi + rel="${rel%/}" + rel="${rel//\//_}" + rel="${rel//./_}" + echo "${rel}" +} + +filter_rust_licenses() { + local rust_file="$1" + if ! has_cmd python3; then + return + fi + if [[ ! -f "${rust_file}" ]]; then + return + fi + + RUST_FILE="${rust_file}" python3 - <<'PY' +import json +import os + +rust_file = os.environ.get("RUST_FILE", "") +ignore_csv = os.environ.get("IGNORE_DIRS_CSV", "") +ignore = [s.strip().lower() for s in ignore_csv.split(",") if s.strip()] + +if not rust_file or not os.path.exists(rust_file): + raise SystemExit(0) + +def has_ignored_segment(value): + if not value: + return False + val = str(value).lower().replace("\\", "/") + for token in ignore: + if f"/{token}/" in val or val.endswith(f"/{token}"): + return True + return False + +def should_ignore(dep): + if not isinstance(dep, dict): + return True + name = str(dep.get("name", "")).lower() + for token in ignore: + if token and token in name: + return True + for key in ("license_file", "notice_file", "repository", "manifest_path"): + if has_ignored_segment(dep.get(key)): + return True + return False + +with open(rust_file) as f: + try: + deps = json.load(f) + except json.JSONDecodeError: + deps = [] + +if isinstance(deps, list): + filtered = [dep for dep in deps if not should_ignore(dep)] +else: + filtered = deps + +with open(rust_file, "w") as f: + json.dump(filtered, f, indent=2) +PY +} + +prepare_rust_manifest() { + local manifest="$1" + local crate_dir + local tmp_manifest + local manifest_path="${manifest}" + crate_dir="$(dirname "${manifest}")" + + if ! has_cmd python3; then + echo "|${manifest_path}" + return + fi + + if has_cmd rg; then + if rg -q '^\[workspace\]' "${manifest}" || rg -q 'workspace\s*=\s*true' "${manifest}"; then + echo "|${manifest_path}" + return + fi + else + if grep -q '^\[workspace\]' "${manifest}" || grep -q 'workspace\s*=\s*true' "${manifest}"; then + echo "|${manifest_path}" + return + fi + fi + + tmp_manifest="$(mktemp "${crate_dir}/.licenses.Cargo.toml.XXXXXX")" + if ! MANIFEST_SRC="${manifest}" MANIFEST_DST="${tmp_manifest}" ROOT_DIR="${ROOT_DIR}" IGNORE_DIRS_CSV="${IGNORE_DIRS_CSV}" python3 - <<'PY' +import os +import re + +src = os.environ.get("MANIFEST_SRC", "") +dst = os.environ.get("MANIFEST_DST", "") +root = os.environ.get("ROOT_DIR", "") +ignore_csv = os.environ.get("IGNORE_DIRS_CSV", "") +ignore = [s.strip().lower() for s in ignore_csv.split(",") if s.strip()] + +if not src or not dst or not root: + raise SystemExit(1) + +base_dir = os.path.dirname(src) + +with open(src) as f: + lines = f.readlines() + +def read_version(dir_name): + cargo_toml = os.path.join(root, dir_name, "Cargo.toml") + if not os.path.exists(cargo_toml): + return None + with open(cargo_toml) as f: + in_package = False + for line in f: + stripped = line.strip() + if stripped.startswith("[package]"): + in_package = True + continue + if in_package and stripped.startswith("[") and stripped.endswith("]"): + break + if in_package: + match = re.match(r'version\s*=\s*"([^"]+)"', stripped) + if match: + return match.group(1) + return None + +dir_versions = {} +dir_paths = {} +for token in ignore: + if not token: + continue + abs_dir = os.path.join(root, token) + if os.path.exists(abs_dir): + dir_paths[token] = abs_dir + version = read_version(token) + if version: + dir_versions[token] = version + +def resolve_path(path_value): + abs_path = os.path.abspath(os.path.join(base_dir, path_value)) + if os.path.exists(abs_path): + return abs_path + normalized = path_value.replace("\\", "/").rstrip("/") + for token in ignore: + if not token: + continue + if normalized == token or normalized.endswith(f"/{token}"): + candidate = os.path.join(root, token) + if os.path.exists(candidate): + return candidate + return abs_path + +out_lines = [] +path_re = re.compile(r'(path\s*=\s*")([^"]+)(")') +table_re = re.compile(r'^\[([^\]]+)\]') +dep_line_re = re.compile(r'^\s*([A-Za-z0-9_.-]+)\s*=') +current_dep = None +skip_table = False +for line in lines: + table_match = table_re.match(line.strip()) + if table_match: + table_name = table_match.group(1) + current_dep = None + skip_table = False + if "." in table_name: + current_dep = table_name.rsplit(".", 1)[-1].strip() + if current_dep in ignore: + skip_table = True + continue + + if skip_table: + continue + + dep_match = dep_line_re.match(line) + if dep_match: + dep_name = dep_match.group(1).strip() + if dep_name in ignore: + continue + + match = path_re.search(line) + if not match: + if current_dep and current_dep in dir_versions: + line = re.sub( + r'version\s*=\s*"[^"]+"', + f'version = "{dir_versions[current_dep]}"', + line, + ) + out_lines.append(line) + continue + resolved = resolve_path(match.group(2)) + updated_line = line[:match.start(2)] + resolved + line[match.end(2):] + for token, abs_dir in dir_paths.items(): + if resolved == abs_dir or resolved.startswith(abs_dir + os.sep): + if token in dir_versions: + updated_line = re.sub( + r'version\s*=\s*"[^"]+"', + f'version = "{dir_versions[token]}"', + updated_line, + ) + break + out_lines.append(updated_line) + +with open(dst, "w") as f: + f.writelines(out_lines) +PY + then + rm -f "${tmp_manifest}" + echo "|${manifest_path}" + return + fi + + echo "${tmp_manifest}|${tmp_manifest}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --install) + INSTALL_MISSING=true + shift + ;; + --scan) + SCAN_TARGETS="${2:-}" + shift 2 + ;; + --fetch) + FETCH_LICENSES=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +has_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +scan_enabled() { + local target="$1" + if [[ "${SCAN_TARGETS}" == "all" ]]; then + return 0 + fi + IFS=',' read -r -a targets <<< "${SCAN_TARGETS}" + for t in "${targets[@]}"; do + if [[ "${t}" == "${target}" ]]; then + return 0 + fi + done + return 1 +} + +report_go() { + local -a go_mods=() + while IFS= read -r mod; do + go_mods+=("${mod}") + done < <(find_manifests "go.mod") + if (( ${#go_mods[@]} == 0 )); then + return + fi + + if ! scan_enabled "go"; then + return + fi + + if ! has_cmd go; then + echo "go not found; skipping Go license scan." + return + fi + + if has_cmd go-licenses; then + for mod in "${go_mods[@]}"; do + local mod_dir + local slug + mod_dir="$(dirname "${mod}")" + slug="$(slugify_path "${mod_dir}")" + echo "Scanning Go dependencies with go-licenses in ${mod_dir}..." + (cd "${mod_dir}" && go-licenses csv ./... > "${OUT_DIR}/go-licenses-${slug}.csv") + echo "Go licenses written to ${OUT_DIR}/go-licenses-${slug}.csv" + done + else + if [[ "${INSTALL_MISSING}" == "true" ]]; then + echo "Installing go-licenses..." + (cd "${ROOT_DIR}" && go install github.com/google/go-licenses@latest) + else + echo "go-licenses not found. Install with:" + echo " go install github.com/google/go-licenses@latest" + return + fi + if has_cmd go-licenses; then + for mod in "${go_mods[@]}"; do + local mod_dir + local slug + mod_dir="$(dirname "${mod}")" + slug="$(slugify_path "${mod_dir}")" + echo "Scanning Go dependencies with go-licenses in ${mod_dir}..." + (cd "${mod_dir}" && go-licenses csv ./... > "${OUT_DIR}/go-licenses-${slug}.csv") + echo "Go licenses written to ${OUT_DIR}/go-licenses-${slug}.csv" + done + else + echo "go-licenses still not found after install attempt." + fi + fi +} + +report_rust() { + local -a cargo_manifests=() + while IFS= read -r manifest; do + cargo_manifests+=("${manifest}") + done < <(find_manifests "Cargo.toml") + if (( ${#cargo_manifests[@]} == 0 )); then + return + fi + + if ! scan_enabled "rust"; then + return + fi + + if ! has_cmd cargo; then + echo "cargo not found; skipping Rust license scan." + return + fi + + if has_cmd cargo-license; then + for manifest in "${cargo_manifests[@]}"; do + local crate_dir + local slug + local temp_manifest + local manifest_path + crate_dir="$(dirname "${manifest}")" + slug="$(slugify_path "${crate_dir}")" + echo "Scanning Rust dependencies with cargo-license in ${crate_dir}..." + IFS='|' read -r temp_manifest manifest_path < <(prepare_rust_manifest "${manifest}") + (cd "${crate_dir}" && cargo license --current-dir "${crate_dir}" --manifest-path "${manifest_path}" --json > "${OUT_DIR}/rust-licenses-${slug}.json") + if [[ -n "${temp_manifest}" ]]; then + rm -f "${temp_manifest}" + fi + filter_rust_licenses "${OUT_DIR}/rust-licenses-${slug}.json" + echo "Rust licenses written to ${OUT_DIR}/rust-licenses-${slug}.json" + done + else + if [[ "${INSTALL_MISSING}" == "true" ]]; then + echo "Installing cargo-license..." + (cd "${ROOT_DIR}" && cargo install cargo-license) + else + echo "cargo-license not found. Install with:" + echo " cargo install cargo-license" + return + fi + if has_cmd cargo-license; then + for manifest in "${cargo_manifests[@]}"; do + local crate_dir + local slug + local temp_manifest + local manifest_path + crate_dir="$(dirname "${manifest}")" + slug="$(slugify_path "${crate_dir}")" + echo "Scanning Rust dependencies with cargo-license in ${crate_dir}..." + IFS='|' read -r temp_manifest manifest_path < <(prepare_rust_manifest "${manifest}") + (cd "${crate_dir}" && cargo license --current-dir "${crate_dir}" --manifest-path "${manifest_path}" --json > "${OUT_DIR}/rust-licenses-${slug}.json") + if [[ -n "${temp_manifest}" ]]; then + rm -f "${temp_manifest}" + fi + filter_rust_licenses "${OUT_DIR}/rust-licenses-${slug}.json" + echo "Rust licenses written to ${OUT_DIR}/rust-licenses-${slug}.json" + done + else + echo "cargo-license still not found after install attempt." + fi + fi +} + +report_go +report_rust + +combine_reports() { + local combined="${OUT_DIR}/all-licenses.csv" + + if ! has_cmd python3; then + echo "python3 not found; skipping combined report." + return + fi + + python3 - <<'PY' +import csv +import glob +import os +import json + +out_dir = os.environ.get("OUT_DIR", "") +combined = os.path.join(out_dir, "all-licenses.csv") +ignore_csv = os.environ.get("IGNORE_DIRS_CSV", "") +ignore = [s.strip().lower() for s in ignore_csv.split(",") if s.strip()] + +def has_ignored_token(value): + if not value: + return False + val = str(value).lower().replace("\\", "/") + for token in ignore: + if token in val: + return True + return False + +def extract_license_value(row): + for key in row.keys(): + key_l = key.lower() + if key_l in ("license", "licenses"): + return row[key] + for key in row.keys(): + if "license" in key.lower(): + return row[key] + return None + +licenses = {} + +for go_file in sorted(glob.glob(os.path.join(out_dir, "go-licenses*.csv"))): + with open(go_file, newline="") as f: + reader = csv.reader(f) + for row in reader: + if len(row) < 3: + continue + module = (row[0] or "").strip() + if has_ignored_token(module): + continue + lic = (row[2] or "").strip() + if not lic or has_ignored_token(lic): + continue + licenses.setdefault(lic, set()).add("go") + +for rust_file in sorted(glob.glob(os.path.join(out_dir, "rust-licenses*.json"))): + with open(rust_file) as f: + try: + rust_deps = json.load(f) + except json.JSONDecodeError: + rust_deps = [] + if isinstance(rust_deps, list): + for dep in rust_deps: + if not isinstance(dep, dict): + continue + name = dep.get("name") or "" + if has_ignored_token(name): + continue + lic = dep.get("license") or dep.get("licenses") + if not lic: + continue + lic = str(lic).strip() + if not lic or has_ignored_token(lic): + continue + licenses.setdefault(lic, set()).add("rust") + +with open(combined, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["license", "languages"]) + for lic in sorted(licenses.keys()): + langs = ",".join(sorted(licenses[lic])) + writer.writerow([lic, langs]) + +print(f"Combined license report written to {combined}") +PY +} + +fetch_go_licenses() { + local cache_dir="${OUT_DIR}/cache/go" + local overrides_file="${OUT_DIR}/license-overrides.csv" + + if [[ "${FETCH_LICENSES}" != "true" ]]; then + return + fi + + if ! scan_enabled "go"; then + return + fi + + if ! has_cmd curl; then + echo "curl not found; skipping license fetch." + return + fi + + mkdir -p "${cache_dir}" + + python3 - <<'PY' +import csv +import glob +import os +import re +import subprocess +from urllib.parse import urlparse + +out_dir = os.environ.get("OUT_DIR", "") +cache_dir = os.path.join(out_dir, "cache", "go") +overrides_file = os.path.join(out_dir, "license-overrides.csv") + +def safe_name(s): + return re.sub(r"[^A-Za-z0-9._-]+", "_", s) + +overrides = {} +if os.path.exists(overrides_file): + with open(overrides_file, newline="") as f: + reader = csv.DictReader(f) + for row in reader: + orig = (row.get("original_url") or "").strip() + over = (row.get("override_url") or "").strip() + if orig and over: + overrides[orig] = over + +for go_file in sorted(glob.glob(os.path.join(out_dir, "go-licenses*.csv"))): + with open(go_file, newline="") as f: + reader = csv.reader(f) + for row in reader: + if len(row) < 2: + continue + module, url = row[0].strip(), row[1].strip() + if not url.startswith("http"): + continue + url = overrides.get(url, url) + parsed = urlparse(url) + host = parsed.netloc.replace(":", "_") + path = parsed.path.strip("/").replace("/", "_") + fname = safe_name(f"{module}__{host}__{path}.txt") + dest = os.path.join(cache_dir, fname) + if os.path.exists(dest) and os.path.getsize(dest) > 0: + continue + try: + subprocess.run( + ["curl", "-fsSL", url, "-o", dest], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + print(f"Fetched {url}") + except subprocess.CalledProcessError: + if os.path.exists(dest): + os.remove(dest) + print(f"Failed to fetch {url}") +PY +} + +fetch_go_licenses + +combine_reports diff --git a/licenses/all-licenses.csv b/licenses/all-licenses.csv new file mode 100644 index 0000000..fb12d70 --- /dev/null +++ b/licenses/all-licenses.csv @@ -0,0 +1,16 @@ +license,languages +(Apache-2.0 OR MIT) AND Unicode-3.0,rust +0BSD OR Apache-2.0 OR MIT,rust +Apache-2.0,rust +Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT,rust +Apache-2.0 OR BSD-1-Clause OR MIT,rust +Apache-2.0 OR BSD-2-Clause OR MIT,rust +Apache-2.0 OR BSL-1.0,rust +Apache-2.0 OR LGPL-2.1-or-later OR MIT,rust +Apache-2.0 OR MIT,rust +Apache-2.0 OR MIT OR Zlib,rust +Apache-2.0 WITH LLVM-exception,rust +BSD-3-Clause,rust +CC0-1.0,rust +MIT,rust +MIT OR Unlicense,rust diff --git a/licenses/rust-licenses-bindings_python.json b/licenses/rust-licenses-bindings_python.json new file mode 100644 index 0000000..53a101e --- /dev/null +++ b/licenses/rust-licenses-bindings_python.json @@ -0,0 +1,1244 @@ +[ + { + "name": "adler2", + "version": "2.0.1", + "authors": "Jonas Schievink |oyvindln ", + "repository": "https://github.com/oyvindln/adler2", + "license": "0BSD OR Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple clean-room implementation of the Adler-32 checksum" + }, + { + "name": "aead", + "version": "0.5.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for Authenticated Encryption with Associated Data (AEAD) algorithms, such as AES-GCM as ChaCha20Poly1305, which provide a high-level API" + }, + { + "name": "anyhow", + "version": "1.0.100", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/anyhow", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Flexible concrete Error type built on std::error::Error" + }, + { + "name": "autocfg", + "version": "1.5.0", + "authors": "Josh Stone ", + "repository": "https://github.com/cuviper/autocfg", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Automatic cfg for Rust compiler features" + }, + { + "name": "base64", + "version": "0.22.1", + "authors": "Marshall Pierce ", + "repository": "https://github.com/marshallpierce/rust-base64", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "encodes and decodes base64 as bytes or utf8" + }, + { + "name": "base64ct", + "version": "1.8.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable \"best effort\" constant-time operation and embedded-friendly no_std support" + }, + { + "name": "bip39", + "version": "2.2.0", + "authors": "Steven Roose ", + "repository": "https://github.com/rust-bitcoin/rust-bip39/", + "license": "CC0-1.0", + "license_file": null, + "description": "Library for BIP-39 Bitcoin mnemonic codes" + }, + { + "name": "bitcoin-internals", + "version": "0.2.0", + "authors": "Andrew Poelstra |The Rust Bitcoin developers", + "repository": "https://github.com/rust-bitcoin/rust-bitcoin/", + "license": "CC0-1.0", + "license_file": null, + "description": "Internal types and macros used by rust-bitcoin ecosystem" + }, + { + "name": "bitcoin_hashes", + "version": "0.13.0", + "authors": "Andrew Poelstra ", + "repository": "https://github.com/rust-bitcoin/rust-bitcoin", + "license": "CC0-1.0", + "license_file": null, + "description": "Hash functions used by the rust-bitcoin eccosystem" + }, + { + "name": "bitflags", + "version": "2.10.0", + "authors": "The Rust Project Developers", + "repository": "https://github.com/bitflags/bitflags", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A macro to generate structures which behave like bitflags." + }, + { + "name": "block-buffer", + "version": "0.10.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Buffer type for block processing of data" + }, + { + "name": "bytes", + "version": "1.11.0", + "authors": "Carl Lerche |Sean McArthur ", + "repository": "https://github.com/tokio-rs/bytes", + "license": "MIT", + "license_file": null, + "description": "Types and traits for working with bytes" + }, + { + "name": "cfg-if", + "version": "1.0.4", + "authors": "Alex Crichton ", + "repository": "https://github.com/rust-lang/cfg-if", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted." + }, + { + "name": "chacha20", + "version": "0.9.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/stream-ciphers", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "The ChaCha20 stream cipher (RFC 8439) implemented in pure Rust using traits from the RustCrypto `cipher` crate, with optional architecture-specific hardware acceleration (AVX2, SSE2). Additionally provides the ChaCha8, ChaCha12, XChaCha20, XChaCha12 and XChaCha8 stream ciphers, and also optional rand_core-compatible RNGs based on those ciphers." + }, + { + "name": "chacha20poly1305", + "version": "0.10.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of the ChaCha20Poly1305 Authenticated Encryption with Additional Data Cipher (RFC 8439) with optional architecture-specific hardware acceleration. Also contains implementations of the XChaCha20Poly1305 extended nonce variant of ChaCha20Poly1305, and the reduced-round ChaCha8Poly1305 and ChaCha12Poly1305 lightweight variants." + }, + { + "name": "cipher", + "version": "0.4.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for describing block ciphers and stream ciphers" + }, + { + "name": "const-oid", + "version": "0.9.6", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/const-oid", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Const-friendly implementation of the ISO/IEC Object Identifier (OID) standard as defined in ITU X.660, with support for BER/DER encoding/decoding as well as heapless no_std (i.e. embedded) support" + }, + { + "name": "cpufeatures", + "version": "0.2.17", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Lightweight runtime CPU feature detection for aarch64, loongarch64, and x86/x86_64 targets, with no_std support and support for mobile targets including Android and iOS" + }, + { + "name": "crc32fast", + "version": "1.5.0", + "authors": "Sam Rijs |Alex Crichton ", + "repository": "https://github.com/srijs/rust-crc32fast", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Fast, SIMD-accelerated CRC32 (IEEE) checksum computation" + }, + { + "name": "crypto-common", + "version": "0.1.7", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Common cryptographic traits" + }, + { + "name": "curve25519-dalek", + "version": "4.1.3", + "authors": "Isis Lovecruft |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/curve25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "A pure-Rust implementation of group operations on ristretto255 and Curve25519" + }, + { + "name": "curve25519-dalek-derive", + "version": "0.1.1", + "authors": null, + "repository": "https://github.com/dalek-cryptography/curve25519-dalek", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "curve25519-dalek Derives" + }, + { + "name": "der", + "version": "0.7.10", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/der", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules (DER) for Abstract Syntax Notation One (ASN.1) as described in ITU X.690 with full support for heapless no_std targets" + }, + { + "name": "digest", + "version": "0.10.7", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for cryptographic hash functions and message authentication codes" + }, + { + "name": "ed25519", + "version": "2.2.3", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/signatures/tree/master/ed25519", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Edwards Digital Signature Algorithm (EdDSA) over Curve25519 (as specified in RFC 8032) support library providing signature type definitions and PKCS#8 private key decoding/encoding support" + }, + { + "name": "ed25519-dalek", + "version": "2.2.0", + "authors": "isis lovecruft |Tony Arcieri |Michael Rosenberg ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "Fast and efficient ed25519 EdDSA key generations, signing, and verification in pure Rust." + }, + { + "name": "errno", + "version": "0.3.14", + "authors": "Chris Wong |Dan Gohman ", + "repository": "https://github.com/lambda-fairy/rust-errno", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Cross-platform interface to the `errno` variable." + }, + { + "name": "fastrand", + "version": "2.3.0", + "authors": "Stjepan Glavina ", + "repository": "https://github.com/smol-rs/fastrand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple and fast random number generator" + }, + { + "name": "fiat-crypto", + "version": "0.2.9", + "authors": "Fiat Crypto library authors ", + "repository": "https://github.com/mit-plv/fiat-crypto", + "license": "Apache-2.0 OR BSD-1-Clause OR MIT", + "license_file": null, + "description": "Fiat-crypto generated Rust" + }, + { + "name": "field-offset", + "version": "0.3.6", + "authors": "Diggory Blake ", + "repository": "https://github.com/Diggsey/rust-field-offset", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Safe pointer-to-member implementation" + }, + { + "name": "filetime", + "version": "0.2.26", + "authors": "Alex Crichton ", + "repository": "https://github.com/alexcrichton/filetime", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Platform-agnostic accessors of timestamps in File metadata" + }, + { + "name": "flate2", + "version": "1.1.5", + "authors": "Alex Crichton |Josh Triplett ", + "repository": "https://github.com/rust-lang/flate2-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "DEFLATE compression and decompression exposed as Read/BufRead/Write streams. Supports miniz_oxide and multiple zlib implementations. Supports zlib, gzip, and raw deflate streams." + }, + { + "name": "generic-array", + "version": "0.14.7", + "authors": "Bart\u0142omiej Kami\u0144ski |Aaron Trent ", + "repository": "https://github.com/fizyk20/generic-array.git", + "license": "MIT", + "license_file": null, + "description": "Generic types implementing functionality of arrays" + }, + { + "name": "getrandom", + "version": "0.2.16", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/getrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A small cross-platform library for retrieving random data from system source" + }, + { + "name": "getrandom", + "version": "0.3.4", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/getrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A small cross-platform library for retrieving random data from system source" + }, + { + "name": "heck", + "version": "0.5.0", + "authors": null, + "repository": "https://github.com/withoutboats/heck", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "heck is a case conversion library." + }, + { + "name": "hex", + "version": "0.4.3", + "authors": "KokaKiwi ", + "repository": "https://github.com/KokaKiwi/rust-hex", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Encoding and decoding data into/from hexadecimal representation." + }, + { + "name": "hex-conservative", + "version": "0.1.2", + "authors": "Andrew Poelstra ", + "repository": "https://github.com/rust-bitcoin/hex-conservative", + "license": "CC0-1.0", + "license_file": null, + "description": "A hex encoding and decoding crate with a conservative MSRV and dependency policy." + }, + { + "name": "hkdf", + "version": "0.12.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/KDFs/", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)" + }, + { + "name": "hmac", + "version": "0.12.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/MACs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Generic implementation of Hash-based Message Authentication Code (HMAC)" + }, + { + "name": "indoc", + "version": "2.0.7", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/indoc", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Indented document literals" + }, + { + "name": "inout", + "version": "0.1.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Custom reference types for code generic over in-place and buffer-to-buffer modes of operation." + }, + { + "name": "itoa", + "version": "1.0.15", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/itoa", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Fast integer primitive to string conversion" + }, + { + "name": "libc", + "version": "0.2.177", + "authors": "The Rust Project Developers", + "repository": "https://github.com/rust-lang/libc", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Raw FFI bindings to platform libraries like libc." + }, + { + "name": "libredox", + "version": "0.1.10", + "authors": "4lDO2 <4lDO2@protonmail.com>", + "repository": "https://gitlab.redox-os.org/redox-os/libredox.git", + "license": "MIT", + "license_file": null, + "description": "Redox stable ABI" + }, + { + "name": "linux-raw-sys", + "version": "0.11.0", + "authors": "Dan Gohman ", + "repository": "https://github.com/sunfishcode/linux-raw-sys", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Generated bindings for Linux's userspace API" + }, + { + "name": "lock_api", + "version": "0.4.14", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Wrappers to create fully-featured Mutex and RwLock types. Compatible with no_std." + }, + { + "name": "memchr", + "version": "2.7.6", + "authors": "Andrew Gallant |bluss", + "repository": "https://github.com/BurntSushi/memchr", + "license": "MIT OR Unlicense", + "license_file": null, + "description": "Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 1, 2 or 3 byte search and single substring search." + }, + { + "name": "memoffset", + "version": "0.9.1", + "authors": "Gilad Naaman ", + "repository": "https://github.com/Gilnaa/memoffset", + "license": "MIT", + "license_file": null, + "description": "offset_of functionality for Rust structs." + }, + { + "name": "miniz_oxide", + "version": "0.8.9", + "authors": "Frommi |oyvindln |Rich Geldreich richgel99@gmail.com", + "repository": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "DEFLATE compression and decompression library rewritten in Rust based on miniz" + }, + { + "name": "mio", + "version": "1.1.0", + "authors": "Carl Lerche |Thomas de Zeeuw |Tokio Contributors ", + "repository": "https://github.com/tokio-rs/mio", + "license": "MIT", + "license_file": null, + "description": "Lightweight non-blocking I/O." + }, + { + "name": "once_cell", + "version": "1.21.3", + "authors": "Aleksey Kladov ", + "repository": "https://github.com/matklad/once_cell", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Single assignment cells and lazy values." + }, + { + "name": "opaque-debug", + "version": "0.3.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macro for opaque Debug trait implementation" + }, + { + "name": "parking_lot", + "version": "0.12.5", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "More compact and efficient implementations of the standard synchronization primitives." + }, + { + "name": "parking_lot_core", + "version": "0.9.12", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "An advanced API for creating custom synchronization primitives." + }, + { + "name": "pin-project-lite", + "version": "0.2.16", + "authors": null, + "repository": "https://github.com/taiki-e/pin-project-lite", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A lightweight version of pin-project written with declarative macros." + }, + { + "name": "pkcs8", + "version": "0.10.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/pkcs8", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification (RFC 5208), with additional support for PKCS#8v2 asymmetric key packages (RFC 5958)" + }, + { + "name": "poly1305", + "version": "0.8.0", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/universal-hashes", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "The Poly1305 universal hash function and message authentication code" + }, + { + "name": "portable-atomic", + "version": "1.11.1", + "authors": null, + "repository": "https://github.com/taiki-e/portable-atomic", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Portable atomic types including support for 128-bit atomics, atomic float, etc." + }, + { + "name": "ppv-lite86", + "version": "0.2.21", + "authors": "The CryptoCorrosion Contributors", + "repository": "https://github.com/cryptocorrosion/cryptocorrosion", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Cross-platform cryptography-oriented low-level SIMD library." + }, + { + "name": "proc-macro2", + "version": "1.0.103", + "authors": "David Tolnay |Alex Crichton ", + "repository": "https://github.com/dtolnay/proc-macro2", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case." + }, + { + "name": "pyo3", + "version": "0.24.2", + "authors": "PyO3 Project and Contributors ", + "repository": "https://github.com/pyo3/pyo3", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Bindings to Python interpreter" + }, + { + "name": "pyo3-build-config", + "version": "0.24.2", + "authors": "PyO3 Project and Contributors ", + "repository": "https://github.com/pyo3/pyo3", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Build configuration for the PyO3 ecosystem" + }, + { + "name": "pyo3-ffi", + "version": "0.24.2", + "authors": "PyO3 Project and Contributors ", + "repository": "https://github.com/pyo3/pyo3", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Python-API bindings for the PyO3 ecosystem" + }, + { + "name": "pyo3-macros", + "version": "0.24.2", + "authors": "PyO3 Project and Contributors ", + "repository": "https://github.com/pyo3/pyo3", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Proc macros for PyO3 package" + }, + { + "name": "pyo3-macros-backend", + "version": "0.24.2", + "authors": "PyO3 Project and Contributors ", + "repository": "https://github.com/pyo3/pyo3", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Code generation for PyO3 package" + }, + { + "name": "quote", + "version": "1.0.42", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/quote", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Quasi-quoting macro quote!(...)" + }, + { + "name": "r-efi", + "version": "5.3.0", + "authors": null, + "repository": "https://github.com/r-efi/r-efi", + "license": "Apache-2.0 OR LGPL-2.1-or-later OR MIT", + "license_file": null, + "description": "UEFI Reference Specification Protocol Constants and Definitions" + }, + { + "name": "rand", + "version": "0.9.2", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Random number generators and other randomness functionality." + }, + { + "name": "rand_chacha", + "version": "0.9.0", + "authors": "The Rand Project Developers|The Rust Project Developers|The CryptoCorrosion Contributors", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "ChaCha random number generator" + }, + { + "name": "rand_core", + "version": "0.6.4", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core random number generator traits and tools for implementation." + }, + { + "name": "rand_core", + "version": "0.9.3", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core random number generator traits and tools for implementation." + }, + { + "name": "redox_syscall", + "version": "0.5.18", + "authors": "Jeremy Soller ", + "repository": "https://gitlab.redox-os.org/redox-os/syscall", + "license": "MIT", + "license_file": null, + "description": "A Rust library to access raw Redox system calls" + }, + { + "name": "rustc_version", + "version": "0.4.1", + "authors": null, + "repository": "https://github.com/djc/rustc-version-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library for querying the version of a installed rustc compiler" + }, + { + "name": "rustix", + "version": "1.1.2", + "authors": "Dan Gohman |Jakub Konka ", + "repository": "https://github.com/bytecodealliance/rustix", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls" + }, + { + "name": "rustversion", + "version": "1.0.22", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/rustversion", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Conditional compilation according to rustc compiler version" + }, + { + "name": "ryu", + "version": "1.0.20", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/ryu", + "license": "Apache-2.0 OR BSL-1.0", + "license_file": null, + "description": "Fast floating point to string conversion" + }, + { + "name": "ryu-js", + "version": "0.2.2", + "authors": "David Tolnay |boa-dev", + "repository": "https://github.com/boa-dev/ryu-js", + "license": "Apache-2.0 OR BSL-1.0", + "license_file": null, + "description": "Fast floating point to string conversion, ECMAScript compliant." + }, + { + "name": "scopeguard", + "version": "1.2.0", + "authors": "bluss", + "repository": "https://github.com/bluss/scopeguard", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A RAII scope guard that will run a given closure when it goes out of scope, even if the code between panics (assuming unwinding panic). Defines the macros `defer!`, `defer_on_unwind!`, `defer_on_success!` as shorthands for guards with one of the implemented strategies." + }, + { + "name": "semver", + "version": "1.0.27", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/semver", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parser and evaluator for Cargo's flavor of Semantic Versioning" + }, + { + "name": "serde", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A generic serialization/deserialization framework" + }, + { + "name": "serde_core", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Serde traits only, with no support for derive -- use the `serde` crate instead" + }, + { + "name": "serde_derive", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]" + }, + { + "name": "serde_jcs", + "version": "0.1.0", + "authors": "l1h3r ", + "repository": "https://github.com/l1h3r/serde_jcs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "JSON Canonicalization Scheme (JCS) for Serde" + }, + { + "name": "serde_json", + "version": "1.0.145", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/json", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A JSON serialization file format" + }, + { + "name": "sha2", + "version": "0.10.9", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/hashes", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of the SHA-2 hash function family including SHA-224, SHA-256, SHA-384, and SHA-512." + }, + { + "name": "signal-hook-registry", + "version": "1.4.7", + "authors": "Michal 'vorner' Vaner |Masaki Hara ", + "repository": "https://github.com/vorner/signal-hook", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Backend crate for signal-hook" + }, + { + "name": "signature", + "version": "2.2.0", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits/tree/master/signature", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for cryptographic signature algorithms (e.g. ECDSA, Ed25519)" + }, + { + "name": "simd-adler32", + "version": "0.3.7", + "authors": "Marvin Countryman ", + "repository": "https://github.com/mcountryman/simd-adler32", + "license": "MIT", + "license_file": null, + "description": "A SIMD-accelerated Adler-32 hash algorithm implementation." + }, + { + "name": "smallvec", + "version": "1.15.1", + "authors": "The Servo Project Developers", + "repository": "https://github.com/servo/rust-smallvec", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "'Small vector' optimization: store up to a small number of items on the stack" + }, + { + "name": "socket2", + "version": "0.6.1", + "authors": "Alex Crichton |Thomas de Zeeuw ", + "repository": "https://github.com/rust-lang/socket2", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Utilities for handling networking sockets with a maximal amount of configuration possible intended." + }, + { + "name": "spki", + "version": "0.7.3", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/spki", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "X.509 Subject Public Key Info (RFC5280) describing public keys as well as their associated AlgorithmIdentifiers (i.e. OIDs)" + }, + { + "name": "subtle", + "version": "2.6.1", + "authors": "Isis Lovecruft |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/subtle", + "license": "BSD-3-Clause", + "license_file": null, + "description": "Pure-Rust traits and utilities for constant-time cryptographic implementations." + }, + { + "name": "syft-crypto-protocol", + "version": "0.1.2-beta.2", + "authors": "Khoa Nguyen |Madhava Jay ", + "repository": "https://github.com/OpenMined/syft-crypto-core", + "license": "Apache-2.0", + "license_file": null, + "description": "Cryptographic protocol implementation for SyftBox using X3DH-style key agreement" + }, + { + "name": "syft-crypto-python", + "version": "0.1.2-beta.2", + "authors": "Khoa Nguyen |Madhava Jay ", + "repository": "https://github.com/OpenMined/syft-crypto-core", + "license": "Apache-2.0", + "license_file": null, + "description": "Python bindings for the Syft crypto protocol" + }, + { + "name": "syn", + "version": "2.0.111", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/syn", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parser for Rust source code" + }, + { + "name": "tar", + "version": "0.4.44", + "authors": "Alex Crichton ", + "repository": "https://github.com/alexcrichton/tar-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A Rust implementation of a TAR file reader and writer. This library does not currently handle compression, but it is abstract over all I/O readers and writers. Additionally, great lengths are taken to ensure that the entire contents are never required to be entirely resident in memory all at once." + }, + { + "name": "target-lexicon", + "version": "0.13.3", + "authors": "Dan Gohman ", + "repository": "https://github.com/bytecodealliance/target-lexicon", + "license": "Apache-2.0 WITH LLVM-exception", + "license_file": null, + "description": "LLVM target triple types" + }, + { + "name": "tempfile", + "version": "3.23.0", + "authors": "Steven Allen |The Rust Project Developers|Ashley Mannix |Jason White ", + "repository": "https://github.com/Stebalien/tempfile", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library for managing temporary files and directories." + }, + { + "name": "thiserror", + "version": "2.0.17", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/thiserror", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "derive(Error)" + }, + { + "name": "thiserror-impl", + "version": "2.0.17", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/thiserror", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Implementation detail of the `thiserror` crate" + }, + { + "name": "tinyvec", + "version": "1.10.0", + "authors": "Lokathor ", + "repository": "https://github.com/Lokathor/tinyvec", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "`tinyvec` provides 100% safe vec-like data structures." + }, + { + "name": "tinyvec_macros", + "version": "0.1.1", + "authors": "Soveu ", + "repository": "https://github.com/Soveu/tinyvec_macros", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "Some macros for tiny containers" + }, + { + "name": "tokio", + "version": "1.48.0", + "authors": "Tokio Contributors ", + "repository": "https://github.com/tokio-rs/tokio", + "license": "MIT", + "license_file": null, + "description": "An event-driven, non-blocking I/O platform for writing asynchronous I/O backed applications." + }, + { + "name": "tokio-macros", + "version": "2.6.0", + "authors": "Tokio Contributors ", + "repository": "https://github.com/tokio-rs/tokio", + "license": "MIT", + "license_file": null, + "description": "Tokio's proc macros." + }, + { + "name": "typenum", + "version": "1.19.0", + "authors": "Paho Lurie-Gregg |Andre Bogus ", + "repository": "https://github.com/paholg/typenum", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Typenum is a Rust library for type-level numbers evaluated at compile time. It currently supports bits, unsigned integers, and signed integers. It also provides a type-level array of type-level numbers, but its implementation is incomplete." + }, + { + "name": "unicode-ident", + "version": "1.0.22", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/unicode-ident", + "license": "(Apache-2.0 OR MIT) AND Unicode-3.0", + "license_file": null, + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31" + }, + { + "name": "unicode-normalization", + "version": "0.1.25", + "authors": "kwantam |Manish Goregaokar ", + "repository": "https://github.com/unicode-rs/unicode-normalization", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "This crate provides functions for normalization of Unicode strings, including Canonical and Compatible Decomposition and Recomposition, as described in Unicode Standard Annex #15." + }, + { + "name": "unindent", + "version": "0.2.4", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/indoc", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Remove a column of leading whitespace from a string" + }, + { + "name": "universal-hash", + "version": "0.5.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits which describe the functionality of universal hash functions (UHFs)" + }, + { + "name": "urlencoding", + "version": "2.1.3", + "authors": "Kornel |Bertram Truong ", + "repository": "https://github.com/kornelski/rust_urlencoding", + "license": "MIT", + "license_file": null, + "description": "A Rust library for doing URL percentage encoding." + }, + { + "name": "version_check", + "version": "0.9.5", + "authors": "Sergio Benitez ", + "repository": "https://github.com/SergioBenitez/version_check", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Tiny crate to check the version of the installed/running rustc." + }, + { + "name": "wasi", + "version": "0.11.1+wasi-snapshot-preview1", + "authors": "The Cranelift Project Developers", + "repository": "https://github.com/bytecodealliance/wasi", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Experimental WASI API bindings for Rust" + }, + { + "name": "wasip2", + "version": "1.0.1+wasi-0.2.4", + "authors": null, + "repository": "https://github.com/bytecodealliance/wasi-rs", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "WASIp2 API bindings for Rust" + }, + { + "name": "widestring", + "version": "0.4.3", + "authors": "Kathryn Long ", + "repository": "https://github.com/starkat99/widestring-rs.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A wide string FFI library for converting to and from wide strings, such as those often used in Windows API or other FFI libraries. Both UTF-16 and UTF-32 types are provided." + }, + { + "name": "winapi", + "version": "0.3.9", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Raw FFI bindings for all of Windows API." + }, + { + "name": "winapi-i686-pc-windows-gnu", + "version": "0.4.0", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libraries for the i686-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead." + }, + { + "name": "winapi-x86_64-pc-windows-gnu", + "version": "0.4.0", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libraries for the x86_64-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead." + }, + { + "name": "windows-acl", + "version": "0.3.0", + "authors": "William Woodruff |yying ", + "repository": "https://github.com/trailofbits/windows-acl", + "license": "MIT", + "license_file": null, + "description": "Rust crate to simplify Windows ACL operations" + }, + { + "name": "windows-link", + "version": "0.2.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Linking for Windows" + }, + { + "name": "windows-sys", + "version": "0.60.2", + "authors": "Microsoft", + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Rust for Windows" + }, + { + "name": "windows-sys", + "version": "0.61.2", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Rust for Windows" + }, + { + "name": "windows-targets", + "version": "0.53.5", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libs for Windows" + }, + { + "name": "windows_aarch64_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_aarch64_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_gnu", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_gnu", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "wit-bindgen", + "version": "0.46.0", + "authors": "Alex Crichton ", + "repository": "https://github.com/bytecodealliance/wit-bindgen", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Rust bindings generator and runtime support for WIT and the component model. Used when compiling Rust programs to the component model." + }, + { + "name": "x25519-dalek", + "version": "2.0.1", + "authors": "Isis Lovecruft |DebugSteven |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/x25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "X25519 elliptic curve Diffie-Hellman key exchange in pure-Rust, using curve25519-dalek." + }, + { + "name": "xattr", + "version": "1.6.1", + "authors": "Steven Allen ", + "repository": "https://github.com/Stebalien/xattr", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "unix extended filesystem attributes" + }, + { + "name": "zerocopy", + "version": "0.8.31", + "authors": "Joshua Liebow-Feeser |Jack Wrenn ", + "repository": "https://github.com/google/zerocopy", + "license": "Apache-2.0 OR BSD-2-Clause OR MIT", + "license_file": null, + "description": "Zerocopy makes zero-cost memory manipulation effortless. We write \"unsafe\" so you don't have to." + }, + { + "name": "zerocopy-derive", + "version": "0.8.31", + "authors": "Joshua Liebow-Feeser |Jack Wrenn ", + "repository": "https://github.com/google/zerocopy", + "license": "Apache-2.0 OR BSD-2-Clause OR MIT", + "license_file": null, + "description": "Custom derive for traits from the zerocopy crate" + }, + { + "name": "zeroize", + "version": "1.8.2", + "authors": "The RustCrypto Project Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Securely clear secrets from memory with a simple trait built on stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler. Uses a portable pure Rust implementation that works everywhere, even WASM!" + }, + { + "name": "zeroize_derive", + "version": "1.4.2", + "authors": "The RustCrypto Project Developers", + "repository": "https://github.com/RustCrypto/utils/tree/master/zeroize/derive", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Custom derive support for zeroize" + } +] \ No newline at end of file diff --git a/licenses/rust-licenses-cli.json b/licenses/rust-licenses-cli.json new file mode 100644 index 0000000..9c0ebc1 --- /dev/null +++ b/licenses/rust-licenses-cli.json @@ -0,0 +1,1280 @@ +[ + { + "name": "adler2", + "version": "2.0.1", + "authors": "Jonas Schievink |oyvindln ", + "repository": "https://github.com/oyvindln/adler2", + "license": "0BSD OR Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple clean-room implementation of the Adler-32 checksum" + }, + { + "name": "aead", + "version": "0.5.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for Authenticated Encryption with Associated Data (AEAD) algorithms, such as AES-GCM as ChaCha20Poly1305, which provide a high-level API" + }, + { + "name": "anstream", + "version": "0.6.21", + "authors": null, + "repository": "https://github.com/rust-cli/anstyle.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "IO stream adapters for writing colored text that will gracefully degrade according to your terminal's capabilities." + }, + { + "name": "anstyle", + "version": "1.0.13", + "authors": null, + "repository": "https://github.com/rust-cli/anstyle.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "ANSI text styling" + }, + { + "name": "anstyle-parse", + "version": "0.2.7", + "authors": null, + "repository": "https://github.com/rust-cli/anstyle.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parse ANSI Style Escapes" + }, + { + "name": "anstyle-query", + "version": "1.1.5", + "authors": null, + "repository": "https://github.com/rust-cli/anstyle.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Look up colored console capabilities" + }, + { + "name": "anstyle-wincon", + "version": "3.0.11", + "authors": null, + "repository": "https://github.com/rust-cli/anstyle.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Styling legacy Windows terminals" + }, + { + "name": "anyhow", + "version": "1.0.100", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/anyhow", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Flexible concrete Error type built on std::error::Error" + }, + { + "name": "autocfg", + "version": "1.5.0", + "authors": "Josh Stone ", + "repository": "https://github.com/cuviper/autocfg", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Automatic cfg for Rust compiler features" + }, + { + "name": "base64", + "version": "0.22.1", + "authors": "Marshall Pierce ", + "repository": "https://github.com/marshallpierce/rust-base64", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "encodes and decodes base64 as bytes or utf8" + }, + { + "name": "base64ct", + "version": "1.8.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable \"best effort\" constant-time operation and embedded-friendly no_std support" + }, + { + "name": "bip39", + "version": "2.2.0", + "authors": "Steven Roose ", + "repository": "https://github.com/rust-bitcoin/rust-bip39/", + "license": "CC0-1.0", + "license_file": null, + "description": "Library for BIP-39 Bitcoin mnemonic codes" + }, + { + "name": "bitcoin-internals", + "version": "0.2.0", + "authors": "Andrew Poelstra |The Rust Bitcoin developers", + "repository": "https://github.com/rust-bitcoin/rust-bitcoin/", + "license": "CC0-1.0", + "license_file": null, + "description": "Internal types and macros used by rust-bitcoin ecosystem" + }, + { + "name": "bitcoin_hashes", + "version": "0.13.0", + "authors": "Andrew Poelstra ", + "repository": "https://github.com/rust-bitcoin/rust-bitcoin", + "license": "CC0-1.0", + "license_file": null, + "description": "Hash functions used by the rust-bitcoin eccosystem" + }, + { + "name": "bitflags", + "version": "2.10.0", + "authors": "The Rust Project Developers", + "repository": "https://github.com/bitflags/bitflags", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A macro to generate structures which behave like bitflags." + }, + { + "name": "block-buffer", + "version": "0.10.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Buffer type for block processing of data" + }, + { + "name": "bytes", + "version": "1.11.0", + "authors": "Carl Lerche |Sean McArthur ", + "repository": "https://github.com/tokio-rs/bytes", + "license": "MIT", + "license_file": null, + "description": "Types and traits for working with bytes" + }, + { + "name": "cfg-if", + "version": "1.0.4", + "authors": "Alex Crichton ", + "repository": "https://github.com/rust-lang/cfg-if", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted." + }, + { + "name": "chacha20", + "version": "0.9.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/stream-ciphers", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "The ChaCha20 stream cipher (RFC 8439) implemented in pure Rust using traits from the RustCrypto `cipher` crate, with optional architecture-specific hardware acceleration (AVX2, SSE2). Additionally provides the ChaCha8, ChaCha12, XChaCha20, XChaCha12 and XChaCha8 stream ciphers, and also optional rand_core-compatible RNGs based on those ciphers." + }, + { + "name": "chacha20poly1305", + "version": "0.10.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of the ChaCha20Poly1305 Authenticated Encryption with Additional Data Cipher (RFC 8439) with optional architecture-specific hardware acceleration. Also contains implementations of the XChaCha20Poly1305 extended nonce variant of ChaCha20Poly1305, and the reduced-round ChaCha8Poly1305 and ChaCha12Poly1305 lightweight variants." + }, + { + "name": "cipher", + "version": "0.4.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for describing block ciphers and stream ciphers" + }, + { + "name": "clap", + "version": "4.5.53", + "authors": null, + "repository": "https://github.com/clap-rs/clap", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple to use, efficient, and full-featured Command Line Argument Parser" + }, + { + "name": "clap_builder", + "version": "4.5.53", + "authors": null, + "repository": "https://github.com/clap-rs/clap", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple to use, efficient, and full-featured Command Line Argument Parser" + }, + { + "name": "clap_derive", + "version": "4.5.49", + "authors": null, + "repository": "https://github.com/clap-rs/clap", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parse command line argument by defining a struct, derive crate." + }, + { + "name": "clap_lex", + "version": "0.7.6", + "authors": null, + "repository": "https://github.com/clap-rs/clap", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Minimal, flexible command line parser" + }, + { + "name": "colorchoice", + "version": "1.0.4", + "authors": null, + "repository": "https://github.com/rust-cli/anstyle.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Global override of color control" + }, + { + "name": "const-oid", + "version": "0.9.6", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/const-oid", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Const-friendly implementation of the ISO/IEC Object Identifier (OID) standard as defined in ITU X.660, with support for BER/DER encoding/decoding as well as heapless no_std (i.e. embedded) support" + }, + { + "name": "cpufeatures", + "version": "0.2.17", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Lightweight runtime CPU feature detection for aarch64, loongarch64, and x86/x86_64 targets, with no_std support and support for mobile targets including Android and iOS" + }, + { + "name": "crc32fast", + "version": "1.5.0", + "authors": "Sam Rijs |Alex Crichton ", + "repository": "https://github.com/srijs/rust-crc32fast", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Fast, SIMD-accelerated CRC32 (IEEE) checksum computation" + }, + { + "name": "crypto-common", + "version": "0.1.7", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Common cryptographic traits" + }, + { + "name": "curve25519-dalek", + "version": "4.1.3", + "authors": "Isis Lovecruft |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/curve25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "A pure-Rust implementation of group operations on ristretto255 and Curve25519" + }, + { + "name": "curve25519-dalek-derive", + "version": "0.1.1", + "authors": null, + "repository": "https://github.com/dalek-cryptography/curve25519-dalek", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "curve25519-dalek Derives" + }, + { + "name": "der", + "version": "0.7.10", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/der", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules (DER) for Abstract Syntax Notation One (ASN.1) as described in ITU X.690 with full support for heapless no_std targets" + }, + { + "name": "digest", + "version": "0.10.7", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for cryptographic hash functions and message authentication codes" + }, + { + "name": "ed25519", + "version": "2.2.3", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/signatures/tree/master/ed25519", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Edwards Digital Signature Algorithm (EdDSA) over Curve25519 (as specified in RFC 8032) support library providing signature type definitions and PKCS#8 private key decoding/encoding support" + }, + { + "name": "ed25519-dalek", + "version": "2.2.0", + "authors": "isis lovecruft |Tony Arcieri |Michael Rosenberg ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "Fast and efficient ed25519 EdDSA key generations, signing, and verification in pure Rust." + }, + { + "name": "errno", + "version": "0.3.14", + "authors": "Chris Wong |Dan Gohman ", + "repository": "https://github.com/lambda-fairy/rust-errno", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Cross-platform interface to the `errno` variable." + }, + { + "name": "fastrand", + "version": "2.3.0", + "authors": "Stjepan Glavina ", + "repository": "https://github.com/smol-rs/fastrand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple and fast random number generator" + }, + { + "name": "fiat-crypto", + "version": "0.2.9", + "authors": "Fiat Crypto library authors ", + "repository": "https://github.com/mit-plv/fiat-crypto", + "license": "Apache-2.0 OR BSD-1-Clause OR MIT", + "license_file": null, + "description": "Fiat-crypto generated Rust" + }, + { + "name": "field-offset", + "version": "0.3.6", + "authors": "Diggory Blake ", + "repository": "https://github.com/Diggsey/rust-field-offset", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Safe pointer-to-member implementation" + }, + { + "name": "filetime", + "version": "0.2.26", + "authors": "Alex Crichton ", + "repository": "https://github.com/alexcrichton/filetime", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Platform-agnostic accessors of timestamps in File metadata" + }, + { + "name": "flate2", + "version": "1.1.5", + "authors": "Alex Crichton |Josh Triplett ", + "repository": "https://github.com/rust-lang/flate2-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "DEFLATE compression and decompression exposed as Read/BufRead/Write streams. Supports miniz_oxide and multiple zlib implementations. Supports zlib, gzip, and raw deflate streams." + }, + { + "name": "generic-array", + "version": "0.14.7", + "authors": "Bart\u0142omiej Kami\u0144ski |Aaron Trent ", + "repository": "https://github.com/fizyk20/generic-array.git", + "license": "MIT", + "license_file": null, + "description": "Generic types implementing functionality of arrays" + }, + { + "name": "getrandom", + "version": "0.2.16", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/getrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A small cross-platform library for retrieving random data from system source" + }, + { + "name": "getrandom", + "version": "0.3.4", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/getrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A small cross-platform library for retrieving random data from system source" + }, + { + "name": "heck", + "version": "0.5.0", + "authors": null, + "repository": "https://github.com/withoutboats/heck", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "heck is a case conversion library." + }, + { + "name": "hex", + "version": "0.4.3", + "authors": "KokaKiwi ", + "repository": "https://github.com/KokaKiwi/rust-hex", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Encoding and decoding data into/from hexadecimal representation." + }, + { + "name": "hex-conservative", + "version": "0.1.2", + "authors": "Andrew Poelstra ", + "repository": "https://github.com/rust-bitcoin/hex-conservative", + "license": "CC0-1.0", + "license_file": null, + "description": "A hex encoding and decoding crate with a conservative MSRV and dependency policy." + }, + { + "name": "hkdf", + "version": "0.12.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/KDFs/", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)" + }, + { + "name": "hmac", + "version": "0.12.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/MACs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Generic implementation of Hash-based Message Authentication Code (HMAC)" + }, + { + "name": "inout", + "version": "0.1.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Custom reference types for code generic over in-place and buffer-to-buffer modes of operation." + }, + { + "name": "is_terminal_polyfill", + "version": "1.70.2", + "authors": null, + "repository": "https://github.com/polyfill-rs/is_terminal_polyfill", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Polyfill for `is_terminal` stdlib feature for use with older MSRVs" + }, + { + "name": "itoa", + "version": "1.0.15", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/itoa", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Fast integer primitive to string conversion" + }, + { + "name": "libc", + "version": "0.2.177", + "authors": "The Rust Project Developers", + "repository": "https://github.com/rust-lang/libc", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Raw FFI bindings to platform libraries like libc." + }, + { + "name": "libredox", + "version": "0.1.10", + "authors": "4lDO2 <4lDO2@protonmail.com>", + "repository": "https://gitlab.redox-os.org/redox-os/libredox.git", + "license": "MIT", + "license_file": null, + "description": "Redox stable ABI" + }, + { + "name": "linux-raw-sys", + "version": "0.11.0", + "authors": "Dan Gohman ", + "repository": "https://github.com/sunfishcode/linux-raw-sys", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Generated bindings for Linux's userspace API" + }, + { + "name": "lock_api", + "version": "0.4.14", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Wrappers to create fully-featured Mutex and RwLock types. Compatible with no_std." + }, + { + "name": "memchr", + "version": "2.7.6", + "authors": "Andrew Gallant |bluss", + "repository": "https://github.com/BurntSushi/memchr", + "license": "MIT OR Unlicense", + "license_file": null, + "description": "Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 1, 2 or 3 byte search and single substring search." + }, + { + "name": "memoffset", + "version": "0.9.1", + "authors": "Gilad Naaman ", + "repository": "https://github.com/Gilnaa/memoffset", + "license": "MIT", + "license_file": null, + "description": "offset_of functionality for Rust structs." + }, + { + "name": "miniz_oxide", + "version": "0.8.9", + "authors": "Frommi |oyvindln |Rich Geldreich richgel99@gmail.com", + "repository": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "DEFLATE compression and decompression library rewritten in Rust based on miniz" + }, + { + "name": "mio", + "version": "1.1.0", + "authors": "Carl Lerche |Thomas de Zeeuw |Tokio Contributors ", + "repository": "https://github.com/tokio-rs/mio", + "license": "MIT", + "license_file": null, + "description": "Lightweight non-blocking I/O." + }, + { + "name": "once_cell", + "version": "1.21.3", + "authors": "Aleksey Kladov ", + "repository": "https://github.com/matklad/once_cell", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Single assignment cells and lazy values." + }, + { + "name": "once_cell_polyfill", + "version": "1.70.2", + "authors": null, + "repository": "https://github.com/polyfill-rs/once_cell_polyfill", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Polyfill for `OnceCell` stdlib feature for use with older MSRVs" + }, + { + "name": "opaque-debug", + "version": "0.3.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macro for opaque Debug trait implementation" + }, + { + "name": "parking_lot", + "version": "0.12.5", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "More compact and efficient implementations of the standard synchronization primitives." + }, + { + "name": "parking_lot_core", + "version": "0.9.12", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "An advanced API for creating custom synchronization primitives." + }, + { + "name": "pin-project-lite", + "version": "0.2.16", + "authors": null, + "repository": "https://github.com/taiki-e/pin-project-lite", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A lightweight version of pin-project written with declarative macros." + }, + { + "name": "pkcs8", + "version": "0.10.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/pkcs8", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification (RFC 5208), with additional support for PKCS#8v2 asymmetric key packages (RFC 5958)" + }, + { + "name": "poly1305", + "version": "0.8.0", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/universal-hashes", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "The Poly1305 universal hash function and message authentication code" + }, + { + "name": "ppv-lite86", + "version": "0.2.21", + "authors": "The CryptoCorrosion Contributors", + "repository": "https://github.com/cryptocorrosion/cryptocorrosion", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Cross-platform cryptography-oriented low-level SIMD library." + }, + { + "name": "proc-macro2", + "version": "1.0.103", + "authors": "David Tolnay |Alex Crichton ", + "repository": "https://github.com/dtolnay/proc-macro2", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case." + }, + { + "name": "quote", + "version": "1.0.42", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/quote", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Quasi-quoting macro quote!(...)" + }, + { + "name": "r-efi", + "version": "5.3.0", + "authors": null, + "repository": "https://github.com/r-efi/r-efi", + "license": "Apache-2.0 OR LGPL-2.1-or-later OR MIT", + "license_file": null, + "description": "UEFI Reference Specification Protocol Constants and Definitions" + }, + { + "name": "rand", + "version": "0.9.2", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Random number generators and other randomness functionality." + }, + { + "name": "rand_chacha", + "version": "0.9.0", + "authors": "The Rand Project Developers|The Rust Project Developers|The CryptoCorrosion Contributors", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "ChaCha random number generator" + }, + { + "name": "rand_core", + "version": "0.6.4", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core random number generator traits and tools for implementation." + }, + { + "name": "rand_core", + "version": "0.9.3", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core random number generator traits and tools for implementation." + }, + { + "name": "redox_syscall", + "version": "0.5.18", + "authors": "Jeremy Soller ", + "repository": "https://gitlab.redox-os.org/redox-os/syscall", + "license": "MIT", + "license_file": null, + "description": "A Rust library to access raw Redox system calls" + }, + { + "name": "rustc_version", + "version": "0.4.1", + "authors": null, + "repository": "https://github.com/djc/rustc-version-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library for querying the version of a installed rustc compiler" + }, + { + "name": "rustix", + "version": "1.1.2", + "authors": "Dan Gohman |Jakub Konka ", + "repository": "https://github.com/bytecodealliance/rustix", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls" + }, + { + "name": "ryu", + "version": "1.0.20", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/ryu", + "license": "Apache-2.0 OR BSL-1.0", + "license_file": null, + "description": "Fast floating point to string conversion" + }, + { + "name": "ryu-js", + "version": "0.2.2", + "authors": "David Tolnay |boa-dev", + "repository": "https://github.com/boa-dev/ryu-js", + "license": "Apache-2.0 OR BSL-1.0", + "license_file": null, + "description": "Fast floating point to string conversion, ECMAScript compliant." + }, + { + "name": "scopeguard", + "version": "1.2.0", + "authors": "bluss", + "repository": "https://github.com/bluss/scopeguard", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A RAII scope guard that will run a given closure when it goes out of scope, even if the code between panics (assuming unwinding panic). Defines the macros `defer!`, `defer_on_unwind!`, `defer_on_success!` as shorthands for guards with one of the implemented strategies." + }, + { + "name": "semver", + "version": "1.0.27", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/semver", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parser and evaluator for Cargo's flavor of Semantic Versioning" + }, + { + "name": "serde", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A generic serialization/deserialization framework" + }, + { + "name": "serde_core", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Serde traits only, with no support for derive -- use the `serde` crate instead" + }, + { + "name": "serde_derive", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]" + }, + { + "name": "serde_jcs", + "version": "0.1.0", + "authors": "l1h3r ", + "repository": "https://github.com/l1h3r/serde_jcs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "JSON Canonicalization Scheme (JCS) for Serde" + }, + { + "name": "serde_json", + "version": "1.0.145", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/json", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A JSON serialization file format" + }, + { + "name": "sha2", + "version": "0.10.9", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/hashes", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of the SHA-2 hash function family including SHA-224, SHA-256, SHA-384, and SHA-512." + }, + { + "name": "signal-hook-registry", + "version": "1.4.7", + "authors": "Michal 'vorner' Vaner |Masaki Hara ", + "repository": "https://github.com/vorner/signal-hook", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Backend crate for signal-hook" + }, + { + "name": "signature", + "version": "2.2.0", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits/tree/master/signature", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for cryptographic signature algorithms (e.g. ECDSA, Ed25519)" + }, + { + "name": "simd-adler32", + "version": "0.3.7", + "authors": "Marvin Countryman ", + "repository": "https://github.com/mcountryman/simd-adler32", + "license": "MIT", + "license_file": null, + "description": "A SIMD-accelerated Adler-32 hash algorithm implementation." + }, + { + "name": "smallvec", + "version": "1.15.1", + "authors": "The Servo Project Developers", + "repository": "https://github.com/servo/rust-smallvec", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "'Small vector' optimization: store up to a small number of items on the stack" + }, + { + "name": "socket2", + "version": "0.6.1", + "authors": "Alex Crichton |Thomas de Zeeuw ", + "repository": "https://github.com/rust-lang/socket2", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Utilities for handling networking sockets with a maximal amount of configuration possible intended." + }, + { + "name": "spki", + "version": "0.7.3", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/spki", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "X.509 Subject Public Key Info (RFC5280) describing public keys as well as their associated AlgorithmIdentifiers (i.e. OIDs)" + }, + { + "name": "strsim", + "version": "0.11.1", + "authors": "Danny Guo |maxbachmann ", + "repository": "https://github.com/rapidfuzz/strsim-rs", + "license": "MIT", + "license_file": null, + "description": "Implementations of string similarity metrics. Includes Hamming, Levenshtein, OSA, Damerau-Levenshtein, Jaro, Jaro-Winkler, and S\u00f8rensen-Dice." + }, + { + "name": "subtle", + "version": "2.6.1", + "authors": "Isis Lovecruft |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/subtle", + "license": "BSD-3-Clause", + "license_file": null, + "description": "Pure-Rust traits and utilities for constant-time cryptographic implementations." + }, + { + "name": "syft-crypto-cli", + "version": "0.1.2-beta.2", + "authors": "Khoa Nguyen |Madhava Jay ", + "repository": "https://github.com/OpenMined/syft-crypto-core", + "license": "Apache-2.0", + "license_file": null, + "description": "Command-line interface for SyftBox cryptographic operations" + }, + { + "name": "syft-crypto-protocol", + "version": "0.1.2-beta.2", + "authors": "Khoa Nguyen |Madhava Jay ", + "repository": "https://github.com/OpenMined/syft-crypto-core", + "license": "Apache-2.0", + "license_file": null, + "description": "Cryptographic protocol implementation for SyftBox using X3DH-style key agreement" + }, + { + "name": "syn", + "version": "2.0.111", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/syn", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parser for Rust source code" + }, + { + "name": "tar", + "version": "0.4.44", + "authors": "Alex Crichton ", + "repository": "https://github.com/alexcrichton/tar-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A Rust implementation of a TAR file reader and writer. This library does not currently handle compression, but it is abstract over all I/O readers and writers. Additionally, great lengths are taken to ensure that the entire contents are never required to be entirely resident in memory all at once." + }, + { + "name": "tempfile", + "version": "3.23.0", + "authors": "Steven Allen |The Rust Project Developers|Ashley Mannix |Jason White ", + "repository": "https://github.com/Stebalien/tempfile", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library for managing temporary files and directories." + }, + { + "name": "thiserror", + "version": "2.0.17", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/thiserror", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "derive(Error)" + }, + { + "name": "thiserror-impl", + "version": "2.0.17", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/thiserror", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Implementation detail of the `thiserror` crate" + }, + { + "name": "tinyvec", + "version": "1.10.0", + "authors": "Lokathor ", + "repository": "https://github.com/Lokathor/tinyvec", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "`tinyvec` provides 100% safe vec-like data structures." + }, + { + "name": "tinyvec_macros", + "version": "0.1.1", + "authors": "Soveu ", + "repository": "https://github.com/Soveu/tinyvec_macros", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "Some macros for tiny containers" + }, + { + "name": "tokio", + "version": "1.48.0", + "authors": "Tokio Contributors ", + "repository": "https://github.com/tokio-rs/tokio", + "license": "MIT", + "license_file": null, + "description": "An event-driven, non-blocking I/O platform for writing asynchronous I/O backed applications." + }, + { + "name": "tokio-macros", + "version": "2.6.0", + "authors": "Tokio Contributors ", + "repository": "https://github.com/tokio-rs/tokio", + "license": "MIT", + "license_file": null, + "description": "Tokio's proc macros." + }, + { + "name": "typenum", + "version": "1.19.0", + "authors": "Paho Lurie-Gregg |Andre Bogus ", + "repository": "https://github.com/paholg/typenum", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Typenum is a Rust library for type-level numbers evaluated at compile time. It currently supports bits, unsigned integers, and signed integers. It also provides a type-level array of type-level numbers, but its implementation is incomplete." + }, + { + "name": "unicode-ident", + "version": "1.0.22", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/unicode-ident", + "license": "(Apache-2.0 OR MIT) AND Unicode-3.0", + "license_file": null, + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31" + }, + { + "name": "unicode-normalization", + "version": "0.1.25", + "authors": "kwantam |Manish Goregaokar ", + "repository": "https://github.com/unicode-rs/unicode-normalization", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "This crate provides functions for normalization of Unicode strings, including Canonical and Compatible Decomposition and Recomposition, as described in Unicode Standard Annex #15." + }, + { + "name": "universal-hash", + "version": "0.5.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits which describe the functionality of universal hash functions (UHFs)" + }, + { + "name": "urlencoding", + "version": "2.1.3", + "authors": "Kornel |Bertram Truong ", + "repository": "https://github.com/kornelski/rust_urlencoding", + "license": "MIT", + "license_file": null, + "description": "A Rust library for doing URL percentage encoding." + }, + { + "name": "utf8parse", + "version": "0.2.2", + "authors": "Joe Wilm |Christian Duerr ", + "repository": "https://github.com/alacritty/vte", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Table-driven UTF-8 parser" + }, + { + "name": "version_check", + "version": "0.9.5", + "authors": "Sergio Benitez ", + "repository": "https://github.com/SergioBenitez/version_check", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Tiny crate to check the version of the installed/running rustc." + }, + { + "name": "wasi", + "version": "0.11.1+wasi-snapshot-preview1", + "authors": "The Cranelift Project Developers", + "repository": "https://github.com/bytecodealliance/wasi", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Experimental WASI API bindings for Rust" + }, + { + "name": "wasip2", + "version": "1.0.1+wasi-0.2.4", + "authors": null, + "repository": "https://github.com/bytecodealliance/wasi-rs", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "WASIp2 API bindings for Rust" + }, + { + "name": "widestring", + "version": "0.4.3", + "authors": "Kathryn Long ", + "repository": "https://github.com/starkat99/widestring-rs.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A wide string FFI library for converting to and from wide strings, such as those often used in Windows API or other FFI libraries. Both UTF-16 and UTF-32 types are provided." + }, + { + "name": "winapi", + "version": "0.3.9", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Raw FFI bindings for all of Windows API." + }, + { + "name": "winapi-i686-pc-windows-gnu", + "version": "0.4.0", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libraries for the i686-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead." + }, + { + "name": "winapi-x86_64-pc-windows-gnu", + "version": "0.4.0", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libraries for the x86_64-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead." + }, + { + "name": "windows-acl", + "version": "0.3.0", + "authors": "William Woodruff |yying ", + "repository": "https://github.com/trailofbits/windows-acl", + "license": "MIT", + "license_file": null, + "description": "Rust crate to simplify Windows ACL operations" + }, + { + "name": "windows-link", + "version": "0.2.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Linking for Windows" + }, + { + "name": "windows-sys", + "version": "0.60.2", + "authors": "Microsoft", + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Rust for Windows" + }, + { + "name": "windows-sys", + "version": "0.61.2", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Rust for Windows" + }, + { + "name": "windows-targets", + "version": "0.53.5", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libs for Windows" + }, + { + "name": "windows_aarch64_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_aarch64_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_gnu", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_gnu", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "wit-bindgen", + "version": "0.46.0", + "authors": "Alex Crichton ", + "repository": "https://github.com/bytecodealliance/wit-bindgen", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Rust bindings generator and runtime support for WIT and the component model. Used when compiling Rust programs to the component model." + }, + { + "name": "x25519-dalek", + "version": "2.0.1", + "authors": "Isis Lovecruft |DebugSteven |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/x25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "X25519 elliptic curve Diffie-Hellman key exchange in pure-Rust, using curve25519-dalek." + }, + { + "name": "xattr", + "version": "1.6.1", + "authors": "Steven Allen ", + "repository": "https://github.com/Stebalien/xattr", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "unix extended filesystem attributes" + }, + { + "name": "zerocopy", + "version": "0.8.31", + "authors": "Joshua Liebow-Feeser |Jack Wrenn ", + "repository": "https://github.com/google/zerocopy", + "license": "Apache-2.0 OR BSD-2-Clause OR MIT", + "license_file": null, + "description": "Zerocopy makes zero-cost memory manipulation effortless. We write \"unsafe\" so you don't have to." + }, + { + "name": "zerocopy-derive", + "version": "0.8.31", + "authors": "Joshua Liebow-Feeser |Jack Wrenn ", + "repository": "https://github.com/google/zerocopy", + "license": "Apache-2.0 OR BSD-2-Clause OR MIT", + "license_file": null, + "description": "Custom derive for traits from the zerocopy crate" + }, + { + "name": "zeroize", + "version": "1.8.2", + "authors": "The RustCrypto Project Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Securely clear secrets from memory with a simple trait built on stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler. Uses a portable pure Rust implementation that works everywhere, even WASM!" + }, + { + "name": "zeroize_derive", + "version": "1.4.2", + "authors": "The RustCrypto Project Developers", + "repository": "https://github.com/RustCrypto/utils/tree/master/zeroize/derive", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Custom derive support for zeroize" + } +] \ No newline at end of file diff --git a/licenses/rust-licenses-protocol.json b/licenses/rust-licenses-protocol.json new file mode 100644 index 0000000..dc2011a --- /dev/null +++ b/licenses/rust-licenses-protocol.json @@ -0,0 +1,1136 @@ +[ + { + "name": "adler2", + "version": "2.0.1", + "authors": "Jonas Schievink |oyvindln ", + "repository": "https://github.com/oyvindln/adler2", + "license": "0BSD OR Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple clean-room implementation of the Adler-32 checksum" + }, + { + "name": "aead", + "version": "0.5.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for Authenticated Encryption with Associated Data (AEAD) algorithms, such as AES-GCM as ChaCha20Poly1305, which provide a high-level API" + }, + { + "name": "anyhow", + "version": "1.0.100", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/anyhow", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Flexible concrete Error type built on std::error::Error" + }, + { + "name": "autocfg", + "version": "1.5.0", + "authors": "Josh Stone ", + "repository": "https://github.com/cuviper/autocfg", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Automatic cfg for Rust compiler features" + }, + { + "name": "base64", + "version": "0.22.1", + "authors": "Marshall Pierce ", + "repository": "https://github.com/marshallpierce/rust-base64", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "encodes and decodes base64 as bytes or utf8" + }, + { + "name": "base64ct", + "version": "1.8.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable \"best effort\" constant-time operation and embedded-friendly no_std support" + }, + { + "name": "bip39", + "version": "2.2.0", + "authors": "Steven Roose ", + "repository": "https://github.com/rust-bitcoin/rust-bip39/", + "license": "CC0-1.0", + "license_file": null, + "description": "Library for BIP-39 Bitcoin mnemonic codes" + }, + { + "name": "bitcoin-internals", + "version": "0.2.0", + "authors": "Andrew Poelstra |The Rust Bitcoin developers", + "repository": "https://github.com/rust-bitcoin/rust-bitcoin/", + "license": "CC0-1.0", + "license_file": null, + "description": "Internal types and macros used by rust-bitcoin ecosystem" + }, + { + "name": "bitcoin_hashes", + "version": "0.13.0", + "authors": "Andrew Poelstra ", + "repository": "https://github.com/rust-bitcoin/rust-bitcoin", + "license": "CC0-1.0", + "license_file": null, + "description": "Hash functions used by the rust-bitcoin eccosystem" + }, + { + "name": "bitflags", + "version": "2.10.0", + "authors": "The Rust Project Developers", + "repository": "https://github.com/bitflags/bitflags", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A macro to generate structures which behave like bitflags." + }, + { + "name": "block-buffer", + "version": "0.10.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Buffer type for block processing of data" + }, + { + "name": "bytes", + "version": "1.11.0", + "authors": "Carl Lerche |Sean McArthur ", + "repository": "https://github.com/tokio-rs/bytes", + "license": "MIT", + "license_file": null, + "description": "Types and traits for working with bytes" + }, + { + "name": "cfg-if", + "version": "1.0.4", + "authors": "Alex Crichton ", + "repository": "https://github.com/rust-lang/cfg-if", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted." + }, + { + "name": "chacha20", + "version": "0.9.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/stream-ciphers", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "The ChaCha20 stream cipher (RFC 8439) implemented in pure Rust using traits from the RustCrypto `cipher` crate, with optional architecture-specific hardware acceleration (AVX2, SSE2). Additionally provides the ChaCha8, ChaCha12, XChaCha20, XChaCha12 and XChaCha8 stream ciphers, and also optional rand_core-compatible RNGs based on those ciphers." + }, + { + "name": "chacha20poly1305", + "version": "0.10.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of the ChaCha20Poly1305 Authenticated Encryption with Additional Data Cipher (RFC 8439) with optional architecture-specific hardware acceleration. Also contains implementations of the XChaCha20Poly1305 extended nonce variant of ChaCha20Poly1305, and the reduced-round ChaCha8Poly1305 and ChaCha12Poly1305 lightweight variants." + }, + { + "name": "cipher", + "version": "0.4.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for describing block ciphers and stream ciphers" + }, + { + "name": "const-oid", + "version": "0.9.6", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/const-oid", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Const-friendly implementation of the ISO/IEC Object Identifier (OID) standard as defined in ITU X.660, with support for BER/DER encoding/decoding as well as heapless no_std (i.e. embedded) support" + }, + { + "name": "cpufeatures", + "version": "0.2.17", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Lightweight runtime CPU feature detection for aarch64, loongarch64, and x86/x86_64 targets, with no_std support and support for mobile targets including Android and iOS" + }, + { + "name": "crc32fast", + "version": "1.5.0", + "authors": "Sam Rijs |Alex Crichton ", + "repository": "https://github.com/srijs/rust-crc32fast", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Fast, SIMD-accelerated CRC32 (IEEE) checksum computation" + }, + { + "name": "crypto-common", + "version": "0.1.7", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Common cryptographic traits" + }, + { + "name": "curve25519-dalek", + "version": "4.1.3", + "authors": "Isis Lovecruft |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/curve25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "A pure-Rust implementation of group operations on ristretto255 and Curve25519" + }, + { + "name": "curve25519-dalek-derive", + "version": "0.1.1", + "authors": null, + "repository": "https://github.com/dalek-cryptography/curve25519-dalek", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "curve25519-dalek Derives" + }, + { + "name": "der", + "version": "0.7.10", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/der", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules (DER) for Abstract Syntax Notation One (ASN.1) as described in ITU X.690 with full support for heapless no_std targets" + }, + { + "name": "digest", + "version": "0.10.7", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for cryptographic hash functions and message authentication codes" + }, + { + "name": "ed25519", + "version": "2.2.3", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/signatures/tree/master/ed25519", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Edwards Digital Signature Algorithm (EdDSA) over Curve25519 (as specified in RFC 8032) support library providing signature type definitions and PKCS#8 private key decoding/encoding support" + }, + { + "name": "ed25519-dalek", + "version": "2.2.0", + "authors": "isis lovecruft |Tony Arcieri |Michael Rosenberg ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "Fast and efficient ed25519 EdDSA key generations, signing, and verification in pure Rust." + }, + { + "name": "errno", + "version": "0.3.14", + "authors": "Chris Wong |Dan Gohman ", + "repository": "https://github.com/lambda-fairy/rust-errno", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Cross-platform interface to the `errno` variable." + }, + { + "name": "fastrand", + "version": "2.3.0", + "authors": "Stjepan Glavina ", + "repository": "https://github.com/smol-rs/fastrand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A simple and fast random number generator" + }, + { + "name": "fiat-crypto", + "version": "0.2.9", + "authors": "Fiat Crypto library authors ", + "repository": "https://github.com/mit-plv/fiat-crypto", + "license": "Apache-2.0 OR BSD-1-Clause OR MIT", + "license_file": null, + "description": "Fiat-crypto generated Rust" + }, + { + "name": "field-offset", + "version": "0.3.6", + "authors": "Diggory Blake ", + "repository": "https://github.com/Diggsey/rust-field-offset", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Safe pointer-to-member implementation" + }, + { + "name": "filetime", + "version": "0.2.26", + "authors": "Alex Crichton ", + "repository": "https://github.com/alexcrichton/filetime", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Platform-agnostic accessors of timestamps in File metadata" + }, + { + "name": "flate2", + "version": "1.1.5", + "authors": "Alex Crichton |Josh Triplett ", + "repository": "https://github.com/rust-lang/flate2-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "DEFLATE compression and decompression exposed as Read/BufRead/Write streams. Supports miniz_oxide and multiple zlib implementations. Supports zlib, gzip, and raw deflate streams." + }, + { + "name": "generic-array", + "version": "0.14.7", + "authors": "Bart\u0142omiej Kami\u0144ski |Aaron Trent ", + "repository": "https://github.com/fizyk20/generic-array.git", + "license": "MIT", + "license_file": null, + "description": "Generic types implementing functionality of arrays" + }, + { + "name": "getrandom", + "version": "0.2.16", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/getrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A small cross-platform library for retrieving random data from system source" + }, + { + "name": "getrandom", + "version": "0.3.4", + "authors": "The Rand Project Developers", + "repository": "https://github.com/rust-random/getrandom", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A small cross-platform library for retrieving random data from system source" + }, + { + "name": "hex", + "version": "0.4.3", + "authors": "KokaKiwi ", + "repository": "https://github.com/KokaKiwi/rust-hex", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Encoding and decoding data into/from hexadecimal representation." + }, + { + "name": "hex-conservative", + "version": "0.1.2", + "authors": "Andrew Poelstra ", + "repository": "https://github.com/rust-bitcoin/hex-conservative", + "license": "CC0-1.0", + "license_file": null, + "description": "A hex encoding and decoding crate with a conservative MSRV and dependency policy." + }, + { + "name": "hkdf", + "version": "0.12.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/KDFs/", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)" + }, + { + "name": "hmac", + "version": "0.12.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/MACs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Generic implementation of Hash-based Message Authentication Code (HMAC)" + }, + { + "name": "inout", + "version": "0.1.4", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Custom reference types for code generic over in-place and buffer-to-buffer modes of operation." + }, + { + "name": "itoa", + "version": "1.0.15", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/itoa", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Fast integer primitive to string conversion" + }, + { + "name": "libc", + "version": "0.2.177", + "authors": "The Rust Project Developers", + "repository": "https://github.com/rust-lang/libc", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Raw FFI bindings to platform libraries like libc." + }, + { + "name": "libredox", + "version": "0.1.10", + "authors": "4lDO2 <4lDO2@protonmail.com>", + "repository": "https://gitlab.redox-os.org/redox-os/libredox.git", + "license": "MIT", + "license_file": null, + "description": "Redox stable ABI" + }, + { + "name": "linux-raw-sys", + "version": "0.11.0", + "authors": "Dan Gohman ", + "repository": "https://github.com/sunfishcode/linux-raw-sys", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Generated bindings for Linux's userspace API" + }, + { + "name": "lock_api", + "version": "0.4.14", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Wrappers to create fully-featured Mutex and RwLock types. Compatible with no_std." + }, + { + "name": "memchr", + "version": "2.7.6", + "authors": "Andrew Gallant |bluss", + "repository": "https://github.com/BurntSushi/memchr", + "license": "MIT OR Unlicense", + "license_file": null, + "description": "Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 1, 2 or 3 byte search and single substring search." + }, + { + "name": "memoffset", + "version": "0.9.1", + "authors": "Gilad Naaman ", + "repository": "https://github.com/Gilnaa/memoffset", + "license": "MIT", + "license_file": null, + "description": "offset_of functionality for Rust structs." + }, + { + "name": "miniz_oxide", + "version": "0.8.9", + "authors": "Frommi |oyvindln |Rich Geldreich richgel99@gmail.com", + "repository": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "DEFLATE compression and decompression library rewritten in Rust based on miniz" + }, + { + "name": "mio", + "version": "1.1.0", + "authors": "Carl Lerche |Thomas de Zeeuw |Tokio Contributors ", + "repository": "https://github.com/tokio-rs/mio", + "license": "MIT", + "license_file": null, + "description": "Lightweight non-blocking I/O." + }, + { + "name": "once_cell", + "version": "1.21.3", + "authors": "Aleksey Kladov ", + "repository": "https://github.com/matklad/once_cell", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Single assignment cells and lazy values." + }, + { + "name": "opaque-debug", + "version": "0.3.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macro for opaque Debug trait implementation" + }, + { + "name": "parking_lot", + "version": "0.12.5", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "More compact and efficient implementations of the standard synchronization primitives." + }, + { + "name": "parking_lot_core", + "version": "0.9.12", + "authors": "Amanieu d'Antras ", + "repository": "https://github.com/Amanieu/parking_lot", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "An advanced API for creating custom synchronization primitives." + }, + { + "name": "pin-project-lite", + "version": "0.2.16", + "authors": null, + "repository": "https://github.com/taiki-e/pin-project-lite", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A lightweight version of pin-project written with declarative macros." + }, + { + "name": "pkcs8", + "version": "0.10.2", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/pkcs8", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification (RFC 5208), with additional support for PKCS#8v2 asymmetric key packages (RFC 5958)" + }, + { + "name": "poly1305", + "version": "0.8.0", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/universal-hashes", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "The Poly1305 universal hash function and message authentication code" + }, + { + "name": "ppv-lite86", + "version": "0.2.21", + "authors": "The CryptoCorrosion Contributors", + "repository": "https://github.com/cryptocorrosion/cryptocorrosion", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Cross-platform cryptography-oriented low-level SIMD library." + }, + { + "name": "proc-macro2", + "version": "1.0.103", + "authors": "David Tolnay |Alex Crichton ", + "repository": "https://github.com/dtolnay/proc-macro2", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case." + }, + { + "name": "quote", + "version": "1.0.42", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/quote", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Quasi-quoting macro quote!(...)" + }, + { + "name": "r-efi", + "version": "5.3.0", + "authors": null, + "repository": "https://github.com/r-efi/r-efi", + "license": "Apache-2.0 OR LGPL-2.1-or-later OR MIT", + "license_file": null, + "description": "UEFI Reference Specification Protocol Constants and Definitions" + }, + { + "name": "rand", + "version": "0.9.2", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Random number generators and other randomness functionality." + }, + { + "name": "rand_chacha", + "version": "0.9.0", + "authors": "The Rand Project Developers|The Rust Project Developers|The CryptoCorrosion Contributors", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "ChaCha random number generator" + }, + { + "name": "rand_core", + "version": "0.6.4", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core random number generator traits and tools for implementation." + }, + { + "name": "rand_core", + "version": "0.9.3", + "authors": "The Rand Project Developers|The Rust Project Developers", + "repository": "https://github.com/rust-random/rand", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Core random number generator traits and tools for implementation." + }, + { + "name": "redox_syscall", + "version": "0.5.18", + "authors": "Jeremy Soller ", + "repository": "https://gitlab.redox-os.org/redox-os/syscall", + "license": "MIT", + "license_file": null, + "description": "A Rust library to access raw Redox system calls" + }, + { + "name": "rustc_version", + "version": "0.4.1", + "authors": null, + "repository": "https://github.com/djc/rustc-version-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library for querying the version of a installed rustc compiler" + }, + { + "name": "rustix", + "version": "1.1.2", + "authors": "Dan Gohman |Jakub Konka ", + "repository": "https://github.com/bytecodealliance/rustix", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls" + }, + { + "name": "ryu", + "version": "1.0.20", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/ryu", + "license": "Apache-2.0 OR BSL-1.0", + "license_file": null, + "description": "Fast floating point to string conversion" + }, + { + "name": "ryu-js", + "version": "0.2.2", + "authors": "David Tolnay |boa-dev", + "repository": "https://github.com/boa-dev/ryu-js", + "license": "Apache-2.0 OR BSL-1.0", + "license_file": null, + "description": "Fast floating point to string conversion, ECMAScript compliant." + }, + { + "name": "scopeguard", + "version": "1.2.0", + "authors": "bluss", + "repository": "https://github.com/bluss/scopeguard", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A RAII scope guard that will run a given closure when it goes out of scope, even if the code between panics (assuming unwinding panic). Defines the macros `defer!`, `defer_on_unwind!`, `defer_on_success!` as shorthands for guards with one of the implemented strategies." + }, + { + "name": "semver", + "version": "1.0.27", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/semver", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parser and evaluator for Cargo's flavor of Semantic Versioning" + }, + { + "name": "serde", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A generic serialization/deserialization framework" + }, + { + "name": "serde_core", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Serde traits only, with no support for derive -- use the `serde` crate instead" + }, + { + "name": "serde_derive", + "version": "1.0.228", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/serde", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]" + }, + { + "name": "serde_jcs", + "version": "0.1.0", + "authors": "l1h3r ", + "repository": "https://github.com/l1h3r/serde_jcs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "JSON Canonicalization Scheme (JCS) for Serde" + }, + { + "name": "serde_json", + "version": "1.0.145", + "authors": "Erick Tryzelaar |David Tolnay ", + "repository": "https://github.com/serde-rs/json", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A JSON serialization file format" + }, + { + "name": "sha2", + "version": "0.10.9", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/hashes", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Pure Rust implementation of the SHA-2 hash function family including SHA-224, SHA-256, SHA-384, and SHA-512." + }, + { + "name": "signal-hook-registry", + "version": "1.4.7", + "authors": "Michal 'vorner' Vaner |Masaki Hara ", + "repository": "https://github.com/vorner/signal-hook", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Backend crate for signal-hook" + }, + { + "name": "signature", + "version": "2.2.0", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits/tree/master/signature", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits for cryptographic signature algorithms (e.g. ECDSA, Ed25519)" + }, + { + "name": "simd-adler32", + "version": "0.3.7", + "authors": "Marvin Countryman ", + "repository": "https://github.com/mcountryman/simd-adler32", + "license": "MIT", + "license_file": null, + "description": "A SIMD-accelerated Adler-32 hash algorithm implementation." + }, + { + "name": "smallvec", + "version": "1.15.1", + "authors": "The Servo Project Developers", + "repository": "https://github.com/servo/rust-smallvec", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "'Small vector' optimization: store up to a small number of items on the stack" + }, + { + "name": "socket2", + "version": "0.6.1", + "authors": "Alex Crichton |Thomas de Zeeuw ", + "repository": "https://github.com/rust-lang/socket2", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Utilities for handling networking sockets with a maximal amount of configuration possible intended." + }, + { + "name": "spki", + "version": "0.7.3", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/formats/tree/master/spki", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "X.509 Subject Public Key Info (RFC5280) describing public keys as well as their associated AlgorithmIdentifiers (i.e. OIDs)" + }, + { + "name": "subtle", + "version": "2.6.1", + "authors": "Isis Lovecruft |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/subtle", + "license": "BSD-3-Clause", + "license_file": null, + "description": "Pure-Rust traits and utilities for constant-time cryptographic implementations." + }, + { + "name": "syft-crypto-protocol", + "version": "0.1.2-beta.2", + "authors": "Khoa Nguyen |Madhava Jay ", + "repository": "https://github.com/OpenMined/syft-crypto-core", + "license": "Apache-2.0", + "license_file": null, + "description": "Cryptographic protocol implementation for SyftBox using X3DH-style key agreement" + }, + { + "name": "syn", + "version": "2.0.111", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/syn", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Parser for Rust source code" + }, + { + "name": "tar", + "version": "0.4.44", + "authors": "Alex Crichton ", + "repository": "https://github.com/alexcrichton/tar-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A Rust implementation of a TAR file reader and writer. This library does not currently handle compression, but it is abstract over all I/O readers and writers. Additionally, great lengths are taken to ensure that the entire contents are never required to be entirely resident in memory all at once." + }, + { + "name": "tempfile", + "version": "3.23.0", + "authors": "Steven Allen |The Rust Project Developers|Ashley Mannix |Jason White ", + "repository": "https://github.com/Stebalien/tempfile", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A library for managing temporary files and directories." + }, + { + "name": "thiserror", + "version": "2.0.17", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/thiserror", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "derive(Error)" + }, + { + "name": "thiserror-impl", + "version": "2.0.17", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/thiserror", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Implementation detail of the `thiserror` crate" + }, + { + "name": "tinyvec", + "version": "1.10.0", + "authors": "Lokathor ", + "repository": "https://github.com/Lokathor/tinyvec", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "`tinyvec` provides 100% safe vec-like data structures." + }, + { + "name": "tinyvec_macros", + "version": "0.1.1", + "authors": "Soveu ", + "repository": "https://github.com/Soveu/tinyvec_macros", + "license": "Apache-2.0 OR MIT OR Zlib", + "license_file": null, + "description": "Some macros for tiny containers" + }, + { + "name": "tokio", + "version": "1.48.0", + "authors": "Tokio Contributors ", + "repository": "https://github.com/tokio-rs/tokio", + "license": "MIT", + "license_file": null, + "description": "An event-driven, non-blocking I/O platform for writing asynchronous I/O backed applications." + }, + { + "name": "tokio-macros", + "version": "2.6.0", + "authors": "Tokio Contributors ", + "repository": "https://github.com/tokio-rs/tokio", + "license": "MIT", + "license_file": null, + "description": "Tokio's proc macros." + }, + { + "name": "typenum", + "version": "1.19.0", + "authors": "Paho Lurie-Gregg |Andre Bogus ", + "repository": "https://github.com/paholg/typenum", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Typenum is a Rust library for type-level numbers evaluated at compile time. It currently supports bits, unsigned integers, and signed integers. It also provides a type-level array of type-level numbers, but its implementation is incomplete." + }, + { + "name": "unicode-ident", + "version": "1.0.22", + "authors": "David Tolnay ", + "repository": "https://github.com/dtolnay/unicode-ident", + "license": "(Apache-2.0 OR MIT) AND Unicode-3.0", + "license_file": null, + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31" + }, + { + "name": "unicode-normalization", + "version": "0.1.25", + "authors": "kwantam |Manish Goregaokar ", + "repository": "https://github.com/unicode-rs/unicode-normalization", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "This crate provides functions for normalization of Unicode strings, including Canonical and Compatible Decomposition and Recomposition, as described in Unicode Standard Annex #15." + }, + { + "name": "universal-hash", + "version": "0.5.1", + "authors": "RustCrypto Developers", + "repository": "https://github.com/RustCrypto/traits", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Traits which describe the functionality of universal hash functions (UHFs)" + }, + { + "name": "urlencoding", + "version": "2.1.3", + "authors": "Kornel |Bertram Truong ", + "repository": "https://github.com/kornelski/rust_urlencoding", + "license": "MIT", + "license_file": null, + "description": "A Rust library for doing URL percentage encoding." + }, + { + "name": "version_check", + "version": "0.9.5", + "authors": "Sergio Benitez ", + "repository": "https://github.com/SergioBenitez/version_check", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Tiny crate to check the version of the installed/running rustc." + }, + { + "name": "wasi", + "version": "0.11.1+wasi-snapshot-preview1", + "authors": "The Cranelift Project Developers", + "repository": "https://github.com/bytecodealliance/wasi", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Experimental WASI API bindings for Rust" + }, + { + "name": "wasip2", + "version": "1.0.1+wasi-0.2.4", + "authors": null, + "repository": "https://github.com/bytecodealliance/wasi-rs", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "WASIp2 API bindings for Rust" + }, + { + "name": "widestring", + "version": "0.4.3", + "authors": "Kathryn Long ", + "repository": "https://github.com/starkat99/widestring-rs.git", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "A wide string FFI library for converting to and from wide strings, such as those often used in Windows API or other FFI libraries. Both UTF-16 and UTF-32 types are provided." + }, + { + "name": "winapi", + "version": "0.3.9", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Raw FFI bindings for all of Windows API." + }, + { + "name": "winapi-i686-pc-windows-gnu", + "version": "0.4.0", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libraries for the i686-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead." + }, + { + "name": "winapi-x86_64-pc-windows-gnu", + "version": "0.4.0", + "authors": "Peter Atashian ", + "repository": "https://github.com/retep998/winapi-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libraries for the x86_64-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead." + }, + { + "name": "windows-acl", + "version": "0.3.0", + "authors": "William Woodruff |yying ", + "repository": "https://github.com/trailofbits/windows-acl", + "license": "MIT", + "license_file": null, + "description": "Rust crate to simplify Windows ACL operations" + }, + { + "name": "windows-link", + "version": "0.2.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Linking for Windows" + }, + { + "name": "windows-sys", + "version": "0.60.2", + "authors": "Microsoft", + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Rust for Windows" + }, + { + "name": "windows-sys", + "version": "0.61.2", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Rust for Windows" + }, + { + "name": "windows-targets", + "version": "0.53.5", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import libs for Windows" + }, + { + "name": "windows_aarch64_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_aarch64_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_gnu", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_i686_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_gnu", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_gnullvm", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "windows_x86_64_msvc", + "version": "0.53.1", + "authors": null, + "repository": "https://github.com/microsoft/windows-rs", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Import lib for Windows" + }, + { + "name": "wit-bindgen", + "version": "0.46.0", + "authors": "Alex Crichton ", + "repository": "https://github.com/bytecodealliance/wit-bindgen", + "license": "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT", + "license_file": null, + "description": "Rust bindings generator and runtime support for WIT and the component model. Used when compiling Rust programs to the component model." + }, + { + "name": "x25519-dalek", + "version": "2.0.1", + "authors": "Isis Lovecruft |DebugSteven |Henry de Valence ", + "repository": "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/x25519-dalek", + "license": "BSD-3-Clause", + "license_file": null, + "description": "X25519 elliptic curve Diffie-Hellman key exchange in pure-Rust, using curve25519-dalek." + }, + { + "name": "xattr", + "version": "1.6.1", + "authors": "Steven Allen ", + "repository": "https://github.com/Stebalien/xattr", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "unix extended filesystem attributes" + }, + { + "name": "zerocopy", + "version": "0.8.31", + "authors": "Joshua Liebow-Feeser |Jack Wrenn ", + "repository": "https://github.com/google/zerocopy", + "license": "Apache-2.0 OR BSD-2-Clause OR MIT", + "license_file": null, + "description": "Zerocopy makes zero-cost memory manipulation effortless. We write \"unsafe\" so you don't have to." + }, + { + "name": "zerocopy-derive", + "version": "0.8.31", + "authors": "Joshua Liebow-Feeser |Jack Wrenn ", + "repository": "https://github.com/google/zerocopy", + "license": "Apache-2.0 OR BSD-2-Clause OR MIT", + "license_file": null, + "description": "Custom derive for traits from the zerocopy crate" + }, + { + "name": "zeroize", + "version": "1.8.2", + "authors": "The RustCrypto Project Developers", + "repository": "https://github.com/RustCrypto/utils", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Securely clear secrets from memory with a simple trait built on stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler. Uses a portable pure Rust implementation that works everywhere, even WASM!" + }, + { + "name": "zeroize_derive", + "version": "1.4.2", + "authors": "The RustCrypto Project Developers", + "repository": "https://github.com/RustCrypto/utils/tree/master/zeroize/derive", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Custom derive support for zeroize" + } +] \ No newline at end of file diff --git a/licenses/rust-licenses-vendor_libsignal-protocol-syft.json b/licenses/rust-licenses-vendor_libsignal-protocol-syft.json new file mode 100644 index 0000000..e69de29 diff --git a/lint.sh b/lint.sh index 4800255..d308dc0 100755 --- a/lint.sh +++ b/lint.sh @@ -26,12 +26,6 @@ BINDINGS="$ROOT_DIR/bindings/python" PY_SRC="$BINDINGS/python" YELLOW='\033[0;33m' -# Check if submodules are initialized -if [[ ! -d "$ROOT_DIR/vendor/libsignal-protocol-syft/crates" ]]; then - echo -e "${YELLOW}⊘ skipped (submodules not initialized)${NC}" - exit 0 -fi - # Setup venv (must be sequential) uv venv --quiet 2>/dev/null || true source .venv/bin/activate diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 52b8200..04ccb64 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syft-crypto-protocol" -description = "Post-quantum cryptographic protocol implementation for SyftBox using Signal's PQXDH" +description = "Cryptographic protocol implementation for SyftBox using X3DH-style key agreement" version.workspace = true edition.workspace = true authors.workspace = true @@ -12,8 +12,10 @@ rust-version.workspace = true workspace = true [dependencies] -libsignal-protocol.workspace = true +ed25519-dalek.workspace = true +x25519-dalek.workspace = true rand.workspace = true +rand_core.workspace = true hex.workspace = true hkdf.workspace = true sha2.workspace = true diff --git a/protocol/src/datasite/bytes.rs b/protocol/src/datasite/bytes.rs index b619833..6164a12 100644 --- a/protocol/src/datasite/bytes.rs +++ b/protocol/src/datasite/bytes.rs @@ -41,7 +41,7 @@ pub struct BytesReadOutput { /// Writes plaintext or encrypted bytes to a datasite path. /// -/// When recipients are specified, encrypts data using PQXDH multi-recipient encryption +/// When recipients are specified, encrypts data using X3DH-style multi-recipient encryption /// where the payload is encrypted once and the key is wrapped separately for each recipient. /// All recipients can independently decrypt using their private keys. /// diff --git a/protocol/src/encryption/file_cipher.rs b/protocol/src/encryption/file_cipher.rs index 58f7955..026fb7f 100644 --- a/protocol/src/encryption/file_cipher.rs +++ b/protocol/src/encryption/file_cipher.rs @@ -14,7 +14,7 @@ pub const FILE_CIPHER_SUITE: &str = "xchacha20poly1305-v1"; /// Encrypts plaintext with XChaCha20-Poly1305. /// -/// Uses Signal's recommended attachment cipher with 192-bit nonces (XChaCha20) +/// Uses the recommended attachment cipher with 192-bit nonces (XChaCha20) /// and Poly1305 authentication tags. /// /// # Arguments @@ -29,9 +29,8 @@ pub(super) fn encrypt_payload( nonce: &[u8; 24], plaintext: &[u8], ) -> Result> { - // libsignal's Rust bindings currently expose PQXDH/session layers but do not provide - // an attachment/file cipher helper. Until that API exists upstream we locally reuse the - // XChaCha20-Poly1305 construction Signal uses elsewhere so callers can seal bytes today. + // We locally reuse the XChaCha20-Poly1305 construction used for attachments so + // callers can seal bytes without additional dependencies. let cipher = XChaCha20Poly1305::new(Key::from_slice(key)); cipher .encrypt( diff --git a/protocol/src/encryption/key_wrap.rs b/protocol/src/encryption/key_wrap.rs index 77d98a9..c02fbcb 100644 --- a/protocol/src/encryption/key_wrap.rs +++ b/protocol/src/encryption/key_wrap.rs @@ -6,12 +6,12 @@ use chacha20poly1305::{ aead::{Aead, Payload}, }; use hkdf::Hkdf; -use rand::{CryptoRng, Rng}; +use rand::{CryptoRng, RngCore}; use sha2::Sha256; use zeroize::Zeroizing; -/// HKDF salt for deriving wrapping keys from PQXDH material -const FILE_HKDF_SALT: &[u8] = b"syc-crypto-core:pqxdh:file"; +/// HKDF salt for deriving wrapping keys from X3DH material +const FILE_HKDF_SALT: &[u8] = b"syc-crypto-core:x3dh:file"; /// HKDF info parameter for key wrapping derivation const KEY_WRAP_INFO: &[u8] = b"syc-wrap-key"; @@ -22,22 +22,22 @@ const KEY_WRAP_AAD: &[u8] = b"syc-key-wrap-v1"; /// Wrapped key format: nonce (24) + encrypted_key (32) + auth_tag (16) pub(super) const WRAPPED_KEY_SIZE: usize = 72; -/// Derives a wrapping key from PQXDH shared material and wraps the file key. +/// Derives a wrapping key from X3DH shared material and wraps the file key. /// /// # Process -/// 1. Derive wrapping key: HKDF-SHA256(PQXDH_material, salt, info) +/// 1. Derive wrapping key: HKDF-SHA256(X3DH_material, salt, info) /// 2. Generate random 24-byte nonce /// 3. Encrypt file key with XChaCha20-Poly1305 /// /// # Returns /// nonce (24 bytes) || wrapped_key (32 bytes plaintext + 16 bytes auth tag) = 72 bytes total -pub(super) fn wrap_file_key( - pqxdh_material: &[u8], +pub(super) fn wrap_file_key( + x3dh_material: &[u8], file_key: &[u8; 32], rng: &mut R, ) -> Result> { - // Derive wrapping key from PQXDH material using HKDF - let hkdf = Hkdf::::new(Some(FILE_HKDF_SALT), pqxdh_material); + // Derive wrapping key from X3DH material using HKDF + let hkdf = Hkdf::::new(Some(FILE_HKDF_SALT), x3dh_material); let mut wrapping_key = Zeroizing::new([0u8; 32]); hkdf.expand(KEY_WRAP_INFO, wrapping_key.as_mut()) .map_err(|_| KeyError::HkdfError)?; @@ -64,7 +64,7 @@ pub(super) fn wrap_file_key( Ok(result) // WRAPPED_KEY_SIZE bytes } -/// Unwraps file encryption key using PQXDH shared material. +/// Unwraps file encryption key using X3DH shared material. /// /// # Process /// 1. Validate input is exactly 72 bytes @@ -79,7 +79,7 @@ pub(super) fn wrap_file_key( /// - `KeyError::InvalidFormat` if input length != 72 bytes /// - `KeyError::InvalidSignature` if authentication fails (wrong key or tampered data) pub(super) fn unwrap_file_key( - pqxdh_material: &[u8], + x3dh_material: &[u8], wrapped_data: &[u8], ) -> Result> { if wrapped_data.len() != WRAPPED_KEY_SIZE { @@ -91,8 +91,8 @@ pub(super) fn unwrap_file_key( let mut nonce = Zeroizing::new([0u8; 24]); nonce.copy_from_slice(nonce_bytes); - // Derive wrapping key from PQXDH material - let hkdf = Hkdf::::new(Some(FILE_HKDF_SALT), pqxdh_material); + // Derive wrapping key from X3DH material + let hkdf = Hkdf::::new(Some(FILE_HKDF_SALT), x3dh_material); let mut wrapping_key = Zeroizing::new([0u8; 32]); hkdf.expand(KEY_WRAP_INFO, wrapping_key.as_mut()) .map_err(|_| KeyError::HkdfError)?; diff --git a/protocol/src/encryption/mod.rs b/protocol/src/encryption/mod.rs index a2eb126..351d3b7 100644 --- a/protocol/src/encryption/mod.rs +++ b/protocol/src/encryption/mod.rs @@ -1,18 +1,18 @@ -//! Multi-recipient file encryption using PQXDH and XChaCha20-Poly1305. +//! Multi-recipient file encryption using X3DH-style key agreement and XChaCha20-Poly1305. //! //! This module provides end-to-end encrypted file sharing with: -//! - **PQXDH key agreement**: Hybrid classical (X25519) + post-quantum (Kyber1024) security +//! - **X3DH key agreement**: X25519-based key agreement with fresh ephemerals //! - **Multi-recipient support**: Encrypt once, wrap the key N times for N recipients -//! - **XChaCha20-Poly1305 AEAD**: Signal's recommended attachment cipher +//! - **XChaCha20-Poly1305 AEAD**: recommended attachment cipher //! - **Forward secrecy**: Fresh ephemeral keys for each encryption mod constant_time; mod file_cipher; mod key_wrap; -mod pqxdh; +mod x3dh; use crate::envelope::{ - EnvelopePayload, ParsedEnvelope, WrappingInfo, build_envelope_with_wrappings, verify_signature, + EnvelopePayload, ParsedEnvelope, build_envelope_with_wrappings, verify_signature, }; use crate::keys::{SyftPrivateKeys, SyftPublicKeyBundle}; use crate::{Result, error::KeyError}; @@ -21,7 +21,7 @@ use chacha20poly1305::{ Key, KeyInit, XChaCha20Poly1305, XNonce, aead::{Aead, Payload}, }; -use rand::{CryptoRng, Rng}; +use rand::{CryptoRng, RngCore}; use subtle::{Choice, ConstantTimeEq}; use zeroize::Zeroizing; @@ -32,7 +32,7 @@ pub use file_cipher::FILE_CIPHER_SUITE; use constant_time::ct_identity_match; use file_cipher::encrypt_payload; use key_wrap::{WRAPPED_KEY_SIZE, unwrap_file_key, wrap_file_key}; -use pqxdh::{derive_recipient_shared_material, derive_sender_shared_material}; +use x3dh::{derive_recipient_shared_material, derive_sender_shared_material}; /// Additional authenticated data for file decryption const FILE_AAD: &[u8] = b"syc-file-v1"; @@ -46,8 +46,8 @@ pub struct EncryptionRecipient<'a> { /// Encrypt plaintext bytes for the provided recipients, returning a fully formed SYC envelope. /// /// Supports multiple recipients - the file is encrypted once with a random key, then that key -/// is wrapped separately for each recipient using PQXDH. -pub fn encrypt_message( +/// is wrapped separately for each recipient using X3DH-derived material. +pub fn encrypt_message( sender_identity: &str, sender_keys: &SyftPrivateKeys, recipients: &[EncryptionRecipient<'_>], @@ -79,23 +79,14 @@ pub fn encrypt_message( let mut wrappings = Vec::with_capacity(recipients.len()); for recipient in recipients { - let (pqxdh_material, mut wrapping_info) = + let (x3dh_material, mut wrapping_info) = derive_sender_shared_material(sender_keys, recipient.identity, recipient.bundle, rng)?; - // Wrap the file key using PQXDH material - let wrapped_key = wrap_file_key(pqxdh_material.as_ref(), &file_key, rng)?; + // Wrap the file key using X3DH material + let wrapped_key = wrap_file_key(x3dh_material.as_ref(), &file_key, rng)?; - // Decode the existing kyber ciphertext from the wrapping - let kyber_ct = URL_SAFE_NO_PAD - .decode(&wrapping_info.wrap_ciphertext) - .map_err(|_| KeyError::InvalidFormat)?; - - // Combine: wrapped_key (72 bytes) || kyber_ct (~1568 bytes) - let mut combined = wrapped_key; - combined.extend_from_slice(&kyber_ct); - - // Update wrapping with combined data - wrapping_info.wrap_ciphertext = URL_SAFE_NO_PAD.encode(&combined); + // Store wrapped key bytes as the wrapping ciphertext + wrapping_info.wrap_ciphertext = URL_SAFE_NO_PAD.encode(&wrapped_key); recipient_vec.push((recipient.identity.to_string(), recipient.bundle.clone())); wrappings.push(wrapping_info); @@ -128,7 +119,7 @@ pub fn decrypt_message( ) -> Result> { let signature_valid = sender_bundle.verify_signatures(); let envelope_signature_valid = - verify_signature(parsed, &sender_bundle.signal_identity_public_key).is_ok(); + verify_signature(parsed, &sender_bundle.identity_signing_public_key).is_ok(); let expected_fp = sender_bundle.identity_fingerprint(); let fingerprint_match = expected_fp .as_bytes() @@ -172,32 +163,20 @@ pub fn decrypt_message( let mut nonce = Zeroizing::new([0u8; 24]); nonce.copy_from_slice(&nonce_bytes); - // Decode wrapping ciphertext: wrapped_key (72 bytes) || kyber_ct - let wrap_ciphertext_combined = URL_SAFE_NO_PAD + // Decode wrapping ciphertext: wrapped_key (72 bytes) + let wrapped_file_key = URL_SAFE_NO_PAD .decode(&wrapping.wrap_ciphertext) .map_err(|_| KeyError::InvalidFormat)?; - if wrap_ciphertext_combined.len() < WRAPPED_KEY_SIZE { + if wrapped_file_key.len() != WRAPPED_KEY_SIZE { return Err(KeyError::InvalidFormat); } - // Split wrapped file key and kyber ciphertext - let (wrapped_file_key, kyber_ct) = wrap_ciphertext_combined.split_at(WRAPPED_KEY_SIZE); - - // Create modified wrapping with only kyber_ct for PQXDH derivation - let pqxdh_wrapping = WrappingInfo { - recipient_identity: wrapping.recipient_identity.clone(), - device_label: wrapping.device_label.clone(), - wrap_ephemeral_public: wrapping.wrap_ephemeral_public.clone(), - wrap_ciphertext: URL_SAFE_NO_PAD.encode(kyber_ct), - }; - - // Derive PQXDH shared material - let pqxdh_material = - derive_recipient_shared_material(recipient_keys, sender_bundle, &pqxdh_wrapping)?; + // Derive X3DH shared material + let x3dh_material = derive_recipient_shared_material(recipient_keys, sender_bundle, wrapping)?; - // Unwrap file key using PQXDH material - let file_key = unwrap_file_key(pqxdh_material.as_ref(), wrapped_file_key)?; + // Unwrap file key using X3DH material + let file_key = unwrap_file_key(x3dh_material.as_ref(), &wrapped_file_key)?; let cipher = XChaCha20Poly1305::new(Key::from_slice(&*file_key)); cipher diff --git a/protocol/src/encryption/pqxdh.rs b/protocol/src/encryption/pqxdh.rs deleted file mode 100644 index 7e7b2c1..0000000 --- a/protocol/src/encryption/pqxdh.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! PQXDH (Post-Quantum Extended Diffie-Hellman) key agreement protocol. - -use crate::envelope::WrappingInfo; -use crate::keys::{SyftPrivateKeys, SyftPublicKeyBundle}; -use crate::{Result, error::KeyError}; -use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; -use libsignal_protocol::{KeyPair, PublicKey}; -use rand::{CryptoRng, Rng}; -use zeroize::Zeroizing; - -/// Serialized libsignal X25519 public keys include a 1-byte key-type tag. -const X25519_PUBLIC_KEY_LEN: usize = 33; - -/// Performs sender-side PQXDH key agreement. -/// -/// Establishes shared secret material by combining X25519 DH operations with Kyber1024 -/// key encapsulation. The sender (Alice) computes: -/// -/// - DH1 = DH(IKA_priv, SPKB_pub) -/// - DH2 = DH(SPKA_priv, IKB_pub) -/// - DH3 = DH(EKA_priv, IKB_pub) [fresh ephemeral key for forward secrecy] -/// - DH4 = DH(EKA_priv, SPKB_pub) -/// - (SS, CT) = Kyber1024.Encapsulate(PQPKB_pub) -/// - PQXDH_material = DH1 || DH2 || DH3 || DH4 || SS -/// -/// Where: -/// - IK = Identity Key, SPK = Signed PreKey, EK = Ephemeral Key, PQPK = Post-Quantum PreKey -/// - A = sender (Alice), B = recipient (Bob) -/// - SS = Kyber shared secret, CT = Kyber ciphertext -/// -/// Returns PQXDH material (~196 bytes) and wrapping metadata (EKA_pub, CT) for the recipient. -/// -/// # Errors -/// - `KeyError::InvalidSignature` if recipient bundle signature verification fails -/// - `KeyError::SignalError` if DH operations or Kyber encapsulation fail -pub(super) fn derive_sender_shared_material( - sender_keys: &SyftPrivateKeys, - recipient_identity: &str, - recipient_bundle: &SyftPublicKeyBundle, - rng: &mut R, -) -> Result<(Zeroizing>, WrappingInfo)> { - if !recipient_bundle.verify_signatures() { - return Err(KeyError::InvalidSignature); - } - - let ephemeral = KeyPair::generate(rng); - - let dh1 = Zeroizing::new( - sender_keys - .identity() - .private_key() - .calculate_agreement(&recipient_bundle.signal_signed_public_pre_key) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - let dh2 = Zeroizing::new( - sender_keys - .signed_pre_key() - .private_key - .calculate_agreement(recipient_bundle.signal_identity_public_key.public_key()) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - let dh3 = Zeroizing::new( - ephemeral - .private_key - .calculate_agreement(recipient_bundle.signal_identity_public_key.public_key()) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - let dh4 = Zeroizing::new( - ephemeral - .private_key - .calculate_agreement(&recipient_bundle.signal_signed_public_pre_key) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - - let (pq_secret_raw, pq_ciphertext) = recipient_bundle - .signal_pq_public_pre_key - .encapsulate(rng) - .map_err(KeyError::SignalError)?; - let pq_secret = Zeroizing::new(pq_secret_raw); - - let mut material = Zeroizing::new(Vec::with_capacity( - dh1.len() + dh2.len() + dh3.len() + dh4.len() + pq_secret.len(), - )); - material.extend_from_slice(dh1.as_ref()); - material.extend_from_slice(dh2.as_ref()); - material.extend_from_slice(dh3.as_ref()); - material.extend_from_slice(dh4.as_ref()); - material.extend_from_slice(pq_secret.as_ref()); - - let wrapping = WrappingInfo { - recipient_identity: Some(recipient_identity.to_owned()), - device_label: Some("default".into()), - wrap_ephemeral_public: URL_SAFE_NO_PAD.encode(ephemeral.public_key.serialize()), - wrap_ciphertext: URL_SAFE_NO_PAD.encode(&pq_ciphertext), - }; - - Ok((material, wrapping)) -} - -/// Performs recipient-side PQXDH key agreement. -/// -/// Derives the same shared secret material as the sender by performing the same DH operations -/// from the recipient's perspective and decapsulating the Kyber ciphertext. The recipient (Bob) computes: -/// -/// - DH1 = DH(SPKB_priv, IKA_pub) -/// - DH2 = DH(IKB_priv, SPKA_pub) -/// - DH3 = DH(IKB_priv, EKA_pub) [EKA_pub received from sender] -/// - DH4 = DH(SPKB_priv, EKA_pub) -/// - SS = Kyber1024.Decapsulate(CT, PQPKB_priv) [CT received from sender] -/// - PQXDH_material = DH1 || DH2 || DH3 || DH4 || SS -/// -/// Where: -/// - IK = Identity Key, SPK = Signed PreKey, EK = Ephemeral Key, PQPK = Post-Quantum PreKey -/// - A = sender (Alice), B = recipient (Bob) -/// - SS = Kyber shared secret, CT = Kyber ciphertext (from wrapping metadata) -/// -/// Returns the same PQXDH material (~196 bytes) computed by the sender. -/// -/// # Errors -/// - `KeyError::InvalidFormat` if ephemeral key or Kyber ciphertext is malformed -/// - `KeyError::SignalError` if DH operations or Kyber decapsulation fail -pub(super) fn derive_recipient_shared_material( - recipient_keys: &SyftPrivateKeys, - sender_bundle: &SyftPublicKeyBundle, - wrapping: &WrappingInfo, -) -> Result>> { - let ephemeral_bytes = URL_SAFE_NO_PAD - .decode(&wrapping.wrap_ephemeral_public) - .map_err(|_| KeyError::InvalidFormat)?; - if ephemeral_bytes.len() != X25519_PUBLIC_KEY_LEN { - return Err(KeyError::InvalidFormat); - } - let pq_ciphertext_bytes = URL_SAFE_NO_PAD - .decode(&wrapping.wrap_ciphertext) - .map_err(|_| KeyError::InvalidFormat)?; - validate_pq_ciphertext(recipient_keys, &pq_ciphertext_bytes)?; - - let ephemeral_public = PublicKey::try_from(ephemeral_bytes.as_slice()) - .map_err(|e| KeyError::SignalError(e.into()))?; - - let dh1 = Zeroizing::new( - recipient_keys - .signed_pre_key() - .private_key - .calculate_agreement(sender_bundle.signal_identity_public_key.public_key()) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - let dh2 = Zeroizing::new( - recipient_keys - .identity() - .private_key() - .calculate_agreement(&sender_bundle.signal_signed_public_pre_key) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - let dh3 = Zeroizing::new( - recipient_keys - .identity() - .private_key() - .calculate_agreement(&ephemeral_public) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - let dh4 = Zeroizing::new( - recipient_keys - .signed_pre_key() - .private_key - .calculate_agreement(&ephemeral_public) - .map_err(|e| KeyError::SignalError(e.into()))?, - ); - - let pq_shared = Zeroizing::new( - recipient_keys - .pq_signed_pre_key() - .secret_key - .decapsulate(&pq_ciphertext_bytes.into_boxed_slice()) - .map_err(KeyError::SignalError)?, - ); - - let mut material = Zeroizing::new(Vec::with_capacity( - dh1.len() + dh2.len() + dh3.len() + dh4.len() + pq_shared.len(), - )); - material.extend_from_slice(dh1.as_ref()); - material.extend_from_slice(dh2.as_ref()); - material.extend_from_slice(dh3.as_ref()); - material.extend_from_slice(dh4.as_ref()); - material.extend_from_slice(pq_shared.as_ref()); - Ok(material) -} - -/// Validates Kyber ciphertext format before decapsulation. -/// -/// Checks that: -/// - Ciphertext length matches public key serialized length -/// - First byte (key type tag) matches expected value -/// -/// This prevents crashes from malformed input to Kyber decapsulation. -fn validate_pq_ciphertext(recipient_keys: &SyftPrivateKeys, ciphertext: &[u8]) -> Result<()> { - let public_key_bytes = recipient_keys.pq_signed_pre_key().public_key.serialize(); - if ciphertext.len() != public_key_bytes.len() { - return Err(KeyError::InvalidFormat); - } - let expected_tag = public_key_bytes - .first() - .copied() - .ok_or(KeyError::InvalidFormat)?; - if ciphertext.first().copied() != Some(expected_tag) { - return Err(KeyError::InvalidFormat); - } - - Ok(()) -} diff --git a/protocol/src/encryption/tests.rs b/protocol/src/encryption/tests.rs index f7bfcad..cec4301 100644 --- a/protocol/src/encryption/tests.rs +++ b/protocol/src/encryption/tests.rs @@ -1,9 +1,9 @@ use crate::{SyftRecoveryKey, error::KeyError}; use rand::{rng, RngCore}; -use super::{key_wrap, pqxdh}; +use super::{key_wrap, x3dh}; #[test] -fn test_pqxdh_round_trip() { +fn test_x3dh_round_trip() { // Alice and Bob generate their keys let mut rng = rng(); let alice_keys = SyftRecoveryKey::generate() @@ -16,40 +16,40 @@ fn test_pqxdh_round_trip() { let alice_bundle = alice_keys.to_public_bundle(&mut rng).expect("alice bundle"); let bob_bundle = bob_keys.to_public_bundle(&mut rng).expect("bob bundle"); - // Alice performs sender-side PQXDH to Bob - let (alice_material, wrapping_info) = pqxdh::derive_sender_shared_material( + // Alice performs sender-side X3DH to Bob + let (alice_material, wrapping_info) = x3dh::derive_sender_shared_material( &alice_keys, "bob@example.org", &bob_bundle, &mut rng, ) - .expect("alice PQXDH"); + .expect("alice X3DH"); - // Bob performs recipient-side PQXDH from Alice - let bob_material = pqxdh::derive_recipient_shared_material( + // Bob performs recipient-side X3DH from Alice + let bob_material = x3dh::derive_recipient_shared_material( &bob_keys, &alice_bundle, &wrapping_info, ) - .expect("bob PQXDH"); + .expect("bob X3DH"); // Both should derive the same shared material assert_eq!( &alice_material[..], &bob_material[..], - "PQXDH materials must match" + "X3DH materials must match" ); - // Material should be ~196 bytes (4×32 DH + 32 Kyber) + // Material should be 128 bytes (4×32 DH) assert!( - alice_material.len() >= 160 && alice_material.len() <= 200, - "Expected ~196 bytes, got {}", + alice_material.len() == 128, + "Expected 128 bytes, got {}", alice_material.len() ); } #[test] -fn test_pqxdh_rejects_invalid_bundle() { +fn test_x3dh_rejects_invalid_bundle() { let mut rng = rng(); let alice_keys = SyftRecoveryKey::generate() .derive_keys() @@ -63,10 +63,10 @@ fn test_pqxdh_rejects_invalid_bundle() { // Replace identity key with Alice's, but keep Charlie's signatures // This creates an invalid bundle (signatures won't match the identity key) - bad_bundle.signal_identity_public_key = (*alice_keys.identity().public_key()).into(); + bad_bundle.identity_signing_public_key = alice_keys.identity().verifying_key(); - // Attempting PQXDH with invalid bundle should fail - let result = pqxdh::derive_sender_shared_material( + // Attempting X3DH with invalid bundle should fail + let result = x3dh::derive_sender_shared_material( &alice_keys, "bob@example.org", &bad_bundle, @@ -75,7 +75,7 @@ fn test_pqxdh_rejects_invalid_bundle() { assert!( result.is_err(), - "PQXDH should reject bundle with invalid signature" + "X3DH should reject bundle with invalid signature" ); if let Err(e) = result { @@ -91,7 +91,7 @@ fn test_pqxdh_rejects_invalid_bundle() { fn test_key_wrapping_round_trip() { let mut rng = rng(); - // Generate PQXDH material (simulate successful key agreement) + // Generate X3DH material (simulate successful key agreement) let alice_keys = SyftRecoveryKey::generate() .derive_keys() .expect("alice key derivation"); @@ -100,13 +100,13 @@ fn test_key_wrapping_round_trip() { .expect("bob key derivation"); let bob_bundle = bob_keys.to_public_bundle(&mut rng).expect("bob bundle"); - let (pqxdh_material, _) = pqxdh::derive_sender_shared_material( + let (x3dh_material, _) = x3dh::derive_sender_shared_material( &alice_keys, "bob@example.org", &bob_bundle, &mut rng, ) - .expect("PQXDH"); + .expect("X3DH"); // Create a random file key let file_key = { @@ -116,7 +116,7 @@ fn test_key_wrapping_round_trip() { }; // Wrap the file key - let wrapped = key_wrap::wrap_file_key(pqxdh_material.as_ref(), &file_key, &mut rng) + let wrapped = key_wrap::wrap_file_key(x3dh_material.as_ref(), &file_key, &mut rng) .expect("wrap file key"); // Verify wrapped size is exactly 72 bytes (24 nonce + 48 ciphertext+tag) @@ -127,7 +127,7 @@ fn test_key_wrapping_round_trip() { ); // Unwrap the file key - let unwrapped = key_wrap::unwrap_file_key(pqxdh_material.as_ref(), &wrapped) + let unwrapped = key_wrap::unwrap_file_key(x3dh_material.as_ref(), &wrapped) .expect("unwrap file key"); // Verify we got the original key back @@ -142,7 +142,7 @@ fn test_key_wrapping_round_trip() { fn test_unwrap_rejects_tampered_data() { let mut rng = rng(); - // Generate PQXDH material + // Generate X3DH material let alice_keys = SyftRecoveryKey::generate() .derive_keys() .expect("alice key derivation"); @@ -151,13 +151,13 @@ fn test_unwrap_rejects_tampered_data() { .expect("bob key derivation"); let bob_bundle = bob_keys.to_public_bundle(&mut rng).expect("bob bundle"); - let (pqxdh_material, _) = pqxdh::derive_sender_shared_material( + let (x3dh_material, _) = x3dh::derive_sender_shared_material( &alice_keys, "bob@example.org", &bob_bundle, &mut rng, ) - .expect("PQXDH"); + .expect("X3DH"); // Wrap a file key let file_key = { @@ -165,14 +165,14 @@ fn test_unwrap_rejects_tampered_data() { rng.fill_bytes(&mut key); key }; - let mut wrapped = key_wrap::wrap_file_key(pqxdh_material.as_ref(), &file_key, &mut rng) + let mut wrapped = key_wrap::wrap_file_key(x3dh_material.as_ref(), &file_key, &mut rng) .expect("wrap file key"); // Tamper with the ciphertext (flip a bit in the middle) wrapped[40] ^= 0x01; // Unwrap should fail due to authentication tag mismatch - let result = key_wrap::unwrap_file_key(pqxdh_material.as_ref(), &wrapped); + let result = key_wrap::unwrap_file_key(x3dh_material.as_ref(), &wrapped); assert!( result.is_err(), @@ -187,12 +187,12 @@ fn test_unwrap_rejects_tampered_data() { ); } - // Also test: wrong PQXDH material should fail - let wrong_material = vec![0u8; pqxdh_material.len()]; // All zeros + // Also test: wrong X3DH material should fail + let wrong_material = vec![0u8; x3dh_material.len()]; // All zeros let result = key_wrap::unwrap_file_key(&wrong_material, &wrapped); assert!( result.is_err(), - "Unwrap should reject wrong PQXDH material" + "Unwrap should reject wrong X3DH material" ); } diff --git a/protocol/src/encryption/x3dh.rs b/protocol/src/encryption/x3dh.rs new file mode 100644 index 0000000..5ba2403 --- /dev/null +++ b/protocol/src/encryption/x3dh.rs @@ -0,0 +1,210 @@ +//! X3DH-style key agreement protocol. + +use crate::envelope::WrappingInfo; +use crate::keys::{SyftPrivateKeys, SyftPublicKeyBundle}; +use crate::{Result, error::KeyError}; +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; +use rand::{CryptoRng, RngCore}; +use rand_core::{CryptoRng as RandCoreCryptoRng, RngCore as RandCoreRngCore}; +use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret}; +use zeroize::Zeroizing; + +/// Serialized X25519 public keys are 32 bytes. +const X25519_PUBLIC_KEY_LEN: usize = 32; + +struct RandCoreAdapter<'a, R>(&'a mut R); + +fn is_all_zero(bytes: &[u8]) -> bool { + bytes.iter().fold(0u8, |acc, &b| acc | b) == 0 +} + +impl<'a, R: RngCore + CryptoRng> RandCoreRngCore for RandCoreAdapter<'a, R> { + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> std::result::Result<(), rand_core::Error> { + self.0.fill_bytes(dest); + Ok(()) + } +} + +impl<'a, R: RngCore + CryptoRng> RandCoreCryptoRng for RandCoreAdapter<'a, R> {} + +/// Performs sender-side X3DH-style key agreement. +/// +/// Establishes shared secret material by combining X25519 DH operations. The sender (Alice) +/// computes: +/// +/// - DH1 = DH(IK_A_priv, SPK_B_pub) +/// - DH2 = DH(SPK_A_priv, IK_B_pub) +/// - DH3 = DH(EK_A_priv, IK_B_pub) [fresh ephemeral key for forward secrecy] +/// - DH4 = DH(EK_A_priv, SPK_B_pub) +/// - X3DH_material = DH1 || DH2 || DH3 || DH4 +/// +/// Where: +/// - IK = Identity DH Key, SPK = Signed PreKey, EK = Ephemeral Key +/// - A = sender (Alice), B = recipient (Bob) +/// +/// Returns X3DH material and wrapping metadata (EK_A_pub) for the recipient. +/// +/// # Errors +/// - `KeyError::InvalidSignature` if recipient bundle signature verification fails +pub(super) fn derive_sender_shared_material( + sender_keys: &SyftPrivateKeys, + recipient_identity: &str, + recipient_bundle: &SyftPublicKeyBundle, + rng: &mut R, +) -> Result<(Zeroizing>, WrappingInfo)> { + if !recipient_bundle.verify_signatures() { + return Err(KeyError::InvalidSignature); + } + + let mut adapter = RandCoreAdapter(rng); + let ephemeral = StaticSecret::random_from_rng(&mut adapter); + let ephemeral_public = X25519PublicKey::from(&ephemeral); + + let dh1 = Zeroizing::new( + sender_keys + .identity_dh() + .diffie_hellman(&recipient_bundle.signed_prekey_public_key) + .to_bytes(), + ); + if is_all_zero(dh1.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + let dh2 = Zeroizing::new( + sender_keys + .signed_pre_key() + .diffie_hellman(&recipient_bundle.identity_dh_public_key) + .to_bytes(), + ); + if is_all_zero(dh2.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + let dh3 = Zeroizing::new( + ephemeral + .diffie_hellman(&recipient_bundle.identity_dh_public_key) + .to_bytes(), + ); + if is_all_zero(dh3.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + let dh4 = Zeroizing::new( + ephemeral + .diffie_hellman(&recipient_bundle.signed_prekey_public_key) + .to_bytes(), + ); + if is_all_zero(dh4.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + + let mut material = Zeroizing::new(Vec::with_capacity( + dh1.len() + dh2.len() + dh3.len() + dh4.len(), + )); + material.extend_from_slice(dh1.as_ref()); + material.extend_from_slice(dh2.as_ref()); + material.extend_from_slice(dh3.as_ref()); + material.extend_from_slice(dh4.as_ref()); + + let wrapping = WrappingInfo { + recipient_identity: Some(recipient_identity.to_owned()), + device_label: Some("default".into()), + wrap_ephemeral_public: URL_SAFE_NO_PAD.encode(ephemeral_public.as_bytes()), + wrap_ciphertext: String::new(), + }; + + Ok((material, wrapping)) +} + +/// Performs recipient-side X3DH-style key agreement. +/// +/// Derives the same shared secret material as the sender by performing the same DH operations +/// from the recipient's perspective. The recipient (Bob) computes: +/// +/// - DH1 = DH(SPK_B_priv, IK_A_pub) +/// - DH2 = DH(IK_B_priv, SPK_A_pub) +/// - DH3 = DH(IK_B_priv, EK_A_pub) [EK_A_pub received from sender] +/// - DH4 = DH(SPK_B_priv, EK_A_pub) +/// - X3DH_material = DH1 || DH2 || DH3 || DH4 +/// +/// Where: +/// - IK = Identity DH Key, SPK = Signed PreKey, EK = Ephemeral Key +/// - A = sender (Alice), B = recipient (Bob) +/// +/// Returns the same X3DH material computed by the sender. +/// +/// # Errors +/// - `KeyError::InvalidFormat` if ephemeral key is malformed +pub(super) fn derive_recipient_shared_material( + recipient_keys: &SyftPrivateKeys, + sender_bundle: &SyftPublicKeyBundle, + wrapping: &WrappingInfo, +) -> Result>> { + if !sender_bundle.verify_signatures() { + return Err(KeyError::InvalidSignature); + } + let ephemeral_bytes = URL_SAFE_NO_PAD + .decode(&wrapping.wrap_ephemeral_public) + .map_err(|_| KeyError::InvalidFormat)?; + if ephemeral_bytes.len() != X25519_PUBLIC_KEY_LEN { + return Err(KeyError::InvalidFormat); + } + let mut ephemeral_key = Zeroizing::new([0u8; X25519_PUBLIC_KEY_LEN]); + ephemeral_key.copy_from_slice(&ephemeral_bytes); + let ephemeral_public = X25519PublicKey::from(*ephemeral_key); + + let dh1 = Zeroizing::new( + recipient_keys + .signed_pre_key() + .diffie_hellman(&sender_bundle.identity_dh_public_key) + .to_bytes(), + ); + if is_all_zero(dh1.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + let dh2 = Zeroizing::new( + recipient_keys + .identity_dh() + .diffie_hellman(&sender_bundle.signed_prekey_public_key) + .to_bytes(), + ); + if is_all_zero(dh2.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + let dh3 = Zeroizing::new( + recipient_keys + .identity_dh() + .diffie_hellman(&ephemeral_public) + .to_bytes(), + ); + if is_all_zero(dh3.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + let dh4 = Zeroizing::new( + recipient_keys + .signed_pre_key() + .diffie_hellman(&ephemeral_public) + .to_bytes(), + ); + if is_all_zero(dh4.as_ref()) { + return Err(KeyError::InvalidSharedSecret); + } + + let mut material = Zeroizing::new(Vec::with_capacity( + dh1.len() + dh2.len() + dh3.len() + dh4.len(), + )); + material.extend_from_slice(dh1.as_ref()); + material.extend_from_slice(dh2.as_ref()); + material.extend_from_slice(dh3.as_ref()); + material.extend_from_slice(dh4.as_ref()); + Ok(material) +} diff --git a/protocol/src/envelope.rs b/protocol/src/envelope.rs index f99bbac..65c206f 100644 --- a/protocol/src/envelope.rs +++ b/protocol/src/envelope.rs @@ -1,6 +1,6 @@ use crate::Result; use crate::keys::{SyftPublicKeyBundle, compute_key_fingerprint}; -use libsignal_protocol::{IdentityKey, IdentityKeyPair}; +use ed25519_dalek::{Signer, SigningKey, VerifyingKey}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -11,13 +11,13 @@ use serde_json::json; use std::convert::TryFrom; use std::time::{SystemTime, UNIX_EPOCH}; -pub const MAGIC: &[u8; 4] = b"SYC1"; +pub const MAGIC: &[u8; 4] = b"SYC2"; pub const CURRENT_VERSION: u8 = 1; pub const PRELUDE_PAD: usize = 4096; const ED25519_SIGNATURE_LEN: usize = 64; const MAX_PRELUDE_SIZE: usize = 10 * 1024 * 1024; // 10 MiB const MAX_RECIPIENTS: usize = 1000; -const SIGNING_CONTEXT: &[u8] = b"SYC1-PRELUDE"; +const SIGNING_CONTEXT: &[u8] = b"SYC2-PRELUDE"; /// Payload data for envelope construction. pub struct EnvelopePayload<'a> { @@ -58,8 +58,6 @@ pub struct RecipientInfo { #[serde(skip_serializing_if = "Option::is_none")] pub spk_fingerprint: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub pqspk_fingerprint: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub signed_prekey_id: Option, } @@ -202,14 +200,14 @@ fn parse_signature_section(bytes: &[u8], cursor: usize) -> Result<(Vec, usiz /// /// This function parses the binary SYC envelope format and extracts all components: /// the prelude metadata, signature, and ciphertext. The envelope format is designed -/// for hybrid encryption with post-quantum security. +/// for hybrid encryption. /// /// # SYC Envelope Binary Format /// /// The envelope has the following structure: /// ```text /// ┌─────────────────────────────────────────────────────────────┐ -/// │ Magic (4 bytes): b"SYC1" │ +/// │ Magic (4 bytes): b"SYC2" │ /// ├─────────────────────────────────────────────────────────────┤ /// │ Version (1 byte): 1 │ /// ├─────────────────────────────────────────────────────────────┤ @@ -225,8 +223,8 @@ fn parse_signature_section(bytes: &[u8], cursor: usize) -> Result<(Vec, usiz /// │ - Signs the prelude bytes (before padding) │ /// ├─────────────────────────────────────────────────────────────┤ /// │ Ciphertext (remainder of file) │ -/// │ - Encrypted with AES-256-GCM │ -/// │ - Key wrapped using PQXDH (X25519 + Kyber1024) │ +/// │ - Encrypted with XChaCha20-Poly1305 │ +/// │ - Key wrapped using X3DH-style X25519 │ /// └─────────────────────────────────────────────────────────────┘ /// ``` /// @@ -283,7 +281,7 @@ fn parse_signature_section(bytes: &[u8], cursor: usize) -> Result<(Vec, usiz /// /// # See Also /// -/// - `build_envelope_with_wrappings()` - Creates envelopes with real signatures and PQXDH wrappings +/// - `build_envelope_with_wrappings()` - Creates envelopes with real signatures and X3DH wrappings /// - `verify_signature()` - Verifies the envelope signature after parsing pub fn parse_envelope(bytes: &[u8]) -> Result { // Validate envelope header (magic bytes + version) @@ -324,22 +322,20 @@ pub fn parse_envelope(bytes: &[u8]) -> Result { /// Sign envelope prelude with sender's identity private key. fn sign_prelude( prelude_bytes: &[u8], - identity_key_pair: &IdentityKeyPair, - rng: &mut R, + identity_key_pair: &SigningKey, + _rng: &mut R, ) -> Result> { let message = signing_message(prelude_bytes); - identity_key_pair - .private_key() - .calculate_signature(&message, rng) - .map_err(|e| format!("Failed to sign envelope prelude: {}", e).into()) + let signature = identity_key_pair.sign(&message); + Ok(signature.to_bytes().to_vec().into_boxed_slice()) } /// Verify envelope signature using sender's identity public key. pub fn verify_signature( parsed_envelope: &ParsedEnvelope, - sender_identity_key: &IdentityKey, + sender_identity_key: &VerifyingKey, ) -> Result<()> { - let expected_fingerprint = compute_key_fingerprint(&sender_identity_key.serialize()); + let expected_fingerprint = compute_key_fingerprint(sender_identity_key.as_bytes()); if !fingerprints_match( &expected_fingerprint, &parsed_envelope.prelude.sender.ik_fingerprint, @@ -348,11 +344,12 @@ pub fn verify_signature( } let message = signing_message(&parsed_envelope.prelude_bytes); - let valid = sender_identity_key - .public_key() - .verify_signature(&message, &parsed_envelope.signature); - - if !valid { + let signature = ed25519_dalek::Signature::from_slice(&parsed_envelope.signature) + .map_err(|_| "SYC envelope signature verification failed")?; + if sender_identity_key + .verify_strict(&message, &signature) + .is_err() + { return Err("SYC envelope signature verification failed".into()); } Ok(()) @@ -386,7 +383,7 @@ fn build_prelude( // Compute real fingerprint for sender's identity key let sender_ik_fingerprint = - compute_key_fingerprint(&sender_public_bundle.signal_identity_public_key.serialize()); + compute_key_fingerprint(sender_public_bundle.identity_signing_public_key.as_bytes()); let sender_info = SenderInfo { identity: sender_identity.to_owned(), @@ -404,14 +401,11 @@ fn build_prelude( .into()); } let spk_fingerprint = - compute_key_fingerprint(&recipient_bundle.signal_signed_public_pre_key.serialize()); - let pqspk_fingerprint = - compute_key_fingerprint(&recipient_bundle.signal_pq_public_pre_key.serialize()); + compute_key_fingerprint(recipient_bundle.signed_prekey_public_key.as_bytes()); recipients_infos.push(RecipientInfo { identity: Some(recipient_identity.clone()), device_label: Some("default".into()), // set to "default" as a placeholder because the current implementation doesn't have a multi-device system yet spk_fingerprint: Some(spk_fingerprint), - pqspk_fingerprint: Some(pqspk_fingerprint), signed_prekey_id: Some(1), // Key rotation not yet supported => Always using ID 1 }); } @@ -423,7 +417,7 @@ fn build_prelude( // Compute recipient set fingerprint from all recipient identity fingerprints using length-prefixing let mut recipient_fps: Vec = recipients .iter() - .map(|(_, bundle)| compute_key_fingerprint(&bundle.signal_identity_public_key.serialize())) + .map(|(_, bundle)| compute_key_fingerprint(bundle.identity_signing_public_key.as_bytes())) .collect(); recipient_fps.sort(); let mut combined = Vec::with_capacity(recipient_fps.len() * (std::mem::size_of::() + 64)); @@ -468,12 +462,12 @@ fn build_prelude( }) } -/// Build a complete SYC envelope with real cryptographic signatures and PQXDH wrappings. +/// Build a complete SYC envelope with real cryptographic signatures and X3DH wrappings. /// /// This creates an envelope with: /// - Real fingerprints from actual public keys /// - Real Ed25519 signature of the prelude -/// - Real PQXDH wrappings for key encapsulation +/// - Real X3DH wrappings for key encapsulation /// - Proper envelope structure with magic, version, padding, etc. /// /// # Arguments @@ -481,7 +475,7 @@ fn build_prelude( /// * `sender_identity_key_pair` - Sender's identity key pair for signing /// * `sender_public_bundle` - Sender's public key bundle /// * `recipients` - Vector of (identity, public_bundle) tuples for each recipient -/// * `wrappings` - PQXDH key wrappings (ephemeral keys + Kyber ciphertext) +/// * `wrappings` - X3DH key wrappings (ephemeral keys + wrapped file key) /// * `payload` - Envelope payload with ciphertext and metadata /// * `rng` - Cryptographically secure random number generator /// @@ -489,11 +483,11 @@ fn build_prelude( /// The complete envelope bytes ready for storage/transmission /// /// # Note -/// This is typically called from `encrypt_message()` which handles PQXDH wrapping. +/// This is typically called from `encrypt_message()` which handles X3DH wrapping. /// Do not use this directly unless you're implementing custom encryption logic. pub fn build_envelope_with_wrappings( sender_identity: &str, - sender_identity_key_pair: &IdentityKeyPair, + sender_identity_key_pair: &SigningKey, sender_public_bundle: &SyftPublicKeyBundle, recipients: &[(String, SyftPublicKeyBundle)], wrappings: &[WrappingInfo], @@ -509,7 +503,8 @@ pub fn build_envelope_with_wrappings( } // Verify that the identity key pair matches the public bundle - if sender_identity_key_pair.identity_key() != &sender_public_bundle.signal_identity_public_key { + if sender_identity_key_pair.verifying_key() != sender_public_bundle.identity_signing_public_key + { return Err("identity key pair does not match public bundle".into()); } if !sender_public_bundle.verify_signatures() { diff --git a/protocol/src/error.rs b/protocol/src/error.rs index 3b2bb22..df6cf1a 100644 --- a/protocol/src/error.rs +++ b/protocol/src/error.rs @@ -42,14 +42,14 @@ pub enum KeyError { #[error("Invalid key format")] InvalidFormat, + /// Invalid shared secret derived from DH + #[error("Invalid shared secret")] + InvalidSharedSecret, + /// Key rotation error #[error("Key rotation failed: {0}")] RotationError(String), - /// libsignal protocol error - #[error("libsignal error: {0}")] - SignalError(#[from] libsignal_protocol::SignalProtocolError), - /// JSON serialization error #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), @@ -151,9 +151,9 @@ pub enum SerializationError { #[error("Missing signed prekey in DID document")] MissingSignedPrekey, - /// Missing PQ prekey in DID document - #[error("Missing PQ prekey in DID document")] - MissingPQPrekey, + /// Missing identity DH key in DID document + #[error("Missing identity DH key in DID document")] + MissingIdentityDhKey, /// Invalid base64 encoding #[error("Invalid base64 encoding: {0}")] diff --git a/protocol/src/keys.rs b/protocol/src/keys.rs index 491df91..8a73172 100644 --- a/protocol/src/keys.rs +++ b/protocol/src/keys.rs @@ -1,22 +1,22 @@ -//! Key structures for SyftBox PQXDH protocol +//! Key structures for SyftBox X3DH-style protocol. //! -//! This module defines the key types used in the SyftBox PQXDH protocol: +//! This module defines the key types used in the SyftBox protocol: //! - SyftRecoveryKey: 32-byte master secret for deterministic key derivation //! - SyftPrivateKeys: Container for all private key material //! - SyftPublicKeyBundle: Container for all public keys and signatures //! -//! The Syft keys wrap libsignal_protocol keys: -//! - IdentityKeyPair: Ed25519 keypair for signing -//! - SignedPreKey: X25519 keypair for ECDH -//! - PQSignedPreKey: Kyber1024 keypair for post-quantum KEM +//! The Syft keys use: +//! - Ed25519 for identity signing +//! - X25519 for identity DH and signed prekeys use crate::error::{RecoveryError, RecoveryResult}; -use libsignal_protocol::{IdentityKey, IdentityKeyPair, KeyPair, PublicKey, kem}; +use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey}; use rand::RngCore; use sha2::{Digest, Sha256}; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; -use zeroize::{Zeroize, ZeroizeOnDrop}; +use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; /// Compute SHA-256 fingerprint of any public key bytes. pub fn compute_key_fingerprint(key_bytes: &[u8]) -> String { @@ -40,11 +40,11 @@ pub fn compute_key_fingerprint(key_bytes: &[u8]) -> String { /// /// let recovery_key = SyftRecoveryKey::generate(); /// let private_keys = recovery_key.derive_keys().unwrap(); -/// let fingerprint = compute_identity_fingerprint(private_keys.identity().identity_key()); +/// let fingerprint = compute_identity_fingerprint(&private_keys.identity().verifying_key()); /// assert_eq!(fingerprint.len(), 64); // SHA-256 = 32 bytes = 64 hex chars /// ``` -pub fn compute_identity_fingerprint(identity_key: &IdentityKey) -> String { - compute_key_fingerprint(&identity_key.serialize()) +pub fn compute_identity_fingerprint(identity_key: &VerifyingKey) -> String { + compute_key_fingerprint(identity_key.as_bytes()) } /// 32-byte recovery key that deterministically derives all private keys. @@ -213,7 +213,6 @@ impl SyftRecoveryKey { /// ``` pub fn derive_keys(&self) -> RecoveryResult { use hkdf::Hkdf; - use rand::SeedableRng; use sha2::Sha256; let recovery_key_bytes = self.as_bytes(); @@ -221,120 +220,88 @@ impl SyftRecoveryKey { // HKDF instance for all key derivations let hk = Hkdf::::new(None, recovery_key_bytes); - // 1. Derive identity key pair (Ed25519) - let mut identity_seed = [0u8; 32]; - hk.expand(b"SyftBox_Identity_Key_v1", &mut identity_seed) - .map_err(|_| RecoveryError::DerivationFailed)?; - - let mut identity_rng = rand::rngs::StdRng::from_seed(identity_seed); - let signal_identity_key_pair = IdentityKeyPair::generate(&mut identity_rng); + // 1. Derive identity signing key (Ed25519) + let mut identity_sign_seed = Zeroizing::new([0u8; 32]); + hk.expand( + b"SyftBox_Identity_Signing_Key_v1", + identity_sign_seed.as_mut(), + ) + .map_err(|_| RecoveryError::DerivationFailed)?; + let identity_signing_key = SigningKey::from_bytes(&identity_sign_seed); - // 2. Derive signed prekey (X25519) - let mut spk_seed = [0u8; 32]; - hk.expand(b"SyftBox_Signed_Prekey_v1", &mut spk_seed) + // 2. Derive identity DH key (X25519) + let mut identity_dh_seed = Zeroizing::new([0u8; 32]); + hk.expand(b"SyftBox_Identity_DH_Key_v1", identity_dh_seed.as_mut()) .map_err(|_| RecoveryError::DerivationFailed)?; + let identity_dh_key = StaticSecret::from(*identity_dh_seed); - let mut spk_rng = rand::rngs::StdRng::from_seed(spk_seed); - let signal_signed_pre_key_pair = KeyPair::generate(&mut spk_rng); - - // 3. Derive PQ prekey (Kyber1024) - let mut pqspk_seed = [0u8; 32]; - hk.expand(b"SyftBox_PQ_Prekey_v1", &mut pqspk_seed) + // 3. Derive signed prekey (X25519) + let mut spk_seed = Zeroizing::new([0u8; 32]); + hk.expand(b"SyftBox_Signed_Prekey_v1", spk_seed.as_mut()) .map_err(|_| RecoveryError::DerivationFailed)?; - - let mut pqspk_rng = rand::rngs::StdRng::from_seed(pqspk_seed); - let signal_pq_signed_pre_key_pair = - kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut pqspk_rng); + let signed_pre_key = StaticSecret::from(*spk_seed); Ok(SyftPrivateKeys { - signal_identity_key_pair: Sensitive::new(signal_identity_key_pair), - signal_signed_pre_key_pair: Sensitive::new(signal_signed_pre_key_pair), - signal_pq_signed_pre_key_pair: Sensitive::new(signal_pq_signed_pre_key_pair), + identity_signing_key: Sensitive::new(identity_signing_key), + identity_dh_key: Sensitive::new(identity_dh_key), + signed_pre_key: Sensitive::new(signed_pre_key), }) } } -/// Container for all private key material needed for PQXDH. +/// Container for all private key material needed for X3DH-style key agreement. /// -/// Bundles identity key pair (Ed25519), signed prekey pair (X25519), and PQ prekey pair (Kyber1024). +/// Bundles identity signing key (Ed25519), identity DH key (X25519), and signed prekey (X25519). pub struct SyftPrivateKeys { - /// Ed25519 identity key pair for signing (wrapped to ensure zeroization). - signal_identity_key_pair: Sensitive, - /// X25519 signed prekey pair for ECDH (wrapped to ensure zeroization). - signal_signed_pre_key_pair: Sensitive, - /// Kyber1024 PQ signed prekey for KEM (wrapped to ensure zeroization). - signal_pq_signed_pre_key_pair: Sensitive, + /// Ed25519 identity signing key. + identity_signing_key: Sensitive, + /// X25519 identity DH key. + identity_dh_key: Sensitive, + /// X25519 signed prekey. + signed_pre_key: Sensitive, } impl SyftPrivateKeys { /// Create a new container for private key material. pub fn new( - identity: IdentityKeyPair, - signed_pre_key: KeyPair, - pq_signed_pre_key: kem::KeyPair, + identity_signing_key: SigningKey, + identity_dh_key: StaticSecret, + signed_pre_key: StaticSecret, ) -> Self { Self { - signal_identity_key_pair: Sensitive::new(identity), - signal_signed_pre_key_pair: Sensitive::new(signed_pre_key), - signal_pq_signed_pre_key_pair: Sensitive::new(pq_signed_pre_key), + identity_signing_key: Sensitive::new(identity_signing_key), + identity_dh_key: Sensitive::new(identity_dh_key), + signed_pre_key: Sensitive::new(signed_pre_key), } } - /// Borrow the identity key pair. - pub fn identity(&self) -> &IdentityKeyPair { - &self.signal_identity_key_pair + /// Borrow the identity signing key. + pub fn identity(&self) -> &SigningKey { + &self.identity_signing_key } - /// Borrow the signed prekey pair. - pub fn signed_pre_key(&self) -> &KeyPair { - &self.signal_signed_pre_key_pair + /// Borrow the identity DH key. + pub fn identity_dh(&self) -> &StaticSecret { + &self.identity_dh_key } - /// Borrow the PQ signed prekey pair. - pub fn pq_signed_pre_key(&self) -> &kem::KeyPair { - &self.signal_pq_signed_pre_key_pair + /// Borrow the signed prekey. + pub fn signed_pre_key(&self) -> &StaticSecret { + &self.signed_pre_key } /// Create public key bundle with all public keys and signatures. pub fn to_public_bundle( &self, - rng: &mut R, - ) -> Result { - SyftPublicKeyBundle::new( - self.identity(), - self.signed_pre_key(), - self.pq_signed_pre_key(), - rng, - ) + _rng: &mut R, + ) -> Result { + SyftPublicKeyBundle::new(self.identity(), self.identity_dh(), self.signed_pre_key()) } } /// Wrapper that zeroizes contained data immediately after it has been dropped. /// -/// # Why This Exists -/// -/// Ideally we would use `zeroize::Zeroizing` which provides safe, guaranteed -/// zeroization. However, the libsignal-protocol types (`IdentityKeyPair`, `KeyPair`, -/// `kem::KeyPair`) do not implement the `Zeroize` trait, so we cannot use the safe API. -/// -/// If libsignal-protocol-syft added `Zeroize` implementations for its key types, -/// this wrapper could be removed entirely in favor of `Zeroizing`. -/// -/// # Why Unsafe Is Required -/// -/// 1. **Volatile writes** (`std::ptr::write_volatile`): Required to prevent the -/// compiler from optimizing away the zeroization as a "dead store". There is -/// no safe API for volatile memory writes in Rust. -/// -/// 2. **ManuallyDrop::drop**: Required to drop the inner value in-place without -/// creating a copy. This is unsafe because calling it twice would cause UB. -/// -/// # Implementation -/// -/// - The inner value is dropped first (in place, no copy created) -/// - Memory is then zeroed using volatile writes -/// - A guard struct ensures zeroization runs even if the inner Drop panics -/// - A compiler fence prevents reordering of the volatile writes +/// This avoids relying on `Zeroize` implementations for external key types. struct Sensitive(ManuallyDrop); impl Sensitive { @@ -359,9 +326,6 @@ impl DerefMut for Sensitive { impl Drop for Sensitive { fn drop(&mut self) { - // Guard struct ensures zeroization runs even if dropping `T` panics. - // When this guard is dropped (either normally or during unwinding), - // it will zero the memory. struct ZeroizeGuard { ptr: *mut u8, size: usize, @@ -369,14 +333,10 @@ impl Drop for Sensitive { impl Drop for ZeroizeGuard { fn drop(&mut self) { - // SAFETY: We have exclusive access to this memory (we're in Drop), - // and the pointer was derived from a valid ManuallyDrop. - // Volatile writes prevent dead-store elimination. unsafe { for i in 0..self.size { std::ptr::write_volatile(self.ptr.add(i), 0); } - // Ensure writes are not reordered past this point std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst); } } @@ -387,11 +347,8 @@ impl Drop for Sensitive { size: std::mem::size_of::(), }; - // SAFETY: We only call this once, and self.0 is valid. - // We use ManuallyDrop specifically to control drop timing. unsafe { ManuallyDrop::drop(&mut self.0) }; - // Guard drops here (or on unwind), zeroing the memory drop(guard); } } @@ -399,74 +356,78 @@ impl Drop for Sensitive { /// Bundle of public keys and signatures for publishing in DID documents. #[derive(Clone)] pub struct SyftPublicKeyBundle { - pub signal_identity_public_key: IdentityKey, - pub signal_signed_public_pre_key: PublicKey, - pub signal_signed_pre_key_signature: Box<[u8]>, - pub signal_pq_public_pre_key: kem::PublicKey, - pub signal_pq_pre_key_signature: Box<[u8]>, + pub identity_signing_public_key: VerifyingKey, + pub identity_dh_public_key: X25519PublicKey, + pub identity_dh_signature: Box<[u8]>, + pub signed_prekey_public_key: X25519PublicKey, + pub signed_prekey_signature: Box<[u8]>, } impl SyftPublicKeyBundle { /// Create a new public key bundle from an identity key pair and prekey pairs. /// - /// This will sign both prekeys with the identity private key. - pub fn new( - identity_key_pair: &IdentityKeyPair, - signed_pre_key_pair: &KeyPair, - pq_pre_key_pair: &kem::KeyPair, - rng: &mut R, - ) -> Result { - // Sign the EC prekey - let signed_pre_key_signature = identity_key_pair - .private_key() - .calculate_signature(&signed_pre_key_pair.public_key.serialize(), rng)?; - - // Sign the PQ prekey - let pq_pre_key_signature = identity_key_pair - .private_key() - .calculate_signature(&pq_pre_key_pair.public_key.serialize(), rng)?; + /// This will sign both the identity DH key and the signed prekey with the identity signing key. + pub fn new( + identity_signing_key: &SigningKey, + identity_dh_key: &StaticSecret, + signed_pre_key: &StaticSecret, + ) -> Result { + let identity_public_key = identity_signing_key.verifying_key(); + let identity_dh_public_key = X25519PublicKey::from(identity_dh_key); + let signed_pre_key_public = X25519PublicKey::from(signed_pre_key); + + let identity_dh_signature = identity_signing_key + .sign(identity_dh_public_key.as_bytes()) + .to_bytes() + .to_vec() + .into_boxed_slice(); + + let signed_pre_key_signature = identity_signing_key + .sign(signed_pre_key_public.as_bytes()) + .to_bytes() + .to_vec() + .into_boxed_slice(); Ok(Self { - signal_identity_public_key: *identity_key_pair.identity_key(), - signal_signed_public_pre_key: signed_pre_key_pair.public_key, - signal_signed_pre_key_signature: signed_pre_key_signature, - signal_pq_public_pre_key: pq_pre_key_pair.public_key.clone(), - signal_pq_pre_key_signature: pq_pre_key_signature, + identity_signing_public_key: identity_public_key, + identity_dh_public_key, + identity_dh_signature, + signed_prekey_public_key: signed_pre_key_public, + signed_prekey_signature: signed_pre_key_signature, }) } - /// Verify both signatures in the bundle. + /// Verify all signatures in the bundle. pub fn verify_signatures(&self) -> bool { - let ec_sig_valid = self - .signal_identity_public_key - .public_key() - .verify_signature( - &self.signal_signed_public_pre_key.serialize(), - &self.signal_signed_pre_key_signature, - ); - - let pq_sig_valid = self - .signal_identity_public_key - .public_key() - .verify_signature( - &self.signal_pq_public_pre_key.serialize(), - &self.signal_pq_pre_key_signature, - ); - - ec_sig_valid && pq_sig_valid + let identity_dh_sig = Signature::from_slice(&self.identity_dh_signature).ok(); + let signed_pre_key_sig = Signature::from_slice(&self.signed_prekey_signature).ok(); + + let identity_dh_valid = identity_dh_sig.is_some_and(|sig| { + self.identity_signing_public_key + .verify_strict(self.identity_dh_public_key.as_bytes(), &sig) + .is_ok() + }); + + let signed_pre_key_valid = signed_pre_key_sig.is_some_and(|sig| { + self.identity_signing_public_key + .verify_strict(self.signed_prekey_public_key.as_bytes(), &sig) + .is_ok() + }); + + identity_dh_valid && signed_pre_key_valid } /// Compute and return the identity public key fingerprint. pub fn identity_fingerprint(&self) -> String { - compute_identity_fingerprint(&self.signal_identity_public_key) + compute_identity_fingerprint(&self.identity_signing_public_key) } /// Get the total size of the bundle in bytes. pub fn total_size(&self) -> usize { - self.signal_identity_public_key.serialize().len() - + self.signal_signed_public_pre_key.serialize().len() - + self.signal_signed_pre_key_signature.len() - + self.signal_pq_public_pre_key.serialize().len() - + self.signal_pq_pre_key_signature.len() + self.identity_signing_public_key.as_bytes().len() + + self.identity_dh_public_key.as_bytes().len() + + self.identity_dh_signature.len() + + self.signed_prekey_public_key.as_bytes().len() + + self.signed_prekey_signature.len() } } diff --git a/protocol/src/serialization.rs b/protocol/src/serialization.rs index e7f694d..614ba6d 100644 --- a/protocol/src/serialization.rs +++ b/protocol/src/serialization.rs @@ -6,29 +6,30 @@ //! //! # DID Document Format //! Public keys are serialized according to W3C DID specification with JWK encoding: -//! - Identity key in `verificationMethod` (Ed25519) -//! - Encryption keys in `keyAgreement` (X25519, Kyber1024) +//! - Identity signing key in `verificationMethod` (Ed25519) +//! - Encryption keys in `keyAgreement` (X25519 identity DH + signed prekey) //! - Base64url encoding (RFC 7515, no padding) //! //! # JWKS Format //! Private keys are stored in a flat JSON structure: //! - `identity_key`: Ed25519 keypair -//! - `signed_prekey`: X25519 keypair with signature -//! - `pq_prekey`: Kyber1024 keypair with signature +//! - `identity_dh`: X25519 identity DH keypair +//! - `signed_prekey`: X25519 signed prekey keypair use crate::error::{SerializationError, SerializationResult}; use crate::keys::{SyftPrivateKeys, SyftPublicKeyBundle}; use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; -use libsignal_protocol::{IdentityKey, IdentityKeyPair, KeyPair, PublicKey, kem}; +use ed25519_dalek::{SigningKey, VerifyingKey}; use serde_json::{Value, json}; +use x25519_dalek::PublicKey as X25519PublicKey; use zeroize::{Zeroize, Zeroizing}; /// Serialize public key bundle to W3C DID document format. /// /// Creates a DID document with: /// - `@context`: W3C DID and security suite contexts -/// - `verificationMethod`: Identity key (Ed25519) for signing -/// - `keyAgreement`: Encryption keys (X25519, Kyber1024) +/// - `verificationMethod`: Identity signing key (Ed25519) +/// - `keyAgreement`: Identity DH key + signed prekey (X25519) /// /// # Arguments /// * `bundle` - Public key bundle to serialize @@ -64,36 +65,36 @@ pub fn serialize_to_did_document( "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", - "x": URL_SAFE_NO_PAD.encode(bundle.signal_identity_public_key.serialize()), + "x": URL_SAFE_NO_PAD.encode(bundle.identity_signing_public_key.as_bytes()), "kid": "identity-key", "use": "sig" } }], "keyAgreement": [ { - "id": format!("{}#signed-prekey", did_id), + "id": format!("{}#identity-dh", did_id), "type": "X25519KeyAgreementKey2020", "controller": controller, "publicKeyJwk": { "kty": "OKP", "crv": "X25519", - "x": URL_SAFE_NO_PAD.encode(bundle.signal_signed_public_pre_key.serialize()), - "kid": "signed-prekey", + "x": URL_SAFE_NO_PAD.encode(bundle.identity_dh_public_key.as_bytes()), + "kid": "identity-dh", "use": "enc", - "signature": URL_SAFE_NO_PAD.encode(&bundle.signal_signed_pre_key_signature) + "signature": URL_SAFE_NO_PAD.encode(&bundle.identity_dh_signature) } }, { - "id": format!("{}#pq-prekey", did_id), - "type": "JsonWebKey2020", // + "id": format!("{}#signed-prekey", did_id), + "type": "X25519KeyAgreementKey2020", "controller": controller, "publicKeyJwk": { - "kty": "PQ", - "crv": "Kyber1024", - "x": URL_SAFE_NO_PAD.encode(bundle.signal_pq_public_pre_key.serialize()), - "kid": "pq-prekey", + "kty": "OKP", + "crv": "X25519", + "x": URL_SAFE_NO_PAD.encode(bundle.signed_prekey_public_key.as_bytes()), + "kid": "signed-prekey", "use": "enc", - "signature": URL_SAFE_NO_PAD.encode(&bundle.signal_pq_pre_key_signature) + "signature": URL_SAFE_NO_PAD.encode(&bundle.signed_prekey_signature) } } ] @@ -103,9 +104,9 @@ pub fn serialize_to_did_document( /// Deserialize public key bundle from DID document. /// /// Parses a W3C DID document and extracts: -/// - Identity key from `verificationMethod` -/// - Signed prekey from `keyAgreement` (X25519) -/// - PQ prekey from `keyAgreement` (Kyber1024) +/// - Identity signing key from `verificationMethod` +/// - Identity DH key from `keyAgreement` +/// - Signed prekey from `keyAgreement` /// /// # Arguments /// * `json` - DID document as JSON value @@ -121,6 +122,16 @@ pub fn deserialize_from_did_document(json: &Value) -> SerializationResult(s: &str) -> SerializationResult<[u8; N]> { + let bytes = decode_base64url(s)?; + if bytes.len() != N { + return Err(SerializationError::InvalidFormat); + } + let mut out = [0u8; N]; + out.copy_from_slice(&bytes); + Ok(out) + } + // Extract identity key from verificationMethod let verification_methods = json["verificationMethod"] .as_array() @@ -131,68 +142,63 @@ pub fn deserialize_from_did_document(json: &Value) -> SerializationResult( identity_method["publicKeyJwk"]["x"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?; - let identity_key = - IdentityKey::decode(&identity_key_bytes).map_err(|_| SerializationError::InvalidFormat)?; + let identity_key = VerifyingKey::from_bytes(&identity_key_bytes) + .map_err(|_| SerializationError::InvalidFormat)?; // Extract encryption keys from keyAgreement let key_agreement = json["keyAgreement"] .as_array() .ok_or(SerializationError::InvalidFormat)?; - // Find X25519 signed prekey - let spk_method = key_agreement + let identity_dh_method = key_agreement .iter() - .find(|m| m["type"] == "X25519KeyAgreementKey2020") - .ok_or(SerializationError::MissingSignedPrekey)?; + .find(|m| m["publicKeyJwk"]["kid"] == "identity-dh") + .ok_or(SerializationError::MissingIdentityDhKey)?; - let spk_bytes = decode_base64url( - spk_method["publicKeyJwk"]["x"] + let identity_dh_bytes = decode_fixed::<32>( + identity_dh_method["publicKeyJwk"]["x"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?; - let spk_signature = decode_base64url( - spk_method["publicKeyJwk"]["signature"] + let identity_dh_signature = decode_base64url( + identity_dh_method["publicKeyJwk"]["signature"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )? .into_boxed_slice(); + let identity_dh_key = X25519PublicKey::from(identity_dh_bytes); - let signed_pre_key = - PublicKey::deserialize(&spk_bytes).map_err(|_| SerializationError::InvalidFormat)?; - - // Find Kyber1024 PQ prekey (JsonWebKey2020 with kty="PQ") - let pqspk_method = key_agreement + let spk_method = key_agreement .iter() - .find(|m| m["type"] == "JsonWebKey2020" && m["publicKeyJwk"]["kty"] == "PQ") - .ok_or(SerializationError::MissingPQPrekey)?; + .find(|m| m["publicKeyJwk"]["kid"] == "signed-prekey") + .ok_or(SerializationError::MissingSignedPrekey)?; - let pqspk_bytes = decode_base64url( - pqspk_method["publicKeyJwk"]["x"] + let spk_bytes = decode_fixed::<32>( + spk_method["publicKeyJwk"]["x"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?; - let pqspk_signature = decode_base64url( - pqspk_method["publicKeyJwk"]["signature"] + let spk_signature = decode_base64url( + spk_method["publicKeyJwk"]["signature"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )? .into_boxed_slice(); - let pq_pre_key = - kem::PublicKey::deserialize(&pqspk_bytes).map_err(|_| SerializationError::InvalidFormat)?; + let signed_pre_key = X25519PublicKey::from(spk_bytes); // Create PublicKeyBundle let bundle = SyftPublicKeyBundle { - signal_identity_public_key: identity_key, - signal_signed_public_pre_key: signed_pre_key, - signal_signed_pre_key_signature: spk_signature, - signal_pq_public_pre_key: pq_pre_key, - signal_pq_pre_key_signature: pqspk_signature, + identity_signing_public_key: identity_key, + identity_dh_public_key: identity_dh_key, + identity_dh_signature, + signed_prekey_public_key: signed_pre_key, + signed_prekey_signature: spk_signature, }; // Verify signatures @@ -207,8 +213,8 @@ pub fn deserialize_from_did_document(json: &Value) -> SerializationResult SerializationResult SerializationResult { + let identity_dh_public = X25519PublicKey::from(keys.identity_dh()); + let signed_pre_key_public = X25519PublicKey::from(keys.signed_pre_key()); + Ok(json!({ "identity_key": { "kty": "OKP", "crv": "Ed25519", - "x": URL_SAFE_NO_PAD.encode(keys.identity().identity_key().serialize()), - "d": URL_SAFE_NO_PAD.encode(keys.identity().serialize()), + "x": URL_SAFE_NO_PAD.encode(keys.identity().verifying_key().as_bytes()), + "d": URL_SAFE_NO_PAD.encode(keys.identity().to_bytes()), "kid": "identity-key", "use": "sig" }, - "signed_prekey": { + "identity_dh": { "kty": "OKP", "crv": "X25519", - "x": URL_SAFE_NO_PAD.encode(keys.signed_pre_key().public_key.serialize()), - "d": URL_SAFE_NO_PAD.encode(keys.signed_pre_key().private_key.serialize()), - "kid": "signed-prekey", + "x": URL_SAFE_NO_PAD.encode(identity_dh_public.as_bytes()), + "d": URL_SAFE_NO_PAD.encode(keys.identity_dh().to_bytes()), + "kid": "identity-dh", "use": "enc" }, - "pq_prekey": { - "kty": "PQ", - "crv": "Kyber1024", - "x": URL_SAFE_NO_PAD.encode(keys.pq_signed_pre_key().public_key.serialize()), - "d": URL_SAFE_NO_PAD.encode(keys.pq_signed_pre_key().secret_key.serialize()), - "kid": "pq-prekey", + "signed_prekey": { + "kty": "OKP", + "crv": "X25519", + "x": URL_SAFE_NO_PAD.encode(signed_pre_key_public.as_bytes()), + "d": URL_SAFE_NO_PAD.encode(keys.signed_pre_key().to_bytes()), + "kid": "signed-prekey", "use": "enc" } })) @@ -254,8 +263,8 @@ pub fn serialize_private_keys(keys: &SyftPrivateKeys) -> SerializationResult SerializationResult(s: &str) -> SerializationResult<[u8; N]> { + let bytes = decode_base64url(s)?; + if bytes.len() != N { + return Err(SerializationError::InvalidFormat); + } + let mut out = [0u8; N]; + out.copy_from_slice(&bytes); + Ok(out) + } + // Extract identity key let identity_obj = json .get("identity_key") .ok_or(SerializationError::MissingIdentityKey)?; - let identity_private_bytes = Zeroizing::new(decode_base64url( + let identity_private_bytes = Zeroizing::new(decode_fixed::<32>( identity_obj["d"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?); - let identity_keypair = IdentityKeyPair::try_from(&identity_private_bytes[..]) - .map_err(|_| SerializationError::InvalidFormat)?; + let identity_public_bytes = decode_fixed::<32>( + identity_obj["x"] + .as_str() + .ok_or(SerializationError::InvalidFormat)?, + )?; - // Extract signed prekey - let spk_obj = json - .get("signed_prekey") - .ok_or(SerializationError::MissingSignedPrekey)?; + let identity_signing_key = SigningKey::from_bytes(&identity_private_bytes); + if identity_signing_key.verifying_key().as_bytes() != &identity_public_bytes { + return Err(SerializationError::InvalidSignature); + } - let spk_private_bytes = Zeroizing::new(decode_base64url( - spk_obj["d"] + // Extract identity DH key + let identity_dh_obj = json + .get("identity_dh") + .ok_or(SerializationError::MissingIdentityDhKey)?; + + let identity_dh_private_bytes = Zeroizing::new(decode_fixed::<32>( + identity_dh_obj["d"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?); - // Need to get public key bytes from JSON as well - let spk_public_bytes = decode_base64url( - spk_obj["x"] + let identity_dh_public_bytes = decode_fixed::<32>( + identity_dh_obj["x"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?; - let spk_keypair = KeyPair::from_public_and_private(&spk_public_bytes, &spk_private_bytes) - .map_err(|_| SerializationError::InvalidFormat)?; + let identity_dh_key = x25519_dalek::StaticSecret::from(*identity_dh_private_bytes); + let identity_dh_public = X25519PublicKey::from(&identity_dh_key); + if identity_dh_public.as_bytes() != &identity_dh_public_bytes { + return Err(SerializationError::InvalidSignature); + } - // Extract PQ prekey - let pqspk_obj = json - .get("pq_prekey") - .ok_or(SerializationError::MissingPQPrekey)?; + // Extract signed prekey + let spk_obj = json + .get("signed_prekey") + .ok_or(SerializationError::MissingSignedPrekey)?; - let pqspk_secret_bytes = Zeroizing::new(decode_base64url( - pqspk_obj["d"] + let spk_private_bytes = Zeroizing::new(decode_fixed::<32>( + spk_obj["d"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?); - // Need to get public key bytes from JSON as well - let pqspk_public_bytes = decode_base64url( - pqspk_obj["x"] + let spk_public_bytes = decode_fixed::<32>( + spk_obj["x"] .as_str() .ok_or(SerializationError::InvalidFormat)?, )?; - let pqspk_keypair = - kem::KeyPair::from_public_and_private(&pqspk_public_bytes, &pqspk_secret_bytes) - .map_err(|_| SerializationError::InvalidFormat)?; + let signed_pre_key = x25519_dalek::StaticSecret::from(*spk_private_bytes); + let signed_pre_key_public = X25519PublicKey::from(&signed_pre_key); + if signed_pre_key_public.as_bytes() != &spk_public_bytes { + return Err(SerializationError::InvalidSignature); + } - // Reconstruct SyftPrivateKeys Ok(SyftPrivateKeys::new( - identity_keypair, - spk_keypair, - pqspk_keypair, + identity_signing_key, + identity_dh_key, + signed_pre_key, )) } diff --git a/protocol/src/tests/envelope_tests.rs b/protocol/src/tests/envelope_tests.rs index c7fafc2..f55ea1b 100644 --- a/protocol/src/tests/envelope_tests.rs +++ b/protocol/src/tests/envelope_tests.rs @@ -54,7 +54,7 @@ fn envelope_builds_and_parses() { assert_eq!(parsed.prelude.sender.identity, "alice@example.org"); // Verify real signature - crate::envelope::verify_signature(&parsed, sender_sk.identity().identity_key()) + crate::envelope::verify_signature(&parsed, &sender_sk.identity().verifying_key()) .expect("signature verify"); } diff --git a/protocol/tests/envelope_signing_test.rs b/protocol/tests/envelope_signing_test.rs index 1562423..7cfd73b 100644 --- a/protocol/tests/envelope_signing_test.rs +++ b/protocol/tests/envelope_signing_test.rs @@ -33,7 +33,7 @@ fn test_build_envelope_and_verify_signature() { // Verify signature let result = syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ); assert!(result.is_ok(), "Signature verification should succeed"); @@ -78,10 +78,7 @@ fn test_envelope_contains_valid_fingerprints() { // Verify recipient fingerprint formats let recipient_info = &parsed.prelude.recipients[0]; let spk_fp = recipient_info.spk_fingerprint.as_ref().unwrap(); - let pqspk_fp = recipient_info.pqspk_fingerprint.as_ref().unwrap(); - assert_eq!(spk_fp.len(), 64, "Should be SHA-256 fingerprint"); - assert_eq!(pqspk_fp.len(), 64, "Should be SHA-256 fingerprint"); } #[test] @@ -116,7 +113,7 @@ fn test_signature_verification_fails_with_wrong_key() { // Try to verify with wrong key let result = syft_crypto_protocol::envelope::verify_signature( &parsed, - wrong_sk.identity().identity_key(), + &wrong_sk.identity().verifying_key(), ); assert!( @@ -149,7 +146,7 @@ fn test_signature_verification_checks_fingerprint() { let err = syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ) .expect_err("fingerprint mismatch should fail"); @@ -213,7 +210,7 @@ fn test_envelope_with_multiple_recipients() { // Verify signature let result = syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ); assert!(result.is_ok(), "Signature should be valid"); } @@ -301,8 +298,8 @@ fn test_envelope_format_has_syc_magic() { // Check magic bytes assert_eq!( &envelope_bytes[0..4], - b"SYC1", - "Should start with SYC1 magic" + b"SYC2", + "Should start with SYC2 magic" ); // Check version @@ -362,14 +359,14 @@ fn test_deterministic_fingerprints_in_envelope() { assert!( syft_crypto_protocol::envelope::verify_signature( &parsed1, - sender_sk.identity().identity_key() + &sender_sk.identity().verifying_key() ) .is_ok() ); assert!( syft_crypto_protocol::envelope::verify_signature( &parsed2, - sender_sk.identity().identity_key() + &sender_sk.identity().verifying_key() ) .is_ok() ); @@ -670,7 +667,7 @@ fn test_envelope_roundtrip_preserves_all_fields() { // Verify signature let verify_result = syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ); assert!(verify_result.is_ok(), "Signature should verify correctly"); } @@ -715,7 +712,7 @@ fn test_envelope_with_large_prelude() { // Verify signature works with large prelude let verify_result = syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ); assert!(verify_result.is_ok()); } @@ -756,7 +753,7 @@ fn test_envelope_with_unicode_identities() { // Verify signature with Unicode identities let verify_result = syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ); assert!(verify_result.is_ok()); } @@ -789,7 +786,7 @@ fn test_envelope_signature_covers_full_prelude() { assert!( syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key() + &sender_sk.identity().verifying_key() ) .is_ok() ); @@ -808,7 +805,7 @@ fn test_envelope_signature_covers_full_prelude() { // Signature should now fail (proves signature covers sender identity) let result = syft_crypto_protocol::envelope::verify_signature( &tampered_parsed, - sender_sk.identity().identity_key(), + &sender_sk.identity().verifying_key(), ); assert!( result.is_err(), @@ -841,7 +838,7 @@ fn test_envelope_padding_not_signed() { assert!( syft_crypto_protocol::envelope::verify_signature( &parsed, - sender_sk.identity().identity_key() + &sender_sk.identity().verifying_key() ) .is_ok() ); @@ -849,25 +846,31 @@ fn test_envelope_padding_not_signed() { // Verify that the signature only covers prelude metadata (plus domain separator), // not the ciphertext or padding let mut message = Vec::new(); - message.extend_from_slice(b"SYC1-PRELUDE"); + message.extend_from_slice(b"SYC2-PRELUDE"); message.push(syft_crypto_protocol::envelope::CURRENT_VERSION); message.extend_from_slice(&parsed.prelude_bytes); assert!( sender_sk .identity() - .identity_key() - .public_key() - .verify_signature(&message, &parsed.signature), + .verifying_key() + .verify_strict( + &message, + &ed25519_dalek::Signature::from_slice(&parsed.signature).unwrap() + ) + .is_ok(), "Signature should verify when domain-separated prelude is provided" ); assert!( - !sender_sk + sender_sk .identity() - .identity_key() - .public_key() - .verify_signature(&parsed.prelude_bytes, &parsed.signature), + .verifying_key() + .verify_strict( + &parsed.prelude_bytes, + &ed25519_dalek::Signature::from_slice(&parsed.signature).unwrap() + ) + .is_err(), "Raw prelude should fail verification without domain separator" ); } @@ -927,14 +930,14 @@ fn test_envelope_timestamp_changes_signature() { assert!( syft_crypto_protocol::envelope::verify_signature( &parsed1, - sender_sk.identity().identity_key() + &sender_sk.identity().verifying_key() ) .is_ok() ); assert!( syft_crypto_protocol::envelope::verify_signature( &parsed2, - sender_sk.identity().identity_key() + &sender_sk.identity().verifying_key() ) .is_ok() ); diff --git a/protocol/tests/file_encryption_test.rs b/protocol/tests/file_encryption_test.rs index 2ee10f5..109af1e 100644 --- a/protocol/tests/file_encryption_test.rs +++ b/protocol/tests/file_encryption_test.rs @@ -749,14 +749,15 @@ fn test_wrapped_key_format() { .decode(&parsed.prelude.wrappings[0].wrap_ciphertext) .expect("decode wrap_ciphertext"); - // Format: wrapped_key (72 bytes) || kyber_ct (~1568 bytes) - assert!( - wrap_ciphertext.len() >= 72, - "wrap_ciphertext should be at least 72 bytes (wrapped key) + Kyber CT" + // Format: wrapped_key (72 bytes) + assert_eq!( + wrap_ciphertext.len(), + 72, + "wrap_ciphertext should be exactly 72 bytes" ); - // The first 72 bytes should be: nonce (24) + encrypted_key (32) + auth_tag (16) - let wrapped_key = &wrap_ciphertext[..72]; + // The 72 bytes should be: nonce (24) + encrypted_key (32) + auth_tag (16) + let wrapped_key = &wrap_ciphertext[..]; assert_eq!( wrapped_key.len(), 72, diff --git a/protocol/tests/key_gen_test.rs b/protocol/tests/key_gen_test.rs index 94a6596..415c33c 100644 --- a/protocol/tests/key_gen_test.rs +++ b/protocol/tests/key_gen_test.rs @@ -1,4 +1,5 @@ use syft_crypto_protocol::SyftRecoveryKey; +use x25519_dalek::PublicKey as X25519PublicKey; #[test] fn test_derive_keys_from_recovery_key() { @@ -7,174 +8,94 @@ fn test_derive_keys_from_recovery_key() { .derive_keys() .expect("Key derivation should succeed"); - // Verify all keys are generated - assert!( - !private_keys.identity().serialize().is_empty(), - "Identity key should exist" - ); - assert!( - !private_keys - .signed_pre_key() - .public_key - .serialize() - .is_empty(), - "Signed prekey should exist" - ); - assert!( - !private_keys - .pq_signed_pre_key() - .public_key - .serialize() - .is_empty(), - "PQ prekey should exist" - ); + assert!(!private_keys.identity().to_bytes().is_empty()); + assert!(!private_keys.identity_dh().to_bytes().is_empty()); + assert!(!private_keys.signed_pre_key().to_bytes().is_empty()); } #[test] fn test_deterministic_key_derivation() { - // Same recovery key should produce same keys let recovery_key = SyftRecoveryKey::generate(); - let keys1 = recovery_key - .derive_keys() - .expect("First derivation should succeed"); - let keys2 = recovery_key - .derive_keys() - .expect("Second derivation should succeed"); - - // Compare identity keys - assert_eq!( - keys1.identity().serialize(), - keys2.identity().serialize(), - "Identity keys should match" - ); + let keys1 = recovery_key.derive_keys().expect("first derivation"); + let keys2 = recovery_key.derive_keys().expect("second derivation"); - // Compare signed prekeys - assert_eq!( - keys1.signed_pre_key().public_key.serialize(), - keys2.signed_pre_key().public_key.serialize(), - "Signed prekeys should match" - ); + assert_eq!(keys1.identity().to_bytes(), keys2.identity().to_bytes()); assert_eq!( - keys1.signed_pre_key().private_key.serialize(), - keys2.signed_pre_key().private_key.serialize(), - "Signed prekey private keys should match" + keys1.identity_dh().to_bytes(), + keys2.identity_dh().to_bytes() ); - - // Compare PQ prekeys assert_eq!( - keys1.pq_signed_pre_key().public_key.serialize(), - keys2.pq_signed_pre_key().public_key.serialize(), - "PQ prekeys should match" - ); - assert_eq!( - keys1.pq_signed_pre_key().secret_key.serialize(), - keys2.pq_signed_pre_key().secret_key.serialize(), - "PQ prekey secret keys should match" + keys1.signed_pre_key().to_bytes(), + keys2.signed_pre_key().to_bytes() ); } #[test] fn test_different_recovery_keys_produce_different_keys() { - // Different recovery keys should produce different keys let recovery_key1 = SyftRecoveryKey::generate(); let recovery_key2 = SyftRecoveryKey::generate(); - let keys1 = recovery_key1 - .derive_keys() - .expect("First derivation should succeed"); - let keys2 = recovery_key2 - .derive_keys() - .expect("Second derivation should succeed"); + let keys1 = recovery_key1.derive_keys().expect("first derivation"); + let keys2 = recovery_key2.derive_keys().expect("second derivation"); - // Identity keys should be different + assert_ne!(keys1.identity().to_bytes(), keys2.identity().to_bytes()); assert_ne!( - keys1.identity().serialize(), - keys2.identity().serialize(), - "Identity keys should be different" + keys1.identity_dh().to_bytes(), + keys2.identity_dh().to_bytes() ); - - // Signed prekeys should be different assert_ne!( - keys1.signed_pre_key().public_key.serialize(), - keys2.signed_pre_key().public_key.serialize(), - "Signed prekeys should be different" - ); - - // PQ prekeys should be different - assert_ne!( - keys1.pq_signed_pre_key().public_key.serialize(), - keys2.pq_signed_pre_key().public_key.serialize(), - "PQ prekeys should be different" + keys1.signed_pre_key().to_bytes(), + keys2.signed_pre_key().to_bytes() ); } #[test] fn test_recovery_key_hex_roundtrip_with_derived_keys() { - // Verify that hex serialization + deserialization produces same keys let recovery_key = SyftRecoveryKey::generate(); - let original_keys = recovery_key - .derive_keys() - .expect("Original derivation should succeed"); + let original_keys = recovery_key.derive_keys().expect("original derivation"); - // Serialize to hex and back let hex = recovery_key.to_hex_string(); - let recovered_key = SyftRecoveryKey::from_hex_string(&hex).expect("Should parse hex"); - let recovered_keys = recovered_key - .derive_keys() - .expect("Recovered derivation should succeed"); + let recovered_key = SyftRecoveryKey::from_hex_string(&hex).expect("parse hex"); + let recovered_keys = recovered_key.derive_keys().expect("recovered derivation"); - // Keys should match assert_eq!( - original_keys.identity().serialize(), - recovered_keys.identity().serialize(), - "Identity keys should match after hex roundtrip" + original_keys.identity().to_bytes(), + recovered_keys.identity().to_bytes() ); assert_eq!( - original_keys.signed_pre_key().public_key.serialize(), - recovered_keys.signed_pre_key().public_key.serialize(), - "Signed prekeys should match after hex roundtrip" + original_keys.identity_dh().to_bytes(), + recovered_keys.identity_dh().to_bytes() ); assert_eq!( - original_keys.pq_signed_pre_key().public_key.serialize(), - recovered_keys.pq_signed_pre_key().public_key.serialize(), - "PQ prekeys should match after hex roundtrip" + original_keys.signed_pre_key().to_bytes(), + recovered_keys.signed_pre_key().to_bytes() ); } #[test] fn test_derived_keys_can_create_public_bundle() { - // Verify derived keys can be used to create a public key bundle let recovery_key = SyftRecoveryKey::generate(); let private_keys = recovery_key .derive_keys() - .expect("Key derivation should succeed"); + .expect("derivation should succeed"); - // Create public bundle let public_bundle = private_keys .to_public_bundle(&mut rand::rng()) - .expect("Should create public bundle"); + .expect("should create public bundle"); - // Verify signatures are valid - assert!( - public_bundle.verify_signatures(), - "Public bundle signatures should be valid" - ); + assert!(public_bundle.verify_signatures()); - // Verify public keys match private keys assert_eq!( - public_bundle.signal_identity_public_key.serialize(), - private_keys.identity().identity_key().serialize(), - "Public identity key should match private key" + public_bundle.identity_signing_public_key.as_bytes(), + private_keys.identity().verifying_key().as_bytes() ); assert_eq!( - public_bundle.signal_signed_public_pre_key.serialize(), - private_keys.signed_pre_key().public_key.serialize(), - "Public signed prekey should match private key" + public_bundle.identity_dh_public_key.as_bytes(), + X25519PublicKey::from(private_keys.identity_dh()).as_bytes() ); assert_eq!( - public_bundle.signal_pq_public_pre_key.serialize(), - private_keys.pq_signed_pre_key().public_key.serialize(), - "Public PQ prekey should match private key" + public_bundle.signed_prekey_public_key.as_bytes(), + X25519PublicKey::from(private_keys.signed_pre_key()).as_bytes() ); } diff --git a/protocol/tests/keys_fingerprint_test.rs b/protocol/tests/keys_fingerprint_test.rs index 5b73951..d89d3d9 100644 --- a/protocol/tests/keys_fingerprint_test.rs +++ b/protocol/tests/keys_fingerprint_test.rs @@ -4,10 +4,10 @@ use syft_crypto_protocol::{SyftRecoveryKey, compute_identity_fingerprint}; fn test_fingerprint_is_deterministic() { let recovery_key = SyftRecoveryKey::generate(); let private_keys = recovery_key.derive_keys().unwrap(); - let identity = private_keys.identity().identity_key(); + let identity = private_keys.identity().verifying_key(); - let fp1 = compute_identity_fingerprint(identity); - let fp2 = compute_identity_fingerprint(identity); + let fp1 = compute_identity_fingerprint(&identity); + let fp2 = compute_identity_fingerprint(&identity); assert_eq!(fp1, fp2, "Same key should produce same fingerprint"); } @@ -17,8 +17,8 @@ fn test_different_keys_different_fingerprints() { let key1 = SyftRecoveryKey::generate().derive_keys().unwrap(); let key2 = SyftRecoveryKey::generate().derive_keys().unwrap(); - let fp1 = compute_identity_fingerprint(key1.identity().identity_key()); - let fp2 = compute_identity_fingerprint(key2.identity().identity_key()); + let fp1 = compute_identity_fingerprint(&key1.identity().verifying_key()); + let fp2 = compute_identity_fingerprint(&key2.identity().verifying_key()); assert_ne!( fp1, fp2, @@ -30,7 +30,7 @@ fn test_different_keys_different_fingerprints() { fn test_fingerprint_format() { let recovery_key = SyftRecoveryKey::generate(); let private_keys = recovery_key.derive_keys().unwrap(); - let fingerprint = compute_identity_fingerprint(private_keys.identity().identity_key()); + let fingerprint = compute_identity_fingerprint(&private_keys.identity().verifying_key()); assert_eq!( fingerprint.len(), @@ -47,7 +47,7 @@ fn test_fingerprint_format() { fn test_fingerprint_is_lowercase_hex() { let recovery_key = SyftRecoveryKey::generate(); let private_keys = recovery_key.derive_keys().unwrap(); - let fingerprint = compute_identity_fingerprint(private_keys.identity().identity_key()); + let fingerprint = compute_identity_fingerprint(&private_keys.identity().verifying_key()); // hex::encode produces lowercase hex assert!( @@ -64,14 +64,14 @@ fn test_fingerprint_from_recovered_key_matches() { let original_recovery = SyftRecoveryKey::generate(); let original_keys = original_recovery.derive_keys().unwrap(); let original_fingerprint = - compute_identity_fingerprint(original_keys.identity().identity_key()); + compute_identity_fingerprint(&original_keys.identity().verifying_key()); // Convert to mnemonic and recover let mnemonic = original_recovery.to_mnemonic(); let recovered_recovery = SyftRecoveryKey::from_mnemonic(&mnemonic).unwrap(); let recovered_keys = recovered_recovery.derive_keys().unwrap(); let recovered_fingerprint = - compute_identity_fingerprint(recovered_keys.identity().identity_key()); + compute_identity_fingerprint(&recovered_keys.identity().verifying_key()); assert_eq!( original_fingerprint, recovered_fingerprint, @@ -86,10 +86,10 @@ fn test_fingerprint_matches_public_bundle() { let public_bundle = private_keys.to_public_bundle(&mut rand::rng()).unwrap(); // Fingerprint from private key - let fp_from_private = compute_identity_fingerprint(private_keys.identity().identity_key()); + let fp_from_private = compute_identity_fingerprint(&private_keys.identity().verifying_key()); // Fingerprint from public bundle - let fp_from_public = compute_identity_fingerprint(&public_bundle.signal_identity_public_key); + let fp_from_public = compute_identity_fingerprint(&public_bundle.identity_signing_public_key); assert_eq!( fp_from_private, fp_from_public, diff --git a/protocol/tests/keys_serialization_test.rs b/protocol/tests/keys_serialization_test.rs index 3e6f0e9..ecc81be 100644 --- a/protocol/tests/keys_serialization_test.rs +++ b/protocol/tests/keys_serialization_test.rs @@ -11,30 +11,22 @@ fn test_did_document_roundtrip() { let private_keys = recovery_key.derive_keys().unwrap(); let original_bundle = private_keys.to_public_bundle(&mut rand::rng()).unwrap(); - // Generate DID using utility function let did_id = generate_did_web_id("alice@example.com", "example.com"); - // Serialize - let did_doc = serialize_to_did_document(&original_bundle, &did_id).expect("Should serialize"); + let did_doc = serialize_to_did_document(&original_bundle, &did_id).expect("serialize"); + let restored_bundle = deserialize_from_did_document(&did_doc).expect("deserialize"); - // Deserialize - let restored_bundle = deserialize_from_did_document(&did_doc).expect("Should deserialize"); - - // Verify keys match assert_eq!( - original_bundle.signal_identity_public_key.serialize(), - restored_bundle.signal_identity_public_key.serialize(), - "Identity keys should match" + original_bundle.identity_signing_public_key.as_bytes(), + restored_bundle.identity_signing_public_key.as_bytes() ); assert_eq!( - original_bundle.signal_signed_public_pre_key.serialize(), - restored_bundle.signal_signed_public_pre_key.serialize(), - "Signed prekeys should match" + original_bundle.identity_dh_public_key.as_bytes(), + restored_bundle.identity_dh_public_key.as_bytes() ); assert_eq!( - original_bundle.signal_pq_public_pre_key.serialize(), - restored_bundle.signal_pq_public_pre_key.serialize(), - "PQ prekeys should match" + original_bundle.signed_prekey_public_key.as_bytes(), + restored_bundle.signed_prekey_public_key.as_bytes() ); } @@ -43,37 +35,20 @@ fn test_private_keys_roundtrip() { let recovery_key = SyftRecoveryKey::generate(); let original_keys = recovery_key.derive_keys().unwrap(); - // Serialize - let jwks = serialize_private_keys(&original_keys).expect("Should serialize"); - - // Deserialize - let restored_keys = deserialize_private_keys(&jwks).expect("Should deserialize"); - - // print out original and restored keys for debugging - println!( - "Original Keys Identity : {:?}", - original_keys.identity().serialize() - ); - println!( - "Restored Keys Identity: {:?}", - restored_keys.identity().serialize() - ); + let jwks = serialize_private_keys(&original_keys).expect("serialize"); + let restored_keys = deserialize_private_keys(&jwks).expect("deserialize"); - // Verify keys match assert_eq!( - original_keys.identity().serialize(), - restored_keys.identity().serialize(), - "Identity keys should match" + original_keys.identity().to_bytes(), + restored_keys.identity().to_bytes() ); assert_eq!( - original_keys.signed_pre_key().public_key.serialize(), - restored_keys.signed_pre_key().public_key.serialize(), - "Signed prekeys should match" + original_keys.identity_dh().to_bytes(), + restored_keys.identity_dh().to_bytes() ); assert_eq!( - original_keys.pq_signed_pre_key().public_key.serialize(), - restored_keys.pq_signed_pre_key().public_key.serialize(), - "PQ prekeys should match" + original_keys.signed_pre_key().to_bytes(), + restored_keys.signed_pre_key().to_bytes() ); } @@ -83,24 +58,20 @@ fn test_did_document_format() { let private_keys = recovery_key.derive_keys().unwrap(); let bundle = private_keys.to_public_bundle(&mut rand::rng()).unwrap(); - // Generate DID using utility function let did_id = generate_did_web_id("alice@example.com", "example.com"); let did_doc = serialize_to_did_document(&bundle, &did_id).unwrap(); - // Verify structure assert!(did_doc["@context"].is_array()); assert_eq!(did_doc["id"], did_id); assert!(did_doc["verificationMethod"].is_array()); assert!(did_doc["keyAgreement"].is_array()); - // Verify identity key let vm = &did_doc["verificationMethod"][0]; assert_eq!(vm["type"], "Ed25519VerificationKey2020"); assert_eq!(vm["publicKeyJwk"]["kty"], "OKP"); assert_eq!(vm["publicKeyJwk"]["crv"], "Ed25519"); - // Verify encryption keys let ka = did_doc["keyAgreement"].as_array().unwrap(); assert_eq!(ka.len(), 2); } diff --git a/protocol/tests/keys_storage_test.rs b/protocol/tests/keys_storage_test.rs index 8bc3754..a6e2348 100644 --- a/protocol/tests/keys_storage_test.rs +++ b/protocol/tests/keys_storage_test.rs @@ -1,3 +1,4 @@ +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; use std::fs; use syft_crypto_protocol::SyftRecoveryKey; use syft_crypto_protocol::did_utils::generate_did_web_id; @@ -11,63 +12,33 @@ fn test_private_keys_storage_roundtrip() { let temp_dir = TempDir::new().unwrap(); let key_path = temp_dir.path().join("test_keys.json"); - // Generate keys let recovery_key = SyftRecoveryKey::generate(); let original_keys = recovery_key.derive_keys().unwrap(); - // Save to disk - save_private_keys(&original_keys, &key_path).expect("Should save keys"); + save_private_keys(&original_keys, &key_path).expect("save keys"); + assert!(key_path.exists()); - // Verify file exists - assert!(key_path.exists(), "Key file should exist"); - - // Check Unix permissions (owner read/write only) #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let metadata = fs::metadata(&key_path).unwrap(); let permissions = metadata.permissions(); - assert_eq!( - permissions.mode() & 0o777, - 0o600, - "Private keys should have 0o600 permissions" - ); + assert_eq!(permissions.mode() & 0o777, 0o600); } - // Load from disk - let loaded_keys = load_private_keys(&key_path).expect("Should load keys"); - - // Verify keys match (both public AND private) - - // Identity key (contains both public and private) - assert_eq!( - original_keys.identity().serialize(), - loaded_keys.identity().serialize(), - "Identity keypairs should match" - ); + let loaded_keys = load_private_keys(&key_path).expect("load keys"); - // Signed prekey - verify both public and private keys - assert_eq!( - original_keys.signed_pre_key().public_key.serialize(), - loaded_keys.signed_pre_key().public_key.serialize(), - "Signed prekey public keys should match" - ); assert_eq!( - original_keys.signed_pre_key().private_key.serialize(), - loaded_keys.signed_pre_key().private_key.serialize(), - "Signed prekey private keys should match" + original_keys.identity().to_bytes(), + loaded_keys.identity().to_bytes() ); - - // PQ prekey - verify both public and secret keys assert_eq!( - original_keys.pq_signed_pre_key().public_key.serialize(), - loaded_keys.pq_signed_pre_key().public_key.serialize(), - "PQ prekey public keys should match" + original_keys.identity_dh().to_bytes(), + loaded_keys.identity_dh().to_bytes() ); assert_eq!( - original_keys.pq_signed_pre_key().secret_key.serialize(), - loaded_keys.pq_signed_pre_key().secret_key.serialize(), - "PQ prekey secret keys should match" + original_keys.signed_pre_key().to_bytes(), + loaded_keys.signed_pre_key().to_bytes() ); } @@ -76,45 +47,35 @@ fn test_did_document_storage_roundtrip() { let temp_dir = TempDir::new().unwrap(); let did_path = temp_dir.path().join("test_did.json"); - // Generate keys and bundle let recovery_key = SyftRecoveryKey::generate(); let private_keys = recovery_key.derive_keys().unwrap(); let original_bundle = private_keys.to_public_bundle(&mut rand::rng()).unwrap(); - // Generate DID using utility function let did_id = generate_did_web_id("alice@example.com", "example.com"); - // Save to disk - save_did_document(&original_bundle, &did_id, &did_path).expect("Should save DID document"); + save_did_document(&original_bundle, &did_id, &did_path).expect("save DID document"); + assert!(did_path.exists()); - // Verify file exists - assert!(did_path.exists(), "DID document file should exist"); + let loaded_bundle = load_did_document(&did_path).expect("load DID document"); - // Load from disk - let loaded_bundle = load_did_document(&did_path).expect("Should load DID document"); - - // Verify keys match assert_eq!( - original_bundle.signal_identity_public_key.serialize(), - loaded_bundle.signal_identity_public_key.serialize(), - "Identity keys should match" + original_bundle.identity_signing_public_key.as_bytes(), + loaded_bundle.identity_signing_public_key.as_bytes() ); assert_eq!( - original_bundle.signal_signed_public_pre_key.serialize(), - loaded_bundle.signal_signed_public_pre_key.serialize(), - "Signed prekeys should match" + original_bundle.identity_dh_public_key.as_bytes(), + loaded_bundle.identity_dh_public_key.as_bytes() ); assert_eq!( - original_bundle.signal_pq_public_pre_key.serialize(), - loaded_bundle.signal_pq_public_pre_key.serialize(), - "PQ prekeys should match" + original_bundle.signed_prekey_public_key.as_bytes(), + loaded_bundle.signed_prekey_public_key.as_bytes() ); } #[test] fn test_load_nonexistent_file() { let result = load_private_keys(std::path::Path::new("/nonexistent/path/keys.json")); - assert!(result.is_err(), "Should fail to load nonexistent file"); + assert!(result.is_err()); } #[test] @@ -122,11 +83,10 @@ fn test_load_invalid_json() { let temp_dir = TempDir::new().unwrap(); let invalid_path = temp_dir.path().join("invalid.json"); - // Write invalid JSON fs::write(&invalid_path, "not valid json").unwrap(); let result = load_private_keys(&invalid_path); - assert!(result.is_err(), "Should fail to load invalid JSON"); + assert!(result.is_err()); } #[test] @@ -138,27 +98,38 @@ fn test_did_document_file_format() { let private_keys = recovery_key.derive_keys().unwrap(); let bundle = private_keys.to_public_bundle(&mut rand::rng()).unwrap(); - // Generate DID using utility function let did_id = generate_did_web_id("alice@example.com", "syftbox.net"); save_did_document(&bundle, &did_id, &did_path).unwrap(); - // Read and parse the file let contents = fs::read_to_string(&did_path).unwrap(); let did_doc: serde_json::Value = serde_json::from_str(&contents).unwrap(); - // Verify structure assert_eq!(did_doc["@context"].as_array().unwrap().len(), 3); assert_eq!(did_doc["id"], did_id); assert!(did_doc["verificationMethod"].is_array()); assert!(did_doc["keyAgreement"].is_array()); - // Verify keyAgreement has PQ key with JsonWebKey2020 type let ka = did_doc["keyAgreement"].as_array().unwrap(); - let pq_key = ka + assert_eq!(ka.len(), 2); + + let identity_dh = ka .iter() - .find(|k| k["publicKeyJwk"]["kty"] == "PQ") + .find(|k| k["publicKeyJwk"]["kid"] == "identity-dh") + .unwrap(); + assert_eq!(identity_dh["type"], "X25519KeyAgreementKey2020"); + assert_eq!(identity_dh["publicKeyJwk"]["crv"], "X25519"); + + let signed_prekey = ka + .iter() + .find(|k| k["publicKeyJwk"]["kid"] == "signed-prekey") + .unwrap(); + assert_eq!(signed_prekey["type"], "X25519KeyAgreementKey2020"); + assert_eq!(signed_prekey["publicKeyJwk"]["crv"], "X25519"); + + // Ensure keys are valid length + let identity_dh_bytes = URL_SAFE_NO_PAD + .decode(identity_dh["publicKeyJwk"]["x"].as_str().unwrap()) .unwrap(); - assert_eq!(pq_key["type"], "JsonWebKey2020"); - assert_eq!(pq_key["publicKeyJwk"]["crv"], "Kyber1024"); + assert_eq!(identity_dh_bytes.len(), 32); } diff --git a/protocol/tests/pqxdh_keys_test.rs b/protocol/tests/pqxdh_keys_test.rs deleted file mode 100644 index e369b5f..0000000 --- a/protocol/tests/pqxdh_keys_test.rs +++ /dev/null @@ -1,497 +0,0 @@ -//! Tests for PQXDH 3-Key Structure (no one-time prekeys) -//! - IK (Identity Key) - Ed25519 -//! - SPK (Signed EC Prekey) - X25519 -//! - PQSPK (PQ Last-Resort Prekey) - Kyber1024 - -use libsignal_protocol::*; -use rand::SeedableRng; -use syft_crypto_protocol::SyftPublicKeyBundle; - -/// Test 1: Generate the 3 keys required for PQXDH -/// -/// Keys generated: -/// 1. IK (Identity Key) - Long-term, never rotated -/// 2. SPK (Signed Prekey) - Medium-term, rotated weekly/monthly -/// 3. PQSPK (PQ Signed Prekey) - Medium-term, rotated weekly/monthly -#[test] -fn test_generate_3_keys() -> Result<(), SignalProtocolError> { - println!("🔐 Test: Generate 3-Key PQXDH Bundle"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // === 1. Identity Key (IK) - Ed25519 === - println!("\n📝 Generating Key 1/3: Identity Key (IK)"); - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - - // Verify key properties - let identity_public = identity_key_pair.public_key(); - assert_eq!(identity_public.serialize().len(), 33); // 32 bytes + type byte - println!(" ✅ Identity key generated"); - println!( - " 📊 Public key size: {} bytes", - identity_public.serialize().len() - ); - println!(" 🔑 Type: Ed25519 (for signing)"); - - // === 2. Signed EC Prekey (SPK) - X25519 === - println!("\n📝 Generating Key 2/3: Signed EC Prekey (SPK)"); - let signed_prekey_pair = KeyPair::generate(&mut rng); - - // Sign the prekey with identity key - let signed_pre_key_signature = identity_key_pair - .private_key() - .calculate_signature(&signed_prekey_pair.public_key.serialize(), &mut rng)?; - - // Verify signature properties - assert_eq!(signed_pre_key_signature.len(), 64); // Ed25519 signature is 64 bytes - assert_eq!(signed_prekey_pair.public_key.serialize().len(), 33); - println!(" ✅ Signed prekey generated"); - println!( - " 📊 Public key size: {} bytes", - signed_prekey_pair.public_key.serialize().len() - ); - println!( - " 📊 Signature size: {} bytes", - signed_pre_key_signature.len() - ); - println!(" 🔑 Type: X25519 (for DH)"); - println!(" 🔏 Signed by: Identity key"); - - // === 3. PQ Last-Resort Prekey (PQSPK) - Kyber1024 === - println!("\n📝 Generating Key 3/3: PQ Last-Resort Prekey (PQSPK)"); - let pq_prekey_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - // Sign the PQ prekey with identity key - let pq_signature = identity_key_pair - .private_key() - .calculate_signature(&pq_prekey_pair.public_key.serialize(), &mut rng)?; - - // Verify PQ key properties - assert_eq!(pq_signature.len(), 64); - // Kyber1024 public key: 1568 bytes + 1 byte type identifier - assert_eq!(pq_prekey_pair.public_key.serialize().len(), 1569); - // Kyber1024 secret key: 3168 bytes + 1 byte type identifier - assert_eq!(pq_prekey_pair.secret_key.serialize().len(), 3169); - println!(" ✅ PQ last-resort prekey generated"); - println!( - " 📊 Public key size: {} bytes", - pq_prekey_pair.public_key.serialize().len() - ); - println!( - " 📊 Secret key size: {} bytes", - pq_prekey_pair.secret_key.serialize().len() - ); - println!(" 📊 Signature size: {} bytes", pq_signature.len()); - println!(" 🔑 Type: Kyber1024 (for KEM)"); - println!(" 🔏 Signed by: Identity key"); - - // === Summary === - println!(); - println!("{}", "=".repeat(60)); - println!("🎯 3-Key Bundle Generated Successfully!"); - println!("{}", "=".repeat(60)); - println!("Key Structure:"); - println!( - " 1. IK (Identity) : {} bytes public", - identity_public.serialize().len() - ); - println!( - " 2. SPK (Signed Prekey) : {} bytes public", - signed_prekey_pair.public_key.serialize().len() - ); - println!( - " 3. PQSPK (PQ Prekey) : {} bytes public", - pq_prekey_pair.public_key.serialize().len() - ); - println!( - "\nTotal public key bundle size: {} bytes", - identity_public.serialize().len() - + signed_prekey_pair.public_key.serialize().len() - + pq_prekey_pair.public_key.serialize().len() - + signed_pre_key_signature.len() - + pq_signature.len() - ); - println!("{}", "=".repeat(60)); - - Ok(()) -} - -/// Test 2: Verify signature on Signed EC Prekey (SPK) -#[test] -fn test_verify_signed_prekey_signature() -> Result<(), SignalProtocolError> { - println!("🔐 Test: Verify Signed Prekey Signature"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([2u8; 32]); - - // Generate identity key and signed prekey - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_prekey_pair = KeyPair::generate(&mut rng); - - // Sign the prekey - let signature = identity_key_pair - .private_key() - .calculate_signature(&signed_prekey_pair.public_key.serialize(), &mut rng)?; - - println!("✅ Signature created: {} bytes", signature.len()); - - // Verify the signature - let verification_result = identity_key_pair - .public_key() - .verify_signature(&signed_prekey_pair.public_key.serialize(), &signature); - - assert!(verification_result, "Signature verification should succeed"); - println!("✅ Signature verified successfully"); - - // Test: Signature should fail with wrong data - println!("\n🔍 Testing signature with wrong data..."); - let wrong_data = b"wrong_data"; - let wrong_verification = identity_key_pair - .public_key() - .verify_signature(wrong_data, &signature); - - assert!(!wrong_verification, "Signature should fail with wrong data"); - println!("✅ Signature correctly rejected for wrong data"); - - println!("{}", "=".repeat(60)); - println!("🎯 Signed Prekey Signature Verification: PASSED"); - println!("{}", "=".repeat(60)); - - Ok(()) -} - -/// Test 3: Verify signature on PQ Last-Resort Prekey (PQSPK) -#[test] -fn test_verify_pq_prekey_signature() -> Result<(), SignalProtocolError> { - println!("🔐 Test: Verify PQ Prekey Signature"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([3u8; 32]); - - // Generate identity key and PQ prekey - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let pq_prekey_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - // Sign the PQ prekey - let pq_signature = identity_key_pair - .private_key() - .calculate_signature(&pq_prekey_pair.public_key.serialize(), &mut rng)?; - - println!("✅ PQ signature created: {} bytes", pq_signature.len()); - - // Verify the signature - let verification_result = identity_key_pair - .public_key() - .verify_signature(&pq_prekey_pair.public_key.serialize(), &pq_signature); - - assert!( - verification_result, - "PQ signature verification should succeed" - ); - println!("✅ PQ signature verified successfully"); - - // Test: Signature should fail with wrong key - println!("\n🔍 Testing signature with wrong PQ key..."); - let wrong_pq_key = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - let wrong_verification = identity_key_pair - .public_key() - .verify_signature(&wrong_pq_key.public_key.serialize(), &pq_signature); - - assert!(!wrong_verification, "Signature should fail with wrong key"); - println!("✅ Signature correctly rejected for wrong key"); - - println!("{}", "=".repeat(60)); - println!("🎯 PQ Prekey Signature Verification: PASSED"); - println!("{}", "=".repeat(60)); - - Ok(()) -} - -/// Test 4: Simulate key bundle publication (what gets stored in DID document) -#[test] -fn test_key_bundle_serialization() -> Result<(), SignalProtocolError> { - println!("🔐 Test: Key Bundle Serialization (DID Document)"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([4u8; 32]); - - // Generate all 3 keys - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_prekey_pair = KeyPair::generate(&mut rng); - let pq_prekey_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - // Create PublicKeyBundle (automatically signs both prekeys) - println!("\n📝 Creating PublicKeyBundle..."); - let bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_prekey_pair, - &pq_prekey_pair, - &mut rng, - )?; - println!(" ✅ Bundle created with both signatures"); - - // Serialize public components (what goes into DID document) - let identity_public_bytes = bundle.signal_identity_public_key.serialize(); - let spk_public_bytes = bundle.signal_signed_public_pre_key.serialize(); - let pqspk_public_bytes = bundle.signal_pq_public_pre_key.serialize(); - let spk_signature = &bundle.signal_signed_pre_key_signature; - let pqspk_signature = &bundle.signal_pq_pre_key_signature; - - println!("\n📦 Public Key Bundle (for DID document):"); - println!(" IK (Identity Key):"); - println!(" - Public key: {} bytes", identity_public_bytes.len()); - println!( - " - Hex (first 16 bytes): {}", - hex::encode(&identity_public_bytes[..16.min(identity_public_bytes.len())]) - ); - - println!("\n SPK (Signed EC Prekey):"); - println!(" - Public key: {} bytes", spk_public_bytes.len()); - println!(" - Signature: {} bytes", spk_signature.len()); - println!( - " - Key hex (first 16 bytes): {}", - hex::encode(&spk_public_bytes[..16.min(spk_public_bytes.len())]) - ); - println!( - " - Sig hex (first 16 bytes): {}", - hex::encode(&spk_signature[..16.min(spk_signature.len())]) - ); - - println!("\n PQSPK (PQ Last-Resort Prekey):"); - println!(" - Public key: {} bytes", pqspk_public_bytes.len()); - println!(" - Signature: {} bytes", pqspk_signature.len()); - println!( - " - Key hex (first 16 bytes): {}", - hex::encode(&pqspk_public_bytes[..16.min(pqspk_public_bytes.len())]) - ); - println!( - " - Sig hex (first 16 bytes): {}", - hex::encode(&pqspk_signature[..16.min(pqspk_signature.len())]) - ); - - let total_bundle_size = identity_public_bytes.len() - + spk_public_bytes.len() - + spk_signature.len() - + pqspk_public_bytes.len() - + pqspk_signature.len(); - - println!( - "\n📊 Total bundle size: {} bytes ({:.2} KB)", - total_bundle_size, - total_bundle_size as f64 / 1024.0 - ); - - // Verify we can deserialize keys back - println!("\n🔄 Testing deserialization..."); - let _identity_restored = IdentityKey::decode(&identity_public_bytes)?; - let _spk_restored = PublicKey::deserialize(&spk_public_bytes)?; - let _pqspk_restored = kem::PublicKey::deserialize(&pqspk_public_bytes)?; - - println!(" ✅ Identity key deserialized"); - println!(" ✅ Signed prekey deserialized"); - println!(" ✅ PQ prekey deserialized"); - - // Verify signatures (using original identity key pair) - let sig1_ok = identity_key_pair - .public_key() - .verify_signature(&spk_public_bytes, spk_signature); - let sig2_ok = identity_key_pair - .public_key() - .verify_signature(&pqspk_public_bytes, pqspk_signature); - assert!( - sig1_ok && sig2_ok, - "Signatures should verify after deserialization" - ); - println!(" ✅ All signatures verified"); - - println!(); - println!("{}", "=".repeat(60)); - println!("🎯 Key Bundle Serialization: PASSED"); - println!(" Can be safely stored in DID document (JSON format)"); - println!("{}", "=".repeat(60)); - - Ok(()) -} - -/// Test 5: Simulate key rotation (SPK and PQSPK rotation) -#[test] -fn test_key_rotation() -> Result<(), SignalProtocolError> { - println!("🔐 Test: Key Rotation (SPK and PQSPK)"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([5u8; 32]); - - // Generate initial keys - println!("\n📝 Step 1: Generate initial key bundle"); - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let old_spk = KeyPair::generate(&mut rng); - let old_pqspk = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - println!(" ✅ Initial keys generated"); - - // Simulate time passing (weekly/monthly rotation) - println!("\n⏰ Step 2: Time passes... (weekly/monthly rotation period)"); - - // Generate new prekeys (identity key stays the same!) - println!("\n🔄 Step 3: Rotate prekeys (identity key unchanged)"); - let new_spk = KeyPair::generate(&mut rng); - let new_pqspk = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - // Sign new prekeys with SAME identity key - let new_spk_sig = identity_key_pair - .private_key() - .calculate_signature(&new_spk.public_key.serialize(), &mut rng)?; - - let new_pqspk_sig = identity_key_pair - .private_key() - .calculate_signature(&new_pqspk.public_key.serialize(), &mut rng)?; - - println!(" ✅ New SPK generated and signed"); - println!(" ✅ New PQSPK generated and signed"); - println!(" ️✅ Identity key remains unchanged"); - - // Verify new keys are different - assert_ne!( - old_spk.public_key.serialize(), - new_spk.public_key.serialize(), - "SPK should change after rotation" - ); - assert_ne!( - old_pqspk.public_key.serialize(), - new_pqspk.public_key.serialize(), - "PQSPK should change after rotation" - ); - println!(" ✅ New keys are different from old keys"); - - // Verify signatures are valid - let sig1_ok = identity_key_pair - .public_key() - .verify_signature(&new_spk.public_key.serialize(), &new_spk_sig); - let sig2_ok = identity_key_pair - .public_key() - .verify_signature(&new_pqspk.public_key.serialize(), &new_pqspk_sig); - assert!(sig1_ok && sig2_ok, "New key signatures should be valid"); - println!(" ✅ New key signatures verified"); - - // Simulate grace period (old keys kept temporarily for delayed messages) - println!("\n⏳ Step 4: Grace period (old keys kept temporarily)"); - println!(" - New keys published to DID document"); - println!(" - Old private keys kept locally for 24-48 hours"); - println!(" - Allows decryption of messages sent before rotation"); - - println!("\n🗑️ Step 5: After grace period"); - println!(" - Old private keys deleted (forward secrecy)"); - println!(" - Only new keys remain"); - - println!(); - println!("{}", "=".repeat(60)); - println!("🎯 Key Rotation: PASSED"); - println!(" Identity key: Never rotated ✅"); - println!(" SPK: Rotated successfully ✅"); - println!(" PQSPK: Rotated successfully ✅"); - println!("{}", "=".repeat(60)); - - Ok(()) -} - -/// Test 6: Compare key sizes between different components -#[test] -fn test_key_size_comparison() { - println!("🔐 Test: Key Size Comparison"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([6u8; 32]); - - // Generate all keys - let ik = IdentityKeyPair::generate(&mut rng); - let spk = KeyPair::generate(&mut rng); - let pqspk = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - println!("\n📊 Key Size Breakdown:"); - println!("\n1. Identity Key (Ed25519):"); - println!(" Public: {} bytes", ik.public_key().serialize().len()); - println!(" Private: {} bytes (estimated)", 32); // Ed25519 private key - - println!("\n2. Signed EC Prekey (X25519):"); - println!(" Public: {} bytes", spk.public_key.serialize().len()); - println!(" Private: {} bytes (estimated)", 32); // X25519 private key - - println!("\n3. PQ Last-Resort Prekey (Kyber1024):"); - println!( - " Public: {} bytes ({:.2} KB)", - pqspk.public_key.serialize().len(), - pqspk.public_key.serialize().len() as f64 / 1024.0 - ); - println!( - " Private: {} bytes ({:.2} KB)", - pqspk.secret_key.serialize().len(), - pqspk.secret_key.serialize().len() as f64 / 1024.0 - ); - - let total_public_size = ik.public_key().serialize().len() - + spk.public_key.serialize().len() - + pqspk.public_key.serialize().len(); - - let total_private_size_estimated = 32 + 32 + pqspk.secret_key.serialize().len(); - - println!("\n📦 Total Size:"); - println!( - " All public keys: {} bytes ({:.2} KB)", - total_public_size, - total_public_size as f64 / 1024.0 - ); - println!( - " All private keys: ~{} bytes ({:.2} KB) [estimated]", - total_private_size_estimated, - total_private_size_estimated as f64 / 1024.0 - ); - - println!("\n💡 Observations:"); - println!(" - Classical keys (IK, SPK) are tiny (~33 bytes each)"); - println!(" - PQ key (PQSPK) is much larger (~1.5 KB public, ~3.1 KB private)"); - println!(" - Trade-off: Larger keys for quantum resistance"); - - println!(); - println!("{}", "=".repeat(60)); - println!("🎯 Key Size Analysis: COMPLETE"); - println!("{}", "=".repeat(60)); -} - -/// Test 7: Verify no one-time prekeys are used -#[test] -fn test_no_one_time_prekeys() { - println!("🔐 Test: Verify No One-Time Prekeys"); - println!("{}", "=".repeat(60)); - - println!("\n✅ Design Decision: Skip One-Time Prekeys"); - println!("\n📝 What we DON'T have:"); - println!(" ❌ One-time EC prekeys (OPK)"); - println!(" ❌ One-time PQ prekeys (PQOPK)"); - - println!("\n📝 What we DO have:"); - println!(" ✅ Identity Key (IK) - never rotated"); - println!(" ✅ Signed EC Prekey (SPK) - rotated periodically"); - println!(" ✅ PQ Last-Resort Prekey (PQSPK) - rotated periodically"); - - println!("\n🎯 Rationale:"); - println!(" 1. SyftBox uses eventual consistency (not atomic operations)"); - println!(" 2. One-time keys require atomic allocation to avoid race conditions"); - println!(" 3. Race conditions would cause key reuse → security failure"); - println!(" 4. Simplified key management is more reliable for file-based sync"); - - println!("\n📊 Security Impact:"); - println!(" ✅ Still post-quantum secure (Kyber1024)"); - println!(" ✅ Still forward secrecy (ephemeral keys in each session)"); - println!(" ✅ Still authenticated (identity key signatures)"); - println!(" ⚠️ Slightly reduced forward secrecy vs one-time keys (acceptable)"); - - println!("\n💡 Trade-off:"); - println!(" Simpler + More Reliable > Perfect Forward Secrecy"); - - println!(); - println!("{}", "=".repeat(60)); - println!("🎯 No One-Time Prekeys: DESIGN VERIFIED"); - println!("{}", "=".repeat(60)); - - // This test always passes - it's documentation -} diff --git a/protocol/tests/pqxdh_security_test.rs b/protocol/tests/pqxdh_security_test.rs deleted file mode 100644 index 0d6d190..0000000 --- a/protocol/tests/pqxdh_security_test.rs +++ /dev/null @@ -1,411 +0,0 @@ -/// Security Tests for PQXDH Implementation -/// -/// These tests verify that the implementation correctly rejects invalid inputs, -/// corrupted data, and potential attack vectors. Inspired by libsignal's security tests. -use libsignal_protocol::*; -use rand::SeedableRng; -use syft_crypto_protocol::SyftPublicKeyBundle; - -/// Test that PublicKeyBundle rejects corrupted EC prekey signature -#[test] -fn test_reject_corrupted_ec_signature() { - println!("🔐 Security Test: Reject Corrupted EC Signature"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // Create valid bundle - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Failed to create bundle"); - - println!("\n✅ Valid bundle created"); - assert!( - bundle.verify_signatures(), - "Original bundle should have valid signatures" - ); - println!("✅ Original signatures verified"); - - // Corrupt the EC signature - println!("\n🔧 Corrupting EC signature (flipping first byte)..."); - bundle.signal_signed_pre_key_signature[0] ^= 0xFF; - - // Verify rejection - println!("🔍 Verifying corrupted bundle..."); - assert!( - !bundle.verify_signatures(), - "Corrupted EC signature should be rejected" - ); - println!("✅ Corrupted EC signature correctly rejected"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: EC signature corruption detected"); - println!("{}", "=".repeat(60)); -} - -/// Test that PublicKeyBundle rejects corrupted PQ prekey signature -#[test] -fn test_reject_corrupted_pq_signature() { - println!("🔐 Security Test: Reject Corrupted PQ Signature"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([2u8; 32]); - - // Create valid bundle - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Failed to create bundle"); - - println!("\n✅ Valid bundle created"); - assert!( - bundle.verify_signatures(), - "Original bundle should have valid signatures" - ); - println!("✅ Original signatures verified"); - - // Corrupt the PQ signature - println!("\n🔧 Corrupting PQ signature (flipping last byte)..."); - let last_idx = bundle.signal_pq_pre_key_signature.len() - 1; - bundle.signal_pq_pre_key_signature[last_idx] ^= 0xFF; - - // Verify rejection - println!("🔍 Verifying corrupted bundle..."); - assert!( - !bundle.verify_signatures(), - "Corrupted PQ signature should be rejected" - ); - println!("✅ Corrupted PQ signature correctly rejected"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: PQ signature corruption detected"); - println!("{}", "=".repeat(60)); -} - -/// Test that signatures from wrong identity key are rejected -#[test] -fn test_reject_wrong_identity_key_signature() { - println!("🔐 Security Test: Reject Wrong Identity Key Signature"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([3u8; 32]); - - // Alice creates her keys - println!("\n📝 Alice generates her keys..."); - let alice_identity = IdentityKeyPair::generate(&mut rng); - let alice_signed_prekey = KeyPair::generate(&mut rng); - let alice_pq_prekey = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - println!(" ✅ Alice's keys generated"); - - // Bob (attacker) creates his identity key - println!("\n📝 Bob (attacker) generates his identity key..."); - let bob_identity = IdentityKeyPair::generate(&mut rng); - println!(" ✅ Bob's identity key generated"); - - // Bob tries to sign Alice's prekeys with his identity key - println!("\n🔧 Bob attempts to sign Alice's prekeys with his identity key..."); - let fake_ec_sig = bob_identity - .private_key() - .calculate_signature(&alice_signed_prekey.public_key.serialize(), &mut rng) - .unwrap(); - - let fake_pq_sig = bob_identity - .private_key() - .calculate_signature(&alice_pq_prekey.public_key.serialize(), &mut rng) - .unwrap(); - println!(" ✅ Fake signatures created"); - - // Create bundle with Alice's keys but Bob's signatures - println!("\n📦 Creating malicious bundle (Alice's keys + Bob's signatures)..."); - let malicious_bundle = SyftPublicKeyBundle { - signal_identity_public_key: *bob_identity.identity_key(), // Bob's identity - signal_signed_public_pre_key: alice_signed_prekey.public_key, - signal_signed_pre_key_signature: fake_ec_sig, - signal_pq_public_pre_key: alice_pq_prekey.public_key.clone(), - signal_pq_pre_key_signature: fake_pq_sig, - }; - - // The signatures ARE valid (Bob signed Alice's keys correctly) - println!("\n🔍 Verifying malicious bundle..."); - assert!( - malicious_bundle.verify_signatures(), - "Signatures are technically valid (Bob signed Alice's keys)" - ); - println!("⚠️ Signatures are technically valid BUT..."); - - // However, the identity key doesn't match Alice's - println!("\n🔍 Checking if identity matches Alice's..."); - assert_ne!( - malicious_bundle.signal_identity_public_key.serialize(), - alice_identity.identity_key().serialize(), - "Identity keys should NOT match" - ); - println!("✅ Identity key mismatch detected!"); - println!(" This bundle claims to be from Bob, not Alice"); - - println!(" When receiving a bundle, we must:"); - println!(" 1. Verify signatures ✓"); - println!(" 2. Verify identity key matches expected sender ✓"); - println!(" 3. Check identity key against trusted source (DID document) ✓"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: Identity key validation is essential"); - println!("{}", "=".repeat(60)); -} - -/// Test that swapped prekeys are detected -#[test] -fn test_reject_swapped_prekeys() { - println!("🔐 Security Test: Detect Swapped Prekeys"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([4u8; 32]); - - // Generate two sets of keys - println!("\n📝 Generating two separate key sets..."); - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - - let signed_prekey_1 = KeyPair::generate(&mut rng); - let signed_prekey_2 = KeyPair::generate(&mut rng); - - let pq_prekey_1 = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - let pq_prekey_2 = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - println!(" ✅ Two key sets generated"); - - // Create bundle with key set 1 - let bundle_1 = - SyftPublicKeyBundle::new(&identity_key_pair, &signed_prekey_1, &pq_prekey_1, &mut rng) - .unwrap(); - println!("\n✅ Bundle 1 created (keys from set 1)"); - - // Create malicious bundle: signatures from bundle 1, but keys from set 2 - println!("\n🔧 Creating malicious bundle (signatures for set 1, but keys from set 2)..."); - let malicious_bundle = SyftPublicKeyBundle { - signal_identity_public_key: bundle_1.signal_identity_public_key, - signal_signed_public_pre_key: signed_prekey_2.public_key, // Swapped! - signal_signed_pre_key_signature: bundle_1.signal_signed_pre_key_signature.clone(), - signal_pq_public_pre_key: pq_prekey_2.public_key.clone(), // Swapped! - signal_pq_pre_key_signature: bundle_1.signal_pq_pre_key_signature.clone(), - }; - - // Verify rejection - println!("\n🔍 Verifying malicious bundle..."); - assert!( - !malicious_bundle.verify_signatures(), - "Swapped keys should fail signature verification" - ); - println!("✅ Swapped keys correctly rejected"); - - println!("\n💡 Security Lesson:"); - println!(" Signatures are bound to specific keys"); - println!(" Cannot reuse signatures with different keys"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: Key swapping detected"); - println!("{}", "=".repeat(60)); -} - -/// Test that empty/zero signatures are rejected -#[test] -fn test_reject_zero_signature() { - println!("🔐 Security Test: Reject Zero/Empty Signatures"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([5u8; 32]); - - // Create valid bundle - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .unwrap(); - - println!("\n✅ Valid bundle created"); - - // Replace EC signature with zeros - println!("\n🔧 Replacing EC signature with zeros..."); - bundle.signal_signed_pre_key_signature = vec![0u8; 64].into_boxed_slice(); - - println!("🔍 Verifying bundle with zero signature..."); - assert!( - !bundle.verify_signatures(), - "Zero signature should be rejected" - ); - println!("✅ Zero EC signature correctly rejected"); - - // Restore EC signature, zero out PQ signature - println!("\n🔧 Replacing PQ signature with zeros..."); - let valid_bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .unwrap(); - - let bundle2 = SyftPublicKeyBundle { - signal_identity_public_key: valid_bundle.signal_identity_public_key, - signal_signed_public_pre_key: valid_bundle.signal_signed_public_pre_key, - signal_signed_pre_key_signature: valid_bundle.signal_signed_pre_key_signature, - signal_pq_public_pre_key: valid_bundle.signal_pq_public_pre_key, - signal_pq_pre_key_signature: vec![0u8; 64].into_boxed_slice(), - }; - - println!("🔍 Verifying bundle with zero PQ signature..."); - assert!( - !bundle2.verify_signatures(), - "Zero PQ signature should be rejected" - ); - println!("✅ Zero PQ signature correctly rejected"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: Zero signatures rejected"); - println!("{}", "=".repeat(60)); -} - -/// Test that bundle size is reasonable (not a DoS vector) -#[test] -fn test_bundle_size_reasonable() { - println!("🔐 Security Test: Bundle Size is Reasonable"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([6u8; 32]); - - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .unwrap(); - - let size = bundle.total_size(); - println!( - "\n📊 Bundle size: {} bytes ({:.2} KB)", - size, - size as f64 / 1024.0 - ); - - // Check upper bound (prevent DoS via huge bundles) - println!("\n🔍 Checking upper bound..."); - assert!( - size < 2000, - "Bundle should be less than 2KB (DoS prevention)" - ); - println!("✅ Size is under 2KB"); - - // Check lower bound (ensure all components present) - println!("\n🔍 Checking lower bound..."); - assert!( - size > 1600, - "Bundle should be at least 1.6KB (all components present)" - ); - println!("✅ Size indicates all components are present"); - - // Component breakdown - println!("\n📊 Component sizes:"); - println!( - " Identity key: {} bytes", - bundle.signal_identity_public_key.serialize().len() - ); - println!( - " Signed prekey: {} bytes", - bundle.signal_signed_public_pre_key.serialize().len() - ); - println!( - " SPK signature: {} bytes", - bundle.signal_signed_pre_key_signature.len() - ); - println!( - " PQ prekey: {} bytes", - bundle.signal_pq_public_pre_key.serialize().len() - ); - println!( - " PQSPK signature: {} bytes", - bundle.signal_pq_pre_key_signature.len() - ); - - println!("\n💡 Security Notes:"); - println!(" - Bundle size is deterministic (no variable length attacks)"); - println!(" - Small enough for network transmission (<2KB)"); - println!(" - Large enough to indicate no missing components (>1.6KB)"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: Bundle size is reasonable"); - println!("{}", "=".repeat(60)); -} - -/// Test that signature verification is constant-time resistant -/// (This is a behavioral test, not a timing test) -#[test] -fn test_signature_verification_consistency() { - println!("🔐 Security Test: Signature Verification Consistency"); - println!("{}", "=".repeat(60)); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([7u8; 32]); - - let identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle = SyftPublicKeyBundle::new( - &identity_key_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .unwrap(); - - println!("\n🔍 Testing multiple verifications produce consistent results..."); - - // Verify 100 times - should always return true - for i in 0..100 { - let result = bundle.verify_signatures(); - assert!(result, "Verification {} should succeed", i); - } - println!("✅ 100 valid verifications: all returned true"); - - // Corrupt and verify 100 times - should always return false - let mut corrupted = bundle.clone(); - corrupted.signal_signed_pre_key_signature[0] ^= 0xFF; - - println!("\n🔍 Testing corrupted bundle consistency..."); - for i in 0..100 { - let result = corrupted.verify_signatures(); - assert!(!result, "Corrupted verification {} should fail", i); - } - println!("✅ 100 corrupted verifications: all returned false"); - - println!("\n💡 Security Note:"); - println!(" Verification results are deterministic and consistent"); - println!(" This is important for cache-timing attack resistance"); - - println!("\n{}", "=".repeat(60)); - println!("🎯 Security Test PASSED: Verification is consistent"); - println!("{}", "=".repeat(60)); -} diff --git a/protocol/tests/pqxdh_test.rs b/protocol/tests/pqxdh_test.rs deleted file mode 100644 index d1aba20..0000000 --- a/protocol/tests/pqxdh_test.rs +++ /dev/null @@ -1,343 +0,0 @@ -use libsignal_protocol::*; -use rand::SeedableRng; - -/// Test PQXDH key component generation -/// -/// PQXDH extends X3DH with post-quantum cryptography: -/// - Classical components: Identity keys (IK), Signed prekeys (SPK), One-time prekeys (OPK) -/// - Post-quantum components: KEM identity keys (IK_KEM), KEM prekeys (SPK_KEM), KEM one-time prekeys (OPK_KEM) -/// - Hybrid approach combines classical ECDH with post-quantum KEM for quantum resistance -/// -/// Note: This is a placeholder implementation. Full PQXDH requires: -/// - Kyber KEM (NIST ML-KEM) or similar post-quantum KEM -/// - Hybrid key derivation combining classical and PQ secrets -#[test] -fn test_pqxdh_key_generation() -> Result<(), SignalProtocolError> { - println!("🔐⚛️ Testing PQXDH Key Generation Components"); - println!("🔑 Note: Using classical keys as placeholder for post-quantum components"); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // === Classical cryptographic components (same as X3DH) === - - // Generate identity key pairs (long-term keys) - let _alice_identity = IdentityKeyPair::generate(&mut rng); - let bob_identity = IdentityKeyPair::generate(&mut rng); - println!("✅ Classical identity keys (IK_A, IK_B) generated"); - - // Generate signed prekey pair - let bob_signed_prekey_pair = KeyPair::generate(&mut rng); - let bob_signed_prekey_signature = bob_identity - .private_key() - .calculate_signature(&bob_signed_prekey_pair.public_key.serialize(), &mut rng)?; - - assert_eq!(bob_signed_prekey_signature.len(), 64); - println!(" Classical signed prekey (SPK_B) with signature generated"); - - // Generate one-time prekey - let _bob_one_time_prekey = KeyPair::generate(&mut rng); - println!(" Classical one-time prekey (OPK_B) generated"); - - // === Post-quantum KEM components (placeholder) === - - // TODO: Replace with actual Kyber/ML-KEM implementation - // For now, using classical keys as placeholders to demonstrate structure - - // KEM identity keys - let _alice_kem_identity = KeyPair::generate(&mut rng); - let _bob_kem_identity = KeyPair::generate(&mut rng); - println!("Post-quantum KEM identity keys (IK_KEM_A, IK_KEM_B) generated [PLACEHOLDER]"); - - // KEM signed prekey - let _bob_kem_signed_prekey = KeyPair::generate(&mut rng); - println!("Post-quantum KEM signed prekey (SPK_KEM_B) generated [PLACEHOLDER]"); - - // KEM one-time prekey - let _bob_kem_one_time_prekey = KeyPair::generate(&mut rng); - println!("Post-quantum KEM one-time prekey (OPK_KEM_B) generated [PLACEHOLDER]"); - - println!("All PQXDH cryptographic components created successfully!"); - println!(" Classical components: IK, SPK, OPK (ECDH-based)"); - println!(" PQ components: IK_KEM, SPK_KEM, OPK_KEM (KEM-based, placeholder)"); - - Ok(()) -} - -/// Test PQXDH key agreement protocol -/// -/// PQXDH performs both classical and post-quantum key agreements: -/// -/// Classical DH operations (same as X3DH): -/// - DH1 = DH(IK_A, SPK_B) -/// - DH2 = DH(EK_A, IK_B) -/// - DH3 = DH(EK_A, SPK_B) -/// - DH4 = DH(EK_A, OPK_B) [if one-time prekey available] -/// -/// Post-quantum KEM operations: -/// - KEM1 = Encap(IK_KEM_B) -> (ciphertext1, shared_secret1) -/// - KEM2 = Encap(SPK_KEM_B) -> (ciphertext2, shared_secret2) -/// - KEM3 = Encap(OPK_KEM_B) -> (ciphertext3, shared_secret3) [if available] -/// -/// Final shared secret: -/// - SK = KDF(DH1 || DH2 || DH3 || DH4 || KEM1 || KEM2 || KEM3) -/// -/// This provides security even if quantum computers break ECDH in the future -#[test] -fn test_pqxdh_key_agreement() -> Result<(), SignalProtocolError> { - println!("= Testing PQXDH Key Agreement Protocol"); - println!("� Note: Using classical DH as placeholder for post-quantum KEM"); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // === Generate Alice's keys === - let alice_identity = IdentityKeyPair::generate(&mut rng); - let alice_ephemeral = KeyPair::generate(&mut rng); - let alice_kem_identity = KeyPair::generate(&mut rng); // Placeholder for KEM - println!(" Alice's keys generated (IK_A, EK_A, IK_KEM_A)"); - - // === Generate Bob's keys === - let bob_identity = IdentityKeyPair::generate(&mut rng); - let bob_signed_prekey = KeyPair::generate(&mut rng); - let bob_one_time_prekey = KeyPair::generate(&mut rng); - let bob_kem_identity = KeyPair::generate(&mut rng); // Placeholder - let bob_kem_signed_prekey = KeyPair::generate(&mut rng); // Placeholder - let bob_kem_one_time_prekey = KeyPair::generate(&mut rng); // Placeholder - println!(" Bob's keys generated (IK_B, SPK_B, OPK_B, IK_KEM_B, SPK_KEM_B, OPK_KEM_B)"); - - // === Classical DH operations (Alice's side) === - - println!("\n=� Classical DH operations:"); - - // DH1 = DH(IK_A, SPK_B) - let dh1_alice = alice_identity - .private_key() - .calculate_agreement(&bob_signed_prekey.public_key)?; - - // DH2 = DH(EK_A, IK_B) - let dh2_alice = alice_ephemeral - .private_key - .calculate_agreement(bob_identity.public_key())?; - - // DH3 = DH(EK_A, SPK_B) - let dh3_alice = alice_ephemeral - .private_key - .calculate_agreement(&bob_signed_prekey.public_key)?; - - // DH4 = DH(EK_A, OPK_B) - let dh4_alice = alice_ephemeral - .private_key - .calculate_agreement(&bob_one_time_prekey.public_key)?; - - println!("  Alice completed 4 classical DH operations"); - - // === Classical DH operations (Bob's side) === - - let dh1_bob = bob_signed_prekey - .private_key - .calculate_agreement(alice_identity.public_key())?; - - let dh2_bob = bob_identity - .private_key() - .calculate_agreement(&alice_ephemeral.public_key)?; - - let dh3_bob = bob_signed_prekey - .private_key - .calculate_agreement(&alice_ephemeral.public_key)?; - - let dh4_bob = bob_one_time_prekey - .private_key - .calculate_agreement(&alice_ephemeral.public_key)?; - - println!("  Bob completed 4 classical DH operations"); - - // Verify DH outputs match - assert_eq!(dh1_alice, dh1_bob, "DH1 outputs must match"); - assert_eq!(dh2_alice, dh2_bob, "DH2 outputs must match"); - assert_eq!(dh3_alice, dh3_bob, "DH3 outputs must match"); - assert_eq!(dh4_alice, dh4_bob, "DH4 outputs must match"); - println!("  All classical DH outputs verified"); - - // === Post-quantum KEM operations (placeholder using DH) === - - println!("\n=� Post-quantum KEM operations (placeholder):"); - - // TODO: Replace with actual KEM operations - // Real KEM: (ciphertext, shared_secret) = Encapsulate(public_key) - // shared_secret = Decapsulate(ciphertext, private_key) - - // KEM1: Alice encapsulates to Bob's KEM identity key - let kem1_alice = alice_kem_identity - .private_key - .calculate_agreement(&bob_kem_identity.public_key)?; - let kem1_bob = bob_kem_identity - .private_key - .calculate_agreement(&alice_kem_identity.public_key)?; - - // KEM2: Alice encapsulates to Bob's KEM signed prekey - let kem2_alice = alice_kem_identity - .private_key - .calculate_agreement(&bob_kem_signed_prekey.public_key)?; - let kem2_bob = bob_kem_signed_prekey - .private_key - .calculate_agreement(&alice_kem_identity.public_key)?; - - // KEM3: Alice encapsulates to Bob's KEM one-time prekey - let kem3_alice = alice_kem_identity - .private_key - .calculate_agreement(&bob_kem_one_time_prekey.public_key)?; - let kem3_bob = bob_kem_one_time_prekey - .private_key - .calculate_agreement(&alice_kem_identity.public_key)?; - - assert_eq!(kem1_alice, kem1_bob, "KEM1 shared secrets must match"); - assert_eq!(kem2_alice, kem2_bob, "KEM2 shared secrets must match"); - assert_eq!(kem3_alice, kem3_bob, "KEM3 shared secrets must match"); - println!("  All PQ-KEM shared secrets verified (placeholder)"); - - // === Hybrid key derivation === - - println!("\n=� Hybrid key derivation:"); - - // Concatenate all key material: DH1 || DH2 || DH3 || DH4 || KEM1 || KEM2 || KEM3 - let mut hybrid_key_material_alice = Vec::new(); - hybrid_key_material_alice.extend_from_slice(&dh1_alice); - hybrid_key_material_alice.extend_from_slice(&dh2_alice); - hybrid_key_material_alice.extend_from_slice(&dh3_alice); - hybrid_key_material_alice.extend_from_slice(&dh4_alice); - hybrid_key_material_alice.extend_from_slice(&kem1_alice); - hybrid_key_material_alice.extend_from_slice(&kem2_alice); - hybrid_key_material_alice.extend_from_slice(&kem3_alice); - - let mut hybrid_key_material_bob = Vec::new(); - hybrid_key_material_bob.extend_from_slice(&dh1_bob); - hybrid_key_material_bob.extend_from_slice(&dh2_bob); - hybrid_key_material_bob.extend_from_slice(&dh3_bob); - hybrid_key_material_bob.extend_from_slice(&dh4_bob); - hybrid_key_material_bob.extend_from_slice(&kem1_bob); - hybrid_key_material_bob.extend_from_slice(&kem2_bob); - hybrid_key_material_bob.extend_from_slice(&kem3_bob); - - assert_eq!( - hybrid_key_material_alice, hybrid_key_material_bob, - "Hybrid key material must match" - ); - println!( - "  Hybrid key material: {} bytes (4 DH + 3 KEM)", - hybrid_key_material_alice.len() - ); - - // Apply HKDF to derive final shared secret - use hkdf::Hkdf; - use sha2::Sha256; - - let salt = [0xFFu8; 32]; - let info = b"PQXDH-SyftBox-v1"; - - let hk = Hkdf::::new(Some(&salt), &hybrid_key_material_alice); - let mut shared_secret_alice = [0u8; 32]; - hk.expand(info, &mut shared_secret_alice) - .map_err(|_| SignalProtocolError::InvalidState("HKDF expand failed", "".to_string()))?; - - let hk = Hkdf::::new(Some(&salt), &hybrid_key_material_bob); - let mut shared_secret_bob = [0u8; 32]; - hk.expand(info, &mut shared_secret_bob) - .map_err(|_| SignalProtocolError::InvalidState("HKDF expand failed", "".to_string()))?; - - // Verify final shared secrets match - assert_eq!( - shared_secret_alice, shared_secret_bob, - "Final shared secrets must match" - ); - - println!( - "  Final shared secret (SK) derived: {} bytes", - shared_secret_alice.len() - ); - println!(" =� SK (hex): {}", hex::encode(&shared_secret_alice[..8])); - - println!("\n<� PQXDH Key Agreement completed successfully!"); - println!(" Hybrid security: Protected against both classical and quantum attacks"); - println!(" Classical: 4 ECDH operations (immediate security)"); - println!(" Post-quantum: 3 KEM operations (future quantum resistance) [PLACEHOLDER]"); - - Ok(()) -} - -/// Test post-quantum KEM simulation -/// -/// This test demonstrates how a real KEM (Key Encapsulation Mechanism) would work. -/// In a real implementation with Kyber/ML-KEM: -/// - Encapsulate(pk) -> (ciphertext, shared_secret) -/// - Decapsulate(sk, ciphertext) -> shared_secret -/// -/// Properties: -/// - Only holder of private key can decapsulate -/// - Ciphertext can be public -/// - Provides IND-CCA2 security (quantum-resistant) -#[test] -fn test_kem_simulation() -> Result<(), SignalProtocolError> { - println!("= Testing KEM Simulation (Placeholder)"); - println!("� Real implementation would use Kyber/ML-KEM"); - - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // Generate KEM key pair (receiver) - let receiver_kem_keypair = KeyPair::generate(&mut rng); - println!(" Receiver KEM key pair generated"); - - // === Sender: Encapsulate === - println!("\n=� Sender encapsulates:"); - - // Real KEM: (ciphertext, shared_secret) = KEM.Encapsulate(receiver_pk) - // Placeholder: Generate ephemeral key and perform DH - let sender_ephemeral = KeyPair::generate(&mut rng); - let shared_secret_sender = sender_ephemeral - .private_key - .calculate_agreement(&receiver_kem_keypair.public_key)?; - - // Ciphertext would be the encapsulated key; here it's the ephemeral public key - let ciphertext = sender_ephemeral.public_key.serialize(); - - println!( - "  Shared secret generated: {} bytes", - shared_secret_sender.len() - ); - println!("  Ciphertext created: {} bytes", ciphertext.len()); - println!(" =� Ciphertext (hex): {}", hex::encode(&ciphertext[..8])); - - // === Receiver: Decapsulate === - println!("\n=� Receiver decapsulates:"); - - // Real KEM: shared_secret = KEM.Decapsulate(receiver_sk, ciphertext) - // Placeholder: Deserialize ephemeral public key and perform DH - let sender_ephemeral_pk = PublicKey::deserialize(&ciphertext)?; - let shared_secret_receiver = receiver_kem_keypair - .private_key - .calculate_agreement(&sender_ephemeral_pk)?; - - println!( - "  Shared secret recovered: {} bytes", - shared_secret_receiver.len() - ); - - // === Verify === - assert_eq!( - shared_secret_sender, shared_secret_receiver, - "Encapsulated and decapsulated secrets must match" - ); - - println!("  Shared secrets match!"); - println!( - " =� Shared secret (hex): {}", - hex::encode(&shared_secret_sender[..8]) - ); - - println!("\n<� KEM simulation completed successfully!"); - println!(" Real Kyber-768 would provide:"); - println!(" - Public key: 1184 bytes"); - println!(" - Ciphertext: 1088 bytes"); - println!(" - Shared secret: 32 bytes"); - println!(" - Security level: NIST Level 3 (quantum-resistant)"); - - Ok(()) -} diff --git a/protocol/tests/private_keys_test.rs b/protocol/tests/private_keys_test.rs index a57afc0..6280510 100644 --- a/protocol/tests/private_keys_test.rs +++ b/protocol/tests/private_keys_test.rs @@ -1,123 +1,65 @@ -use libsignal_protocol::{IdentityKeyPair, KeyPair, kem}; -use rand::{SeedableRng, rngs::StdRng}; +use ed25519_dalek::SigningKey; +use rand::{RngCore, SeedableRng, rngs::StdRng}; use syft_crypto_protocol::SyftPrivateKeys; +use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret}; #[test] fn test_private_keys_getters_expose_expected_material() { let mut rng = StdRng::from_seed([42u8; 32]); - let identity = IdentityKeyPair::generate(&mut rng); - let signed_pre_key = KeyPair::generate(&mut rng); - let pq_signed_pre_key = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - // Keep copies for comparison before moving into SyftPrivateKeys - let identity_pub_bytes = identity.identity_key().serialize(); - let signed_pre_key_bytes = signed_pre_key.public_key.serialize(); - let pq_pre_key_bytes = pq_signed_pre_key.public_key.serialize(); + let mut identity_seed = [0u8; 32]; + rng.fill_bytes(&mut identity_seed); + let identity = SigningKey::from_bytes(&identity_seed); - let keys = SyftPrivateKeys::new(identity, signed_pre_key, pq_signed_pre_key); + let mut identity_dh_seed = [0u8; 32]; + rng.fill_bytes(&mut identity_dh_seed); + let identity_dh = StaticSecret::from(identity_dh_seed); + + let mut signed_prekey_seed = [0u8; 32]; + rng.fill_bytes(&mut signed_prekey_seed); + let signed_prekey = StaticSecret::from(signed_prekey_seed); + + let identity_pub_bytes = *identity.verifying_key().as_bytes(); + let identity_dh_pub_bytes = *X25519PublicKey::from(&identity_dh).as_bytes(); + let signed_prekey_pub_bytes = *X25519PublicKey::from(&signed_prekey).as_bytes(); + + let keys = SyftPrivateKeys::new(identity, identity_dh, signed_prekey); assert_eq!( - keys.identity().identity_key().serialize(), - identity_pub_bytes + keys.identity().verifying_key().as_bytes(), + &identity_pub_bytes ); assert_eq!( - keys.signed_pre_key().public_key.serialize(), - signed_pre_key_bytes + X25519PublicKey::from(keys.identity_dh()).as_bytes(), + &identity_dh_pub_bytes ); assert_eq!( - keys.pq_signed_pre_key().public_key.serialize(), - pq_pre_key_bytes + X25519PublicKey::from(keys.signed_pre_key()).as_bytes(), + &signed_prekey_pub_bytes ); } #[test] -fn test_to_public_bundle_respects_supplied_rng() { +fn test_to_public_bundle_signatures_valid() { let mut rng = StdRng::from_seed([7u8; 32]); - let identity = IdentityKeyPair::generate(&mut rng); - let signed_pre_key = KeyPair::generate(&mut rng); - let pq_signed_pre_key = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - let keys = SyftPrivateKeys::new(identity, signed_pre_key, pq_signed_pre_key); + let mut identity_seed = [0u8; 32]; + rng.fill_bytes(&mut identity_seed); + let identity = SigningKey::from_bytes(&identity_seed); - let mut rng1 = StdRng::from_seed([1u8; 32]); - let mut rng2 = StdRng::from_seed([1u8; 32]); + let mut identity_dh_seed = [0u8; 32]; + rng.fill_bytes(&mut identity_dh_seed); + let identity_dh = StaticSecret::from(identity_dh_seed); - let bundle1 = keys - .to_public_bundle(&mut rng1) - .expect("bundle creation succeeds"); - let bundle2 = keys - .to_public_bundle(&mut rng2) - .expect("bundle creation succeeds"); + let mut signed_prekey_seed = [0u8; 32]; + rng.fill_bytes(&mut signed_prekey_seed); + let signed_prekey = StaticSecret::from(signed_prekey_seed); - assert!(bundle1.verify_signatures()); - assert!(bundle2.verify_signatures()); - // Identical RNG seeds should produce identical deterministic signatures - assert_eq!( - bundle1.signal_signed_pre_key_signature.as_ref(), - bundle2.signal_signed_pre_key_signature.as_ref() - ); - assert_eq!( - bundle1.signal_pq_pre_key_signature, - bundle2.signal_pq_pre_key_signature - ); + let keys = SyftPrivateKeys::new(identity, identity_dh, signed_prekey); - // Different RNG seeds must produce different signatures - let mut rng3 = StdRng::from_seed([9u8; 32]); - let bundle3 = keys - .to_public_bundle(&mut rng3) + let bundle = keys + .to_public_bundle(&mut rng) .expect("bundle creation succeeds"); - assert_ne!( - bundle1.signal_signed_pre_key_signature.as_ref(), - bundle3.signal_signed_pre_key_signature.as_ref() - ); - assert_ne!( - bundle1.signal_pq_pre_key_signature.as_ref(), - bundle3.signal_pq_pre_key_signature.as_ref() - ); -} - -/// Verifies that `SyftPrivateKeys` zeroizes its memory when dropped. -/// -/// # Why Unsafe Is Required In This Test -/// -/// There is no safe way to verify zeroization because we need to: -/// 1. Control where the value lives in memory (MaybeUninit gives stable address) -/// 2. Drop the value manually (drop_in_place) -/// 3. Read the memory AFTER the value is dropped (normally prevented by Rust) -/// -/// This is a security test that verifies implementation details - the unsafe -/// is confined to the test, not the production code being tested. -#[test] -fn test_syft_private_keys_memory_zeroized_on_drop() { - use std::mem::{MaybeUninit, size_of}; - - let mut rng = StdRng::from_seed([99u8; 32]); - let identity = IdentityKeyPair::generate(&mut rng); - let signed_pre_key = KeyPair::generate(&mut rng); - let pq_signed_pre_key = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - // Use MaybeUninit to get a stable memory location we can inspect after drop - let mut slot = MaybeUninit::::uninit(); - - // SAFETY: This test intentionally reads memory after drop to verify zeroization. - // - We write a valid value to the slot - // - We drop it in place (triggering Sensitive's zeroization) - // - We read the raw bytes to verify they are all zero - // This would be UB in normal code, but is acceptable for security testing. - unsafe { - let ptr = slot.as_mut_ptr(); - ptr.write(SyftPrivateKeys::new( - identity, - signed_pre_key, - pq_signed_pre_key, - )); - ptr.drop_in_place(); - let raw = std::slice::from_raw_parts(ptr as *const u8, size_of::()); - assert!( - raw.iter().all(|&b| b == 0), - "SyftPrivateKeys memory not zeroized: first bytes = {:?}", - &raw[..raw.len().min(16)] - ); - } + assert!(bundle.verify_signatures()); } diff --git a/protocol/tests/recovery_key_mnemonic_test.rs b/protocol/tests/recovery_key_mnemonic_test.rs index 61cecf0..eea603f 100644 --- a/protocol/tests/recovery_key_mnemonic_test.rs +++ b/protocol/tests/recovery_key_mnemonic_test.rs @@ -32,8 +32,8 @@ fn test_mnemonic_roundtrip() { let recovered_keys = recovered_key.derive_keys().unwrap(); assert_eq!( - original_keys.identity().serialize(), - recovered_keys.identity().serialize(), + original_keys.identity().to_bytes(), + recovered_keys.identity().to_bytes(), "Identity keys should match after mnemonic roundtrip" ); } @@ -138,12 +138,12 @@ fn test_mnemonic_case_insensitive() { let mixed_keys = recovered_mixed.derive_keys().unwrap(); assert_eq!( - original_keys.identity().serialize(), - upper_keys.identity().serialize() + original_keys.identity().to_bytes(), + upper_keys.identity().to_bytes() ); assert_eq!( - original_keys.identity().serialize(), - mixed_keys.identity().serialize() + original_keys.identity().to_bytes(), + mixed_keys.identity().to_bytes() ); } @@ -173,8 +173,8 @@ fn test_mnemonic_extra_whitespace() { assert_eq!(recovery_key, recovered, "Recovery keys should match"); assert_eq!( - original_keys.identity().serialize(), - recovered_keys.identity().serialize(), + original_keys.identity().to_bytes(), + recovered_keys.identity().to_bytes(), "Should handle extra whitespace correctly" ); } @@ -201,21 +201,19 @@ fn test_mnemonic_derives_same_keys_as_original() { // Verify all keys match assert_eq!( - original_bundle.signal_identity_public_key.serialize(), - recovered_bundle.signal_identity_public_key.serialize(), + original_bundle.identity_signing_public_key.as_bytes(), + recovered_bundle.identity_signing_public_key.as_bytes(), "Identity public keys should match" ); - assert_eq!( - original_keys.signed_pre_key().public_key.serialize(), - recovered_keys.signed_pre_key().public_key.serialize(), - "Signed prekeys should match" + original_bundle.identity_dh_public_key.as_bytes(), + recovered_bundle.identity_dh_public_key.as_bytes(), + "Identity DH keys should match" ); - assert_eq!( - original_keys.pq_signed_pre_key().public_key.serialize(), - recovered_keys.pq_signed_pre_key().public_key.serialize(), - "PQ prekeys should match" + original_keys.signed_pre_key().to_bytes(), + recovered_keys.signed_pre_key().to_bytes(), + "Signed prekeys should match" ); } @@ -244,8 +242,8 @@ fn test_mnemonic_format_and_parsing() { let parsed_keys = parsed_key.derive_keys().unwrap(); assert_eq!( - original_keys.identity().serialize(), - parsed_keys.identity().serialize(), + original_keys.identity().to_bytes(), + parsed_keys.identity().to_bytes(), "Parsed mnemonic should produce same keys" ); } diff --git a/protocol/tests/storage_permissions_test.rs b/protocol/tests/storage_permissions_test.rs index 683ede1..958d1d4 100644 --- a/protocol/tests/storage_permissions_test.rs +++ b/protocol/tests/storage_permissions_test.rs @@ -238,7 +238,7 @@ fn test_save_produces_valid_json() { json.get("signed_prekey").is_some(), "Should have signed_prekey" ); - assert!(json.get("pq_prekey").is_some(), "Should have pq_prekey"); + assert!(json.get("identity_dh").is_some(), "Should have identity_dh"); } /// Cross-platform test: verify atomic write behavior (file only appears when complete). diff --git a/protocol/tests/syft_private_public_keys_test.rs b/protocol/tests/syft_private_public_keys_test.rs index 82861b9..c8a9c14 100644 --- a/protocol/tests/syft_private_public_keys_test.rs +++ b/protocol/tests/syft_private_public_keys_test.rs @@ -1,430 +1,63 @@ -//! Tests for SyftPrivateKeys and SyftPublicKeyBundle -//! -//! Note: SyftRecoveryKey tests are in syft_recovery_key_test.rs - -use libsignal_protocol::{IdentityKeyPair, KeyPair, kem}; -use syft_crypto_protocol::{SyftPrivateKeys, SyftPublicKeyBundle}; - -// ============================================================================ -// SyftPrivateKeys Tests -// ============================================================================ - -#[test] -fn test_private_keys_to_public_bundle() { - let mut rng = rand::rng(); - - let signal_identity_key_pair = IdentityKeyPair::generate(&mut rng); - let signal_signed_pre_key_pair = KeyPair::generate(&mut rng); - let signal_pq_signed_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let private_keys = SyftPrivateKeys::new( - signal_identity_key_pair, - signal_signed_pre_key_pair, - signal_pq_signed_pre_key_pair.clone(), - ); - - let public_bundle = private_keys - .to_public_bundle(&mut rng) - .expect("Bundle creation should succeed"); - - // Verify the public bundle has correct keys - assert_eq!( - public_bundle.signal_identity_public_key.serialize(), - signal_identity_key_pair.identity_key().serialize(), - "Identity key should match" - ); - - assert_eq!( - public_bundle.signal_signed_public_pre_key.serialize(), - signal_signed_pre_key_pair.public_key.serialize(), - "Signed pre key should match" - ); - - assert_eq!( - public_bundle.signal_pq_public_pre_key.serialize(), - signal_pq_signed_pre_key_pair.public_key.serialize(), - "PQ pre key should match" - ); - - // Verify signatures are valid - assert!( - public_bundle.verify_signatures(), - "Public bundle signatures should be valid" - ); -} - -#[test] -fn test_private_keys_multiple_bundles_same_keys() { - let mut rng = rand::rng(); - - let private_keys = SyftPrivateKeys::new( - IdentityKeyPair::generate(&mut rng), - KeyPair::generate(&mut rng), - kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng), - ); - - let bundle1 = private_keys - .to_public_bundle(&mut rng) - .expect("Bundle 1 creation should succeed"); - let bundle2 = private_keys - .to_public_bundle(&mut rng) - .expect("Bundle 2 creation should succeed"); - - // Same keys should produce bundles with same public keys - assert_eq!( - bundle1.signal_identity_public_key.serialize(), - bundle2.signal_identity_public_key.serialize(), - "Identity keys should match" - ); - - assert_eq!( - bundle1.signal_signed_public_pre_key.serialize(), - bundle2.signal_signed_public_pre_key.serialize(), - "Signed pre keys should match" - ); - - assert_eq!( - bundle1.signal_pq_public_pre_key.serialize(), - bundle2.signal_pq_public_pre_key.serialize(), - "PQ pre keys should match" - ); - - // Both bundles should have valid signatures - assert!(bundle1.verify_signatures(), "Bundle 1 should be valid"); - assert!(bundle2.verify_signatures(), "Bundle 2 should be valid"); -} - -// ============================================================================ -// SyftPublicKeyBundle Tests -// ============================================================================ +use syft_crypto_protocol::SyftRecoveryKey; #[test] fn test_public_key_bundle_creation() { let mut rng = rand::rng(); + let keys = SyftRecoveryKey::generate().derive_keys().unwrap(); + let bundle = keys.to_public_bundle(&mut rng).unwrap(); - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - assert_eq!( - bundle.signal_identity_public_key.serialize(), - identity_pair.identity_key().serialize() - ); - assert_eq!( - bundle.signal_signed_public_pre_key.serialize(), - signed_pre_key_pair.public_key.serialize() - ); - assert_eq!( - bundle.signal_pq_public_pre_key.serialize(), - pq_pre_key_pair.public_key.serialize() - ); -} - -#[test] -fn test_public_key_bundle_signature_verification() { - let mut rng = rand::rng(); - - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - assert!( - bundle.verify_signatures(), - "Valid bundle signatures should verify" - ); -} - -#[test] -fn test_public_key_bundle_detects_tampered_ec_key() { - let mut rng = rand::rng(); - - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - // Tamper with EC key by replacing it - let different_key_pair = KeyPair::generate(&mut rng); - bundle.signal_signed_public_pre_key = different_key_pair.public_key; - - assert!( - !bundle.verify_signatures(), - "Tampered EC key should fail verification" - ); -} - -#[test] -fn test_public_key_bundle_detects_tampered_pq_key() { - let mut rng = rand::rng(); - - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - // Tamper with PQ key by replacing it - let different_pq_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - bundle.signal_pq_public_pre_key = different_pq_pair.public_key; - - assert!( - !bundle.verify_signatures(), - "Tampered PQ key should fail verification" - ); + assert!(bundle.verify_signatures()); + assert_eq!(bundle.identity_fingerprint().len(), 64); } #[test] -fn test_public_key_bundle_detects_tampered_ec_signature() { +fn test_public_key_bundle_detects_tampered_identity_dh_signature() { let mut rng = rand::rng(); + let keys = SyftRecoveryKey::generate().derive_keys().unwrap(); + let mut bundle = keys.to_public_bundle(&mut rng).unwrap(); - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); + let mut sig = bundle.identity_dh_signature.to_vec(); + sig[0] ^= 0xFF; + bundle.identity_dh_signature = sig.into_boxed_slice(); - // Tamper with EC signature - let mut tampered_sig = bundle.signal_signed_pre_key_signature.to_vec(); - tampered_sig[0] ^= 0xFF; // Flip bits in first byte - bundle.signal_signed_pre_key_signature = tampered_sig.into_boxed_slice(); - - assert!( - !bundle.verify_signatures(), - "Tampered EC signature should fail verification" - ); + assert!(!bundle.verify_signatures()); } #[test] -fn test_public_key_bundle_detects_tampered_pq_signature() { +fn test_public_key_bundle_detects_tampered_signed_prekey_signature() { let mut rng = rand::rng(); + let keys = SyftRecoveryKey::generate().derive_keys().unwrap(); + let mut bundle = keys.to_public_bundle(&mut rng).unwrap(); - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let mut bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - // Tamper with PQ signature - let mut tampered_sig = bundle.signal_pq_pre_key_signature.to_vec(); - tampered_sig[0] ^= 0xFF; // Flip bits in first byte - bundle.signal_pq_pre_key_signature = tampered_sig.into_boxed_slice(); + let mut sig = bundle.signed_prekey_signature.to_vec(); + sig[0] ^= 0xFF; + bundle.signed_prekey_signature = sig.into_boxed_slice(); - assert!( - !bundle.verify_signatures(), - "Tampered PQ signature should fail verification" - ); + assert!(!bundle.verify_signatures()); } #[test] fn test_public_key_bundle_total_size() { let mut rng = rand::rng(); + let keys = SyftRecoveryKey::generate().derive_keys().unwrap(); + let bundle = keys.to_public_bundle(&mut rng).unwrap(); - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - let total = bundle.total_size(); + let expected = 32 // Ed25519 identity public key + + 32 // X25519 identity DH public key + + 64 // identity DH signature + + 32 // X25519 signed prekey public key + + 64; // signed prekey signature - // Calculate expected size manually - let expected = bundle.signal_identity_public_key.serialize().len() - + bundle.signal_signed_public_pre_key.serialize().len() - + bundle.signal_signed_pre_key_signature.len() - + bundle.signal_pq_public_pre_key.serialize().len() - + bundle.signal_pq_pre_key_signature.len(); - - assert_eq!(total, expected, "Total size should match sum of parts"); - - // Verify size is reasonable (identity ~32, signed_pre ~32, sigs ~64 each, pq ~1568) - // Total should be around 1760 bytes - assert!( - total > 1500, - "Bundle should be at least 1500 bytes (Kyber public key is large)" - ); - assert!( - total < 2000, - "Bundle should be less than 2000 bytes for reasonable sizes" - ); + assert_eq!(bundle.total_size(), expected); } #[test] fn test_public_key_bundle_clone() { let mut rng = rand::rng(); + let keys = SyftRecoveryKey::generate().derive_keys().unwrap(); + let bundle = keys.to_public_bundle(&mut rng).unwrap(); + let cloned = bundle.clone(); - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle1 = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle creation should succeed"); - - let bundle2 = bundle1.clone(); - - // Verify all fields are identical - assert_eq!( - bundle1.signal_identity_public_key.serialize(), - bundle2.signal_identity_public_key.serialize() - ); - assert_eq!( - bundle1.signal_signed_public_pre_key.serialize(), - bundle2.signal_signed_public_pre_key.serialize() - ); - assert_eq!( - bundle1.signal_signed_pre_key_signature, - bundle2.signal_signed_pre_key_signature - ); - assert_eq!( - bundle1.signal_pq_public_pre_key.serialize(), - bundle2.signal_pq_public_pre_key.serialize() - ); - assert_eq!( - bundle1.signal_pq_pre_key_signature, - bundle2.signal_pq_pre_key_signature - ); - - // Both should verify - assert!(bundle1.verify_signatures()); - assert!(bundle2.verify_signatures()); -} - -#[test] -fn test_public_key_bundle_cross_contamination() { - // Ensure signatures from one bundle don't validate keys from another - let mut rng = rand::rng(); - - let identity_pair_1 = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair_1 = KeyPair::generate(&mut rng); - let pq_pre_key_pair_1 = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let identity_pair_2 = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair_2 = KeyPair::generate(&mut rng); - let pq_pre_key_pair_2 = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle1 = SyftPublicKeyBundle::new( - &identity_pair_1, - &signed_pre_key_pair_1, - &pq_pre_key_pair_1, - &mut rng, - ) - .expect("Bundle 1 creation should succeed"); - - let bundle2 = SyftPublicKeyBundle::new( - &identity_pair_2, - &signed_pre_key_pair_2, - &pq_pre_key_pair_2, - &mut rng, - ) - .expect("Bundle 2 creation should succeed"); - - // Create a mixed bundle: identity from bundle1, but keys and sigs from bundle2 - let mixed_bundle = SyftPublicKeyBundle { - signal_identity_public_key: bundle1.signal_identity_public_key, - signal_signed_public_pre_key: bundle2.signal_signed_public_pre_key, - signal_signed_pre_key_signature: bundle2.signal_signed_pre_key_signature.clone(), - signal_pq_public_pre_key: bundle2.signal_pq_public_pre_key.clone(), - signal_pq_pre_key_signature: bundle2.signal_pq_pre_key_signature.clone(), - }; - - assert!( - !mixed_bundle.verify_signatures(), - "Mixed bundle with mismatched identity should fail verification" - ); -} - -#[test] -fn test_public_key_bundle_deterministic_within_session() { - // Verify that creating multiple bundles from the same keys produces valid bundles - // (Note: signatures may differ due to randomness, but all should verify) - let mut rng = rand::rng(); - - let identity_pair = IdentityKeyPair::generate(&mut rng); - let signed_pre_key_pair = KeyPair::generate(&mut rng); - let pq_pre_key_pair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng); - - let bundle1 = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle 1 should be created"); - - let bundle2 = SyftPublicKeyBundle::new( - &identity_pair, - &signed_pre_key_pair, - &pq_pre_key_pair, - &mut rng, - ) - .expect("Bundle 2 should be created"); - - // Both should verify independently - assert!(bundle1.verify_signatures(), "Bundle 1 should verify"); - assert!(bundle2.verify_signatures(), "Bundle 2 should verify"); - - // Public keys should be identical - assert_eq!( - bundle1.signal_identity_public_key.serialize(), - bundle2.signal_identity_public_key.serialize() - ); - assert_eq!( - bundle1.signal_signed_public_pre_key.serialize(), - bundle2.signal_signed_public_pre_key.serialize() - ); - assert_eq!( - bundle1.signal_pq_public_pre_key.serialize(), - bundle2.signal_pq_public_pre_key.serialize() - ); + assert_eq!(bundle.identity_fingerprint(), cloned.identity_fingerprint()); + assert!(cloned.verify_signatures()); } diff --git a/protocol/tests/x3dh_test.rs b/protocol/tests/x3dh_test.rs index f39774c..176effb 100644 --- a/protocol/tests/x3dh_test.rs +++ b/protocol/tests/x3dh_test.rs @@ -1,176 +1,127 @@ -use libsignal_protocol::*; -use rand::SeedableRng; +use rand::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use x25519_dalek::{PublicKey, StaticSecret}; + +fn static_secret_from_rng(rng: &mut ChaCha20Rng) -> StaticSecret { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + StaticSecret::from(bytes) +} /// Test X3DH key component generation /// /// This demonstrates the basic cryptographic building blocks of X3DH: -/// - Identity key pairs (IK_A, IK_B) - long-term authentication keys -/// - Signed prekeys (SPK_B) - medium-term keys signed by identity key -/// - One-time prekeys (OPK_B) - ephemeral keys for forward secrecy +/// - Identity DH key pairs (IK_A, IK_B) - long-term authentication keys +/// - Signed prekeys (SPK_B) - medium-term keys signed by identity signing key +/// - Ephemeral keys (EK_A) - per-message forward secrecy #[test] -fn test_x3dh_key_generation() -> Result<(), SignalProtocolError> { - println!("🔐 Testing X3DH Key Generation Components"); - - // Use ChaCha20 RNG with fixed seed for reproducible tests - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // Generate identity key pairs (long-term keys) - let alice_identity = IdentityKeyPair::generate(&mut rng); - let bob_identity = IdentityKeyPair::generate(&mut rng); +fn test_x3dh_key_generation() { + let mut rng = ChaCha20Rng::from_seed([1u8; 32]); - // Verify identity key structure - assert_eq!(alice_identity.public_key().serialize().len(), 33); // 32 bytes + type byte - assert_eq!(bob_identity.public_key().serialize().len(), 33); - println!("✅ Identity keys (IK_A, IK_B) generated"); + let alice_identity_dh = static_secret_from_rng(&mut rng); + let bob_identity_dh = static_secret_from_rng(&mut rng); - // Generate signed prekey pair - let bob_signed_prekey_pair: KeyPair = KeyPair::generate(&mut rng); - let bob_signed_prekey_signature = bob_identity - .private_key() - .calculate_signature(&bob_signed_prekey_pair.public_key.serialize(), &mut rng)?; + let alice_identity_dh_pub = PublicKey::from(&alice_identity_dh); + let bob_identity_dh_pub = PublicKey::from(&bob_identity_dh); - // Verify Ed25519 signature is 64 bytes - assert_eq!(bob_signed_prekey_signature.len(), 64); - println!("✅ Signed prekey (SPK_B) with signature generated"); + assert_eq!(alice_identity_dh_pub.as_bytes().len(), 32); + assert_eq!(bob_identity_dh_pub.as_bytes().len(), 32); - // Generate one-time prekey (ephemeral) - let _bob_one_time_prekey = KeyPair::generate(&mut rng); - println!("✅ One-time prekey (OPK_B) generated"); + let bob_signed_prekey = static_secret_from_rng(&mut rng); + let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + assert_eq!(bob_signed_prekey_pub.as_bytes().len(), 32); - println!("🎯 All X3DH cryptographic components created successfully!"); - Ok(()) + let _alice_ephemeral = static_secret_from_rng(&mut rng); } -/// Test 3DH key agreement protocol (X3DH without one-time prekeys) +/// Test 4DH key agreement protocol (X3DH-style) /// -/// This implements the core 3DH operations: -/// - DH1 = DH(IK_A, SPK_B) - Alice's identity key with Bob's signed prekey -/// - DH2 = DH(EK_A, IK_B) - Alice's ephemeral key with Bob's identity key -/// - DH3 = DH(EK_A, SPK_B) - Alice's ephemeral key with Bob's signed prekey -/// - SK = KDF(DH1 || DH2 || DH3) - Derive shared secret from concatenated DH outputs +/// This implements the core DH operations: +/// - DH1 = DH(IK_A, SPK_B) +/// - DH2 = DH(SPK_A, IK_B) +/// - DH3 = DH(EK_A, IK_B) +/// - DH4 = DH(EK_A, SPK_B) +/// - SK = KDF(DH1 || DH2 || DH3 || DH4) #[test] -fn test_3dh_key_agreement() -> Result<(), SignalProtocolError> { - println!("🔐 Testing 3DH Key Agreement Protocol"); - - // Use ChaCha20 RNG with fixed seed for reproducible tests - let mut rng = rand_chacha::ChaCha20Rng::from_seed([1u8; 32]); - - // Generate Alice's keys - let alice_identity = IdentityKeyPair::generate(&mut rng); - let alice_ephemeral = KeyPair::generate(&mut rng); - println!("✅ Alice's keys generated (IK_A, EK_A)"); - - // Generate Bob's keys - let bob_identity = IdentityKeyPair::generate(&mut rng); - let bob_signed_prekey = KeyPair::generate(&mut rng); - - // Bob signs his prekey with his identity key - // let bob_spk_signature = bob_identity - // .private_key() - // .calculate_signature(&bob_signed_prekey.public_key.serialize(), &mut rng)?; - - println!("✅ Bob's keys generated (IK_B, SPK_B) with signature"); - - // === Alice's side: Perform 3DH operations === - - // DH1 = DH(IK_A, SPK_B) - // Alice's identity private key with Bob's signed prekey public key - let dh1_alice = alice_identity - .private_key() - .calculate_agreement(&bob_signed_prekey.public_key)?; - - // DH2 = DH(EK_A, IK_B) - // Alice's ephemeral private key with Bob's identity public key - let dh2_alice = alice_ephemeral - .private_key - .calculate_agreement(bob_identity.public_key())?; - - // DH3 = DH(EK_A, SPK_B) - // Alice's ephemeral private key with Bob's signed prekey public key +fn test_4dh_key_agreement() { + let mut rng = ChaCha20Rng::from_seed([1u8; 32]); + + let alice_identity_dh = static_secret_from_rng(&mut rng); + let alice_signed_prekey = static_secret_from_rng(&mut rng); + let alice_ephemeral = static_secret_from_rng(&mut rng); + + let bob_identity_dh = static_secret_from_rng(&mut rng); + let bob_signed_prekey = static_secret_from_rng(&mut rng); + + let bob_identity_dh_pub = PublicKey::from(&bob_identity_dh); + let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + + let alice_identity_dh_pub = PublicKey::from(&alice_identity_dh); + let alice_signed_prekey_pub = PublicKey::from(&alice_signed_prekey); + let alice_ephemeral_pub = PublicKey::from(&alice_ephemeral); + + // Alice side + let dh1_alice = alice_identity_dh + .diffie_hellman(&bob_signed_prekey_pub) + .to_bytes(); + let dh2_alice = alice_signed_prekey + .diffie_hellman(&bob_identity_dh_pub) + .to_bytes(); let dh3_alice = alice_ephemeral - .private_key - .calculate_agreement(&bob_signed_prekey.public_key)?; - - println!("✅ Alice completed 3 DH operations"); + .diffie_hellman(&bob_identity_dh_pub) + .to_bytes(); + let dh4_alice = alice_ephemeral + .diffie_hellman(&bob_signed_prekey_pub) + .to_bytes(); - // === Bob's side: Perform matching 3DH operations === - - // DH1 = DH(SPK_B, IK_A) - Same as DH(IK_A, SPK_B) due to DH symmetry + // Bob side let dh1_bob = bob_signed_prekey - .private_key - .calculate_agreement(alice_identity.public_key())?; - - // DH2 = DH(IK_B, EK_A) - Same as DH(EK_A, IK_B) - let dh2_bob = bob_identity - .private_key() - .calculate_agreement(&alice_ephemeral.public_key)?; + .diffie_hellman(&alice_identity_dh_pub) + .to_bytes(); + let dh2_bob = bob_identity_dh + .diffie_hellman(&alice_signed_prekey_pub) + .to_bytes(); + let dh3_bob = bob_identity_dh + .diffie_hellman(&alice_ephemeral_pub) + .to_bytes(); + let dh4_bob = bob_signed_prekey + .diffie_hellman(&alice_ephemeral_pub) + .to_bytes(); + + assert_eq!(dh1_alice, dh1_bob); + assert_eq!(dh2_alice, dh2_bob); + assert_eq!(dh3_alice, dh3_bob); + assert_eq!(dh4_alice, dh4_bob); - // DH3 = DH(SPK_B, EK_A) - Same as DH(EK_A, SPK_B) - let dh3_bob = bob_signed_prekey - .private_key - .calculate_agreement(&alice_ephemeral.public_key)?; - - println!("✅ Bob completed 3 DH operations"); - - // === Verify DH outputs match === - assert_eq!(dh1_alice, dh1_bob, "DH1 outputs must match"); - assert_eq!(dh2_alice, dh2_bob, "DH2 outputs must match"); - assert_eq!(dh3_alice, dh3_bob, "DH3 outputs must match"); - println!("✅ All DH outputs match between Alice and Bob"); - - // === Key Derivation Function (KDF) === - // Concatenate DH outputs: DH1 || DH2 || DH3 let mut key_material_alice = Vec::new(); key_material_alice.extend_from_slice(&dh1_alice); key_material_alice.extend_from_slice(&dh2_alice); key_material_alice.extend_from_slice(&dh3_alice); + key_material_alice.extend_from_slice(&dh4_alice); let mut key_material_bob = Vec::new(); key_material_bob.extend_from_slice(&dh1_bob); key_material_bob.extend_from_slice(&dh2_bob); key_material_bob.extend_from_slice(&dh3_bob); + key_material_bob.extend_from_slice(&dh4_bob); - // Verify key material is identical - assert_eq!( - key_material_alice, key_material_bob, - "Key material must be identical" - ); - println!("✅ Key material (DH1||DH2||DH3) matches"); + assert_eq!(key_material_alice, key_material_bob); - // Apply HKDF to derive shared secret use hkdf::Hkdf; use sha2::Sha256; - let salt = [0xFFu8; 32]; // Salt of all 0xFF bytes as per X3DH spec - let info = b"3DH-SyftBox"; // Protocol identifier + let salt = [0xFFu8; 32]; + let info = b"4DH-SyftBox"; let hk = Hkdf::::new(Some(&salt), &key_material_alice); let mut shared_secret_alice = [0u8; 32]; hk.expand(info, &mut shared_secret_alice) - .map_err(|_| SignalProtocolError::InvalidState("HKDF expand failed", "".to_string()))?; + .expect("hkdf expand"); let hk = Hkdf::::new(Some(&salt), &key_material_bob); let mut shared_secret_bob = [0u8; 32]; hk.expand(info, &mut shared_secret_bob) - .map_err(|_| SignalProtocolError::InvalidState("HKDF expand failed", "".to_string()))?; - - // Verify both parties derive the same shared secret - assert_eq!( - shared_secret_alice, shared_secret_bob, - "Shared secrets must match" - ); - assert_eq!( - shared_secret_alice.len(), - 32, - "Shared secret must be 32 bytes" - ); - - println!("✅ Shared secret (SK) derived successfully"); - println!("📊 SK length: {} bytes", shared_secret_alice.len()); - println!("📊 SK (hex): {}", hex::encode(&shared_secret_alice[..8])); // Show first 8 bytes - - println!("🎯 3DH Key Agreement Protocol completed successfully!"); - println!(" Both Alice and Bob now share the same 256-bit secret key"); - - Ok(()) + .expect("hkdf expand"); + + assert_eq!(shared_secret_alice, shared_secret_bob); } diff --git a/vendor/libsignal-protocol-syft b/vendor/libsignal-protocol-syft deleted file mode 160000 index 413591b..0000000 --- a/vendor/libsignal-protocol-syft +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 413591b0afa35e9408095a699b77f13293342a3e