Compare commits

..

64 Commits

Author SHA1 Message Date
Katherine Watson
9fd3df67ed chore: Fix typos and add various comments 2024-08-07 23:11:13 -07:00
Karolin Varner
6d47169a5c feat: Set CLOEXEC flag on claimed fds and mask them
Masking the file descriptors (by replaying them with a file descriptor pointing towards /dev/null)
mitigates use after free (on file descriptor) attacks. In case some
piece of code still holds a reference to the file descriptor, that
file descriptor now merely holds a reference to /dev/null.

Otherwise, the file descriptor might be reused and the reference
could now mistakenly point to all sorts of – potentially more harmful – files, such as memfd_secret
file descriptors, storing our secret keys.
2024-08-05 16:16:09 +02:00
Karolin Varner
4bcd38a4ea feat: Infrastructure for the Rosenpass API 2024-08-03 16:51:18 +02:00
Karolin Varner
730a03957a feat: A variety of utilities in preparation for implementing the API 2024-08-03 16:50:21 +02:00
Karolin Varner
ea071f5363 feat: Convenience functions and traits to automatically handle ErrorKind::{Interrupt, WouldBlock} 2024-08-03 16:49:02 +02:00
Karolin Varner
3063d3e4c2 feat: Convenience traits to get the ErrorKind of an io error for match clauses 2024-08-03 16:48:25 +02:00
Karolin Varner
1bf0eed90a feat: Convenience function to just call a function 2024-08-03 16:46:48 +02:00
Karolin Varner
138e6b6553 chore: to crate documentation indendation (purely cosmetic) 2024-08-03 16:32:02 +02:00
Karolin Varner
2dde0a2b47 chore: Refactor integration_tests (purely cosmetic) 2024-08-03 16:31:19 +02:00
Karolin Varner
3cc3b6009f chore: Move CliCommand::run -> CliArgs::run; do not mutate the configuration
This way CliArgs::run has access to all command line parameters.
Avoided mutating the CliArgs (or rather CliCommand) structure here,
because doing so is simply bad style. There is no good reasoning for
why this function should mutate CliCommand, except for a bit of
convenience.
2024-08-03 16:29:19 +02:00
Karolin Varner
1ab457ed37 fix: Print stack trace to errors propagated to main function 2024-08-03 15:50:14 +02:00
Karolin Varner
c9c266fe7c fix: Flush stdout after printing key update notification
Otherwise, the notification might not be delivered due to buffering.
2024-08-03 15:50:14 +02:00
Karolin Varner
8d3c8790fe chore: Reorganize memfd secret policy
- Policy is now set in main.rs, not cli.rs.
- Feature is called experiment_memfd_secret, not enable_memfd_alloc

This also fixes the last remaining warnings.
2024-08-03 15:17:09 +02:00
Karolin Varner
648a94ead8 chore: Clippy fixes on wireguard-broker 2024-08-03 15:02:49 +02:00
Karolin Varner
54ac5eecdb chore: Warnings & clippy hints 2024-08-03 14:13:03 +02:00
Karolin Varner
40c5bbd167 chore: Ensure that rustAnalyzer is installed in dev environment 2024-08-03 14:06:19 +02:00
Karolin Varner
a4b8fc2226 chore: Move memcmp test API doc to test memcmp test module 2024-08-03 14:05:22 +02:00
Karolin Varner
37f7b3e4e9 fix: Consistently use feature flag experiment_libcrux
Before this, some parts of the code used an incorrect feature flag
name, preventing libcrux from being used.
2024-08-03 14:03:31 +02:00
Karolin Varner
deafc1c1af chore: Style adjustments – Cargo.toml 2024-08-03 14:03:31 +02:00
Karolin Varner
6bbe85a57b chore: Remove unnecessary imports 2024-08-03 13:59:55 +02:00
Karolin Varner
e70c5b33a8 chore: Ignore vscode directory 2024-08-03 13:35:31 +02:00
dependabot[bot]
25fdfef4d0 build(deps): bump clap from 4.5.11 to 4.5.13 (#384)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.11 to 4.5.13.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.11...v4.5.13)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-01 09:47:20 +02:00
dependabot[bot]
6ab8fafe59 build(deps): bump clap from 4.5.9 to 4.5.11
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.9 to 4.5.11.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.9...v4.5.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 14:28:22 +02:00
dependabot[bot]
c1aacf76b8 build(deps): bump mio from 0.8.11 to 1.0.1 (#380)
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.11 to 1.0.1.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/commits)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-27 15:59:48 +02:00
dependabot[bot]
1bcaf5781f build(deps): bump tokio from 1.38.1 to 1.39.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.38.1 to 1.39.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.1...tokio-1.39.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-25 19:05:30 +02:00
Paul Spooren
de60e5f8f0 Docs: run prettier over CONTRIBUTING.md
... or else the CI fails on all PRs

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-07-25 15:56:54 +02:00
Alice Bowman
b50ddda151 Documentation: pointed to website documentation in readme 2024-07-23 10:46:52 +02:00
Alice Bowman
7282fba3b3 Docs: migrated cooking recipe from wiki 2024-07-23 10:41:44 +02:00
dependabot[bot]
0cca389f10 build(deps): bump thiserror from 1.0.62 to 1.0.63 (#371)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.62 to 1.0.63.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.62...1.0.63)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-18 14:29:08 +02:00
Karolin Varner
8a08d49215 Merge pull request #370 from rosenpass/dependabot/cargo/tokio-1.38.1
build(deps): bump tokio from 1.38.0 to 1.38.1
2024-07-17 08:35:06 +02:00
dependabot[bot]
8637bc7884 build(deps): bump tokio from 1.38.0 to 1.38.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.38.0 to 1.38.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.0...tokio-1.38.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 23:32:14 +00:00
dependabot[bot]
4412c2bdd1 build(deps): bump thiserror from 1.0.61 to 1.0.62 (#366)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.61 to 1.0.62.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-12 14:28:18 +02:00
Karolin Varner
ecc815dd8e Merge pull request #363 from aparcar/regression-ci
Regression CI and fixup
2024-07-10 21:09:16 +02:00
Paul Spooren
b7d7c03e35 Merge branch 'main' into regression-ci 2024-07-10 20:06:33 +02:00
Paul Spooren
f6320c3c35 ci: fixup regression test
Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-07-10 18:57:45 +02:00
Karolin Varner
19f7905bc9 Merge pull request #362 from rosenpass/dev/karo/libcrux_chacha20poly1305
feat: Experimental support for encryption using libcrux
2024-07-10 15:08:31 +02:00
Karolin Varner
9b5b7ee620 Merge pull request #338 from aparcar/no-unused
drop unused import of WG_B64_LEN
2024-07-10 15:04:35 +02:00
dependabot[bot]
4fdd271de7 build(deps): bump clap from 4.5.8 to 4.5.9 (#365)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.8 to 4.5.9.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.8...v4.5.9)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 14:17:45 +02:00
dependabot[bot]
860e65965a build(deps): bump serde from 1.0.203 to 1.0.204 (#364)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.203 to 1.0.204.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.203...v1.0.204)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 09:08:54 +02:00
Prabhpreet Dua
87144233da Prettier 2024-07-08 13:54:26 +02:00
Prabhpreet Dua
d0a6e99a1f feat: Regression CI based on misc/generate_configs.py 2024-07-08 13:54:26 +02:00
Paul Spooren
79b634fadf drop unused import of WG_B64_LEN
This causes warnings

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-07-08 13:48:00 +02:00
Karolin Varner
99ac3c0902 feat: Experimental support for encryption using libcrux
Libcrux is a library for formally verified implementations of
cryptographic primitives. It uses multiple back ends; one of which is
libjade. A cryptographic library written in the jasmin assembly
language for high assurance cryptographic implementations.

To use compile with the experiment_libcrux feature enabled:

    cargo build --features experiment_libcrux
2024-07-03 21:46:40 +02:00
dependabot[bot]
010c14dadf build(deps): bump zerocopy from 0.7.34 to 0.7.35 (#361)
Bumps [zerocopy](https://github.com/google/zerocopy) from 0.7.34 to 0.7.35.
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/commits)

---
updated-dependencies:
- dependency-name: zerocopy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 11:08:42 +02:00
dependabot[bot]
45b6132312 build(deps): bump clap from 4.5.7 to 4.5.8 (#360)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.7 to 4.5.8.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.7...v4.5.8)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-29 20:18:42 +02:00
dependabot[bot]
77f9fd38f3 build(deps): bump log from 0.4.21 to 0.4.22 (#359)
Bumps [log](https://github.com/rust-lang/log) from 0.4.21 to 0.4.22.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.21...0.4.22)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-29 20:17:25 +02:00
Karolin Varner
775ed86adc Merge pull request #356 from pqcfox/hotfix/fix-kyber-encaps-fuzz-test
Fix Kyber encapsulation fuzz test shared key length to make test pass
2024-06-28 16:59:05 +02:00
Katherine Watson
40377dce1f fix: Fix shared_secret length in Kyber encaps fuzz test 2024-06-27 09:17:05 -07:00
Karolin Varner
19293471e8 Merge pull request #357 from rosenpass/dev/cve/new_name
meta: Use my new name
2024-06-27 11:15:52 +02:00
Clara Engler
cc5877dd83 meta: Use my new name 2024-06-27 10:30:34 +02:00
Karolin Varner
ebb591aa6f Merge pull request #354 from pqcfox/hotfix/fix-static-kem-branch-errors
Fix CI after merge of branch introducing PublicBox
2024-06-25 08:57:50 +02:00
Katherine Watson
07146d9914 fix: update handle_msg.rs fuzz test and handshake.rs bench to use PublicBox 2024-06-21 18:21:33 -07:00
Karolin Varner
cd04dbc4eb Move static KEM public key to new PublicBox struct 2024-06-21 13:06:05 +02:00
Katherine Watson
cc22165dc4 chore: Ensure punctuation is consistent in doc comments 2024-06-17 20:53:19 -07:00
Katherine Watson
8496571765 test: Modify existing tests to cover load/store for PublicBox as well 2024-06-17 20:49:40 -07:00
Katherine Watson
ee3a1f580e Refactor PublicBox to reuse Public code and minimize stack overhead 2024-06-17 20:49:40 -07:00
Katherine Watson
89584645c3 Migrate PublicBox to above tests 2024-06-17 20:49:40 -07:00
Katherine Watson
3286e49370 Replace &* incantations with .deref() 2024-06-17 20:49:40 -07:00
Karolin Varner
100d7b6e1c chore: Simplify some dereferencing incantations in PublicBox 2024-06-17 20:49:40 -07:00
Katherine Watson
921b2bfc39 Fix comments in PublicBox impl to refer to PublicBox 2024-06-17 20:49:40 -07:00
Katherine Watson
a18658847c Move static KEM public key to new PublicBox struct 2024-06-17 20:49:40 -07:00
Alice Michaela Bowman
bdad414c90 Add cargo-test runner for macos x86-64 (#348)
* added cargo-test runner for macos 86-64
---------

Co-authored-by: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com>
2024-06-17 15:48:19 +02:00
Paul Spooren
7c54a37618 misc: add generate_configs.py script
The script can be used to simulate setups of different sizes. A short
description is added to the `misc/` folder for further information.

This can be used for both benchmarking but also hunting down bugs which
may occur with larger setups.

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-06-13 11:11:53 +02:00
Prabhpreet Dua
7a4f700186 feat: Improved memfd-secret allocation (#347)
Improve memfd-secret guard page allocation by using combination of mmap to map allocation area, and nest memfd-secret mapping and meta information with different permissions within the area

Implemented in quininer/memsec#18 

Co-authored-by: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com>
Co-authored-by: Karolin Varner <karo@cupdev.net>
2024-06-13 10:04:35 +05:30
91 changed files with 3596 additions and 353 deletions

33
.ci/run-regression.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/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

@@ -110,7 +110,12 @@ jobs:
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
cargo-test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-13]
# - ubuntu is x86-64
# - macos-13 is also x86-64 architecture
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3

21
.github/workflows/regressions.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
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,3 +20,8 @@ _markdown_*
**/result
**/result-*
.direnv
# Visual studio code
.vscode
/output

1
.mailmap Normal file
View File

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

38
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,38 @@
**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.

217
Cargo.lock generated
View File

@@ -387,9 +387,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.7"
version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
dependencies = [
"clap_builder",
"clap_derive",
@@ -397,9 +397,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.7"
version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
dependencies = [
"anstream",
"anstyle",
@@ -409,9 +409,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.5"
version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [
"heck",
"proc-macro2",
@@ -984,8 +984,10 @@ 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]]
@@ -1066,6 +1068,18 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
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"
@@ -1222,6 +1236,38 @@ 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"
@@ -1233,6 +1279,16 @@ 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"
@@ -1261,9 +1317,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
@@ -1335,14 +1391,15 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.11"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1527,16 +1584,6 @@ 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"
@@ -1885,14 +1932,17 @@ name = "rosenpass"
version = "0.2.1"
dependencies = [
"anyhow",
"clap 4.5.7",
"clap 4.5.13",
"criterion",
"derive_builder 0.20.0",
"env_logger",
"heck",
"hex",
"hex-literal",
"home",
"log",
"memoffset 0.9.1",
"mio 0.8.11",
"mio 1.0.1",
"paste",
"procspawn",
"rand 0.8.5",
@@ -1907,10 +1957,12 @@ dependencies = [
"serial_test",
"stacker",
"static_assertions",
"tempfile",
"test_bin",
"thiserror",
"toml",
"zerocopy",
"zeroize",
]
[[package]]
@@ -1924,6 +1976,7 @@ dependencies = [
"anyhow",
"blake2",
"chacha20poly1305",
"libcrux",
"rosenpass-constant-time",
"rosenpass-oqs",
"rosenpass-secret-memory",
@@ -1997,9 +2050,12 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64ct",
"mio 1.0.1",
"rustix",
"static_assertions",
"thiserror",
"typenum",
"zerocopy",
"zeroize",
]
@@ -2008,11 +2064,11 @@ name = "rosenpass-wireguard-broker"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.5.7",
"clap 4.5.13",
"derive_builder 0.20.0",
"env_logger",
"log",
"mio 0.8.11",
"mio 1.0.1",
"postcard",
"procspawn",
"rand 0.8.5",
@@ -2148,18 +2204,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
@@ -2325,6 +2381,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "take-until"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4e17d8598067a8c134af59cd33c1c263470e089924a11ab61cf61690919fe3b"
[[package]]
name = "tempfile"
version = "3.10.1"
@@ -2360,18 +2422,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "thiserror"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
@@ -2390,28 +2452,27 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.38.0"
version = "1.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio 0.8.11",
"num_cpus",
"mio 1.0.1",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
@@ -2645,15 +2706,6 @@ 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"
@@ -2678,21 +2730,6 @@ 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"
@@ -2715,12 +2752,6 @@ 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"
@@ -2733,12 +2764,6 @@ 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"
@@ -2751,12 +2776,6 @@ 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"
@@ -2775,12 +2794,6 @@ 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"
@@ -2793,12 +2806,6 @@ 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"
@@ -2811,12 +2818,6 @@ 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"
@@ -2829,12 +2830,6 @@ 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"
@@ -2857,8 +2852,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ba4e9811befc20af3b6efb15924a7238ee5e8e8706a196576462a00b9f1af1"
dependencies = [
"derive_builder 0.10.2",
"hex",
"libc",
"neli",
"take-until",
"thiserror",
]
@@ -2886,9 +2883,9 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.34"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
@@ -2896,9 +2893,9 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
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.61"
thiserror = "1.0.63"
paste = "1.0.15"
env_logger = "0.10.2"
toml = "0.7.8"
@@ -44,26 +44,30 @@ 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.21" }
clap = { version = "4.5.7", features = ["derive"] }
serde = { version = "1.0.203", features = ["derive"] }
log = { version = "0.4.22" }
clap = { version = "4.5.13", features = ["derive"] }
serde = { version = "1.0.204", features = ["derive"] }
arbitrary = { version = "1.3.2", features = ["derive"] }
anyhow = { version = "1.0.86", features = ["backtrace", "std"] }
mio = { version = "0.8.11", features = ["net", "os-poll"] }
mio = { version = "1.0.1", 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.34", features = ["derive"] }
zerocopy = { version = "0.7.35", features = ["derive"] }
home = "0.5.9"
derive_builder = "0.20.0"
tokio = { version = "1.38", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.39", 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"
@@ -77,6 +81,6 @@ procspawn = {version = "1.0.0", features= ["test-support"]}
#Broker dependencies (might need cleanup or changes)
wireguard-uapi = "3.0.0"
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
command-fds = "0.2.3"
rustix = { version = "0.38.27", features = ["net"] }
rustix = { version = "0.38.27", features = ["net", "fs"] }

View File

@@ -10,8 +10,6 @@
//! 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,6 +9,9 @@ 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 }
@@ -20,3 +23,4 @@ static_assertions = { workspace = true }
zeroize = { workspace = true }
chacha20poly1305 = { workspace = true }
blake2 = { workspace = true }
libcrux = { workspace = true, optional = true }

View File

@@ -9,7 +9,12 @@ 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

@@ -0,0 +1,60 @@
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,4 +1,7 @@
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,18 +7,16 @@
///
/// 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 Emil Engler
.An Clara 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 Emil Engler
.An Clara 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,6 +4,9 @@ version = "0.0.1"
publish = false
edition = "2021"
[features]
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
[package.metadata]
cargo-fuzz = true
@@ -81,4 +84,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,8 +15,7 @@ pub struct Input {
}
fuzz_target!(|input: Input| {
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
ciphertext.resize(input.plaintext.len() + 16, 0);
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
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::Secret;
use rosenpass_secret_memory::{PublicBox, 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 = Secret::from_slice(&[0; StaticKem::PK_LEN]);
let pk = PublicBox::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::SK_LEN];
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
});

40
misc/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Additional files
This folder contains additional files that are used in the project.
## `generate_configs.py`
The script is used to generate configuration files for a benchmark setup
consisting of a device under testing (DUT) and automatic test equipment (ATE),
basically a strong machine capable of running multiple Rosenpass instances at
once.
At the top of the script multiple variables can be set to configure the DUT IP
address and more. Once configured you may run `python3 generate_configs.py` to
create the configuration files.
A new folder called `output/` is created containing the subfolder `dut/` and
`ate/`. The former has to be copied on the DUT, ideally reproducible hardware
like a Raspberry Pi, while the latter is copied to the ATE, i.e. a laptop.
### Running a benchmark
On the ATE a run script is required since multiple instances of `rosenpass` are
started with different configurations in parallel. The scripts are named after
the number of instances they start, e.g. `run-50.sh` starts 50 instances.
```shell
# on the ATE aka laptop
cd output/ate
./run-10.sh
```
On the DUT you start a single Rosenpass instance with the configuration matching
the ATE number of peers.
```shell
# on the DUT aka Raspberry Pi
rosenpass exchange-config configs/dut-10.toml
```
Use whatever measurement tool you like to monitor the DUT and ATE.

105
misc/generate_configs.py Normal file
View File

@@ -0,0 +1,105 @@
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",
dut_port=9999,
path_to_rosenpass_bin=os.getcwd() + "/target/release/rosenpass",
)
print(config)
output_dir = Path("output")
output_dir.mkdir(exist_ok=True)
template_dut = """
public_key = "keys/dut-public-key"
secret_key = "keys/dut-secret-key"
listen = ["{dut_ip}:{dut_port}"]
verbosity = "Quiet"
"""
template_dut_peer = """
[[peers]] # ATE-{i}
public_key = "keys/ate-{i}-public-key"
endpoint = "{ate_ip}:{ate_port}"
key_out = "out/key_out_{i}"
"""
template_ate = """
public_key = "keys/ate-{i}-public-key"
secret_key = "keys/ate-{i}-secret-key"
listen = ["{ate_ip}:{ate_port}"]
verbosity = "Quiet"
[[peers]] # DUT
public_key = "keys/dut-public-key"
endpoint = "{dut_ip}:{dut_port}"
key_out = "out/key_out_{i}"
"""
(output_dir / "dut" / "keys").mkdir(exist_ok=True, parents=True)
(output_dir / "dut" / "out").mkdir(exist_ok=True, parents=True)
(output_dir / "dut" / "configs").mkdir(exist_ok=True, parents=True)
(output_dir / "ate" / "keys").mkdir(exist_ok=True, parents=True)
(output_dir / "ate" / "out").mkdir(exist_ok=True, parents=True)
(output_dir / "ate" / "configs").mkdir(exist_ok=True, parents=True)
for peer_count in config["peer_counts"]:
dut_config = template_dut.format(**config)
for i in range(peer_count):
dut_config += template_dut_peer.format(**config, i=i, ate_port=50000 + i)
(output_dir / "dut" / "configs" / f"dut-{peer_count}.toml").write_text(dut_config)
if not (output_dir / "dut" / "keys" / "dut-public-key").exists():
print("Generate DUT keys")
run(
[
config["path_to_rosenpass_bin"],
"gen-keys",
f"configs/dut-{peer_count}.toml",
],
cwd=output_dir / "dut",
)
else:
print("DUT keys already exist")
# copy the DUT public key to the ATE
(output_dir / "ate" / "keys" / "dut-public-key").write_bytes(
(output_dir / "dut" / "keys" / "dut-public-key").read_bytes()
)
ate_script = "(trap 'kill 0' SIGINT; \\\n"
for i in range(config["peer_count_max"]):
(output_dir / "ate" / "configs" / f"ate-{i}.toml").write_text(
template_ate.format(**config, i=i, ate_port=50000 + i)
)
if not (output_dir / "ate" / "keys" / f"ate-{i}-public-key").exists():
# generate ATE keys
run(
[config["path_to_rosenpass_bin"], "gen-keys", f"configs/ate-{i}.toml"],
cwd=output_dir / "ate",
)
else:
print(f"ATE-{i} keys already exist")
# copy the ATE public keys to the DUT
(output_dir / "dut" / "keys" / f"ate-{i}-public-key").write_bytes(
(output_dir / "ate" / "keys" / f"ate-{i}-public-key").read_bytes()
)
ate_script += (
f"{config['path_to_rosenpass_bin']} exchange-config configs/ate-{i}.toml & \\\n"
)
if (i + 1) in config["peer_counts"]:
write_script = ate_script
write_script += "wait)"
(output_dir / "ate" / f"run-{i+1}.sh").write_text(write_script)

View File

@@ -66,6 +66,8 @@ 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,6 +13,15 @@ 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
@@ -40,6 +49,10 @@ 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 }
@@ -50,7 +63,12 @@ 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"]
enable_memfd_alloc = []
experiment_memfd_secret = []
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
experiment_api = ["hex-literal"]
internal_testing = []
internal_bin_gen_ipc_msg_types = ["hex", "heck"]

View File

@@ -1,5 +1,6 @@
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;
@@ -40,7 +41,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.secret_mut())?;
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
Ok((sk, pk))
}

View File

@@ -0,0 +1,116 @@
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

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,117 @@
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

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,96 @@
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

@@ -0,0 +1,107 @@
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

@@ -0,0 +1,103 @@
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

@@ -0,0 +1,108 @@
// 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

@@ -0,0 +1,40 @@
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)
}
}

40
rosenpass/src/api/cli.rs Normal file
View File

@@ -0,0 +1,40 @@
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

@@ -0,0 +1,41 @@
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

@@ -0,0 +1,44 @@
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

@@ -0,0 +1,167 @@
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

@@ -0,0 +1,93 @@
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

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

9
rosenpass/src/api/mod.rs Normal file
View File

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

@@ -0,0 +1,86 @@
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,13 +3,11 @@ use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::file::StoreSecret;
use rosenpass_secret_memory::{
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
};
use rosenpass_util::file::{LoadValue, LoadValueB64};
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
use rosenpass_wireguard_broker::brokers::native_unix::{
NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError,
};
use std::ops::DerefMut;
use std::path::PathBuf;
use crate::app_server::AppServerTest;
@@ -34,11 +32,21 @@ 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
///
@@ -151,21 +159,14 @@ pub enum CliCommand {
Man,
}
impl CliCommand {
impl CliArgs {
/// 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 {
match &self.command {
Man => {
let man_cmd = std::process::Command::new("man")
.args(["1", "rosenpass"])
@@ -177,7 +178,7 @@ impl CliCommand {
}
GenConfig { config_file, force } => {
ensure!(
force || !config_file.exists(),
*force || !config_file.exists(),
"config file {config_file:?} already exists"
);
@@ -192,9 +193,9 @@ impl CliCommand {
let mut secret_key: Option<PathBuf> = None;
// Manual arg parsing, since clap wants to prefix flags with "--"
let mut args = args.into_iter();
let mut args = args.iter();
loop {
match (args.next().as_deref(), args.next()) {
match (args.next().map(|x| x.as_str()), args.next()) {
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
secret_key = Some(opt.into());
}
@@ -236,7 +237,7 @@ impl CliCommand {
(config.public_key, config.secret_key)
}
(_, Some(pkf), Some(skf)) => (pkf, skf),
(_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()),
_ => {
bail!("either a config-file or both public-key and secret-key file are required")
}
@@ -268,31 +269,36 @@ impl CliCommand {
"config file '{config_file:?}' does not exist"
);
let config = config::Rosenpass::load(config_file)?;
let mut config = config::Rosenpass::load(config_file)?;
config.validate()?;
self.apply_to_config(&mut config)?;
Self::event_loop(config, test_helpers)?;
}
Exchange {
first_arg,
mut rest_of_args,
rest_of_args,
config_file,
} => {
rest_of_args.insert(0, first_arg);
let mut rest_of_args = rest_of_args.clone();
rest_of_args.insert(0, first_arg.clone());
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 = p;
config.store(p)?;
config.config_file_path.clone_from(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() {
@@ -314,6 +320,7 @@ impl CliCommand {
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)?;
@@ -322,11 +329,13 @@ impl CliCommand {
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
sk,
pk,
config.listen,
config.listen.clone(),
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 {
@@ -370,7 +379,19 @@ impl CliCommand {
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.secret_mut())?;
StaticKem::keygen(ssk.secret_mut(), spk.deref_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,6 +19,8 @@ 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
@@ -27,6 +29,10 @@ 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:
@@ -54,7 +60,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)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Verbosity {
Quiet,
Verbose,
@@ -157,6 +163,12 @@ 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
@@ -206,6 +218,8 @@ 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,3 +1,5 @@
#[cfg(feature = "experiment_api")]
pub mod api;
pub mod app_server;
pub mod cli;
pub mod config;
@@ -11,4 +13,8 @@ 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,6 +8,14 @@ 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)
@@ -26,10 +34,10 @@ pub fn main() {
// error!("error dummy");
}
match args.command.run(None) {
match args.run(None) {
Ok(_) => {}
Err(e) => {
error!("{e}");
error!("{e:?}");
exit(1);
}
}

View File

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

View File

@@ -0,0 +1,180 @@
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,9 +15,19 @@ 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();
@@ -94,14 +104,11 @@ fn run_server_client_exchange(
.unwrap();
std::thread::spawn(move || {
cli.command
.run(Some(
server_test_builder
.termination_handler(Some(server_terminate_rx))
.build()
.unwrap(),
))
let test_helpers = server_test_builder
.termination_handler(Some(server_terminate_rx))
.build()
.unwrap();
cli.run(Some(test_helpers)).unwrap();
});
let cli = CliArgs::try_parse_from(
@@ -112,14 +119,11 @@ fn run_server_client_exchange(
.unwrap();
std::thread::spawn(move || {
cli.command
.run(Some(
client_test_builder
.termination_handler(Some(client_terminate_rx))
.build()
.unwrap(),
))
let test_helpers = client_test_builder
.termination_handler(Some(client_terminate_rx))
.build()
.unwrap();
cli.run(Some(test_helpers)).unwrap();
});
// give them some time to do the key exchange under load
@@ -134,6 +138,7 @@ 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");
@@ -206,6 +211,7 @@ fn check_exchange_under_normal() {
#[test]
#[serial]
fn check_exchange_under_dos() {
setup_tests();
setup_logging();
//Generate binary with responder with feature integration_test
@@ -283,6 +289,7 @@ struct MockBrokerInner {
interface: Option<String>,
}
#[allow(dead_code)]
#[derive(Debug, Default)]
struct MockBroker {
inner: Arc<Mutex<MockBrokerInner>>,
@@ -321,7 +328,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.clone()),
peer_id: Some(*config.peer_id),
interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()),
};
break Ok(());

View File

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

View File

@@ -2,7 +2,9 @@ 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,11 +1,12 @@
use std::{
fs::{self, DirBuilder},
ops::DerefMut,
os::unix::fs::{DirBuilderExt, PermissionsExt},
path::Path,
};
use anyhow::{anyhow, Result};
use rosenpass_util::file::{LoadValueB64, StoreValueB64};
use rosenpass_util::file::{LoadValueB64, StoreValue, StoreValueB64};
use zeroize::Zeroize;
use rosenpass::protocol::{SPk, SSk};
@@ -56,8 +57,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.secret_mut())?;
pqpk.store_secret(pqpk_path)?;
StaticKem::keygen(pqsk.secret_mut(), pqpk.deref_mut())?;
pqpk.store(pqpk_path)?;
pqsk.store_secret(pqsk_path)?;
} else {
eprintln!(

View File

@@ -11,9 +11,9 @@ mod key;
#[tokio::main]
async fn main() {
#[cfg(feature = "enable_memfd_alloc")]
#[cfg(feature = "experiment_memfd_secret")]
policy::secret_policy_try_use_memfd_secrets();
#[cfg(not(feature = "enable_memfd_alloc"))]
#[cfg(not(feature = "experiment_memfd_secret"))]
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,6 +6,7 @@ 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,12 +172,154 @@ 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;
use crate::{Public, PublicBox};
use rosenpass_util::{
b64::b64_encode,
file::{
@@ -185,32 +327,35 @@ mod tests {
Visibility,
},
};
use std::{fs, os::unix::fs::PermissionsExt};
use std::{fs, ops::Deref, os::unix::fs::PermissionsExt};
use tempfile::tempdir;
/// 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;
/// Number of bytes in payload for load and store tests
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 public to an example file in the temporary directory
// Store the original bytes to an example file in the temporary directory
let example_file = temp_dir.path().join("example_file");
std::fs::write(example_file.clone(), &original_bytes).unwrap();
std::fs::write(&example_file, original_bytes).unwrap();
// Load the public from the example file
// Load the value from the example file into our generic type
let loaded_public = T::load(&example_file).unwrap();
let loaded_public = Public::load(&example_file).unwrap();
// Check that the loaded value matches the original bytes
assert_eq!(loaded_public.deref(), &original_bytes);
// 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
// Store the loaded value to a different file in the temporary directory
let new_file = temp_dir.path().join("new_file");
loaded_public.store(&new_file).unwrap();
@@ -224,10 +369,13 @@ mod tests {
assert_eq!(new_file_contents, original_file_contents);
}
/// 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;
/// 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]>,
>() {
// Generate original random bytes
let original_bytes: [u8; N] = [rand::random(); N];
// Create a temporary directory
@@ -238,9 +386,9 @@ mod tests {
std::fs::write(&example_file, encoded_public).unwrap();
// Load the public from the example file
let loaded_public = Public::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
let loaded_public = T::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
// Check that the loaded public matches the original bytes
assert_eq!(&loaded_public.value, &original_bytes);
assert_eq!(loaded_public.deref(), &original_bytes);
// Store the loaded public to a different file in the temporary directory
let new_file = temp_dir.path().join("new_file");
@@ -253,7 +401,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);
@@ -271,9 +419,35 @@ 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,6 +1,5 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::path::Path;
@@ -387,7 +386,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.clone(), &original_bytes).unwrap();
std::fs::write(&example_file, 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,3 +18,6 @@ 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,12 +1,50 @@
use std::os::fd::{OwnedFd, RawFd};
use rustix::{
fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
io::{fcntl_dupfd_cloexec, DupFlags},
};
/// Clone some file descriptor
use crate::mem::Forgetting;
/// Prepare a file descriptor for use in Rust code.
///
/// 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};
// This is safe since [dup] will simply raise
let fd = unsafe { dup(BorrowedFd::borrow_raw(fd))? };
Ok(fd)
/// 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)
}
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())
}

View File

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

110
util/src/io.rs Normal file
View File

@@ -0,0 +1,110 @@
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

@@ -0,0 +1,359 @@
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

@@ -0,0 +1,381 @@
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

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

View File

@@ -4,8 +4,13 @@ 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,5 +1,7 @@
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?
@@ -31,3 +33,62 @@ 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)
}
}

39
util/src/mio.rs Normal file
View File

@@ -0,0 +1,39 @@
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,5 +1,4 @@
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]
@@ -97,3 +96,14 @@ 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,15 +19,22 @@ 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

7
util/src/zerocopy/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
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

@@ -0,0 +1,107 @@
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

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,39 @@
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 {}

2
util/src/zeroize/mod.rs Normal file
View File

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

View File

@@ -0,0 +1,10 @@
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.38.0", features = ["sync", "full", "mio"] }
tokio = { version = "1.39.1", features = ["sync", "full", "mio"] }
anyhow = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
@@ -44,6 +44,7 @@ path = "src/bin/priviledged.rs"
test = false
doc = false
required-features=["enable_broker_api"]
cfg = { target_os = "linux" }
[[bin]]
name = "rosenpass-wireguard-broker-socket-handler"
@@ -51,3 +52,4 @@ test = false
path = "src/bin/socket_handler.rs"
doc = false
required-features=["enable_broker_api"]
cfg = { target_os = "linux" }

View File

@@ -88,7 +88,7 @@ where
None => return Ok(None),
};
let typ = res.get(0).ok_or(invalid_msg_poller())?;
let typ = res.first().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(|e| BrokerClientSetPskError::BrokerError(e))?;
let config = config.map_err(BrokerClientSetPskError::BrokerError)?;
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> 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,
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,
additional_params: &[],
}
}

View File

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

View File

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

View File

@@ -1,56 +1,66 @@
use std::io::{stdin, stdout, Read, Write};
use std::result::Result;
fn main() {
#[cfg(target_os = "linux")]
linux::main().unwrap();
use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer;
use rosenpass_wireguard_broker::brokers::netlink as wg;
#[derive(thiserror::Error, Debug)]
pub enum BrokerAppError {
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
WgConnectError(#[from] wg::ConnectError),
#[error(transparent)]
WgSetPskError(#[from] wg::SetPskError),
#[error("Oversized message {}; something about the request is fatally wrong", .0)]
OversizedMessage(u64),
#[cfg(not(target_os = "linux"))]
panic!("This binary is only supported on Linux");
}
fn main() -> Result<(), BrokerAppError> {
let mut broker = BrokerServer::new(wg::NetlinkWireGuardBroker::new()?);
#[cfg(target_os = "linux")]
pub mod linux {
use std::io::{stdin, stdout, Read, Write};
let mut stdin = stdin().lock();
let mut stdout = stdout().lock();
loop {
// Read the message length
let mut len = [0u8; 8];
stdin.read_exact(&mut len)?;
use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer;
use rosenpass_wireguard_broker::brokers::netlink as wg;
// Parse the message length
let len = u64::from_le_bytes(len);
if (len as usize) > msgs::REQUEST_MSG_BUFFER_SIZE {
return Err(BrokerAppError::OversizedMessage(len));
}
#[derive(thiserror::Error, Debug)]
pub enum BrokerAppError {
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
WgConnectError(#[from] wg::ConnectError),
#[error(transparent)]
WgSetPskError(#[from] wg::SetPskError),
#[error("Oversized message {}; something about the request is fatally wrong", .0)]
OversizedMessage(u64),
}
// Read the message itself
let mut req_buf = [0u8; msgs::REQUEST_MSG_BUFFER_SIZE];
let req_buf = &mut req_buf[..(len as usize)];
stdin.read_exact(req_buf)?;
pub fn main() -> Result<(), BrokerAppError> {
let mut broker = BrokerServer::new(wg::NetlinkWireGuardBroker::new()?);
// Process the message
let mut res_buf = [0u8; msgs::RESPONSE_MSG_BUFFER_SIZE];
let res = match broker.handle_message(req_buf, &mut res_buf) {
Ok(len) => &res_buf[..len],
Err(e) => {
eprintln!("Error processing message for wireguard PSK broker: {e:?}");
continue;
let mut stdin = stdin().lock();
let mut stdout = stdout().lock();
loop {
// Read the message length
let mut len = [0u8; 8];
stdin.read_exact(&mut len)?;
// Parse the message length
let len = u64::from_le_bytes(len);
if (len as usize) > msgs::REQUEST_MSG_BUFFER_SIZE {
return Err(BrokerAppError::OversizedMessage(len));
}
};
// Write the response
stdout.write_all(&(res.len() as u64).to_le_bytes())?;
stdout.write_all(&res)?;
stdout.flush()?;
// Read the message itself
let mut req_buf = [0u8; msgs::REQUEST_MSG_BUFFER_SIZE];
let req_buf = &mut req_buf[..(len as usize)];
stdin.read_exact(req_buf)?;
// Process the message
let mut res_buf = [0u8; msgs::RESPONSE_MSG_BUFFER_SIZE];
let res = match broker.handle_message(req_buf, &mut res_buf) {
Ok(len) => &res_buf[..len],
Err(e) => {
eprintln!("Error processing message for wireguard PSK broker: {e:?}");
continue;
}
};
// Write the response
stdout.write_all(&(res.len() as u64).to_le_bytes())?;
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 as usize, 0);
res_buf.resize(len, 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 as usize, 0);
req_buf.resize(len, 0);
stream.read_exact(&mut req_buf[..len]).await?;
// Handle the message

View File

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

View File

@@ -1,6 +1,6 @@
#[cfg(feature = "enable_broker_api")]
pub mod mio_client;
#[cfg(feature = "enable_broker_api")]
#[cfg(all(feature = "enable_broker_api", target_os = "linux"))]
pub mod netlink;
pub mod native_unix;

View File

@@ -1,3 +1,5 @@
#![cfg(target_os = "linux")]
use std::fmt::Debug;
use wireguard_uapi::linux as wg;
@@ -85,21 +87,20 @@ impl WireGuardBroker for NetlinkWireGuardBroker {
.sock
.get_device(wg::DeviceInterface::from_name(config.iface))?;
if state
if !state
.peers
.iter()
.find(|p| &p.public_key == &config.peer_id.value)
.is_none()
.any(|p| p.public_key == config.peer_id.value)
{
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, result::Result};
use std::fmt::Debug;
pub const WG_KEY_LEN: usize = 32;
pub const WG_PEER_LEN: usize = 32;

View File

@@ -36,6 +36,7 @@ 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();