Compare commits

..

14 Commits

Author SHA1 Message Date
Alice Bowman
60a411e363 removed m1 cargo test 2024-06-17 15:39:43 +02:00
Alice Bowman
caf8801004 trialing cargo m1 test 2024-06-13 15:42:02 +02:00
Alice Bowman
858c0904ad refactored different cargo os tests into an array 2024-06-13 15:19:20 +02:00
Alice Bowman
e9838722b0 changed qc arm test to mac-14 to avoid bug 2024-06-13 15:12:34 +02:00
Alice Bowman
69043fbae7 added Mac ARM cargo test 2024-06-13 15:01:20 +02:00
Prabhpreet Dua
a70b71d509 Include uncommited changes 2024-06-13 17:35:10 +05:30
Prabhpreet Dua
96bed38ad0 Enable privileged only on linux 2024-06-13 17:34:48 +05:30
Prabhpreet Dua
a75d7a53e0 Add cfg flag to bin .rs files 2024-06-13 17:19:57 +05:30
Prabhpreet Dua
c4314c0eff Limit wireguard broker privledged/socket handler to linux 2024-06-13 17:13:00 +05:30
Alice Bowman
511a59fe1b Alter target OS for mod.rs 2024-06-13 13:18:23 +02:00
Alice Bowman
5e4a7c3b7f netlink.rs and cargo.toml features re: xplatform 2024-06-13 13:07:51 +02:00
Alice Bowman
139e62d1fb fixed indentation 2024-06-13 12:34:06 +02:00
Alice Bowman
c417fafe2a fixed qc-yaml test name 2024-06-13 12:30:56 +02:00
Alice Bowman
8f4141a159 added cargo-test runner for macos 86-64 2024-06-13 12:27:58 +02:00
88 changed files with 310 additions and 3374 deletions

View File

@@ -1,33 +0,0 @@
#!/usr/bin/env bash
iterations="$1"
sleep_time="$2"
PWD="$(pwd)"
EXEC="$PWD/target/release/rosenpass"
LOGS="$PWD/output/logs"
mkdir -p "$LOGS"
run_command() {
local file=$1
local log_file="$2"
("$EXEC" exchange-config "$file" 2>&1 | tee -a "$log_file") &
echo $!
}
pids=()
(cd output/dut && run_command "configs/dut-$iterations.toml" "$LOGS/dut.log")
for (( x=0; x<iterations; x++ )); do
(cd output/ate && run_command "configs/ate-$x.toml" "$LOGS/ate-$x.log") & pids+=($!)
done
sleep "$sleep_time"
lsof -i :9999 | awk 'NR!=1 {print $2}' | xargs kill
for (( x=0; x<iterations; x++ )); do
port=$((x + 50000))
lsof -i :$port | awk 'NR!=1 {print $2}' | xargs kill
done

View File

@@ -1,21 +0,0 @@
name: QC
on:
pull_request:
push:
branches: [main]
permissions:
checks: write
contents: read
jobs:
multi-peer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: cargo build --bin rosenpass --release
- run: python misc/generate_configs.py
- run: chmod +x .ci/run-regression.sh
- run: .ci/run-regression.sh 100 20
- run: |
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]

5
.gitignore vendored
View File

@@ -20,8 +20,3 @@ _markdown_*
**/result
**/result-*
.direnv
# Visual studio code
.vscode
/output

View File

@@ -1 +0,0 @@
Clara Engler <cve@cve.cx> <me@emilengler.com>

View File

@@ -1,38 +0,0 @@
**Making a new Release of Rosenpass — Cooking Recipe**
If you have to change a file, do what it takes to get the change as commit on the main branch, then **start from step 0**.
If any other issue occurs
0. Make sure you are in the root directory of the project
- `cd "$(git rev-parse --show-toplevel)"`
1. Make sure you locally checked out the head of the main branch
- `git stash --include-untracked && git checkout main && git pull`
2. Make sure all tests pass
- `cargo test`
3. Make sure the current version in `rosenpass/Cargo.toml` matches that in the [last release on GitHub](https://github.com/rosenpass/rosenpass/releases)
- Only normal releases count, release candidates and draft releases can be ignored
4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)
- See `cargo release --help` for more information on the available release types
- Pick `rc` if in doubt
5. Try to release a new version
- `cargo release rc --package rosenpass`
- An issue was reported? Go fix it, start again with step 0!
6. Actually make the release
- `cargo release rc --package rosenpass --execute`
- Tentatively wait for any interactions, such as entering ssh keys etc.
- You may be asked for your ssh key multiple times!
**Frequently Asked Questions (FAQ)**
- You have untracked files, which `cargo release` complains about?
- `git stash --include-untracked`
- You cannot push to crates.io because you are not logged in?
- Follow the steps displayed in [`cargo login`](https://doc.rust-lang.org/cargo/commands/cargo-login.html)
- How is the release page added to [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) itself?
- Our CI Pipeline will create the release, once `cargo release` pushed the new version tag to the repo. The new release should pop up almost immediately in [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) after the [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml) pipeline started.
- No new release pops up in the `Release` sidebar element on the [main page](https://github.com/rosenpass/rosenpass)
- Did you push a `rc` release? This view only shows non-draft release, but `rc` releases are considered as draft. See [Releases](https://github.com/rosenpass/rosenpass/releases) page to see all (including draft!) releases.
- The release page was created on GitHub, but there are no assets/artifacts other than the source code tar ball/zip?
- The artifacts are generated and pushed automatically to the release, but this takes some time (a couple of minutes). You can check the respective CI pipeline: [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml), which should start immediately after `cargo release` pushed the new release tag to the repo. The release artifacts only are added later to the release, once all jobs in bespoke pipeline finished.
- How are the release artifacts generated, and what are they?
- The release artifacts are built using one Nix derivation per platform, `nix build .#release-package`. It contains both statically linked versions of `rosenpass` itself and OCI container images.

203
Cargo.lock generated
View File

@@ -387,9 +387,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.13"
version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -397,9 +397,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.13"
version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
dependencies = [
"anstream",
"anstyle",
@@ -409,9 +409,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.13"
version = "4.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
dependencies = [
"heck",
"proc-macro2",
@@ -984,10 +984,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -1074,12 +1072,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "home"
version = "0.5.9"
@@ -1236,38 +1228,6 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libcrux"
version = "0.0.2-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d9dcd435758db03438089760c55a45e6bcab7e4e299ee261f75225ab29d482"
dependencies = [
"getrandom 0.2.15",
"libcrux-hacl",
"libcrux-platform",
"libjade-sys",
"rand 0.8.5",
]
[[package]]
name = "libcrux-hacl"
version = "0.0.2-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52b2581ce493c5c22700077b5552b47be69b67b8176716572b02856218db0b68"
dependencies = [
"cc",
"libcrux-platform",
]
[[package]]
name = "libcrux-platform"
version = "0.0.2-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "647e39666194b11df17c19451d1154b9be79df98b9821532560c2ecad0cf3410"
dependencies = [
"libc",
]
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
@@ -1279,16 +1239,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "libjade-sys"
version = "0.0.2-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec4d22bba476bf8f5aebe36ccfd0e56dba8707e0c3b5c76996576028f48ffb8e"
dependencies = [
"cc",
"libcrux-platform",
]
[[package]]
name = "libloading"
version = "0.8.3"
@@ -1317,9 +1267,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
@@ -1391,15 +1341,14 @@ dependencies = [
[[package]]
name = "mio"
version = "1.0.1"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -1584,6 +1533,16 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.3.9",
"libc",
]
[[package]]
name = "object"
version = "0.35.0"
@@ -1932,17 +1891,14 @@ name = "rosenpass"
version = "0.2.1"
dependencies = [
"anyhow",
"clap 4.5.13",
"clap 4.5.7",
"criterion",
"derive_builder 0.20.0",
"env_logger",
"heck",
"hex",
"hex-literal",
"home",
"log",
"memoffset 0.9.1",
"mio 1.0.1",
"mio 0.8.11",
"paste",
"procspawn",
"rand 0.8.5",
@@ -1957,12 +1913,10 @@ dependencies = [
"serial_test",
"stacker",
"static_assertions",
"tempfile",
"test_bin",
"thiserror",
"toml",
"zerocopy",
"zeroize",
]
[[package]]
@@ -1976,7 +1930,6 @@ dependencies = [
"anyhow",
"blake2",
"chacha20poly1305",
"libcrux",
"rosenpass-constant-time",
"rosenpass-oqs",
"rosenpass-secret-memory",
@@ -2050,12 +2003,9 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64ct",
"mio 1.0.1",
"rustix",
"static_assertions",
"thiserror",
"typenum",
"zerocopy",
"zeroize",
]
@@ -2064,11 +2014,11 @@ name = "rosenpass-wireguard-broker"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.5.13",
"clap 4.5.7",
"derive_builder 0.20.0",
"env_logger",
"log",
"mio 1.0.1",
"mio 0.8.11",
"postcard",
"procspawn",
"rand 0.8.5",
@@ -2204,18 +2154,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.204"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.204"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
@@ -2422,18 +2372,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "thiserror"
version = "1.0.63"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
@@ -2452,27 +2402,28 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.39.1"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio 1.0.1",
"mio 0.8.11",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
@@ -2706,6 +2657,15 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -2730,6 +2690,21 @@ dependencies = [
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
@@ -2752,6 +2727,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
@@ -2764,6 +2745,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
@@ -2776,6 +2763,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
@@ -2794,6 +2787,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
@@ -2806,6 +2805,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
@@ -2818,6 +2823,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
@@ -2830,6 +2841,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
@@ -2883,9 +2900,9 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.35"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"byteorder",
"zerocopy-derive",
@@ -2893,9 +2910,9 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -35,7 +35,7 @@ doc-comment = "0.3.3"
base64ct = {version = "1.6.0", default-features=false}
zeroize = "1.8.1"
memoffset = "0.9.1"
thiserror = "1.0.63"
thiserror = "1.0.61"
paste = "1.0.15"
env_logger = "0.10.2"
toml = "0.7.8"
@@ -44,30 +44,26 @@ allocator-api2 = "0.2.14"
memsec = { git="https://github.com/rosenpass/memsec.git" ,rev="aceb9baee8aec6844125bd6612f92e9a281373df", features = [ "alloc_ext", ] }
rand = "0.8.5"
typenum = "1.17.0"
log = { version = "0.4.22" }
clap = { version = "4.5.13", features = ["derive"] }
serde = { version = "1.0.204", features = ["derive"] }
log = { version = "0.4.21" }
clap = { version = "4.5.7", features = ["derive"] }
serde = { version = "1.0.203", features = ["derive"] }
arbitrary = { version = "1.3.2", features = ["derive"] }
anyhow = { version = "1.0.86", features = ["backtrace", "std"] }
mio = { version = "1.0.1", features = ["net", "os-poll"] }
mio = { version = "0.8.11", features = ["net", "os-poll"] }
oqs-sys = { version = "0.9.1", default-features = false, features = [
'classic_mceliece',
'kyber',
'classic_mceliece',
'kyber',
] }
blake2 = "0.10.6"
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
"std",
"heapless",
"std",
"heapless",
] }
zerocopy = { version = "0.7.35", features = ["derive"] }
zerocopy = { version = "0.7.34", features = ["derive"] }
home = "0.5.9"
derive_builder = "0.20.0"
tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.38", features = ["macros", "rt-multi-thread"] }
postcard= {version = "1.0.8", features = ["alloc"]}
libcrux = { version = "0.0.2-pre.2" }
hex-literal = { version = "0.4.1" }
hex = { version = "0.4.3" }
heck = { version = "0.5.0" }
#Dev dependencies
serial_test = "3.1.1"
@@ -83,4 +79,4 @@ procspawn = {version = "1.0.0", features= ["test-support"]}
#Broker dependencies (might need cleanup or changes)
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
command-fds = "0.2.3"
rustix = { version = "0.38.27", features = ["net", "fs"] }
rustix = { version = "0.38.27", features = ["net"] }

View File

@@ -10,6 +10,8 @@
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
use std::result::Result;
/// Key Encapsulation Mechanism
///
/// The KEM interface defines three operations: Key generation, key encapsulation and key

View File

@@ -9,9 +9,6 @@ homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[features]
experiment_libcrux = ["dep:libcrux"]
[dependencies]
anyhow = { workspace = true }
rosenpass-to = { workspace = true }
@@ -23,4 +20,3 @@ static_assertions = { workspace = true }
zeroize = { workspace = true }
chacha20poly1305 = { workspace = true }
blake2 = { workspace = true }
libcrux = { workspace = true, optional = true }

View File

@@ -9,12 +9,7 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN);
/// Authenticated encryption with associated data
pub mod aead {
#[cfg(not(feature = "experiment_libcrux"))]
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
#[cfg(feature = "experiment_libcrux")]
pub use crate::subtle::chacha20poly1305_ietf_libcrux::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
}
/// Authenticated encryption with associated data with a constant nonce

View File

@@ -1,60 +0,0 @@
use rosenpass_to::ops::copy_slice;
use rosenpass_to::To;
use zeroize::Zeroize;
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
pub const TAG_LEN: usize = 16;
pub const NONCE_LEN: usize = 12;
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
use libcrux::aead as C;
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
let crux_iv = C::Iv(nonce.try_into().unwrap());
copy_slice(plaintext).to(ciphertext);
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
copy_slice(crux_tag.as_ref()).to(mac);
match crux_key {
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
_ => panic!(),
}
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
use libcrux::aead as C;
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
let crux_iv = C::Iv(nonce.try_into().unwrap());
let crux_tag = C::Tag::from_slice(mac).unwrap();
copy_slice(ciphertext).to(plaintext);
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
match crux_key {
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
_ => panic!(),
}
Ok(())
}

View File

@@ -1,7 +1,4 @@
pub mod blake2b;
#[cfg(not(feature = "experiment_libcrux"))]
pub mod chacha20poly1305_ietf;
#[cfg(feature = "experiment_libcrux")]
pub mod chacha20poly1305_ietf_libcrux;
pub mod incorrect_hmac_blake2b;
pub mod xchacha20poly1305_ietf;

View File

@@ -7,16 +7,18 @@
///
/// The execution time of the function grows approx. linear with the length of the input. This is
/// considered safe.
///
/// ## Tests
/// [`tests::memcmp_runs_in_constant_time`] runs a stasticial test that the equality of the two
/// input parameters does not correlate with the run time.
///
/// For discussion on how to (further) ensure the constant-time execution of this function,
/// see <https://github.com/rosenpass/rosenpass/issues/232>
#[inline]
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
}
/// [tests::memcmp_runs_in_constant_time] runs a stasticial test that the equality of the two
/// input parameters does not correlate with the run time.
///
/// For discussion on how to (further) ensure the constant-time execution of this function,
/// see <https://github.com/rosenpass/rosenpass/issues/232>
#[cfg(all(test, feature = "constant_time_tests"))]
mod tests {
use super::*;

View File

@@ -108,7 +108,7 @@ Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
.Pp
This manual page was written by
.An Clara Engler
.An Emil Engler
.Sh BUGS
The bugs are tracked at
.Lk https://github.com/rosenpass/rosenpass/issues .

View File

@@ -113,7 +113,7 @@ Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
.Pp
This manual page was written by
.An Clara Engler
.An Emil Engler
.Sh BUGS
The bugs are tracked at
.Lk https://github.com/rosenpass/rosenpass/issues .

View File

@@ -411,12 +411,12 @@
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
inputsFrom = [ packages.default ];
nativeBuildInputs = with pkgs; [
inputs.fenix.packages.${system}.complete.toolchain
cmake # override the fakecmake from the main step above
cargo-release
clippy
nodePackages.prettier
nushell # for the .ci/gen-workflow-files.nu script
rustfmt
packages.proverif-patched
];
};

View File

@@ -4,9 +4,6 @@ version = "0.0.1"
publish = false
edition = "2021"
[features]
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
[package.metadata]
cargo-fuzz = true
@@ -84,4 +81,4 @@ doc = false
name = "fuzz_vec_secret_alloc_memfdsec_mallocfb"
path = "fuzz_targets/vec_secret_alloc_memfdsec_mallocfb.rs"
test = false
doc = false
doc = false

View File

@@ -15,7 +15,8 @@ pub struct Input {
}
fuzz_target!(|input: Input| {
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
ciphertext.resize(input.plaintext.len() + 16, 0);
aead::encrypt(
ciphertext.as_mut_slice(),

View File

@@ -7,14 +7,14 @@ use rosenpass::protocol::CryptoServer;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::policy::*;
use rosenpass_secret_memory::{PublicBox, Secret};
use rosenpass_secret_memory::Secret;
use std::sync::Once;
static ONCE: Once = Once::new();
fuzz_target!(|rx_buf: &[u8]| {
ONCE.call_once(secret_policy_use_only_malloc_secrets);
let sk = Secret::from_slice(&[0; StaticKem::SK_LEN]);
let pk = PublicBox::from_slice(&[0; StaticKem::PK_LEN]);
let pk = Secret::from_slice(&[0; StaticKem::PK_LEN]);
let mut cs = CryptoServer::new(sk, pk);
let mut tx_buf = [0; 10240];

View File

@@ -14,7 +14,7 @@ pub struct Input {
fuzz_target!(|input: Input| {
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
let mut shared_secret = [0u8; EphemeralKem::SK_LEN];
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
});

View File

@@ -1,14 +1,14 @@
from pathlib import Path
from subprocess import run
import os
config = dict(
peer_counts=[1, 5, 10, 50, 100, 500],
peer_count_max=100,
ate_ip="127.0.0.1",
dut_ip="127.0.0.1",
ate_ip="192.168.2.1",
dut_ip="192.168.2.4",
dut_port=9999,
path_to_rosenpass_bin=os.getcwd() + "/target/release/rosenpass",
path_to_rosenpass_bin="/Users/user/src/rosenppass/rosenpass/target/debug/rosenpass",
)
print(config)

View File

@@ -66,8 +66,6 @@ A wrapper script provides instant feedback about which queries execute as expect
# Getting Rosenpass
Documentation and installation guides can be found at the [Rosenpass website](https://rosenpass.eu/docs).
Rosenpass is packaged for more and more distributions, maybe also for the distribution of your choice?
[![Packaging status](https://repology.org/badge/vertical-allrepos/rosenpass.svg)](https://repology.org/project/rosenpass/versions)

View File

@@ -13,15 +13,6 @@ readme = "readme.md"
name = "rosenpass"
path = "src/main.rs"
[[bin]]
name = "rosenpass-gen-ipc-msg-types"
path = "src/bin/gen-ipc-msg-types.rs"
required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"]
[[test]]
name = "api-integration-tests"
required-features = ["experiment_api", "internal_testing"]
[[bench]]
name = "handshake"
harness = false
@@ -49,10 +40,6 @@ zerocopy = { workspace = true }
home = { workspace = true }
derive_builder = {workspace = true}
rosenpass-wireguard-broker = {workspace = true}
zeroize = { workspace = true }
hex-literal = { workspace = true, optional = true }
hex = { workspace = true, optional = true }
heck = { workspace = true, optional = true }
[build-dependencies]
anyhow = { workspace = true }
@@ -63,12 +50,7 @@ test_bin = { workspace = true }
stacker = { workspace = true }
serial_test = {workspace = true}
procspawn = {workspace = true}
tempfile = { workspace = true }
[features]
enable_broker_api = ["rosenpass-wireguard-broker/enable_broker_api"]
experiment_memfd_secret = []
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
experiment_api = ["hex-literal"]
internal_testing = []
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
enable_memfd_alloc = []

View File

@@ -1,6 +1,5 @@
use anyhow::Result;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
use std::ops::DerefMut;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
@@ -41,7 +40,7 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
fn keygen() -> Result<(SSk, SPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
Ok((sk, pk))
}

View File

@@ -1,116 +0,0 @@
use zerocopy::{ByteSlice, Ref};
use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt};
use super::{
PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef,
ResponseMsgType, ResponseRef,
};
pub trait ByteSliceRefExt: ByteSlice {
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
self.zk_ref_maker()
}
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse()
}
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_prefix()
}
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_suffix()
}
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker().parse_request_msg_type()
}
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker()
.from_prefix()?
.parse_request_msg_type()
}
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker()
.from_suffix()?
.parse_request_msg_type()
}
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker().parse_response_msg_type()
}
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker()
.from_prefix()?
.parse_response_msg_type()
}
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker()
.from_suffix()?
.parse_response_msg_type()
}
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse(self)
}
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse_from_prefix(self)
}
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse_from_suffix(self)
}
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse(self)
}
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse_from_prefix(self)
}
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse_from_suffix(self)
}
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
self.zk_ref_maker()
}
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse()
}
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_prefix()
}
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_suffix()
}
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
self.zk_ref_maker()
}
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse()
}
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_prefix()
}
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_suffix()
}
}
impl<B: ByteSlice> ByteSliceRefExt for B {}

View File

@@ -1,29 +0,0 @@
use zerocopy::{ByteSliceMut, Ref};
use rosenpass_util::zerocopy::RefMaker;
use super::RawMsgType;
pub trait Message {
type Payload;
type MessageClass: Into<RawMsgType>;
const MESSAGE_TYPE: Self::MessageClass;
fn from_payload(payload: Self::Payload) -> Self;
fn init(&mut self);
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
}
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
}
impl<B, T> ZerocopyResponseMakerSetupMessageExt<B, T> for RefMaker<B, T>
where
B: ByteSliceMut,
T: Message,
{
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
T::setup(self.into_buf())
}
}

View File

@@ -1,117 +0,0 @@
use hex_literal::hex;
use rosenpass_util::zerocopy::RefMaker;
use zerocopy::ByteSlice;
use crate::RosenpassError::{self, InvalidApiMessageType};
pub type RawMsgType = u128;
// constants generated by gen-ipc-msg-types:
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request
pub const PING_REQUEST: RawMsgType =
RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d 0b02 ea31 45d3 4999"));
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Response
pub const PING_RESPONSE: RawMsgType =
RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260"));
pub trait MessageAttributes {
fn message_size(&self) -> usize;
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum RequestMsgType {
Ping,
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum ResponseMsgType {
Ping,
}
impl MessageAttributes for RequestMsgType {
fn message_size(&self) -> usize {
match self {
Self::Ping => std::mem::size_of::<super::PingRequest>(),
}
}
}
impl MessageAttributes for ResponseMsgType {
fn message_size(&self) -> usize {
match self {
Self::Ping => std::mem::size_of::<super::PingResponse>(),
}
}
}
impl TryFrom<RawMsgType> for RequestMsgType {
type Error = RosenpassError;
fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
use RequestMsgType as E;
Ok(match value {
self::PING_REQUEST => E::Ping,
_ => return Err(InvalidApiMessageType(value)),
})
}
}
impl From<RequestMsgType> for RawMsgType {
fn from(val: RequestMsgType) -> Self {
use RequestMsgType as E;
match val {
E::Ping => self::PING_REQUEST,
}
}
}
impl TryFrom<RawMsgType> for ResponseMsgType {
type Error = RosenpassError;
fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
use ResponseMsgType as E;
Ok(match value {
self::PING_RESPONSE => E::Ping,
_ => return Err(InvalidApiMessageType(value)),
})
}
}
impl From<ResponseMsgType> for RawMsgType {
fn from(val: ResponseMsgType) -> Self {
use ResponseMsgType as E;
match val {
E::Ping => self::PING_RESPONSE,
}
}
}
pub trait RawMsgTypeExt {
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
}
impl RawMsgTypeExt for RawMsgType {
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError> {
self.try_into()
}
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError> {
self.try_into()
}
}
pub trait RefMakerRawMsgTypeExt {
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
}
impl<B: ByteSlice> RefMakerRawMsgTypeExt for RefMaker<B, RawMsgType> {
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType> {
Ok(self.parse()?.read().try_into()?)
}
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
Ok(self.parse()?.read().try_into()?)
}
}

View File

@@ -1,17 +0,0 @@
mod byte_slice_ext;
mod message_trait;
mod message_type;
mod payload;
mod request_ref;
mod request_response;
mod response_ref;
mod server;
pub use byte_slice_ext::*;
pub use message_trait::*;
pub use message_type::*;
pub use payload::*;
pub use request_ref::*;
pub use request_response::*;
pub use response_ref::*;
pub use server::*;

View File

@@ -1,96 +0,0 @@
use rosenpass_util::zerocopy::ZerocopyMutSliceExt;
use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
/// Size required to fit any message in binary form
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct Envelope<M: AsBytes + FromBytes> {
/// Which message this is
pub msg_type: RawMsgType,
/// The actual Paylod
pub payload: M,
}
pub type RequestEnvelope<M> = Envelope<M>;
pub type ResponseEnvelope<M> = Envelope<M>;
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct PingRequestPayload {
/// Randomly generated connection id
pub echo: [u8; 256],
}
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
impl PingRequest {
pub fn new(echo: [u8; 256]) -> Self {
Self::from_payload(PingRequestPayload { echo })
}
}
impl Message for PingRequest {
type Payload = PingRequestPayload;
type MessageClass = RequestMsgType;
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::Ping;
fn from_payload(payload: Self::Payload) -> Self {
Self {
msg_type: Self::MESSAGE_TYPE.into(),
payload,
}
}
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
r.init();
Ok(r)
}
fn init(&mut self) {
self.msg_type = Self::MESSAGE_TYPE.into();
}
}
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct PingResponsePayload {
/// Randomly generated connection id
pub echo: [u8; 256],
}
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
impl PingResponse {
pub fn new(echo: [u8; 256]) -> Self {
Self::from_payload(PingResponsePayload { echo })
}
}
impl Message for PingResponse {
type Payload = PingResponsePayload;
type MessageClass = ResponseMsgType;
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::Ping;
fn from_payload(payload: Self::Payload) -> Self {
Self {
msg_type: Self::MESSAGE_TYPE.into(),
payload,
}
}
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
r.init();
Ok(r)
}
fn init(&mut self) {
self.msg_type = Self::MESSAGE_TYPE.into();
}
}

View File

@@ -1,107 +0,0 @@
use anyhow::ensure;
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
struct RequestRefMaker<B> {
buf: B,
msg_type: RequestMsgType,
}
impl<B: ByteSlice> RequestRef<B> {
pub fn parse(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.parse()
}
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.from_prefix()?.parse()
}
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.from_suffix()?.parse()
}
pub fn message_type(&self) -> RequestMsgType {
match self {
Self::Ping(_) => RequestMsgType::Ping,
}
}
}
impl<B> From<Ref<B, PingRequest>> for RequestRef<B> {
fn from(v: Ref<B, PingRequest>) -> Self {
Self::Ping(v)
}
}
impl<B: ByteSlice> RequestRefMaker<B> {
fn new(buf: B) -> anyhow::Result<Self> {
let msg_type = buf.deref().request_msg_type_from_prefix()?;
Ok(Self { buf, msg_type })
}
fn target_size(&self) -> usize {
self.msg_type.message_size()
}
fn parse(self) -> anyhow::Result<RequestRef<B>> {
Ok(match self.msg_type {
RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?),
})
}
#[allow(clippy::wrong_self_convention)]
fn from_prefix(self) -> anyhow::Result<Self> {
self.ensure_fit()?;
let point = self.target_size();
let Self { buf, msg_type } = self;
let (buf, _) = buf.split_at(point);
Ok(Self { buf, msg_type })
}
#[allow(clippy::wrong_self_convention)]
fn from_suffix(self) -> anyhow::Result<Self> {
self.ensure_fit()?;
let point = self.buf.len() - self.target_size();
let Self { buf, msg_type } = self;
let (buf, _) = buf.split_at(point);
Ok(Self { buf, msg_type })
}
pub fn ensure_fit(&self) -> anyhow::Result<()> {
let have = self.buf.len();
let need = self.target_size();
ensure!(
need <= have,
"Buffer is undersized at {have} bytes (need {need} bytes)!"
);
Ok(())
}
}
pub enum RequestRef<B> {
Ping(Ref<B, PingRequest>),
}
impl<B> RequestRef<B>
where
B: ByteSlice,
{
pub fn bytes(&self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes(),
}
}
}
impl<B> RequestRef<B>
where
B: ByteSliceMut,
{
pub fn bytes_mut(&mut self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes_mut(),
}
}
}

View File

@@ -1,103 +0,0 @@
use rosenpass_util::zerocopy::{
RefMaker, ZerocopyEmancipateExt, ZerocopyEmancipateMutExt, ZerocopySliceExt,
};
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{Message, PingRequest, PingResponse};
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
pub trait RequestMsg: Sized + Message {
type ResponseMsg: ResponseMsg;
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
buf.zk_ref_maker()
}
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).setup_msg()
}
fn setup_response_from_prefix<B: ByteSliceMut>(
buf: B,
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
}
fn setup_response_from_suffix<B: ByteSliceMut>(
buf: B,
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
}
}
pub trait ResponseMsg: Message {
type RequestMsg: RequestMsg;
}
impl RequestMsg for PingRequest {
type ResponseMsg = PingResponse;
}
impl ResponseMsg for PingResponse {
type RequestMsg = PingRequest;
}
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
pub enum RequestResponsePair<B1, B2> {
Ping(PingPair<B1, B2>),
}
impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> {
fn from(v: PingPair<B1, B2>) -> Self {
RequestResponsePair::Ping(v)
}
}
impl<B1, B2> RequestResponsePair<B1, B2>
where
B1: ByteSlice,
B2: ByteSlice,
{
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
match self {
Self::Ping((req, res)) => {
let req = RequestRef::Ping(req.emancipate());
let res = ResponseRef::Ping(res.emancipate());
(req, res)
}
}
}
pub fn request(&self) -> RequestRef<&[u8]> {
self.both().0
}
pub fn response(&self) -> ResponseRef<&[u8]> {
self.both().1
}
}
impl<B1, B2> RequestResponsePair<B1, B2>
where
B1: ByteSliceMut,
B2: ByteSliceMut,
{
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
match self {
Self::Ping((req, res)) => {
let req = RequestRef::Ping(req.emancipate_mut());
let res = ResponseRef::Ping(res.emancipate_mut());
(req, res)
}
}
}
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
self.both_mut().0
}
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
self.both_mut().1
}
}

View File

@@ -1,108 +0,0 @@
// TODO: This is copied verbatim from ResponseRef…not pretty
use anyhow::ensure;
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
struct ResponseRefMaker<B> {
buf: B,
msg_type: ResponseMsgType,
}
impl<B: ByteSlice> ResponseRef<B> {
pub fn parse(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.parse()
}
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
}
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
}
pub fn message_type(&self) -> ResponseMsgType {
match self {
Self::Ping(_) => ResponseMsgType::Ping,
}
}
}
impl<B> From<Ref<B, PingResponse>> for ResponseRef<B> {
fn from(v: Ref<B, PingResponse>) -> Self {
Self::Ping(v)
}
}
impl<B: ByteSlice> ResponseRefMaker<B> {
fn new(buf: B) -> anyhow::Result<Self> {
let msg_type = buf.deref().response_msg_type_from_prefix()?;
Ok(Self { buf, msg_type })
}
fn target_size(&self) -> usize {
self.msg_type.message_size()
}
fn parse(self) -> anyhow::Result<ResponseRef<B>> {
Ok(match self.msg_type {
ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?),
})
}
#[allow(clippy::wrong_self_convention)]
fn from_prefix(self) -> anyhow::Result<Self> {
self.ensure_fit()?;
let point = self.target_size();
let Self { buf, msg_type } = self;
let (buf, _) = buf.split_at(point);
Ok(Self { buf, msg_type })
}
#[allow(clippy::wrong_self_convention)]
fn from_suffix(self) -> anyhow::Result<Self> {
self.ensure_fit()?;
let point = self.buf.len() - self.target_size();
let Self { buf, msg_type } = self;
let (buf, _) = buf.split_at(point);
Ok(Self { buf, msg_type })
}
pub fn ensure_fit(&self) -> anyhow::Result<()> {
let have = self.buf.len();
let need = self.target_size();
ensure!(
need <= have,
"Buffer is undersized at {have} bytes (need {need} bytes)!"
);
Ok(())
}
}
pub enum ResponseRef<B> {
Ping(Ref<B, PingResponse>),
}
impl<B> ResponseRef<B>
where
B: ByteSlice,
{
pub fn bytes(&self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes(),
}
}
}
impl<B> ResponseRef<B>
where
B: ByteSliceMut,
{
pub fn bytes_mut(&mut self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes_mut(),
}
}
}

View File

@@ -1,40 +0,0 @@
use zerocopy::{ByteSlice, ByteSliceMut};
use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair};
pub trait Server {
fn ping(&mut self, req: &PingRequest, res: &mut PingResponse) -> anyhow::Result<()>;
fn dispatch<ReqBuf, ResBuf>(
&mut self,
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
) -> anyhow::Result<()>
where
ReqBuf: ByteSlice,
ResBuf: ByteSliceMut,
{
match p {
RequestResponsePair::Ping((req, res)) => self.ping(req, res),
}
}
fn handle_message<ReqBuf, ResBuf>(&mut self, req: ReqBuf, res: ResBuf) -> anyhow::Result<usize>
where
ReqBuf: ByteSlice,
ResBuf: ByteSliceMut,
{
let req = req.parse_request_from_prefix()?;
// TODO: This is not pretty; This match should be moved into RequestRef
let mut pair = match req {
RequestRef::Ping(req) => {
let mut res = res.ping_response_from_prefix()?;
res.init();
RequestResponsePair::Ping((req, res))
}
};
self.dispatch(&mut pair)?;
let res_len = pair.request().bytes().len();
Ok(res_len)
}
}

View File

@@ -1,40 +0,0 @@
use std::path::PathBuf;
use clap::Args;
use crate::config::Rosenpass as RosenpassConfig;
use super::config::ApiConfig;
#[cfg(feature = "experiment_api")]
#[derive(Args, Debug)]
pub struct ApiCli {
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
/// connections on.
#[arg(long)]
api_listen_path: Vec<PathBuf>,
/// When rosenpass is called from another process, the other process can open and bind the
/// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
#[arg(long)]
api_listen_fd: Vec<i32>,
/// When rosenpass is called from another process, the other process can connect the unix socket for the API
/// themselves, for instance using the `socketpair(2)` system call.
#[arg(long)]
api_stream_fd: Vec<i32>,
}
impl ApiCli {
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
self.apply_to_api_config(&mut cfg.api)
}
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
cfg.listen_path.extend_from_slice(&self.api_listen_path);
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
cfg.stream_fd.extend_from_slice(&self.api_stream_fd);
Ok(())
}
}

View File

@@ -1,41 +0,0 @@
use std::path::PathBuf;
use mio::net::UnixListener;
use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt};
use serde::{Deserialize, Serialize};
use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct ApiConfig {
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
/// connections on
pub listen_path: Vec<PathBuf>,
/// When rosenpass is called from another process, the other process can open and bind the
/// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
pub listen_fd: Vec<i32>,
/// When rosenpass is called from another process, the other process can connect the unix socket for the API
/// themselves, for instance using the `socketpair(2)` system call.
pub stream_fd: Vec<i32>,
}
impl ApiConfig {
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
for path in self.listen_path.iter() {
srv.add_api_listener(UnixListener::bind(path)?)?;
}
for fd in self.listen_fd.iter() {
srv.add_api_listener(UnixListenerExt::claim_fd(*fd)?)?;
}
for fd in self.stream_fd.iter() {
srv.add_api_connection(UnixStreamExt::claim_fd(*fd)?)?;
}
Ok(())
}
}

View File

@@ -1,44 +0,0 @@
use rosenpass_to::{ops::copy_slice, To};
use crate::protocol::CryptoServer;
use super::Server as ApiServer;
#[derive(Debug)]
pub struct CryptoServerApiState {
_dummy: (),
}
impl CryptoServerApiState {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self { _dummy: () }
}
pub fn acquire_backend<'a>(
&'a mut self,
crypto: &'a mut Option<CryptoServer>,
) -> CryptoServerApiHandler<'a> {
let state = self;
CryptoServerApiHandler { state, crypto }
}
}
pub struct CryptoServerApiHandler<'a> {
#[allow(unused)] // TODO: Remove
crypto: &'a mut Option<CryptoServer>,
#[allow(unused)] // TODO: Remove
state: &'a mut CryptoServerApiState,
}
impl<'a> ApiServer for CryptoServerApiHandler<'a> {
fn ping(
&mut self,
req: &super::PingRequest,
res: &mut super::PingResponse,
) -> anyhow::Result<()> {
let (req, res) = (&req.payload, &mut res.payload);
copy_slice(&req.echo).to(&mut res.echo);
Ok(())
}
}

View File

@@ -1,167 +0,0 @@
use mio::{net::UnixStream, Interest};
use rosenpass_util::{
io::{IoResultKindHintExt, TryIoResultKindHintExt},
length_prefix_encoding::{
decoder::{self as lpe_decoder, LengthPrefixDecoder},
encoder::{self as lpe_encoder, LengthPrefixEncoder},
},
};
use zeroize::Zeroize;
use crate::{api::Server, app_server::MioTokenDispenser, protocol::CryptoServer};
use super::super::{CryptoServerApiState, MAX_REQUEST_LEN, MAX_RESPONSE_LEN};
#[derive(Debug)]
pub struct MioConnection {
io: UnixStream,
invalid_read: bool,
read_buffer: LengthPrefixDecoder<[u8; MAX_REQUEST_LEN]>,
write_buffer: LengthPrefixEncoder<[u8; MAX_RESPONSE_LEN]>,
api_state: CryptoServerApiState,
}
impl MioConnection {
pub fn new(
mut io: UnixStream,
registry: &mio::Registry,
token_dispenser: &mut MioTokenDispenser, // TODO: We should actually start using tokens…
) -> std::io::Result<Self> {
registry.register(
&mut io,
token_dispenser.dispense(),
Interest::READABLE | Interest::WRITABLE,
)?;
let invalid_read = false;
let read_buffer = LengthPrefixDecoder::new([0u8; MAX_REQUEST_LEN]);
let write_buffer = LengthPrefixEncoder::from_buffer([0u8; MAX_RESPONSE_LEN]);
let api_state = CryptoServerApiState::new();
Ok(Self {
io,
invalid_read,
read_buffer,
write_buffer,
api_state,
})
}
pub fn poll(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
self.flush_write_buffer()?;
if self.write_buffer.exhausted() {
self.recv(crypto)?;
}
Ok(())
}
// This is *exclusively* called by recv if the read_buffer holds a message
fn handle_incoming_message(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
// Unwrap is allowed because recv() confirms before the call that a message was
// received
let req = self.read_buffer.message().unwrap().unwrap();
// TODO: The API should not return anyhow::Result
let response_len = self
.api_state
.acquire_backend(crypto)
.handle_message(req, self.write_buffer.buffer_bytes_mut())?;
self.read_buffer.zeroize(); // clear for new message to read
self.write_buffer
.restart_write_with_new_message(response_len)?;
self.flush_write_buffer()?;
Ok(())
}
fn flush_write_buffer(&mut self) -> anyhow::Result<()> {
if self.write_buffer.exhausted() {
return Ok(());
}
loop {
use lpe_encoder::WriteToIoReturn as Ret;
use std::io::ErrorKind as K;
match self
.write_buffer
.write_to_stdio(&self.io)
.io_err_kind_hint()
{
// Done
Ok(Ret { done: true, .. }) => {
self.write_buffer.zeroize(); // clear for new message to write
break;
}
// Would block
Ok(Ret {
bytes_written: 0, ..
}) => break,
Err((_e, K::WouldBlock)) => break,
// Just continue
Ok(_) => continue, /* Ret { bytes_written > 0, done = false } acc. to previous cases*/
Err((_e, K::Interrupted)) => continue,
// Other errors
Err((e, _ek)) => Err(e)?,
}
}
Ok(())
}
fn recv(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
if !self.write_buffer.exhausted() || self.invalid_read {
return Ok(());
}
loop {
use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret};
use std::io::ErrorKind as K;
match self
.read_buffer
.read_from_stdio(&self.io)
.try_io_err_kind_hint()
{
// We actually received a proper message
// (Impl below match to appease borrow checker)
Ok(Ret {
message: Some(_msg),
..
}) => {}
// Message does not fit in buffer
Err((e @ E::MessageTooLargeError { .. }, _)) => {
log::warn!("Received message on API that was too big to fit in our buffers; \
looks like the client is broken. Stopping to process messages of the client.\n\
Error: {e:?}");
// TODO: We should properly close down the socket in this case, but to do that,
// we need to have the facilities in the Rosenpass IO handling system to close
// open connections.
// Just leaving the API connections dangling for now.
// This should be fixed for non-experimental use of the API.
self.invalid_read = true;
break;
}
// Would block
Ok(Ret { bytes_read: 0, .. }) => break,
Err((_, Some(K::WouldBlock))) => break,
// Just keep going
Ok(Ret { bytes_read: _, .. }) => continue,
Err((_, Some(K::Interrupted))) => continue,
// Other IO Error (just pass on to the caller)
Err((E::IoError(e), _)) => Err(e)?,
};
self.handle_incoming_message(crypto)?;
break; // Handle just one message, leave some room for other IO handlers
}
Ok(())
}
}

View File

@@ -1,93 +0,0 @@
use std::io;
use mio::net::{UnixListener, UnixStream};
use rosenpass_util::{io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW};
use crate::{app_server::MioTokenDispenser, protocol::CryptoServer};
use super::MioConnection;
#[derive(Default, Debug)]
pub struct MioManager {
listeners: Vec<UnixListener>,
connections: Vec<MioConnection>,
}
impl MioManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_listener(
&mut self,
mut listener: UnixListener,
registry: &mio::Registry,
token_dispenser: &mut MioTokenDispenser,
) -> io::Result<()> {
registry.register(&mut listener, token_dispenser.dispense(), MIO_RW)?;
self.listeners.push(listener);
Ok(())
}
pub fn add_connection(
&mut self,
connection: UnixStream,
registry: &mio::Registry,
token_dispenser: &mut MioTokenDispenser,
) -> io::Result<()> {
let connection = MioConnection::new(connection, registry, token_dispenser)?;
self.connections.push(connection);
Ok(())
}
pub fn poll(
&mut self,
crypto: &mut Option<CryptoServer>,
registry: &mio::Registry,
token_dispenser: &mut MioTokenDispenser,
) -> anyhow::Result<()> {
self.accept_connections(registry, token_dispenser)?;
self.poll_connections(crypto)?;
Ok(())
}
fn accept_connections(
&mut self,
registry: &mio::Registry,
token_dispenser: &mut MioTokenDispenser,
) -> io::Result<()> {
for idx in 0..self.listeners.len() {
self.accept_from(idx, registry, token_dispenser)?;
}
Ok(())
}
fn accept_from(
&mut self,
idx: usize,
registry: &mio::Registry,
token_dispenser: &mut MioTokenDispenser,
) -> io::Result<()> {
// Accept connection until the socket would block or returns another error
// TODO: This currently only adds connections--we eventually need the ability to remove
// them as well, see the note in connection.rs
loop {
match nonblocking_handle_io_errors(|| self.listeners[idx].accept())? {
None => break,
Some((conn, _addr)) => {
self.add_connection(conn, registry, token_dispenser)?;
}
};
}
Ok(())
}
fn poll_connections(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
for conn in self.connections.iter_mut() {
conn.poll(crypto)?
}
Ok(())
}
}

View File

@@ -1,5 +0,0 @@
mod connection;
mod manager;
pub use connection::*;
pub use manager::*;

View File

@@ -1,9 +0,0 @@
mod boilerplate;
mod crypto_server_api_handler;
pub use boilerplate::*;
pub use crypto_server_api_handler::*;
pub mod cli;
pub mod config;
pub mod mio;

View File

@@ -1,6 +1,5 @@
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use derive_builder::Builder;
use log::{error, info, warn};
@@ -17,9 +16,7 @@ use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::stdout;
use std::io::ErrorKind;
use std::io::Write;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
@@ -66,7 +63,7 @@ pub struct MioTokenDispenser {
}
impl MioTokenDispenser {
pub fn dispense(&mut self) -> Token {
fn dispense(&mut self) -> Token {
let r = self.counter;
self.counter += 1;
Token(r)
@@ -147,7 +144,7 @@ pub struct AppServerTest {
// TODO add user control via unix domain socket and stdin/stdout
#[derive(Debug)]
pub struct AppServer {
pub crypt: Option<CryptoServer>,
pub crypt: CryptoServer,
pub sockets: Vec<mio::net::UdpSocket>,
pub events: mio::Events,
pub mio_poll: mio::Poll,
@@ -162,8 +159,6 @@ pub struct AppServer {
pub unpolled_count: usize,
pub last_update_time: Instant,
pub test_helpers: Option<AppServerTest>,
#[cfg(feature = "experiment_api")]
pub api_manager: crate::api::mio::MioManager,
}
/// A socket pointer is an index assigned to a socket;
@@ -606,7 +601,7 @@ impl AppServer {
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux
Ok(Self {
crypt: Some(CryptoServer::new(sk, pk)),
crypt: CryptoServer::new(sk, pk),
peers: Vec::new(),
verbosity,
sockets,
@@ -621,23 +616,9 @@ impl AppServer {
unpolled_count: 0,
last_update_time: Instant::now(),
test_helpers,
#[cfg(feature = "experiment_api")]
api_manager: crate::api::mio::MioManager::default(),
})
}
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
self.crypt
.as_ref()
.context("Cryptography handler not initialized")
}
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
self.crypt
.as_mut()
.context("Cryptography handler not initialized")
}
pub fn verbose(&self) -> bool {
matches!(self.verbosity, Verbosity::Verbose)
}
@@ -688,7 +669,7 @@ impl AppServer {
broker_peer: Option<BrokerPeer>,
hostname: Option<String>,
) -> anyhow::Result<AppPeerPtr> {
let PeerPtr(pn) = self.crypto_server_mut()?.add_peer(psk, pk)?;
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
assert!(pn == self.peers.len());
let initial_endpoint = hostname
.map(Endpoint::discovery_from_hostname)
@@ -731,7 +712,7 @@ impl AppServer {
);
if tries_left > 0 {
error!("re-initializing networking in {sleep}! {tries_left} tries left.");
std::thread::sleep(self.crypto_server_mut()?.timebase.dur(sleep));
std::thread::sleep(self.crypt.timebase.dur(sleep));
continue;
}
@@ -777,11 +758,11 @@ impl AppServer {
match self.poll(&mut *rx)? {
#[allow(clippy::redundant_closure_call)]
SendInitiation(peer) => tx_maybe_with!(peer, || self
.crypto_server_mut()?
.crypt
.initiate_handshake(peer.lower(), &mut *tx))?,
#[allow(clippy::redundant_closure_call)]
SendRetransmission(peer) => tx_maybe_with!(peer, || self
.crypto_server_mut()?
.crypt
.retransmit_handshake(peer.lower(), &mut *tx))?,
DeleteKey(peer) => {
self.output_key(peer, Stale, &SymKey::random())?;
@@ -802,9 +783,7 @@ impl AppServer {
DoSOperation::UnderLoad => {
self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx)
}
DoSOperation::Normal => {
self.crypto_server_mut()?.handle_msg(&rx[..len], &mut *tx)
}
DoSOperation::Normal => self.crypt.handle_msg(&rx[..len], &mut *tx),
};
match msg_result {
Err(ref e) => {
@@ -832,8 +811,7 @@ impl AppServer {
ap.get_app_mut(self).current_endpoint = Some(endpoint);
// TODO: Maybe we should rather call the key "rosenpass output"?
let osk = &self.crypto_server_mut()?.osk(p)?;
self.output_key(ap, Exchanged, osk)?;
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
}
}
}
@@ -849,9 +827,9 @@ impl AppServer {
tx: &mut [u8],
) -> Result<crate::protocol::HandleMsgResult> {
match endpoint {
Endpoint::SocketBoundAddress(socket) => self
.crypto_server_mut()?
.handle_msg_under_load(rx, &mut *tx, socket),
Endpoint::SocketBoundAddress(socket) => {
self.crypt.handle_msg_under_load(rx, &mut *tx, socket)
}
Endpoint::Discovery(_) => {
anyhow::bail!("Host-path discovery is not supported under load")
}
@@ -864,7 +842,7 @@ impl AppServer {
why: KeyOutputReason,
key: &SymKey,
) -> anyhow::Result<()> {
let peerid = peer.lower().get(self.crypto_server()?).pidt()?;
let peerid = peer.lower().get(&self.crypt).pidt()?;
if self.verbose() {
let msg = match why {
@@ -892,14 +870,10 @@ impl AppServer {
// this is intentionally writing to stdout instead of stderr, because
// it is meant to allow external detection of a successful key-exchange
let stdout = stdout();
let mut stdout = stdout.lock();
writeln!(
stdout,
println!(
"output-key peer {} key-file {of:?} {why}",
peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>()
)?;
stdout.flush()?;
);
}
peer.set_psk(self, key)?;
@@ -911,7 +885,7 @@ impl AppServer {
use crate::protocol::PollResult as C;
use AppPollResult as A;
loop {
return Ok(match self.crypto_server_mut()?.poll()? {
return Ok(match self.crypt.poll()? {
C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)),
C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)),
C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)),
@@ -1039,33 +1013,6 @@ impl AppServer {
broker.process_poll()?;
}
// API poll
#[cfg(feature = "experiment_api")]
self.api_manager.poll(
&mut self.crypt,
self.mio_poll.registry(),
&mut self.mio_token_dispenser,
)?;
Ok(None)
}
#[cfg(feature = "experiment_api")]
pub fn add_api_connection(&mut self, connection: mio::net::UnixStream) -> std::io::Result<()> {
self.api_manager.add_connection(
connection,
self.mio_poll.registry(),
&mut self.mio_token_dispenser,
)
}
#[cfg(feature = "experiment_api")]
pub fn add_api_listener(&mut self, listener: mio::net::UnixListener) -> std::io::Result<()> {
self.api_manager.add_listener(
listener,
self.mio_poll.registry(),
&mut self.mio_token_dispenser,
)
}
}

View File

@@ -1,86 +0,0 @@
use anyhow::{Context, Result};
use heck::ToShoutySnakeCase;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
match values.split_first() {
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
None => Ok(hd.into_value()),
}
}
fn print_literal(path: &[&str]) -> Result<()> {
let val = calculate_hash_value(HashDomain::zero(), path)?;
let (last, prefix) = path.split_last().context("developer error!")?;
let var_name = last.to_shouty_snake_case();
print!("// hash domain hash of: ");
for n in prefix.iter() {
print!("{n} -> ");
}
println!("{last}");
let c = hex::encode(val)
.chars()
.collect::<Vec<char>>()
.chunks_exact(4)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<_>>();
println!("const {var_name} : RawMsgType = RawMsgType::from_le_bytes(hex!(\"{} {} {} {} {} {} {} {}\"));",
c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
Ok(())
}
#[derive(Debug, Clone)]
enum Tree {
Branch(String, Vec<Tree>),
Leaf(String),
}
impl Tree {
fn name(&self) -> &str {
match self {
Self::Branch(name, _) => name,
Self::Leaf(name) => name,
}
}
fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> {
let mut path = prefix.to_owned();
path.push(self.name());
match self {
Self::Branch(_, ref children) => {
for c in children.iter() {
c.gen_code_inner(&path)?
}
}
Self::Leaf(_) => print_literal(&path)?,
};
Ok(())
}
fn gen_code(&self) -> Result<()> {
self.gen_code_inner(&[])
}
}
fn main() -> Result<()> {
let tree = Tree::Branch(
"Rosenpass IPC API".to_owned(),
vec![Tree::Branch(
"Rosenpass Protocol Server".to_owned(),
vec![
Tree::Leaf("Ping Request".to_owned()),
Tree::Leaf("Ping Response".to_owned()),
],
)],
);
println!("type RawMsgType = u128;");
println!();
tree.gen_code()
}

View File

@@ -3,11 +3,13 @@ use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::file::StoreSecret;
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
use rosenpass_secret_memory::{
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
};
use rosenpass_util::file::{LoadValue, LoadValueB64};
use rosenpass_wireguard_broker::brokers::native_unix::{
NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError,
};
use std::ops::DerefMut;
use std::path::PathBuf;
use crate::app_server::AppServerTest;
@@ -32,21 +34,11 @@ pub struct CliArgs {
#[arg(short, long, group = "log-level")]
quiet: bool,
#[command(flatten)]
#[cfg(feature = "experiment_api")]
api: crate::api::cli::ApiCli,
#[command(subcommand)]
pub command: CliCommand,
}
impl CliArgs {
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_config(_cfg)?;
Ok(())
}
/// returns the log level filter set by CLI args
/// returns `None` if the user did not specify any log level filter via CLI
///
@@ -159,14 +151,21 @@ pub enum CliCommand {
Man,
}
impl CliArgs {
impl CliCommand {
/// runs the command specified via CLI
///
/// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
pub fn run(self, test_helpers: Option<AppServerTest>) -> anyhow::Result<()> {
//Specify secret policy
#[cfg(feature = "enable_memfd_alloc")]
secret_policy_try_use_memfd_secrets();
#[cfg(not(feature = "enable_memfd_alloc"))]
secret_policy_use_only_malloc_secrets();
use CliCommand::*;
match &self.command {
match self {
Man => {
let man_cmd = std::process::Command::new("man")
.args(["1", "rosenpass"])
@@ -178,7 +177,7 @@ impl CliArgs {
}
GenConfig { config_file, force } => {
ensure!(
*force || !config_file.exists(),
force || !config_file.exists(),
"config file {config_file:?} already exists"
);
@@ -193,9 +192,9 @@ impl CliArgs {
let mut secret_key: Option<PathBuf> = None;
// Manual arg parsing, since clap wants to prefix flags with "--"
let mut args = args.iter();
let mut args = args.into_iter();
loop {
match (args.next().map(|x| x.as_str()), args.next()) {
match (args.next().as_deref(), args.next()) {
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
secret_key = Some(opt.into());
}
@@ -237,7 +236,7 @@ impl CliArgs {
(config.public_key, config.secret_key)
}
(_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()),
(_, Some(pkf), Some(skf)) => (pkf, skf),
_ => {
bail!("either a config-file or both public-key and secret-key file are required")
}
@@ -269,36 +268,31 @@ impl CliArgs {
"config file '{config_file:?}' does not exist"
);
let mut config = config::Rosenpass::load(config_file)?;
let config = config::Rosenpass::load(config_file)?;
config.validate()?;
self.apply_to_config(&mut config)?;
Self::event_loop(config, test_helpers)?;
}
Exchange {
first_arg,
rest_of_args,
mut rest_of_args,
config_file,
} => {
let mut rest_of_args = rest_of_args.clone();
rest_of_args.insert(0, first_arg.clone());
rest_of_args.insert(0, first_arg);
let args = rest_of_args;
let mut config = config::Rosenpass::parse_args(args)?;
if let Some(p) = config_file {
config.store(p)?;
config.config_file_path.clone_from(p);
config.store(&p)?;
config.config_file_path = p;
}
config.validate()?;
self.apply_to_config(&mut config)?;
Self::event_loop(config, test_helpers)?;
}
Validate { config_files } => {
for file in config_files {
match config::Rosenpass::load(file) {
match config::Rosenpass::load(&file) {
Ok(config) => {
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
match config.validate() {
@@ -320,7 +314,6 @@ impl CliArgs {
test_helpers: Option<AppServerTest>,
) -> anyhow::Result<()> {
const MAX_PSK_SIZE: usize = 1000;
// load own keys
let sk = SSk::load(&config.secret_key)?;
let pk = SPk::load(&config.public_key)?;
@@ -329,13 +322,11 @@ impl CliArgs {
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
sk,
pk,
config.listen.clone(),
config.listen,
config.verbosity,
test_helpers,
)?);
config.apply_to_app_server(&mut srv)?;
let broker_store_ptr = srv.register_broker(Box::new(NativeUnixBroker::new()))?;
fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error {
@@ -379,19 +370,7 @@ impl CliArgs {
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(secret_key)?;
spk.store(public_key)
}
#[cfg(feature = "internal_testing")]
pub mod testing {
use super::*;
pub fn generate_and_save_keypair(
secret_key: PathBuf,
public_key: PathBuf,
) -> anyhow::Result<()> {
super::generate_and_save_keypair(secret_key, public_key)
}
}

View File

@@ -19,8 +19,6 @@ use anyhow::{bail, ensure};
use rosenpass_util::file::{fopen_w, Visibility};
use serde::{Deserialize, Serialize};
use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass {
/// path to the public key file
@@ -29,10 +27,6 @@ pub struct Rosenpass {
/// path to the secret key file
pub secret_key: PathBuf,
/// Location of the API listen sockets
#[cfg(feature = "experiment_api")]
pub api: crate::api::config::ApiConfig,
/// list of [`SocketAddr`] to listen on
///
/// Examples:
@@ -60,7 +54,7 @@ pub struct Rosenpass {
/// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Verbosity {
Quiet,
Verbose,
@@ -163,12 +157,6 @@ impl Rosenpass {
self.store(&self.config_file_path)
}
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_app_server(_srv)?;
Ok(())
}
/// Validate a configuration
///
/// ## TODO
@@ -218,8 +206,6 @@ impl Rosenpass {
public_key: PathBuf::from(public_key.as_ref()),
secret_key: PathBuf::from(secret_key.as_ref()),
listen: vec![],
#[cfg(feature = "experiment_api")]
api: crate::api::config::ApiConfig::default(),
verbosity: Verbosity::Quiet,
peers: vec![],
config_file_path: PathBuf::new(),

View File

@@ -1,5 +1,3 @@
#[cfg(feature = "experiment_api")]
pub mod api;
pub mod app_server;
pub mod cli;
pub mod config;
@@ -13,8 +11,4 @@ pub enum RosenpassError {
BufferSizeMismatch,
#[error("invalid message type")]
InvalidMessageType(u8),
#[error("invalid API message type")]
InvalidApiMessageType(u128),
#[error("could not parse API message")]
InvalidApiMessage,
}

View File

@@ -8,14 +8,6 @@ pub fn main() {
// parse CLI arguments
let args = CliArgs::parse();
{
use rosenpass_secret_memory as SM;
#[cfg(feature = "experiment_memfd_secret")]
SM::secret_policy_try_use_memfd_secrets();
#[cfg(not(feature = "experiment_memfd_secret"))]
SM::secret_policy_use_only_malloc_secrets();
}
// init logging
{
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
@@ -34,10 +26,10 @@ pub fn main() {
// error!("error dummy");
}
match args.run(None) {
match args.command.run(None) {
Ok(_) => {}
Err(e) => {
error!("{e:?}");
error!("{e}");
exit(1);
}
}

View File

@@ -19,7 +19,6 @@
//! [CryptoServer].
//!
//! ```
//! use std::ops::DerefMut;
//! use rosenpass_secret_memory::policy::*;
//! use rosenpass_cipher_traits::Kem;
//! use rosenpass_ciphers::kem::StaticKem;
@@ -33,11 +32,11 @@
//!
//! // initialize secret and public key for peer a ...
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
//!
//! // ... and for peer b
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?;
//!
//! // initialize server and a pre-shared key
//! let psk = SymKey::random();
@@ -72,7 +71,6 @@
use std::convert::Infallible;
use std::mem::size_of;
use std::ops::Deref;
use std::{
collections::hash_map::{
Entry::{Occupied, Vacant},
@@ -90,7 +88,7 @@ use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_constant_time as constant_time;
use rosenpass_secret_memory::{Public, PublicBox, Secret};
use rosenpass_secret_memory::{Public, Secret};
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
use zerocopy::{AsBytes, FromBytes, Ref};
@@ -165,7 +163,7 @@ pub fn has_happened(ev: Timing, now: Timing) -> bool {
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
pub type SPk = PublicBox<{ StaticKem::PK_LEN }>;
pub type SPk = Secret<{ StaticKem::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
pub type SSk = Secret<{ StaticKem::SK_LEN }>;
pub type EPk = Public<{ EphemeralKem::PK_LEN }>;
pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
@@ -550,7 +548,7 @@ impl CryptoServer {
pub fn pidm(&self) -> Result<PeerId> {
Ok(Public::new(
hash_domains::peerid()?
.mix(self.spkm.deref())?
.mix(self.spkm.secret())?
.into_value()))
}
@@ -710,7 +708,7 @@ impl Peer {
pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new(
hash_domains::peerid()?
.mix(self.spkt.deref())?
.mix(self.spkt.secret())?
.into_value()))
}
}
@@ -1019,7 +1017,7 @@ impl CryptoServer {
let cookie_value = active_cookie_value.unwrap();
let cookie_key = hash_domains::cookie_key()?
.mix(self.spkm.deref())?
.mix(self.spkm.secret())?
.into_value();
let mut msg_out = truncating_cast_into::<CookieReply>(tx_buf)?;
@@ -1511,7 +1509,7 @@ where
/// Calculate the message authentication code (`mac`) and also append cookie value
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = hash_domains::mac()?
.mix(peer.get(srv).spkt.deref())?
.mix(peer.get(srv).spkt.secret())?
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
self.mac.copy_from_slice(mac.into_value()[..16].as_ref());
self.seal_cookie(peer, srv)?;
@@ -1538,7 +1536,7 @@ where
/// Check the message authentication code
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
let expected = hash_domains::mac()?
.mix(srv.spkm.deref())?
.mix(srv.spkm.secret())?
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
Ok(constant_time::memcmp(
&self.mac,
@@ -1643,7 +1641,7 @@ impl HandshakeState {
// calculate ad contents
let ad = hash_domains::biscuit_ad()?
.mix(srv.spkm.deref())?
.mix(srv.spkm.secret())?
.mix(self.sidi.as_slice())?
.mix(self.sidr.as_slice())?
.into_value();
@@ -1678,7 +1676,7 @@ impl HandshakeState {
// Calculate additional data fields
let ad = hash_domains::biscuit_ad()?
.mix(srv.spkm.deref())?
.mix(srv.spkm.secret())?
.mix(sidi.as_slice())?
.mix(sidr.as_slice())?
.into_value();
@@ -1765,7 +1763,7 @@ impl CryptoServer {
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
// IHI1
hs.core.init(peer.get(self).spkt.deref())?;
hs.core.init(peer.get(self).spkt.secret())?;
// IHI2
hs.core.sidi.randomize();
@@ -1782,7 +1780,7 @@ impl CryptoServer {
hs.core
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
ih.sctr.as_mut_slice(),
peer.get(self).spkt.deref(),
peer.get(self).spkt.secret(),
)?;
// IHI6
@@ -1791,7 +1789,7 @@ impl CryptoServer {
// IHI7
hs.core
.mix(self.spkm.deref())?
.mix(self.spkm.secret())?
.mix(peer.get(self).psk.secret())?;
// IHI8
@@ -1809,7 +1807,7 @@ impl CryptoServer {
core.sidi = SessionId::from_slice(&ih.sidi);
// IHR1
core.init(self.spkm.deref())?;
core.init(self.spkm.secret())?;
// IHR4
core.mix(&ih.sidi)?.mix(&ih.epki)?;
@@ -1817,7 +1815,7 @@ impl CryptoServer {
// IHR5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(),
self.spkm.deref(),
self.spkm.secret(),
&ih.sctr,
)?;
@@ -1830,7 +1828,7 @@ impl CryptoServer {
};
// IHR7
core.mix(peer.get(self).spkt.deref())?
core.mix(peer.get(self).spkt.secret())?
.mix(peer.get(self).psk.secret())?;
// IHR8
@@ -1850,7 +1848,7 @@ impl CryptoServer {
// RHR5
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
&mut rh.scti,
peer.get(self).spkt.deref(),
peer.get(self).spkt.secret(),
)?;
// RHR6
@@ -1911,14 +1909,14 @@ impl CryptoServer {
// RHI4
core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
hs!().eski.secret(),
hs!().epki.deref(),
&*hs!().epki,
&rh.ecti,
)?;
// RHI5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(),
self.spkm.deref(),
self.spkm.secret(),
&rh.scti,
)?;
@@ -2115,7 +2113,7 @@ impl CryptoServer {
),
}?;
let spkt = peer.get(self).spkt.deref();
let spkt = peer.get(self).spkt.secret();
let cookie_key = hash_domains::cookie_key()?.mix(spkt)?.into_value();
let cookie_value = peer.cv().update_mut(self).unwrap();
@@ -2148,7 +2146,7 @@ fn truncating_cast_into_nomut<T: FromBytes>(buf: &[u8]) -> Result<Ref<&[u8], T>,
#[cfg(test)]
mod test {
use std::{net::SocketAddrV4, ops::DerefMut, thread::sleep, time::Duration};
use std::{net::SocketAddrV4, thread::sleep, time::Duration};
use super::*;
use serial_test::serial;
@@ -2257,7 +2255,7 @@ mod test {
fn keygen() -> Result<(SSk, SPk)> {
// TODO: Copied from the benchmark; deduplicate
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
Ok((sk, pk))
}

View File

@@ -1,180 +0,0 @@
use std::{
io::{BufRead, BufReader},
net::ToSocketAddrs,
os::unix::net::UnixStream,
process::Stdio,
};
use anyhow::{bail, Context};
use rosenpass::api;
use rosenpass_to::{ops::copy_slice_least_src, To};
use rosenpass_util::zerocopy::ZerocopySliceExt;
use rosenpass_util::{
file::LoadValueB64,
length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder},
};
use tempfile::TempDir;
use zerocopy::AsBytes;
use rosenpass::protocol::SymKey;
#[test]
fn api_integration_test() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
macro_rules! tempfile {
($($lst:expr),+) => {{
let mut buf = dir.path().to_path_buf();
$(buf.push($lst);)*
buf
}}
}
let peer_a_endpoint = "[::1]:61423";
let peer_a_osk = tempfile!("a.osk");
let peer_b_osk = tempfile!("b.osk");
use rosenpass::config;
let peer_a = config::Rosenpass {
config_file_path: tempfile!("a.config"),
secret_key: tempfile!("a.sk"),
public_key: tempfile!("a.pk"),
listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident
verbosity: config::Verbosity::Verbose,
api: api::config::ApiConfig {
listen_path: vec![tempfile!("a.sock")],
listen_fd: vec![],
stream_fd: vec![],
},
peers: vec![config::RosenpassPeer {
public_key: tempfile!("b.pk"),
key_out: Some(peer_a_osk.clone()),
endpoint: None,
pre_shared_key: None,
wg: None,
}],
};
let peer_b = config::Rosenpass {
config_file_path: tempfile!("b.config"),
secret_key: tempfile!("b.sk"),
public_key: tempfile!("b.pk"),
listen: vec![],
verbosity: config::Verbosity::Verbose,
api: api::config::ApiConfig {
listen_path: vec![tempfile!("b.sock")],
listen_fd: vec![],
stream_fd: vec![],
},
peers: vec![config::RosenpassPeer {
public_key: tempfile!("a.pk"),
key_out: Some(peer_b_osk.clone()),
endpoint: Some(peer_a_endpoint.to_owned()),
pre_shared_key: None,
wg: None,
}],
};
// Generate the keys
rosenpass::cli::testing::generate_and_save_keypair(
peer_a.secret_key.clone(),
peer_a.public_key.clone(),
)?;
rosenpass::cli::testing::generate_and_save_keypair(
peer_b.secret_key.clone(),
peer_b.public_key.clone(),
)?;
// Write the configuration files
peer_a.commit()?;
peer_b.commit()?;
// Start peer a
let proc_a = std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
.args([
"exchange-config",
peer_a.config_file_path.to_str().context("")?,
])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()?;
// Start peer b
let proc_b = std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
.args([
"exchange-config",
peer_b.config_file_path.to_str().context("")?,
])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()?;
// Acquire stdout
let mut out_a = BufReader::new(proc_a.stdout.context("")?).lines();
let mut out_b = BufReader::new(proc_b.stdout.context("")?).lines();
// Wait for the keys to successfully exchange a key
let mut attempt = 0;
loop {
let line_a = out_a.next().context("")??;
let line_b = out_b.next().context("")??;
let words_a = line_a.split(' ').collect::<Vec<_>>();
let words_b = line_b.split(' ').collect::<Vec<_>>();
// FIXED FIXED PEER-ID FIXED FILENAME STATUS
// output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged
let peer_a_id = words_b
.get(2)
.with_context(|| format!("Bad rosenpass output: `{line_b}`"))?;
let peer_b_id = words_a
.get(2)
.with_context(|| format!("Bad rosenpass output: `{line_a}`"))?;
assert_eq!(
line_a,
format!(
"output-key peer {peer_b_id} key-file \"{}\" exchanged",
peer_a_osk.to_str().context("")?
)
);
assert_eq!(
line_b,
format!(
"output-key peer {peer_a_id} key-file \"{}\" exchanged",
peer_b_osk.to_str().context("")?
)
);
// Read OSKs
let osk_a = SymKey::load_b64::<64, _>(peer_a_osk.clone())?;
let osk_b = SymKey::load_b64::<64, _>(peer_b_osk.clone())?;
match osk_a.secret() == osk_b.secret() {
true => break,
false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"),
false => {},
};
attempt += 1;
}
// Now connect to the peers
let api_a = UnixStream::connect(&peer_a.api.listen_path[0])?;
let api_b = UnixStream::connect(&peer_b.api.listen_path[0])?;
for conn in ([api_a, api_b]).iter() {
let mut echo = [0u8; 256];
copy_slice_least_src("Hello World".as_bytes()).to(&mut echo);
let req = api::PingRequest::new(echo);
LengthPrefixEncoder::from_message(req.as_bytes()).write_all_to_stdio(conn)?;
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
let res = decoder.read_all_from_stdio(conn)?;
let res = res.zk_parse::<api::PingResponse>()?;
assert_eq!(*res, api::PingResponse::new(echo));
}
Ok(())
}

View File

@@ -15,19 +15,9 @@ use std::io::Write;
const BIN: &str = "rosenpass";
fn setup_tests() {
use rosenpass_secret_memory as SM;
#[cfg(feature = "experiment_memfd_secret")]
SM::secret_policy_try_use_memfd_secrets();
#[cfg(not(feature = "experiment_memfd_secret"))]
SM::secret_policy_use_only_malloc_secrets();
}
// check that we can generate keys
#[test]
fn generate_keys() {
setup_tests();
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen");
fs::create_dir_all(&tmpdir).unwrap();
@@ -104,11 +94,14 @@ fn run_server_client_exchange(
.unwrap();
std::thread::spawn(move || {
let test_helpers = server_test_builder
.termination_handler(Some(server_terminate_rx))
.build()
cli.command
.run(Some(
server_test_builder
.termination_handler(Some(server_terminate_rx))
.build()
.unwrap(),
))
.unwrap();
cli.run(Some(test_helpers)).unwrap();
});
let cli = CliArgs::try_parse_from(
@@ -119,11 +112,14 @@ fn run_server_client_exchange(
.unwrap();
std::thread::spawn(move || {
let test_helpers = client_test_builder
.termination_handler(Some(client_terminate_rx))
.build()
cli.command
.run(Some(
client_test_builder
.termination_handler(Some(client_terminate_rx))
.build()
.unwrap(),
))
.unwrap();
cli.run(Some(test_helpers)).unwrap();
});
// give them some time to do the key exchange under load
@@ -138,7 +134,6 @@ fn run_server_client_exchange(
#[test]
#[serial]
fn check_exchange_under_normal() {
setup_tests();
setup_logging();
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
@@ -211,7 +206,6 @@ fn check_exchange_under_normal() {
#[test]
#[serial]
fn check_exchange_under_dos() {
setup_tests();
setup_logging();
//Generate binary with responder with feature integration_test
@@ -289,7 +283,6 @@ struct MockBrokerInner {
interface: Option<String>,
}
#[allow(dead_code)]
#[derive(Debug, Default)]
struct MockBroker {
inner: Arc<Mutex<MockBrokerInner>>,
@@ -328,7 +321,7 @@ impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker {
if let Ok(ref mut mutex) = lock {
**mutex = MockBrokerInner {
psk: Some(config.psk.clone()),
peer_id: Some(*config.peer_id),
peer_id: Some(config.peer_id.clone()),
interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()),
};
break Ok(());

View File

@@ -39,5 +39,4 @@ tempfile = {workspace = true}
stacker = {workspace = true}
[features]
experiment_memfd_secret = []
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
enable_memfd_alloc = []

View File

@@ -2,9 +2,7 @@ use std::{net::SocketAddr, path::PathBuf};
use anyhow::Result;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use crate::key::WG_B64_LEN;
#[derive(Default)]
pub struct ExchangePeer {
pub public_keys_dir: PathBuf,

View File

@@ -1,12 +1,11 @@
use std::{
fs::{self, DirBuilder},
ops::DerefMut,
os::unix::fs::{DirBuilderExt, PermissionsExt},
path::Path,
};
use anyhow::{anyhow, Result};
use rosenpass_util::file::{LoadValueB64, StoreValue, StoreValueB64};
use rosenpass_util::file::{LoadValueB64, StoreValueB64};
use zeroize::Zeroize;
use rosenpass::protocol::{SPk, SSk};
@@ -57,8 +56,8 @@ pub fn genkey(private_keys_dir: &Path) -> Result<()> {
if !pqsk_path.exists() && !pqpk_path.exists() {
let mut pqsk = SSk::random();
let mut pqpk = SPk::random();
StaticKem::keygen(pqsk.secret_mut(), pqpk.deref_mut())?;
pqpk.store(pqpk_path)?;
StaticKem::keygen(pqsk.secret_mut(), pqpk.secret_mut())?;
pqpk.store_secret(pqpk_path)?;
pqsk.store_secret(pqsk_path)?;
} else {
eprintln!(

View File

@@ -11,9 +11,9 @@ mod key;
#[tokio::main]
async fn main() {
#[cfg(feature = "experiment_memfd_secret")]
#[cfg(feature = "enable_memfd_alloc")]
policy::secret_policy_try_use_memfd_secrets();
#[cfg(not(feature = "experiment_memfd_secret"))]
#[cfg(not(feature = "enable_memfd_alloc"))]
policy::secret_policy_use_only_malloc_secrets();
let cli = match Cli::parse(std::env::args().peekable()) {

View File

@@ -23,4 +23,4 @@ log = { workspace = true }
allocator-api2-tests = { workspace = true }
tempfile = {workspace = true}
base64ct = {workspace = true}
procspawn = {workspace = true}
procspawn = {workspace = true}

View File

@@ -6,7 +6,6 @@ pub mod alloc;
mod public;
pub use crate::public::Public;
pub use crate::public::PublicBox;
mod secret;
pub use crate::secret::Secret;

View File

@@ -172,154 +172,12 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
}
}
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct PublicBox<const N: usize> {
pub inner: Box<Public<N>>,
}
impl<const N: usize> PublicBox<N> {
/// Create a new [PublicBox] from a byte slice
pub fn from_slice(value: &[u8]) -> Self {
Self {
inner: Box::new(Public::from_slice(value)),
}
}
/// Create a new [PublicBox] from a byte array
pub fn new(value: [u8; N]) -> Self {
Self {
inner: Box::new(Public::new(value)),
}
}
/// Create a zero initialized [PublicBox]
pub fn zero() -> Self {
Self {
inner: Box::new(Public::zero()),
}
}
/// Create a random initialized [PublicBox]
pub fn random() -> Self {
Self {
inner: Box::new(Public::random()),
}
}
/// Randomize all bytes in an existing [PublicBox]
pub fn randomize(&mut self) {
self.inner.randomize()
}
}
impl<const N: usize> Randomize for PublicBox<N> {
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.inner.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for PublicBox<N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&**self, fmt)
}
}
impl<const N: usize> Deref for PublicBox<N> {
type Target = [u8; N];
fn deref(&self) -> &[u8; N] {
self.inner.deref()
}
}
impl<const N: usize> DerefMut for PublicBox<N> {
fn deref_mut(&mut self) -> &mut [u8; N] {
self.inner.deref_mut()
}
}
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
fn borrow(&self) -> &[u8] {
self.deref()
}
}
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
fn borrow_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
impl<const N: usize> LoadValue for PublicBox<N> {
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut p = Self::random();
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
Ok(p)
}
}
impl<const N: usize> StoreValue for PublicBox<N> {
type Error = anyhow::Error;
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store(path)
}
}
impl<const N: usize> LoadValueB64 for PublicBox<N> {
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
{
// A vector is used here to ensure heap allocation without copy from stack
let mut f = vec![0u8; F];
let mut v = PublicBox::zero();
let p = path.as_ref();
let len = fopen_r(p)?
.read_slice_to_end(&mut f)
.with_context(|| format!("Could not load file {p:?}"))?;
b64_decode(&f[0..len], v.deref_mut())
.with_context(|| format!("Could not decode base64 file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> StoreValueB64 for PublicBox<N> {
type Error = anyhow::Error;
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store_b64::<F, P>(path)
}
}
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
type Error = anyhow::Error;
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
writer: W,
) -> Result<(), Self::Error> {
self.inner.store_b64_writer::<F, W>(writer)
}
}
#[cfg(test)]
mod tests {
#[cfg(test)]
#[allow(clippy::module_inception)]
mod tests {
use crate::{Public, PublicBox};
use crate::Public;
use rosenpass_util::{
b64::b64_encode,
file::{
@@ -327,35 +185,32 @@ mod tests {
Visibility,
},
};
use std::{fs, ops::Deref, os::unix::fs::PermissionsExt};
use std::{fs, os::unix::fs::PermissionsExt};
use tempfile::tempdir;
/// Number of bytes in payload for load and store tests
const N: usize = 100;
/// test loading a public from an example file, and then storing it again in a different file
#[test]
fn test_public_load_store() {
const N: usize = 100;
/// Convenience function for running a load/store test
fn run_load_store_test<
T: LoadValue<Error = anyhow::Error>
+ StoreValue<Error = anyhow::Error>
+ Deref<Target = [u8; N]>,
>() {
// Generate original random bytes
let original_bytes: [u8; N] = [rand::random(); N];
// Create a temporary directory
let temp_dir = tempdir().unwrap();
// Store the original bytes to an example file in the temporary directory
// Store the original public to an example file in the temporary directory
let example_file = temp_dir.path().join("example_file");
std::fs::write(&example_file, original_bytes).unwrap();
std::fs::write(example_file.clone(), &original_bytes).unwrap();
// Load the value from the example file into our generic type
let loaded_public = T::load(&example_file).unwrap();
// Load the public from the example file
// Check that the loaded value matches the original bytes
assert_eq!(loaded_public.deref(), &original_bytes);
let loaded_public = Public::load(&example_file).unwrap();
// Store the loaded value to a different file in the temporary directory
// Check that the loaded public matches the original bytes
assert_eq!(&loaded_public.value, &original_bytes);
// Store the loaded public to a different file in the temporary directory
let new_file = temp_dir.path().join("new_file");
loaded_public.store(&new_file).unwrap();
@@ -369,13 +224,10 @@ mod tests {
assert_eq!(new_file_contents, original_file_contents);
}
/// Convenience function for running a base64 load/store test
fn run_base64_load_store_test<
T: LoadValueB64<Error = anyhow::Error>
+ StoreValueB64<Error = anyhow::Error>
+ StoreValueB64Writer<Error = anyhow::Error>
+ Deref<Target = [u8; N]>,
>() {
/// test loading a base64 encoded public from an example file, and then storing it again in a different file
#[test]
fn test_public_load_store_base64() {
const N: usize = 100;
// Generate original random bytes
let original_bytes: [u8; N] = [rand::random(); N];
// Create a temporary directory
@@ -386,9 +238,9 @@ mod tests {
std::fs::write(&example_file, encoded_public).unwrap();
// Load the public from the example file
let loaded_public = T::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
let loaded_public = Public::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
// Check that the loaded public matches the original bytes
assert_eq!(loaded_public.deref(), &original_bytes);
assert_eq!(&loaded_public.value, &original_bytes);
// Store the loaded public to a different file in the temporary directory
let new_file = temp_dir.path().join("new_file");
@@ -401,7 +253,7 @@ mod tests {
// Check that the contents of the new file match the original file
assert_eq!(new_file_contents, original_file_contents);
// Check new file permissions are public
//Check new file permissions are public
let metadata = fs::metadata(&new_file).unwrap();
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
@@ -419,35 +271,9 @@ mod tests {
// Check that the contents of the new file match the original file
assert_eq!(new_file_contents, original_file_contents);
// Check new file permissions are public
//Check new file permissions are public
let metadata = fs::metadata(&new_file).unwrap();
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
}
/// Test loading a [Public] from an example file, and then storing it again in a new file
#[test]
fn test_public_load_store() {
run_load_store_test::<Public<N>>();
}
/// Test loading a [PublicBox] from an example file, and then storing it again in a new file
#[test]
fn test_public_box_load_store() {
run_load_store_test::<PublicBox<N>>();
}
/// Test loading a base64-encoded [Public] from an example file, and then storing it again
/// in a different file
#[test]
fn test_public_load_store_base64() {
run_base64_load_store_test::<Public<N>>();
}
/// Test loading a base64-encoded [PublicBox] from an example file, and then storing it
/// again in a different file
#[test]
fn test_public_box_load_store_base64() {
run_base64_load_store_test::<PublicBox<N>>();
}
}
}

View File

@@ -1,5 +1,6 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::path::Path;
@@ -386,7 +387,7 @@ mod test {
// Store the original secret to an example file in the temporary directory
let example_file = temp_dir.path().join("example_file");
std::fs::write(&example_file, original_bytes).unwrap();
std::fs::write(example_file.clone(), &original_bytes).unwrap();
// Load the secret from the example file
let loaded_secret = Secret::load(&example_file).unwrap();

View File

@@ -6,8 +6,8 @@
//! - `Dst: ?Sized`; (e.g. [u8]) The target to write to
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) A reference to the target to write to
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) Some value that
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
//! `Dst` (e.g. `[u8; 64]`).
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
//! `Dst` (e.g. `[u8; 64]`).
//! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as `Dst`
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).

View File

@@ -18,6 +18,3 @@ typenum = { workspace = true }
static_assertions = { workspace = true }
rustix = {workspace = true}
zeroize = {workspace = true}
zerocopy = { workspace = true }
thiserror = { workspace = true }
mio = { workspace = true }

View File

@@ -1,50 +1,12 @@
use rustix::{
fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
io::{fcntl_dupfd_cloexec, DupFlags},
};
use std::os::fd::{OwnedFd, RawFd};
use crate::mem::Forgetting;
/// Prepare a file descriptor for use in Rust code.
/// Clone some file descriptor
///
/// Checks if the file descriptor is valid and duplicates it to a new file descriptor.
/// The old file descriptor is masked to avoid potential use after free (on file descriptor)
/// in case the given file descriptor is still used somewhere
pub fn claim_fd(fd: RawFd) -> rustix::io::Result<OwnedFd> {
let new = clone_fd_cloexec(unsafe { BorrowedFd::borrow_raw(fd) })?;
mask_fd(fd)?;
Ok(new)
}
/// If the file descriptor is invalid, an error will be raised.
pub fn claim_fd(fd: RawFd) -> anyhow::Result<OwnedFd> {
use rustix::{fd::BorrowedFd, io::dup};
pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
// Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting,
// it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd
let mut owned = Forgetting::new(unsafe { OwnedFd::from_raw_fd(fd) });
clone_fd_to_cloexec(open_nullfd()?, &mut owned)
}
pub fn clone_fd_cloexec<Fd: AsFd>(fd: Fd) -> rustix::io::Result<OwnedFd> {
const MINFD: RawFd = 3; // Avoid stdin, stdout, and stderr
fcntl_dupfd_cloexec(fd, MINFD)
}
#[cfg(target_os = "linux")]
pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
use rustix::io::dup3;
dup3(fd, new, DupFlags::CLOEXEC)
}
#[cfg(not(target_os = "linux"))]
pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
use rustix::io::{dup2, fcntl_setfd, FdFlags};
dup2(&fd, new)?;
fcntl_setfd(&new, FdFlags::CLOEXEC)
}
/// Open a "blocked" file descriptor. I.e. a file descriptor that is neither meant for reading nor
/// writing
pub fn open_nullfd() -> rustix::io::Result<OwnedFd> {
use rustix::fs::{open, Mode, OFlags};
// TODO: Add tests showing that this will throw errors on use
open("/dev/null", OFlags::CLOEXEC, Mode::empty())
// This is safe since [dup] will simply raise
let fd = unsafe { dup(BorrowedFd::borrow_raw(fd))? };
Ok(fd)
}

View File

@@ -2,6 +2,7 @@ use anyhow::ensure;
use std::fs::File;
use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
use std::result::Result;
use std::{fs::OpenOptions, path::Path};
pub enum Visibility {

View File

@@ -13,7 +13,3 @@ where
f(&v);
v
}
pub fn run<R, F: FnOnce() -> R>(f: F) -> R {
f()
}

View File

@@ -1,110 +0,0 @@
use std::{borrow::Borrow, io};
pub trait IoErrorKind {
fn io_error_kind(&self) -> io::ErrorKind;
}
impl<T: Borrow<io::Error>> IoErrorKind for T {
fn io_error_kind(&self) -> io::ErrorKind {
self.borrow().kind()
}
}
pub trait TryIoErrorKind {
fn try_io_error_kind(&self) -> Option<io::ErrorKind>;
}
impl<T: IoErrorKind> TryIoErrorKind for T {
fn try_io_error_kind(&self) -> Option<io::ErrorKind> {
Some(self.io_error_kind())
}
}
pub trait IoResultKindHintExt<T>: Sized {
type Error;
fn io_err_kind_hint(self) -> Result<T, (Self::Error, io::ErrorKind)>;
}
impl<T, E: IoErrorKind> IoResultKindHintExt<T> for Result<T, E> {
type Error = E;
fn io_err_kind_hint(self) -> Result<T, (E, io::ErrorKind)> {
self.map_err(|e| {
let kind = e.borrow().io_error_kind();
(e, kind)
})
}
}
pub trait TryIoResultKindHintExt<T>: Sized {
type Error;
fn try_io_err_kind_hint(self) -> Result<T, (Self::Error, Option<io::ErrorKind>)>;
}
impl<T, E: TryIoErrorKind> TryIoResultKindHintExt<T> for Result<T, E> {
type Error = E;
fn try_io_err_kind_hint(self) -> Result<T, (E, Option<io::ErrorKind>)> {
self.map_err(|e| {
let opt_kind = e.borrow().try_io_error_kind();
(e, opt_kind)
})
}
}
/// Automatically handles `std::io::ErrorKind::Interrupted`.
///
/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))`
/// - `Interrupted` is handled internally, by retrying the IO operation
/// - Other errors are returned as is
pub fn handle_interrupted<R, E, F>(mut iofn: F) -> Result<Option<R>, E>
where
E: TryIoErrorKind,
F: FnMut() -> Result<R, E>,
{
use io::ErrorKind as E;
loop {
match iofn().try_io_err_kind_hint() {
Ok(v) => return Ok(Some(v)),
Err((_, Some(E::Interrupted))) => continue, // try again
Err((e, _)) => return Err(e),
};
}
}
/// Automatically handles `std::io::ErrorKind::{WouldBlock, Interrupted}`.
///
/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))`
/// - `Interrupted` is handled internally, by retrying the IO operation
/// - `WouldBlock` is handled by returning `Ok(None)`,
/// - Other errors are returned as is
pub fn nonblocking_handle_io_errors<R, E, F>(mut iofn: F) -> Result<Option<R>, E>
where
E: TryIoErrorKind,
F: FnMut() -> Result<R, E>,
{
use io::ErrorKind as E;
loop {
match iofn().try_io_err_kind_hint() {
Ok(v) => return Ok(Some(v)),
Err((_, Some(E::WouldBlock))) => return Ok(None), // no data to read
Err((_, Some(E::Interrupted))) => continue, // try again
Err((e, _)) => return Err(e),
};
}
}
pub trait ReadNonblockingWithBoringErrorsHandledExt {
/// Convenience wrapper using [nonblocking_handle_io_errors] with [std::io::Read]
fn read_nonblocking_with_boring_errors_handled(
&mut self,
buf: &mut [u8],
) -> io::Result<Option<usize>>;
}
impl<T: io::Read> ReadNonblockingWithBoringErrorsHandledExt for T {
fn read_nonblocking_with_boring_errors_handled(
&mut self,
buf: &mut [u8],
) -> io::Result<Option<usize>> {
nonblocking_handle_io_errors(|| self.read(buf))
}
}

View File

@@ -1,359 +0,0 @@
use std::{borrow::BorrowMut, cmp::min, io};
use thiserror::Error;
use zeroize::Zeroize;
use crate::{
io::{TryIoErrorKind, TryIoResultKindHintExt},
result::ensure_or,
};
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
#[derive(Error, Debug)]
pub enum SanityError {
#[error("Offset is out of read buffer bounds")]
OutOfBufferBounds,
#[error("Offset is out of message buffer bounds")]
OutOfMessageBounds,
}
#[derive(Error, Debug)]
#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")]
pub struct MessageTooLargeError {
msg_size: usize,
buf_size: usize,
}
impl MessageTooLargeError {
pub fn new(msg_size: usize, buf_size: usize) -> Self {
Self { msg_size, buf_size }
}
pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> {
let err = MessageTooLargeError { msg_size, buf_size };
ensure_or(msg_size <= buf_size, err)
}
}
#[derive(Debug)]
pub struct ReadFromIoReturn<'a> {
pub bytes_read: usize,
pub message: Option<&'a mut [u8]>,
}
impl<'a> ReadFromIoReturn<'a> {
pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self {
Self {
bytes_read,
message,
}
}
}
#[derive(Debug, Error)]
pub enum ReadFromIoError {
#[error("Error reading from the underlying stream")]
IoError(#[from] io::Error),
#[error("Message size out of buffer bounds")]
MessageTooLargeError(#[from] MessageTooLargeError),
}
impl TryIoErrorKind for ReadFromIoError {
fn try_io_error_kind(&self) -> Option<io::ErrorKind> {
match self {
ReadFromIoError::IoError(ioe) => Some(ioe.kind()),
_ => None,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
header: [u8; HEADER_SIZE],
buf: Buf,
off: usize,
}
impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
pub fn new(buf: Buf) -> Self {
let header = Default::default();
let off = 0;
Self { header, buf, off }
}
pub fn clear(&mut self) {
self.zeroize()
}
pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self {
Self { header, buf, off }
}
pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) {
let Self { header, buf, off } = self;
(header, buf, off)
}
pub fn read_all_from_stdio<R: io::Read>(
&mut self,
mut r: R,
) -> Result<&mut [u8], ReadFromIoError> {
use io::ErrorKind as K;
loop {
match self.read_from_stdio(&mut r).try_io_err_kind_hint() {
// Success (appeasing the borrow checker by calling message_mut())
Ok(ReadFromIoReturn {
message: Some(_), ..
}) => break Ok(self.message_mut().unwrap().unwrap()),
// Unexpected EOF
Ok(ReadFromIoReturn { bytes_read: 0, .. }) => {
break Err(ReadFromIoError::IoError(io::Error::new(
K::UnexpectedEof,
"",
)))
}
// Retry
Ok(ReadFromIoReturn { message: None, .. }) => continue,
Err((_, Some(K::Interrupted))) => continue,
// Other error
Err((e, _)) => break Err(e),
}
}
}
pub fn read_from_stdio<R: io::Read>(
&mut self,
mut r: R,
) -> Result<ReadFromIoReturn, ReadFromIoError> {
Ok(match self.next_slice_to_write_to()? {
// Read some bytes; any MessageTooLargeError in the call to self.message_mut() is
// ignored to ensure this function changes no state upon errors; the user should rerun
// the function and collect the MessageTooLargeError on the following invocation
Some(buf) => {
let bytes_read = r.read(buf)?;
self.advance(bytes_read).unwrap();
let message = self.message_mut().ok().flatten();
ReadFromIoReturn {
bytes_read,
message,
}
}
// Message is already fully read; full delegation to self.message_mut()
None => ReadFromIoReturn {
bytes_read: 0,
message: self.message_mut()?,
},
})
}
pub fn next_slice_to_write_to(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> {
match buf.is_empty() {
true => None,
false => Some(buf),
}
}
macro_rules! return_if_nonempty_some {
($opt:expr) => {{
// Deliberate double expansion of $opt to appease the borrow checker *sigh*
if $opt.and_then(some_if_nonempty).is_some() {
return Ok($opt);
}
}};
}
return_if_nonempty_some!(Some(self.header_buffer_left_mut()));
return_if_nonempty_some!(self.message_fragment_left_mut()?);
Ok(None)
}
pub fn advance(&mut self, count: usize) -> Result<(), SanityError> {
let off = self.off + count;
let msg_off = off.saturating_sub(HEADER_SIZE);
use SanityError as E;
let alloc = self.message_buffer().len();
let msgsz = self.message_size();
ensure_or(msg_off <= alloc, E::OutOfBufferBounds)?;
ensure_or(
msgsz.map(|s| msg_off <= s).unwrap_or(true),
E::OutOfMessageBounds,
)?;
self.off = off;
Ok(())
}
pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> {
let buf_size = self.message_buffer().len();
let msg_size = match self.get_header() {
None => return Ok(()),
Some(v) => v,
};
MessageTooLargeError::ensure(msg_size, buf_size)
}
pub fn header_buffer(&self) -> &[u8] {
&self.header[..]
}
pub fn header_buffer_mut(&mut self) -> &mut [u8] {
&mut self.header[..]
}
pub fn message_buffer(&self) -> &[u8] {
self.buf.borrow()
}
pub fn message_buffer_mut(&mut self) -> &mut [u8] {
self.buf.borrow_mut()
}
pub fn bytes_read(&self) -> &usize {
&self.off
}
pub fn into_message_buffer(self) -> Buf {
let Self { buf, .. } = self;
buf
}
pub fn header_buffer_offset(&self) -> usize {
min(self.off, HEADER_SIZE)
}
pub fn message_buffer_offset(&self) -> usize {
self.off.saturating_sub(HEADER_SIZE)
}
pub fn has_header(&self) -> bool {
self.header_buffer_offset() == HEADER_SIZE
}
pub fn has_message(&self) -> Result<bool, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?;
let msg_size = match self.get_header() {
None => return Ok(false),
Some(v) => v,
};
Ok(self.message_buffer_avail().len() == msg_size)
}
pub fn header_buffer_avail(&self) -> &[u8] {
let off = self.header_buffer_offset();
&self.header_buffer()[..off]
}
pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] {
let off = self.header_buffer_offset();
&mut self.header_buffer_mut()[..off]
}
pub fn header_buffer_left(&self) -> &[u8] {
let off = self.header_buffer_offset();
&self.header_buffer()[off..]
}
pub fn header_buffer_left_mut(&mut self) -> &mut [u8] {
let off = self.header_buffer_offset();
&mut self.header_buffer_mut()[off..]
}
pub fn message_buffer_avail(&self) -> &[u8] {
let off = self.message_buffer_offset();
&self.message_buffer()[..off]
}
pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] {
let off = self.message_buffer_offset();
&mut self.message_buffer_mut()[..off]
}
pub fn message_buffer_left(&self) -> &[u8] {
let off = self.message_buffer_offset();
&self.message_buffer()[off..]
}
pub fn message_buffer_left_mut(&mut self) -> &mut [u8] {
let off = self.message_buffer_offset();
&mut self.message_buffer_mut()[off..]
}
pub fn get_header(&self) -> Option<usize> {
match self.header_buffer_offset() == HEADER_SIZE {
false => None,
true => Some(u64::from_le_bytes(self.header) as usize),
}
}
pub fn message_size(&self) -> Option<usize> {
self.get_header()
}
pub fn encoded_message_bytes(&self) -> Option<usize> {
self.message_size().map(|sz| sz + HEADER_SIZE)
}
pub fn message_fragment(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?;
Ok(self.message_size().map(|sz| &self.message_buffer()[..sz]))
}
pub fn message_fragment_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?;
Ok(self
.message_size()
.map(|sz| &mut self.message_buffer_mut()[..sz]))
}
pub fn message_fragment_avail(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len();
self.message_fragment()
.map(|frag| frag.map(|frag| &frag[..off]))
}
pub fn message_fragment_avail_mut(
&mut self,
) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len();
self.message_fragment_mut()
.map(|frag| frag.map(|frag| &mut frag[..off]))
}
pub fn message_fragment_left(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len();
self.message_fragment()
.map(|frag| frag.map(|frag| &frag[off..]))
}
pub fn message_fragment_left_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len();
self.message_fragment_mut()
.map(|frag| frag.map(|frag| &mut frag[off..]))
}
pub fn message(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
let sz = self.message_size();
self.message_fragment_avail()
.map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag)))
}
pub fn message_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
let sz = self.message_size();
self.message_fragment_avail_mut()
.map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag)))
}
}
impl<Buf: BorrowMut<[u8]>> Zeroize for LengthPrefixDecoder<Buf> {
fn zeroize(&mut self) {
self.header.zeroize();
self.message_buffer_mut().zeroize();
self.off.zeroize();
}
}

View File

@@ -1,381 +0,0 @@
use std::{
borrow::{Borrow, BorrowMut},
cmp::min,
io,
};
use thiserror::Error;
use zeroize::Zeroize;
use crate::{io::IoResultKindHintExt, result::ensure_or};
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of buffer bounds")]
pub struct PositionOutOfBufferBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of message bounds")]
pub struct PositionOutOfMessageBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of header bounds")]
pub struct PositionOutOfHeaderBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Message length is bigger than buffer length")]
pub struct MessageTooLarge;
#[derive(Error, Debug, Clone, Copy)]
pub enum MessageLenSanityError {
#[error("{0:?}")]
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
#[error("{0:?}")]
MessageTooLarge(#[from] MessageTooLarge),
}
#[derive(Error, Debug, Clone, Copy)]
pub enum PositionSanityError {
#[error("{0:?}")]
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
#[error("{0:?}")]
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
}
#[derive(Error, Debug, Clone, Copy)]
pub enum SanityError {
#[error("{0:?}")]
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
#[error("{0:?}")]
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
#[error("{0:?}")]
MessageTooLarge(#[from] MessageTooLarge),
}
impl TryFrom<SanityError> for MessageLenSanityError {
type Error = PositionOutOfBufferBounds;
fn try_from(value: SanityError) -> Result<Self, Self::Error> {
use {MessageLenSanityError as T, SanityError as F};
match value {
F::PositionOutOfMessageBounds(e) => Ok(T::PositionOutOfMessageBounds(e)),
F::MessageTooLarge(e) => Ok(T::MessageTooLarge(e)),
F::PositionOutOfBufferBounds(e) => Err(e),
}
}
}
impl From<MessageLenSanityError> for SanityError {
fn from(value: MessageLenSanityError) -> Self {
use {MessageLenSanityError as F, SanityError as T};
match value {
F::PositionOutOfMessageBounds(e) => T::PositionOutOfMessageBounds(e),
F::MessageTooLarge(e) => T::MessageTooLarge(e),
}
}
}
impl From<PositionSanityError> for SanityError {
fn from(value: PositionSanityError) -> Self {
use {PositionSanityError as F, SanityError as T};
match value {
F::PositionOutOfBufferBounds(e) => T::PositionOutOfBufferBounds(e),
F::PositionOutOfMessageBounds(e) => T::PositionOutOfMessageBounds(e),
}
}
}
pub struct WriteToIoReturn {
pub bytes_written: usize,
pub done: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
buf: Buf,
header: [u8; HEADER_SIZE],
pos: usize,
}
impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
pub fn from_buffer(buf: Buf) -> Self {
let (header, pos) = ([0u8; HEADER_SIZE], 0);
let mut r = Self { buf, header, pos };
r.clear();
r
}
pub fn from_message(msg: Buf) -> Self {
let mut r = Self::from_buffer(msg);
r.restart_write_with_new_message(r.buffer_bytes().len())
.unwrap();
r
}
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
let mut r = Self::from_message(msg);
r.set_message_len(len)?;
Ok(r)
}
pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result<Self, SanityError> {
let mut r = Self::from_buffer(buf);
r.set_msg_len_and_position(len, pos)?;
Ok(r)
}
pub fn into_buffer(self) -> Buf {
let Self { buf, .. } = self;
buf
}
pub fn into_parts(self) -> (Buf, usize, usize) {
let len = self.message_len();
let pos = self.writing_position();
let buf = self.into_buffer();
(buf, len, pos)
}
pub fn clear(&mut self) {
self.set_msg_len_and_position(0, 0).unwrap();
self.set_message_offset(0).unwrap();
}
pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
use io::ErrorKind as K;
loop {
match self.write_to_stdio(&mut w).io_err_kind_hint() {
// Done
Ok(WriteToIoReturn { done: true, .. }) => break Ok(()),
// Retry
Ok(WriteToIoReturn { done: false, .. }) => continue,
Err((_, K::Interrupted)) => continue,
Err((e, _)) => break Err(e),
}
}
}
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
if self.exhausted() {
return Ok(WriteToIoReturn {
bytes_written: 0,
done: true,
});
}
let buf = self.next_slice_to_write();
let bytes_written = w.write(buf)?;
self.advance(bytes_written).unwrap();
let done = self.exhausted();
Ok(WriteToIoReturn {
bytes_written,
done,
})
}
pub fn restart_write(&mut self) {
self.set_writing_position(0).unwrap()
}
pub fn restart_write_with_new_message(
&mut self,
len: usize,
) -> Result<(), MessageLenSanityError> {
self.set_msg_len_and_position(len, 0)
.map_err(|e| e.try_into().unwrap())
}
pub fn next_slice_to_write(&self) -> &[u8] {
let s = self.header_left();
if !s.is_empty() {
return s;
}
let s = self.message_left();
if !s.is_empty() {
return s;
}
&[]
}
pub fn exhausted(&self) -> bool {
self.next_slice_to_write().is_empty()
}
pub fn message(&self) -> &[u8] {
&self.buffer_bytes()[..self.message_len()]
}
pub fn header_written(&self) -> &[u8] {
&self.header()[..self.header_offset()]
}
pub fn header_left(&self) -> &[u8] {
&self.header()[self.header_offset()..]
}
pub fn message_written(&self) -> &[u8] {
&self.message()[..self.message_offset()]
}
pub fn message_left(&self) -> &[u8] {
&self.message()[self.message_offset()..]
}
pub fn buf(&self) -> &Buf {
&self.buf
}
pub fn buffer_bytes(&self) -> &[u8] {
self.buf().borrow()
}
pub fn decode_header(&self) -> u64 {
u64::from_le_bytes(self.header)
}
pub fn header(&self) -> &[u8; HEADER_SIZE] {
&self.header
}
pub fn message_len(&self) -> usize {
self.decode_header() as usize
}
pub fn encoded_message_bytes(&self) -> usize {
self.message_len() + HEADER_SIZE
}
pub fn writing_position(&self) -> usize {
self.pos
}
pub fn header_offset(&self) -> usize {
min(self.writing_position(), HEADER_SIZE)
}
pub fn message_offset(&self) -> usize {
self.writing_position().saturating_sub(HEADER_SIZE)
}
pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> {
self.offset_transaction(|t| {
t.header = header;
t.ensure_msg_in_buf_bounds()?;
t.ensure_pos_in_msg_bounds()?;
Ok(())
})
}
pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> {
self.set_header(header.to_le_bytes())
}
pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> {
self.encode_and_set_header(len as u64)
}
pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> {
self.offset_transaction(|t| {
t.pos = pos;
t.ensure_pos_in_buf_bounds()?;
t.ensure_pos_in_msg_bounds()?;
Ok(())
})
}
pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> {
ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?;
self.set_writing_position(off).unwrap();
Ok(())
}
pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> {
self.set_writing_position(off + HEADER_SIZE)
}
pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> {
self.set_writing_position(self.writing_position() + off)
}
pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> {
self.pos = 0;
self.set_message_len(len)?;
self.set_writing_position(pos)?;
Ok(())
}
fn offset_transaction<E, F>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(&mut LengthPrefixEncoder<&[u8]>) -> Result<(), E>,
{
let (header, pos) = {
let (buf, header, pos) = (self.buffer_bytes(), self.header, self.pos);
let mut tmp = LengthPrefixEncoder { buf, header, pos };
f(&mut tmp)?;
Ok((tmp.header, tmp.pos))
}?;
(self.header, self.pos) = (header, pos);
Ok(())
}
fn ensure_pos_in_buf_bounds(&self) -> Result<(), PositionOutOfBufferBounds> {
ensure_or(
self.message_offset() <= self.buffer_bytes().len(),
PositionOutOfBufferBounds,
)
}
fn ensure_pos_in_msg_bounds(&self) -> Result<(), PositionOutOfMessageBounds> {
ensure_or(
self.message_offset() <= self.message_len(),
PositionOutOfMessageBounds,
)
}
fn ensure_msg_in_buf_bounds(&self) -> Result<(), MessageTooLarge> {
ensure_or(
self.message_len() <= self.buffer_bytes().len(),
MessageTooLarge,
)
}
}
impl<Buf: BorrowMut<[u8]>> LengthPrefixEncoder<Buf> {
pub fn buf_mut(&mut self) -> &mut Buf {
&mut self.buf
}
pub fn buffer_bytes_mut(&mut self) -> &mut [u8] {
self.buf.borrow_mut()
}
pub fn message_mut(&mut self) -> &mut [u8] {
let off = self.message_len();
&mut self.buffer_bytes_mut()[..off]
}
pub fn message_written_mut(&mut self) -> &mut [u8] {
let off = self.message_offset();
&mut self.message_mut()[..off]
}
pub fn message_left_mut(&mut self) -> &mut [u8] {
let off = self.message_offset();
&mut self.message_mut()[off..]
}
}
impl<Buf: BorrowMut<[u8]>> Zeroize for LengthPrefixEncoder<Buf> {
fn zeroize(&mut self) {
self.buffer_bytes_mut().zeroize();
self.header.zeroize();
self.pos.zeroize();
self.clear();
}
}

View File

@@ -1,2 +0,0 @@
pub mod decoder;
pub mod encoder;

View File

@@ -4,13 +4,8 @@ pub mod b64;
pub mod fd;
pub mod file;
pub mod functional;
pub mod io;
pub mod length_prefix_encoding;
pub mod mem;
pub mod mio;
pub mod ord;
pub mod result;
pub mod time;
pub mod typenum;
pub mod zerocopy;
pub mod zeroize;

View File

@@ -1,7 +1,5 @@
use std::borrow::{Borrow, BorrowMut};
use std::cmp::min;
use std::mem::{forget, swap};
use std::ops::{Deref, DerefMut};
/// Concatenate two byte arrays
// TODO: Zeroize result?
@@ -33,62 +31,3 @@ pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d
let len = min(src.len(), dst.len());
dst[..len].copy_from_slice(&src[..len]);
}
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)]
pub struct Forgetting<T> {
value: Option<T>,
}
impl<T> Forgetting<T> {
pub fn new(value: T) -> Self {
let value = Some(value);
Self { value }
}
pub fn extract(mut self) -> T {
let mut value = None;
swap(&mut value, &mut self.value);
value.unwrap()
}
}
impl<T> From<T> for Forgetting<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T> Deref for Forgetting<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value.as_ref().unwrap()
}
}
impl<T> DerefMut for Forgetting<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value.as_mut().unwrap()
}
}
impl<T> Borrow<T> for Forgetting<T> {
fn borrow(&self) -> &T {
self.deref()
}
}
impl<T> BorrowMut<T> for Forgetting<T> {
fn borrow_mut(&mut self) -> &mut T {
self.deref_mut()
}
}
impl<T> Drop for Forgetting<T> {
fn drop(&mut self) {
let mut value = None;
swap(&mut self.value, &mut value);
forget(value)
}
}

View File

@@ -1,39 +0,0 @@
use mio::net::{UnixListener, UnixStream};
use rustix::fd::RawFd;
use crate::fd::claim_fd;
pub mod interest {
use mio::Interest;
pub const R: Interest = Interest::READABLE;
pub const W: Interest = Interest::WRITABLE;
pub const RW: Interest = R.add(W);
}
pub trait UnixListenerExt: Sized {
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
}
impl UnixListenerExt for UnixListener {
fn claim_fd(fd: RawFd) -> anyhow::Result<Self> {
use std::os::unix::net::UnixListener as StdUnixListener;
let sock = StdUnixListener::from(claim_fd(fd)?);
sock.set_nonblocking(true)?;
Ok(UnixListener::from_std(sock))
}
}
pub trait UnixStreamExt: Sized {
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
}
impl UnixStreamExt for UnixStream {
fn claim_fd(fd: RawFd) -> anyhow::Result<Self> {
use std::os::unix::net::UnixStream as StdUnixStream;
let sock = StdUnixStream::from(claim_fd(fd)?);
sock.set_nonblocking(true)?;
Ok(UnixStream::from_std(sock))
}
}

View File

@@ -1,4 +1,5 @@
use std::convert::Infallible;
use std::result::Result;
/// Try block basically…returns a result and allows the use of the question mark operator inside
#[macro_export]
@@ -96,14 +97,3 @@ impl<T> GuaranteedValue for Guaranteed<T> {
self.unwrap()
}
}
pub fn ensure_or<E>(b: bool, err: E) -> Result<(), E> {
match b {
true => Ok(()),
false => Err(err),
}
}
pub fn bail_if<E>(b: bool, err: E) -> Result<(), E> {
ensure_or(!b, err)
}

View File

@@ -19,22 +19,15 @@ pub trait IntoConst<T> {
const VALUE: T;
}
#[allow(dead_code)]
struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
#[allow(dead_code)]
struct ConstApplyPosSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
#[allow(dead_code)]
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param); // impl IntoConst<T>
#[allow(dead_code)]
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
/// Assigns an unsigned type to a signed type

View File

@@ -1,7 +0,0 @@
mod ref_maker;
mod zerocopy_ref_ext;
mod zerocopy_slice_ext;
pub use ref_maker::*;
pub use zerocopy_ref_ext::*;
pub use zerocopy_slice_ext::*;

View File

@@ -1,107 +0,0 @@
use std::marker::PhantomData;
use anyhow::{ensure, Context};
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use zeroize::Zeroize;
use crate::zeroize::ZeroizedExt;
#[derive(Clone, Copy, Debug)]
pub struct RefMaker<B: Sized, T> {
buf: B,
_phantom_t: PhantomData<T>,
}
impl<B, T> RefMaker<B, T> {
pub fn new(buf: B) -> Self {
let _phantom_t = PhantomData;
Self { buf, _phantom_t }
}
pub const fn target_size() -> usize {
std::mem::size_of::<T>()
}
pub fn into_buf(self) -> B {
self.buf
}
pub fn buf(&self) -> &B {
&self.buf
}
pub fn buf_mut(&mut self) -> &mut B {
&mut self.buf
}
}
impl<B: ByteSlice, T> RefMaker<B, T> {
pub fn parse(self) -> anyhow::Result<Ref<B, T>> {
self.ensure_fit()?;
Ref::<B, T>::new(self.buf).context("Parser error!")
}
pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> {
self.ensure_fit()?;
let (head, tail) = self.buf.split_at(Self::target_size());
Ok((Self::new(head), tail))
}
pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> {
self.ensure_fit()?;
let (head, tail) = self.buf.split_at(Self::target_size());
Ok((Self::new(head), Self::new(tail)))
}
pub fn from_prefix(self) -> anyhow::Result<Self> {
Ok(Self::from_prefix_with_tail(self)?.0)
}
pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> {
self.ensure_fit()?;
let point = self.bytes().len() - Self::target_size();
let (head, tail) = self.buf.split_at(point);
Ok((Self::new(tail), head))
}
pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> {
self.ensure_fit()?;
let point = self.bytes().len() - Self::target_size();
let (head, tail) = self.buf.split_at(point);
Ok((Self::new(head), Self::new(tail)))
}
pub fn from_suffix(self) -> anyhow::Result<Self> {
Ok(Self::from_suffix_with_head(self)?.0)
}
pub fn bytes(&self) -> &[u8] {
self.buf().deref()
}
pub fn ensure_fit(&self) -> anyhow::Result<()> {
let have = self.bytes().len();
let need = Self::target_size();
ensure!(
need <= have,
"Buffer is undersized at {have} bytes (need {need} bytes)!"
);
Ok(())
}
}
impl<B: ByteSliceMut, T> RefMaker<B, T> {
pub fn make_zeroized(self) -> anyhow::Result<Ref<B, T>> {
self.zeroized().parse()
}
pub fn bytes_mut(&mut self) -> &mut [u8] {
self.buf_mut().deref_mut()
}
}
impl<B: ByteSliceMut, T> Zeroize for RefMaker<B, T> {
fn zeroize(&mut self) {
self.bytes_mut().zeroize()
}
}

View File

@@ -1,27 +0,0 @@
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
pub trait ZerocopyEmancipateExt<B, T> {
fn emancipate(&self) -> Ref<&[u8], T>;
}
pub trait ZerocopyEmancipateMutExt<B, T> {
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>;
}
impl<B, T> ZerocopyEmancipateExt<B, T> for Ref<B, T>
where
B: ByteSlice,
{
fn emancipate(&self) -> Ref<&[u8], T> {
Ref::new(self.bytes()).unwrap()
}
}
impl<B, T> ZerocopyEmancipateMutExt<B, T> for Ref<B, T>
where
B: ByteSliceMut,
{
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T> {
Ref::new(self.bytes_mut()).unwrap()
}
}

View File

@@ -1,39 +0,0 @@
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::RefMaker;
pub trait ZerocopySliceExt: Sized + ByteSlice {
fn zk_ref_maker<T>(self) -> RefMaker<Self, T> {
RefMaker::<Self, T>::new(self)
}
fn zk_parse<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().parse()
}
fn zk_parse_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_prefix()?.parse()
}
fn zk_parse_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_suffix()?.parse()
}
}
impl<B: ByteSlice> ZerocopySliceExt for B {}
pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut {
fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().make_zeroized()
}
fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_prefix()?.make_zeroized()
}
fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_suffix()?.make_zeroized()
}
}
impl<B: ByteSliceMut> ZerocopyMutSliceExt for B {}

View File

@@ -1,2 +0,0 @@
mod zeroized_ext;
pub use zeroized_ext::*;

View File

@@ -1,10 +0,0 @@
use zeroize::Zeroize;
pub trait ZeroizedExt: Zeroize + Sized {
fn zeroized(mut self) -> Self {
self.zeroize();
self
}
}
impl<T: Zeroize + Sized> ZeroizedExt for T {}

View File

@@ -19,7 +19,7 @@ wireguard-uapi = { workspace = true }
# Socket handler only
rosenpass-to = { workspace = true }
tokio = { version = "1.39.1", features = ["sync", "full", "mio"] }
tokio = { version = "1.38.0", features = ["sync", "full", "mio"] }
anyhow = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }

View File

@@ -88,7 +88,7 @@ where
None => return Ok(None),
};
let typ = res.first().ok_or(invalid_msg_poller())?;
let typ = res.get(0).ok_or(invalid_msg_poller())?;
let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type
@@ -113,7 +113,7 @@ where
fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> {
let config: Result<NetworkBrokerConfig, NetworkBrokerConfigErr> = config.try_into();
let config = config.map_err(BrokerClientSetPskError::BrokerError)?;
let config = config.map_err(|e| BrokerClientSetPskError::BrokerError(e))?;
use BrokerClientSetPskError::*;
const BUF_SIZE: usize = REQUEST_MSG_BUFFER_SIZE;

View File

@@ -11,12 +11,12 @@ pub struct NetworkBrokerConfig<'a> {
pub psk: &'a Secret<WG_KEY_LEN>,
}
impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> {
Self {
interface: src.iface.as_bytes(),
peer_id: src.peer_id,
psk: src.psk,
impl<'a> Into<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
fn into(self) -> SerializedBrokerConfig<'a> {
SerializedBrokerConfig {
interface: self.iface.as_bytes(),
peer_id: self.peer_id,
psk: self.psk,
additional_params: &[],
}
}

View File

@@ -1,3 +1,4 @@
use std::result::Result;
use std::str::{from_utf8, Utf8Error};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
@@ -42,7 +43,7 @@ impl SetPskRequest {
self.iface_size = iface.len() as u8;
self.iface_buf = [0; 255];
self.iface_buf[..iface.len()].copy_from_slice(iface);
(&mut self.iface_buf[..iface.len()]).copy_from_slice(iface);
Some(())
}

View File

@@ -1,4 +1,5 @@
use std::borrow::BorrowMut;
use std::result::Result;
use rosenpass_secret_memory::{Public, Secret};
@@ -48,7 +49,7 @@ where
) -> Result<usize, BrokerServerError> {
use BrokerServerError::*;
let typ = req.first().ok_or(InvalidMessage)?;
let typ = req.get(0).ok_or(InvalidMessage)?;
let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type

View File

@@ -9,6 +9,7 @@ fn main() {
#[cfg(target_os = "linux")]
pub mod linux {
use std::io::{stdin, stdout, Read, Write};
use std::result::Result;
use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer;
@@ -59,7 +60,7 @@ pub mod linux {
// Write the response
stdout.write_all(&(res.len() as u64).to_le_bytes())?;
stdout.write_all(res)?;
stdout.write_all(&res)?;
stdout.flush()?;
}
}

View File

@@ -121,7 +121,7 @@ async fn direct_broker_process(
// Read the message itself
let mut res_buf = request; // Avoid allocating memory if we don't have to
res_buf.resize(len, 0);
res_buf.resize(len as usize, 0);
stdout.read_exact(&mut res_buf[..len]).await?;
// Return to the unix socket connection worker
@@ -163,7 +163,7 @@ async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -
);
// Read the message itself
req_buf.resize(len, 0);
req_buf.resize(len as usize, 0);
stream.read_exact(&mut req_buf[..len]).await?;
// Handle the message

View File

@@ -52,17 +52,23 @@ impl MioBrokerClient {
// This sucks
match self.inner.poll_response() {
Ok(res) => Ok(res),
Err(BrokerClientPollResponseError::IoError(e)) => Err(e),
Err(BrokerClientPollResponseError::InvalidMessage) => bail!("Invalid message"),
}
Ok(res) => {
return Ok(res);
}
Err(BrokerClientPollResponseError::IoError(e)) => {
return Err(e);
}
Err(BrokerClientPollResponseError::InvalidMessage) => {
bail!("Invalid message");
}
};
}
}
impl WireGuardBroker for MioBrokerClient {
type Error = anyhow::Error;
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> anyhow::Result<()> {
fn set_psk<'a>(&mut self, config: SerializedBrokerConfig<'a>) -> anyhow::Result<()> {
use BrokerClientSetPskError::*;
let e = self.inner.set_psk(config);
match e {
@@ -109,7 +115,7 @@ impl BrokerClientIo for MioBrokerClientIo {
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
self.flush()?;
self.send_or_buffer(&(buf.len() as u64).to_le_bytes())?;
self.send_or_buffer(buf)?;
self.send_or_buffer(&buf)?;
self.flush()?;
Ok(())
@@ -186,7 +192,7 @@ impl MioBrokerClientIo {
self.send_buf.drain(..written);
self.socket.try_io(|| (&self.socket).flush())?;
(&self.socket).try_io(|| (&self.socket).flush())?;
res
}
@@ -198,7 +204,7 @@ impl MioBrokerClientIo {
off += raw_send(&self.socket, buf)?;
}
self.send_buf.extend(buf[off..].iter());
self.send_buf.extend((&buf[off..]).iter());
Ok(())
}
@@ -225,7 +231,7 @@ fn raw_send(mut socket: &mio::net::UnixStream, data: &[u8]) -> anyhow::Result<us
}
})?;
Ok(off)
return Ok(off);
}
fn raw_recv(mut socket: &mio::net::UnixStream, out: &mut [u8]) -> anyhow::Result<usize> {
@@ -249,5 +255,5 @@ fn raw_recv(mut socket: &mio::net::UnixStream, out: &mut [u8]) -> anyhow::Result
}
})?;
Ok(off)
return Ok(off);
}

View File

@@ -87,20 +87,21 @@ impl WireGuardBroker for NetlinkWireGuardBroker {
.sock
.get_device(wg::DeviceInterface::from_name(config.iface))?;
if !state
if state
.peers
.iter()
.any(|p| p.public_key == config.peer_id.value)
.find(|p| &p.public_key == &config.peer_id.value)
.is_none()
{
return Err(SetPskError::NoSuchPeer);
}
// Peer update description
let mut set_peer = wireguard_uapi::set::Peer::from_public_key(config.peer_id);
let mut set_peer = wireguard_uapi::set::Peer::from_public_key(&config.peer_id);
set_peer
.flags
.push(wireguard_uapi::linux::set::WgPeerF::UpdateOnly);
set_peer.preshared_key = Some(config.psk.secret());
set_peer.preshared_key = Some(&config.psk.secret());
// Device update description
let mut set_dev = wireguard_uapi::set::Device::from_ifname(config.iface);

View File

@@ -1,5 +1,5 @@
use rosenpass_secret_memory::{Public, Secret};
use std::fmt::Debug;
use std::{fmt::Debug, result::Result};
pub const WG_KEY_LEN: usize = 32;
pub const WG_PEER_LEN: usize = 32;

View File

@@ -36,7 +36,6 @@ mod integration_tests {
impl WireGuardBroker for MockServerBroker {
type Error = SetPskError;
#[allow(clippy::clone_on_copy)]
fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> {
loop {
let mut lock = self.inner.try_lock();