From a66debdece33f9dc2a7c3d163a5399caf34dcb90 Mon Sep 17 00:00:00 2001 From: Sunil Thakares Date: Thu, 7 May 2026 21:56:58 +0530 Subject: [PATCH] Initial ChronoSeal release --- .github/workflows/rust.yml | 21 + .gitignore | 7 + CONTRIBUTING.md | 22 + Cargo.lock | 1302 ++++++++++++++++++++++++++++++++ Cargo.toml | 8 + Dockerfile | 23 + LICENSE.md | 5 + README.md | 49 ++ SECURITY.md | 20 + chronoseal.service | 35 + docker-compose.yml | 16 + docs/ARCHITECTURE.md | 36 + docs/DEPLOYMENT.md | 36 + frontend/entropy.js | 10 + frontend/heartbeat.js | 72 ++ frontend/index.html | 12 + frontend/main.js | 5 + frontend/transport.js | 9 + scripts/build.sh | 10 + scripts/dev.sh | 4 + scripts/release.sh | 5 + server/Cargo.toml | 20 + server/src/cleanup.rs | 14 + server/src/crypto.rs | 27 + server/src/fingerprint.rs | 10 + server/src/main.rs | 47 ++ server/src/middleware.rs | 11 + server/src/ratelimit.rs | 27 + server/src/routes/heartbeat.rs | 30 + server/src/routes/init.rs | 17 + server/src/routes/mod.rs | 2 + server/src/session.rs | 98 +++ server/src/storage.rs | 23 + server/src/trust.rs | 31 + server/src/vm.rs | 33 + shared/Cargo.toml | 13 + shared/src/constants.rs | 11 + shared/src/hashing.rs | 42 ++ shared/src/lib.rs | 3 + shared/src/protocol.rs | 62 ++ wasm/Cargo.toml | 20 + wasm/src/anti_debug.rs | 2 + wasm/src/crypto.rs | 47 ++ wasm/src/entropy.rs | 2 + wasm/src/fingerprint.rs | 1 + wasm/src/lib.rs | 6 + wasm/src/transport.rs | 1 + wasm/src/vm.rs | 55 ++ 48 files changed, 2362 insertions(+) create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 chronoseal.service create mode 100644 docker-compose.yml create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DEPLOYMENT.md create mode 100644 frontend/entropy.js create mode 100644 frontend/heartbeat.js create mode 100644 frontend/index.html create mode 100644 frontend/main.js create mode 100644 frontend/transport.js create mode 100644 scripts/build.sh create mode 100644 scripts/dev.sh create mode 100644 scripts/release.sh create mode 100644 server/Cargo.toml create mode 100644 server/src/cleanup.rs create mode 100644 server/src/crypto.rs create mode 100644 server/src/fingerprint.rs create mode 100644 server/src/main.rs create mode 100644 server/src/middleware.rs create mode 100644 server/src/ratelimit.rs create mode 100644 server/src/routes/heartbeat.rs create mode 100644 server/src/routes/init.rs create mode 100644 server/src/routes/mod.rs create mode 100644 server/src/session.rs create mode 100644 server/src/storage.rs create mode 100644 server/src/trust.rs create mode 100644 server/src/vm.rs create mode 100644 shared/Cargo.toml create mode 100644 shared/src/constants.rs create mode 100644 shared/src/hashing.rs create mode 100644 shared/src/lib.rs create mode 100644 shared/src/protocol.rs create mode 100644 wasm/Cargo.toml create mode 100644 wasm/src/anti_debug.rs create mode 100644 wasm/src/crypto.rs create mode 100644 wasm/src/entropy.rs create mode 100644 wasm/src/fingerprint.rs create mode 100644 wasm/src/lib.rs create mode 100644 wasm/src/transport.rs create mode 100644 wasm/src/vm.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..0d32a25 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,21 @@ +name: Rust + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build + run: cargo build --workspace --release + + - name: Test + run: cargo test --workspace diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b00867a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +target/ +pkg/ +node_modules/ +dist/ +*.log +.env +.idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a9b6b97 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing + +## Requirements + +- Rust stable +- wasm-pack +- NodeJS (optional frontend tooling) + +## Development + +```bash +cargo fmt +cargo clippy +cargo test +``` + +## Guidelines + +- Keep security-sensitive logic inside Rust/WASM +- Avoid placing trust logic in JavaScript +- Preserve silent-failure behavior +- Maintain deterministic protocol serialization diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..39e3e6f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1302 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "antibot-server" +version = "0.2.0" +dependencies = [ + "axum", + "base64", + "ed25519-dalek", + "hex", + "rand", + "rusqlite", + "serde", + "serde_json", + "shared", + "tokio", + "tower 0.4.13", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "antibot-wasm" +version = "0.2.0" +dependencies = [ + "base64", + "blake3", + "ed25519-dalek", + "getrandom", + "hex", + "rand", + "serde", + "serde-wasm-bindgen", + "serde_json", + "shared", + "wasm-bindgen", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared" +version = "0.2.0" +dependencies = [ + "base64", + "blake3", + "ed25519-dalek", + "hex", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.52.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..edbc756 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +resolver = "2" + +members = [ + "shared", + "server", + "wasm" +] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d58123 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM rust:1.88-bookworm AS builder + +WORKDIR /app + +COPY . . + +RUN cargo build -p server --release + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/chronoseal + +COPY --from=builder /app/target/release/server /usr/local/bin/chronoseal + +EXPOSE 3000 + +ENV RUST_LOG=info + +CMD ["chronoseal"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b0069d8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,5 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +This project is licensed under GPLv3. +https://www.gnu.org/licenses/gpl-3.0.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f3f2a2 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# ChronoSeal + +ChronoSeal is a high-security anti-automation and anti-AI-scraping framework built using Rust, WASM, cryptographic heartbeat ledgers, and behavioral attestation. + +## Features + +- Rust + Axum backend +- WASM runtime verification +- Ed25519 signatures +- Blake3 hash-chain continuity +- Behavioral entropy collection +- Silent anti-bot mitigation +- Adaptive trust scoring +- Stateless HTTP verification +- GPLv3 licensed + +## Architecture + +```text +Browser + ├── WASM VM + ├── Heartbeat protocol + ├── Cryptographic ledger + └── Behavioral attestation + +Server + ├── Session validation + ├── Hash-chain verification + ├── Trust scoring + └── Adaptive mitigation +``` + +## Build + +### Backend + +```bash +cargo run -p server --release +``` + +### WASM + +```bash +wasm-pack build wasm --target web --release +``` + +## License + +GPLv3 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6e3b672 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +## Reporting Vulnerabilities + +Please do not disclose security vulnerabilities publicly before responsible disclosure. + +Contact maintainers privately with: +- reproduction steps +- affected versions +- impact assessment +- proof-of-concept if applicable + +## Scope + +ChronoSeal intentionally operates as: +- anti-automation middleware +- behavioral attestation layer +- cryptographic continuity verifier + +Security hardening evolves continuously. diff --git a/chronoseal.service b/chronoseal.service new file mode 100644 index 0000000..ad3f850 --- /dev/null +++ b/chronoseal.service @@ -0,0 +1,35 @@ +[Unit] +Description=ChronoSeal Anti-Bot Service +After=network.target + +[Service] +Type=simple + +User=chronoseal +Group=chronoseal + +WorkingDirectory=/opt/chronoseal + +ExecStart=/usr/local/bin/chronoseal + +Restart=always +RestartSec=3 + +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true + +MemoryDenyWriteExecute=true +RestrictRealtime=true +RestrictSUIDSGID=true + +LockPersonality=true + +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8ce389a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +version: "3.9" + +services: + chronoseal: + build: . + container_name: chronoseal + restart: unless-stopped + + ports: + - "3000:3000" + + environment: + RUST_LOG: info + + tmpfs: + - /tmp diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..cebfed0 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,36 @@ +# ChronoSeal Architecture + +## Core Principles + +- Continuous browser attestation +- Cryptographic heartbeat chains +- WASM-isolated secrets +- Behavioral entropy verification +- Silent mitigation + +## Components + +### WASM Runtime + +Responsible for: +- heartbeat generation +- signature generation +- entropy collection +- VM execution + +### Server + +Responsible for: +- session verification +- trust scoring +- chain validation +- mitigation + +## Threat Model + +Designed to increase: +- scraping cost +- operational complexity +- synchronization burden + +ChronoSeal does not attempt impossible perfect prevention. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..2a7b365 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,36 @@ +# Deployment + +## Native + +```bash +cargo build -p server --release +sudo cp target/release/server /usr/local/bin/chronoseal +``` + +## systemd + +```bash +sudo cp chronoseal.service /etc/systemd/system/ + +sudo systemctl daemon-reload +sudo systemctl enable chronoseal +sudo systemctl start chronoseal +``` + +## Docker + +```bash +docker compose up -d --build +``` + +## Reverse Proxy + +Recommended: +- nginx +- Nginx Proxy Manager +- HAProxy + +Enable: +- HTTP/2 +- TLS 1.3 +- aggressive timeout policies diff --git a/frontend/entropy.js b/frontend/entropy.js new file mode 100644 index 0000000..5a97959 --- /dev/null +++ b/frontend/entropy.js @@ -0,0 +1,10 @@ +const events = []; + +document.addEventListener('mousemove', (e) => { + events.push({ x: e.clientX, y: e.clientY, t: performance.now() }); + if (events.length > 300) events.shift(); +}); + +export function collectEntropy(since) { + return events.filter(e => e.t > since); +} \ No newline at end of file diff --git a/frontend/heartbeat.js b/frontend/heartbeat.js new file mode 100644 index 0000000..c591ee2 --- /dev/null +++ b/frontend/heartbeat.js @@ -0,0 +1,72 @@ +import init, { generate_keypair, sign_message, compute_next_hash, run_program } from './pkg/antibot_wasm.js'; +import { collectEntropy } from './entropy.js'; +import { sendRequest } from './transport.js'; + +let session, prevHash, currentSalt, opcodesB64, lastTime; + +export async function initHeartbeat() { + await init(); + const pubHex = generate_keypair(); + const initResp = await sendRequest('/init', 'POST', { public_key: pubHex }); + session = initResp.session_id; + prevHash = initResp.initial_hash; + currentSalt = initResp.salt; + opcodesB64 = initResp.opcodes_b64; + lastTime = performance.now(); + scheduleNext(); +} + +function scheduleNext() { + const delay = 12000 + Math.random() * 13000; + setTimeout(sendHeartbeat, delay); +} + +async function sendHeartbeat() { + try { + const now = performance.now(); + const events = collectEntropy(lastTime); + lastTime = now; + + const stackState = JSON.stringify(run_program(opcodesB64)); + const fingerprint = { + aspectRatio: (screen.width / screen.height).toFixed(10), + devicePixelRatio: String(window.devicePixelRatio), + hardwareConcurrency: navigator.hardwareConcurrency || 1 + }; + const timestamp = Date.now(); + const entropyData = { events: events.map(e => ({ x: e.x, y: e.y, t: e.t })) }; + const entropyJson = JSON.stringify(entropyData); + + const signable = { + sessionId: session, + prevHash: prevHash, + timestamp: timestamp, + entropyData: entropyData, + stackState: JSON.parse(stackState), + fingerprint: fingerprint + }; + const msg = JSON.stringify(signable, Object.keys(signable).sort()); + const sig = sign_message(msg); + + const resp = await sendRequest('/hb', 'POST', { + session_id: session, + prev_hash: prevHash, + timestamp, + entropy_data: entropyData, + stack_state: JSON.parse(stackState), + fingerprint, + signature: sig + }); + + if (resp.next_salt) { + currentSalt = resp.next_salt; + prevHash = compute_next_hash(prevHash, timestamp, entropyJson, stackState, currentSalt); + } else { + console.warn('Heartbeat rejected'); + } + } catch (e) { + console.error(e); + } finally { + scheduleNext(); + } +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..b3dc31e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + Anti-Scraper Demo + + +

Protected Page

+

Move your mouse to generate entropy.

+ + + \ No newline at end of file diff --git a/frontend/main.js b/frontend/main.js new file mode 100644 index 0000000..e44681f --- /dev/null +++ b/frontend/main.js @@ -0,0 +1,5 @@ +import { initHeartbeat } from './heartbeat.js'; + +(async () => { + await initHeartbeat(); +})(); \ No newline at end of file diff --git a/frontend/transport.js b/frontend/transport.js new file mode 100644 index 0000000..0423ed0 --- /dev/null +++ b/frontend/transport.js @@ -0,0 +1,9 @@ +export async function sendRequest(url, method, body) { + const res = await fetch(url, { + method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!res.ok) throw new Error(`Request failed: ${res.status}`); + return res.json(); +} \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..9883cbd --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e +echo "Building WASM..." +cd ../wasm +wasm-pack build --target web +mv pkg ../frontend/pkg +echo "Building server..." +cd ../server +cargo build --release +echo "Done." \ No newline at end of file diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100644 index 0000000..9a6840d --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Starting server with static frontend serving..." +cd ../server +cargo run --release \ No newline at end of file diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100644 index 0000000..f923c19 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,5 @@ +#!/bin/bash +bash build.sh +echo "Release artifacts:" +echo " - server/target/release/antibot-server" +echo " - frontend/ (including pkg/)" \ No newline at end of file diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..0e09a0b --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "antibot-server" +version = "0.2.0" +edition = "2021" + +[dependencies] +shared = { path = "../shared" } +axum = "0.7" +tokio = { version = "1", features = ["full"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +rusqlite = { version = "0.31", features = ["bundled"] } +tracing = "0.1" +tracing-subscriber = "0.3" +tower = "0.4" +tower-http = { version = "0.5", features = ["cors", "fs"] } +hex = "0.4" +base64 = "0.22" +rand = "0.8" +ed25519-dalek = "2" \ No newline at end of file diff --git a/server/src/cleanup.rs b/server/src/cleanup.rs new file mode 100644 index 0000000..da8943e --- /dev/null +++ b/server/src/cleanup.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; +use crate::session::AppState; + +pub async fn cleanup_loop(state: Arc) { + loop { + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + let db = state.db.lock().await; // this is infallible + let now = crate::storage::current_time_ms(); + let _ = db.execute( + "DELETE FROM sessions WHERE expires_at < ?1", + rusqlite::params![now], + ); + } +} \ No newline at end of file diff --git a/server/src/crypto.rs b/server/src/crypto.rs new file mode 100644 index 0000000..3c6e4f9 --- /dev/null +++ b/server/src/crypto.rs @@ -0,0 +1,27 @@ +use ed25519_dalek::{VerifyingKey, Signature}; +use shared::protocol::HeartbeatRequest; + +pub fn verify_signature( + pub_key_bytes: &[u8], + req: &HeartbeatRequest, +) -> Result<(), Box> { + let pk = VerifyingKey::from_bytes( + &pub_key_bytes.try_into().map_err(|_| "invalid pubkey")?, + )?; + let sig_bytes = hex::decode(&req.signature)?; + let sig = Signature::from_slice(&sig_bytes)?; + + // Build canonical JSON exactly as client signed (sorted keys, no extra spaces) + let payload = serde_json::json!({ + "sessionId": req.session_id, + "prevHash": req.prev_hash, + "timestamp": req.timestamp, + "entropyData": req.entropy_data, + "stackState": req.stack_state, + "fingerprint": req.fingerprint, + }); + let message = serde_json::to_string(&payload)?; + + pk.verify_strict(message.as_bytes(), &sig)?; + Ok(()) +} \ No newline at end of file diff --git a/server/src/fingerprint.rs b/server/src/fingerprint.rs new file mode 100644 index 0000000..19f27b8 --- /dev/null +++ b/server/src/fingerprint.rs @@ -0,0 +1,10 @@ +use shared::protocol::Fingerprint; + +pub fn validate(fp: &Fingerprint) -> Result<(), Box> { + let ar: f64 = fp.aspect_ratio.parse().map_err(|_| "ar")?; + if ar < 0.5 || ar > 3.0 { return Err("aspect ratio".into()); } + let dpr: f64 = fp.device_pixel_ratio.parse().map_err(|_| "dpr")?; + if dpr <= 0.0 || dpr > 5.0 { return Err("dpr".into()); } + if fp.hardware_concurrency == 0 { return Err("hw".into()); } + Ok(()) +} \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..c1d395f --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,47 @@ +mod cleanup; +mod crypto; +mod fingerprint; +mod middleware; +mod ratelimit; +mod routes; +mod session; +mod storage; +mod trust; +mod vm; + +use axum::Router; +use std::sync::Arc; +use tokio::sync::Mutex; +use tracing::info; + +use session::AppState; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + let conn = storage::init_db().expect("DB init"); + let state = Arc::new(AppState { + db: Mutex::new(conn), + rate_limiter: Mutex::new(ratelimit::RateLimiter::new( + shared::constants::RATE_LIMIT_COUNT, + shared::constants::RATE_LIMIT_WINDOW_SECS, + )), + }); + + // Periodic cleanup + let bg_state = state.clone(); + tokio::spawn(async move { cleanup::cleanup_loop(bg_state).await }); + + let app = Router::new() + .route("/init", axum::routing::post(routes::init::handler)) + .route("/hb", axum::routing::post(routes::heartbeat::handler)) + .nest_service("/", tower_http::services::ServeDir::new("../frontend")) + .layer(tower_http::cors::CorsLayer::permissive()) + .layer(axum::middleware::from_fn(middleware::log_request)) + .with_state(state); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + info!("Server running on :3000"); + axum::serve(listener, app).await.unwrap(); +} \ No newline at end of file diff --git a/server/src/middleware.rs b/server/src/middleware.rs new file mode 100644 index 0000000..a39396b --- /dev/null +++ b/server/src/middleware.rs @@ -0,0 +1,11 @@ +use axum::extract::Request; +use axum::middleware::Next; +use axum::response::Response; + +pub async fn log_request(req: Request, next: Next) -> Response { + let method = req.method().clone(); + let uri = req.uri().clone(); + let response = next.run(req).await; + tracing::info!("{} {} -> {}", method, uri, response.status()); + response +} \ No newline at end of file diff --git a/server/src/ratelimit.rs b/server/src/ratelimit.rs new file mode 100644 index 0000000..852ac6c --- /dev/null +++ b/server/src/ratelimit.rs @@ -0,0 +1,27 @@ +use std::collections::HashMap; +use std::time::Instant; + +pub struct RateLimiter { + buckets: HashMap, + limit: u32, + window_secs: u64, +} + +impl RateLimiter { + pub fn new(limit: u32, window_secs: u64) -> Self { + Self { buckets: HashMap::new(), limit, window_secs } + } + pub fn check(&mut self, key: &str) -> bool { + let now = Instant::now(); + let entry = self.buckets.entry(key.to_string()).or_insert((0, now)); + if now.duration_since(entry.1).as_secs() >= self.window_secs { + *entry = (1, now); + true + } else if entry.0 >= self.limit { + false + } else { + entry.0 += 1; + true + } + } +} \ No newline at end of file diff --git a/server/src/routes/heartbeat.rs b/server/src/routes/heartbeat.rs new file mode 100644 index 0000000..731a37e --- /dev/null +++ b/server/src/routes/heartbeat.rs @@ -0,0 +1,30 @@ +use axum::{extract::State, http::StatusCode, Json}; +use std::sync::Arc; +use shared::protocol::{HeartbeatRequest, HeartbeatResponse}; +use crate::session::AppState; + +pub async fn handler( + State(state): State>, + Json(payload): Json, +) -> (StatusCode, Json) { + // Rate limiting + { + let mut rl = state.rate_limiter.lock().await; + if !rl.check(&payload.session_id) { + tracing::debug!("Rate limit hit: {}", payload.session_id); + return (StatusCode::OK, Json(HeartbeatResponse { status: "ok".into(), next_salt: None })); + } + } + + let db = state.db.lock().await; + match crate::session::verify_heartbeat(&db, &payload) { + Ok(next_salt) => ( + StatusCode::OK, + Json(HeartbeatResponse { status: "ok".into(), next_salt: Some(next_salt) }), + ), + Err(e) => { + tracing::warn!("Heartbeat failed for {}: {}", payload.session_id, e); + (StatusCode::OK, Json(HeartbeatResponse { status: "ok".into(), next_salt: None })) + } + } +} \ No newline at end of file diff --git a/server/src/routes/init.rs b/server/src/routes/init.rs new file mode 100644 index 0000000..f931968 --- /dev/null +++ b/server/src/routes/init.rs @@ -0,0 +1,17 @@ +use axum::{extract::State, http::StatusCode, Json}; +use std::sync::Arc; +use shared::protocol::{InitRequest, InitResponse}; +use crate::session::AppState; + +pub async fn handler( + State(state): State>, + Json(payload): Json, +) -> Result, (StatusCode, String)> { + let db = state.db.lock().await; + crate::session::create_session(&db, &payload.public_key) + .map(Json) + .map_err(|e| { + tracing::error!("Init error: {}", e); + (StatusCode::INTERNAL_SERVER_ERROR, "Internal".into()) + }) +} \ No newline at end of file diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs new file mode 100644 index 0000000..f2317ce --- /dev/null +++ b/server/src/routes/mod.rs @@ -0,0 +1,2 @@ +pub mod init; +pub mod heartbeat; diff --git a/server/src/session.rs b/server/src/session.rs new file mode 100644 index 0000000..de92101 --- /dev/null +++ b/server/src/session.rs @@ -0,0 +1,98 @@ +pub struct AppState { + pub db: tokio::sync::Mutex, + pub rate_limiter: tokio::sync::Mutex, +} + +use rusqlite::params; +use shared::protocol::{HeartbeatRequest, InitResponse}; +use crate::{crypto, trust, fingerprint, vm, storage}; + +pub fn create_session( + conn: &rusqlite::Connection, + pub_key_hex: &str, +) -> Result> { + let pub_key = hex::decode(pub_key_hex)?; + if pub_key.len() != shared::constants::SESSION_ID_LEN { + return Err("invalid pubkey len".into()); + } + let session_id = hex::encode(rand::random::<[u8; shared::constants::SESSION_ID_LEN]>()); + let salt = rand::random::<[u8; shared::constants::SALT_LEN]>(); + let now = storage::current_time_ms(); + let expires_at = now + (shared::constants::EXPIRATION_MINUTES as u64) * 60 * 1000; + + let initial_hash = shared::hashing::initial_hash(&session_id, &pub_key, &salt); + + conn.execute( + "INSERT INTO sessions (session_id, public_key, salt, last_hash, created_at, last_seen, expires_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![session_id, pub_key, salt.to_vec(), initial_hash, now, now, expires_at], + )?; + + let opcodes = vm::generate_random_program(8..=16); + let opcodes_b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &opcodes); + + Ok(InitResponse { + session_id, + salt: hex::encode(salt), + opcodes_b64, + initial_hash: hex::encode(&initial_hash), + expires_at, + }) +} + +pub fn verify_heartbeat( + conn: &rusqlite::Connection, + req: &HeartbeatRequest, +) -> Result> { + let mut stmt = conn.prepare( + "SELECT public_key, salt, last_hash, expires_at FROM sessions WHERE session_id = ?1", + )?; + let (pub_key, salt, stored_last_hash, expires_at): (Vec, Vec, Vec, u64) = + stmt.query_row(params![req.session_id], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + })?; + + let now = storage::current_time_ms(); + if now > expires_at { + return Err("expired".into()); + } + + // 1. Verify signature + crypto::verify_signature(&pub_key, req)?; + + // 2. Check chain continuity + if stored_last_hash != hex::decode(&req.prev_hash)? { + return Err("chain broken".into()); + } + + // 3. Time window + let diff = (now as i64) - (req.timestamp as i64); + if diff.abs() > shared::constants::MAX_TIMESTAMP_DRIFT_MS { + return Err("timestamp drift".into()); + } + + // 4. Trusted mouse & fingerprint + trust::validate_mouse(&req.entropy_data)?; + fingerprint::validate(&req.fingerprint)?; + + // 5. Compute new hash + let prev_hash_bytes = hex::decode(&req.prev_hash)?; + let new_hash = shared::hashing::next_chain_hash( + &prev_hash_bytes, + req.timestamp, + &req.entropy_data, + &req.stack_state, + &salt, + ); + + // 6. New salt for client + let next_salt = rand::random::<[u8; shared::constants::SALT_LEN]>(); + let next_salt_hex = hex::encode(next_salt); + + conn.execute( + "UPDATE sessions SET last_hash=?1, salt=?2, chain_length=chain_length+1, last_seen=?3 WHERE session_id=?4", + params![new_hash, next_salt.to_vec(), now, req.session_id], + )?; + + Ok(next_salt_hex) +} \ No newline at end of file diff --git a/server/src/storage.rs b/server/src/storage.rs new file mode 100644 index 0000000..fe7ffc7 --- /dev/null +++ b/server/src/storage.rs @@ -0,0 +1,23 @@ +use rusqlite::Connection; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn init_db() -> Result { + let conn = Connection::open_in_memory()?; + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS sessions ( + session_id TEXT PRIMARY KEY, + public_key BLOB NOT NULL, + salt BLOB NOT NULL, + last_hash BLOB NOT NULL, + chain_length INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + last_seen INTEGER NOT NULL, + expires_at INTEGER NOT NULL + );", + )?; + Ok(conn) +} + +pub fn current_time_ms() -> u64 { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64 +} \ No newline at end of file diff --git a/server/src/trust.rs b/server/src/trust.rs new file mode 100644 index 0000000..d85c665 --- /dev/null +++ b/server/src/trust.rs @@ -0,0 +1,31 @@ +use shared::protocol::EntropyData; + +pub fn validate_mouse(data: &EntropyData) -> Result<(), Box> { + let events = &data.events; + if events.len() < 3 { + return Err("few events".into()); + } + let mut total_dist = 0.0; + let mut pauses = 0u32; + for i in 1..events.len() { + let p = &events[i-1]; + let c = &events[i]; + let dx = c.x - p.x; + let dy = c.y - p.y; + let dt = (c.timestamp_ms - p.timestamp_ms).max(1.0); + let dist = (dx*dx + dy*dy).sqrt(); + total_dist += dist; + if dist < 0.2 && dt > 50.0 { pauses += 1; } + } + if total_dist < shared::constants::MIN_MOUSE_TOTAL_DIST { + return Err("insufficient distance".into()); + } + let avg_speed = total_dist / events.len() as f64; + if avg_speed > shared::constants::MAX_MOUSE_AVG_SPEED { + return Err("speed too high".into()); + } + if pauses < shared::constants::MIN_PAUSE_COUNT { + return Err("no pause".into()); + } + Ok(()) +} \ No newline at end of file diff --git a/server/src/vm.rs b/server/src/vm.rs new file mode 100644 index 0000000..451126d --- /dev/null +++ b/server/src/vm.rs @@ -0,0 +1,33 @@ +use rand::Rng; + +pub fn generate_random_program(len_range: std::ops::RangeInclusive) -> Vec { + // Same logic as earlier, using shared::hashing for HASH if needed + let mut rng = rand::thread_rng(); + let count = rng.gen_range(len_range); + let mut ops = Vec::new(); + let mut depth: i32 = 0; + for _ in 0..count { + if depth < 2 { + ops.push(0x00); // PUSH + let val = rng.gen::(); + ops.extend_from_slice(&val.to_le_bytes()); + depth += 1; + } else { + let op = rng.gen_range(0..10); + match op { + 0x00 => { + ops.push(0x00); + let val = rng.gen::(); + ops.extend_from_slice(&val.to_le_bytes()); + depth += 1; + } + 0x01..=0x08 => { ops.push(op as u8); depth -= 1; } + 0x09 => { ops.push(0x09); depth = 1; } + _ => unreachable!(), + } + } + } + ops +} + +// Server does not need to execute the program; client does. \ No newline at end of file diff --git a/shared/Cargo.toml b/shared/Cargo.toml new file mode 100644 index 0000000..390f21c --- /dev/null +++ b/shared/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "shared" +version = "0.2.0" +edition = "2021" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" # <- add this line +blake3 = "1" +hex = "0.4" +base64 = "0.22" +rand = "0.8" +ed25519-dalek = { version = "2", features = ["rand_core"] } \ No newline at end of file diff --git a/shared/src/constants.rs b/shared/src/constants.rs new file mode 100644 index 0000000..9a14d3a --- /dev/null +++ b/shared/src/constants.rs @@ -0,0 +1,11 @@ +pub const SESSION_ID_LEN: usize = 32; +pub const SALT_LEN: usize = 16; +pub const HEARTBEAT_MIN_INTERVAL_MS: u64 = 12_000; +pub const HEARTBEAT_MAX_INTERVAL_MS: u64 = 25_000; +pub const EXPIRATION_MINUTES: i64 = 30; +pub const RATE_LIMIT_COUNT: u32 = 5; +pub const RATE_LIMIT_WINDOW_SECS: u64 = 10; +pub const MAX_TIMESTAMP_DRIFT_MS: i64 = 30_000; +pub const MIN_MOUSE_TOTAL_DIST: f64 = 10.0; +pub const MAX_MOUSE_AVG_SPEED: f64 = 2.0; // px/ms +pub const MIN_PAUSE_COUNT: u32 = 1; \ No newline at end of file diff --git a/shared/src/hashing.rs b/shared/src/hashing.rs new file mode 100644 index 0000000..7c4ae30 --- /dev/null +++ b/shared/src/hashing.rs @@ -0,0 +1,42 @@ +use blake3::Hasher; +use crate::protocol::{EntropyData, StackState}; + +/// Initial hash for a brand-new session: Blake3(session_id || pub_key || salt) +pub fn initial_hash(session_id: &str, pub_key: &[u8], salt: &[u8]) -> Vec { + let mut h = Hasher::new(); + h.update(session_id.as_bytes()); + h.update(pub_key); + h.update(salt); + h.finalize().as_bytes().to_vec() +} + +/// Next hash in the chain: Blake3 with the salt mixed in (no keyed mode needed) +pub fn next_chain_hash( + prev_hash: &[u8], + timestamp: u64, + entropy: &EntropyData, + stack: &StackState, + salt: &[u8], +) -> Vec { + let entropy_json = serde_json::to_string(entropy).unwrap(); + let stack_json = serde_json::to_string(stack).unwrap(); + + let entropy_hash = blake3::hash(entropy_json.as_bytes()); + let stack_hash = blake3::hash(stack_json.as_bytes()); + + let mut h = Hasher::new(); + // Mix the salt into the hash state + h.update(salt); + h.update(prev_hash); + h.update(×tamp.to_le_bytes()); + h.update(entropy_hash.as_bytes()); + h.update(stack_hash.as_bytes()); + h.finalize().as_bytes().to_vec() +} + +/// Hash of all stack items for VM HASH opcode +pub fn hash_stack(stack: &[u32]) -> u32 { + let data: Vec = stack.iter().flat_map(|x| x.to_le_bytes()).collect(); + let hash = blake3::hash(&data); + u32::from_le_bytes(hash.as_bytes()[..4].try_into().unwrap()) +} \ No newline at end of file diff --git a/shared/src/lib.rs b/shared/src/lib.rs new file mode 100644 index 0000000..7e45bb6 --- /dev/null +++ b/shared/src/lib.rs @@ -0,0 +1,3 @@ +pub mod constants; +pub mod hashing; +pub mod protocol; \ No newline at end of file diff --git a/shared/src/protocol.rs b/shared/src/protocol.rs new file mode 100644 index 0000000..44a62fc --- /dev/null +++ b/shared/src/protocol.rs @@ -0,0 +1,62 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub struct InitRequest { + pub public_key: String, +} + +#[derive(Serialize)] +pub struct InitResponse { + pub session_id: String, + pub salt: String, + pub opcodes_b64: String, + pub initial_hash: String, + pub expires_at: u64, +} + +#[derive(Deserialize, Serialize)] +pub struct HeartbeatRequest { + pub session_id: String, + pub prev_hash: String, + pub timestamp: u64, + pub entropy_data: EntropyData, + pub stack_state: StackState, + pub fingerprint: Fingerprint, + pub signature: String, +} + +#[derive(Serialize)] +pub struct HeartbeatResponse { + pub status: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub next_salt: Option, +} + +#[derive(Deserialize, Serialize)] +pub struct Fingerprint { + #[serde(rename = "aspectRatio")] + pub aspect_ratio: String, + #[serde(rename = "devicePixelRatio")] + pub device_pixel_ratio: String, + #[serde(rename = "hardwareConcurrency")] + pub hardware_concurrency: u32, +} + +#[derive(Deserialize, Serialize)] +pub struct EntropyData { + pub events: Vec, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct MouseEvent { + pub x: f64, + pub y: f64, + #[serde(rename = "t")] + pub timestamp_ms: f64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StackState { + pub stack: Vec, + pub ip: u16, +} \ No newline at end of file diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml new file mode 100644 index 0000000..c464c5b --- /dev/null +++ b/wasm/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "antibot-wasm" +version = "0.2.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +shared = { path = "../shared" } +wasm-bindgen = "0.2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +ed25519-dalek = { version = "2", features = ["rand_core"] } +rand = "0.8" # <-- add this +blake3 = "1" +getrandom = { version = "0.2", features = ["js"] } +hex = "0.4" +base64 = "0.22" +serde-wasm-bindgen = "0.6" \ No newline at end of file diff --git a/wasm/src/anti_debug.rs b/wasm/src/anti_debug.rs new file mode 100644 index 0000000..edc1522 --- /dev/null +++ b/wasm/src/anti_debug.rs @@ -0,0 +1,2 @@ +// Example: break debugger detection, console clearing, etc. +// Currently empty. \ No newline at end of file diff --git a/wasm/src/crypto.rs b/wasm/src/crypto.rs new file mode 100644 index 0000000..e759817 --- /dev/null +++ b/wasm/src/crypto.rs @@ -0,0 +1,47 @@ +use ed25519_dalek::{SigningKey, Signer}; +use std::cell::RefCell; +use wasm_bindgen::prelude::*; + +thread_local! { + static KEYPAIR: RefCell> = RefCell::new(None); +} + +#[wasm_bindgen] +pub fn generate_keypair() -> String { + let mut rng = rand::thread_rng(); + let sk = SigningKey::generate(&mut rng); + let pk = sk.verifying_key(); + let hex_pub = hex::encode(pk.as_bytes()); + KEYPAIR.with(|kp| *kp.borrow_mut() = Some(sk)); + hex_pub +} + +#[wasm_bindgen] +pub fn get_public_key() -> String { + KEYPAIR.with(|kp| hex::encode(kp.borrow().as_ref().unwrap().verifying_key().as_bytes())) +} + +#[wasm_bindgen] +pub fn sign_message(message_json: &str) -> String { + KEYPAIR.with(|kp| { + let sk = kp.borrow(); + let sig = sk.as_ref().unwrap().sign(message_json.as_bytes()); + hex::encode(sig.to_bytes()) + }) +} + +#[wasm_bindgen] +pub fn compute_next_hash( + prev_hash_hex: &str, + timestamp: u64, + entropy_data_json: &str, + stack_state_json: &str, + salt_hex: &str, +) -> String { + let prev = hex::decode(prev_hash_hex).unwrap(); + let salt = hex::decode(salt_hex).unwrap(); + let entropy = serde_json::from_str::(entropy_data_json).unwrap(); + let stack = serde_json::from_str::(stack_state_json).unwrap(); + let new = shared::hashing::next_chain_hash(&prev, timestamp, &entropy, &stack, &salt); + hex::encode(&new) +} \ No newline at end of file diff --git a/wasm/src/entropy.rs b/wasm/src/entropy.rs new file mode 100644 index 0000000..67915a1 --- /dev/null +++ b/wasm/src/entropy.rs @@ -0,0 +1,2 @@ +// This module is handled on the JS side; WASM only receives the prepared entropy data. +// Could be used to add extra entropy sources (e.g., from JS via import). \ No newline at end of file diff --git a/wasm/src/fingerprint.rs b/wasm/src/fingerprint.rs new file mode 100644 index 0000000..f3274de --- /dev/null +++ b/wasm/src/fingerprint.rs @@ -0,0 +1 @@ +// Fingerprint collection is done in JS, this module is a placeholder. \ No newline at end of file diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs new file mode 100644 index 0000000..f6b57e6 --- /dev/null +++ b/wasm/src/lib.rs @@ -0,0 +1,6 @@ +pub mod anti_debug; +pub mod crypto; +pub mod entropy; +pub mod fingerprint; +pub mod transport; +pub mod vm; \ No newline at end of file diff --git a/wasm/src/transport.rs b/wasm/src/transport.rs new file mode 100644 index 0000000..6b16e53 --- /dev/null +++ b/wasm/src/transport.rs @@ -0,0 +1 @@ +// Could contain WebTransport related code if needed later. \ No newline at end of file diff --git a/wasm/src/vm.rs b/wasm/src/vm.rs new file mode 100644 index 0000000..f07b695 --- /dev/null +++ b/wasm/src/vm.rs @@ -0,0 +1,55 @@ +use wasm_bindgen::prelude::*; +use shared::protocol::StackState; + +#[wasm_bindgen] +pub fn run_program(program_b64: &str) -> JsValue { + use base64::Engine; + let bytes = base64::engine::general_purpose::STANDARD.decode(program_b64).unwrap(); + let state = execute(&bytes); + serde_wasm_bindgen::to_value(&state).unwrap() +} + +fn execute(program: &[u8]) -> StackState { + let mut stack: Vec = Vec::new(); + let mut ip: usize = 0; + while ip < program.len() { + let op = program[ip]; + ip += 1; + match op { + 0x00 => { + if ip + 4 > program.len() { break; } + let val = u32::from_le_bytes([program[ip], program[ip+1], program[ip+2], program[ip+3]]); + ip += 4; + stack.push(val); + } + 0x01..=0x07 => { + if stack.len() < 2 { break; } + let b = stack.pop().unwrap(); + let a = stack.pop().unwrap(); + let r = match op { + 0x01 => a.wrapping_add(b), + 0x02 => a.wrapping_sub(b), + 0x03 => a.wrapping_mul(b), + 0x04 => a ^ b, + 0x05 => a & b, + 0x06 => a | b, + 0x07 => a.rotate_left(b % 32), + _ => unreachable!(), + }; + stack.push(r); + } + 0x08 => { + if stack.is_empty() { break; } + let a = stack.pop().unwrap(); + stack.push(!a); + } + 0x09 => { + let r = shared::hashing::hash_stack(&stack); + stack.clear(); + stack.push(r); + } + _ => break, + } + } + StackState { stack, ip: ip as u16 } +} \ No newline at end of file