Compare commits

...

59 Commits

Author SHA1 Message Date
Benjamin Lipp
bf67344e86 feat: build with feature experiment_api 2025-01-10 01:09:03 +01:00
dependabot[bot]
d2539e445f build(deps): bump serde from 1.0.216 to 1.0.217 (#570)
Some checks failed
Nix / Build x86_64-linux.rosenpass-static (push) Has been cancelled
Nix / Build x86_64-linux.rp-static (push) Has been cancelled
Nix / Build x86_64-linux.whitepaper (push) Has been cancelled
Nix / Run Nix checks on x86_64-linux (push) Has been cancelled
Nix / Upload whitepaper x86_64-linux (push) Has been cancelled
QC / prettier (push) Has been cancelled
QC / Shellcheck (push) Has been cancelled
QC / Rust Format (push) Has been cancelled
QC / cargo-bench (push) Has been cancelled
QC / mandoc (push) Has been cancelled
QC / cargo-audit (push) Has been cancelled
QC / cargo-clippy (push) Has been cancelled
QC / cargo-doc (push) Has been cancelled
QC / cargo-test (macos-13) (push) Has been cancelled
QC / cargo-test (ubuntu-latest) (push) Has been cancelled
QC / cargo-test-nix-devshell-x86_64-linux (push) Has been cancelled
QC / cargo-fuzz (push) Has been cancelled
QC / codecov (push) Has been cancelled
Regressions / boot-race (push) Has been cancelled
Nix / Build i686-linux.default (push) Has been cancelled
Nix / Build i686-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-darwin.default (push) Has been cancelled
Nix / Build x86_64-darwin.release-package (push) Has been cancelled
Nix / Build x86_64-darwin.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.default (push) Has been cancelled
Nix / Build x86_64-linux.proof-proverif (push) Has been cancelled
Nix / Build x86_64-linux.release-package (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build aarch64-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Has been cancelled
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.216 to 1.0.217.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.216...v1.0.217)

---
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-12-29 17:48:54 +01:00
dependabot[bot]
6dc58cc6c1 build(deps): bump anyhow from 1.0.94 to 1.0.95 (#569)
Some checks failed
Nix / Build aarch64-linux.rp (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-static (push) Has been cancelled
Nix / Build x86_64-linux.rp-static (push) Has been cancelled
Nix / Build x86_64-linux.whitepaper (push) Has been cancelled
Nix / Run Nix checks on x86_64-linux (push) Has been cancelled
Nix / Upload whitepaper x86_64-linux (push) Has been cancelled
QC / prettier (push) Has been cancelled
QC / cargo-bench (push) Has been cancelled
QC / mandoc (push) Has been cancelled
QC / cargo-audit (push) Has been cancelled
QC / cargo-clippy (push) Has been cancelled
QC / cargo-doc (push) Has been cancelled
QC / cargo-test (macos-13) (push) Has been cancelled
QC / cargo-test (ubuntu-latest) (push) Has been cancelled
QC / cargo-test-nix-devshell-x86_64-linux (push) Has been cancelled
QC / cargo-fuzz (push) Has been cancelled
QC / codecov (push) Has been cancelled
Regressions / multi-peer (push) Has been cancelled
Regressions / boot-race (push) Has been cancelled
Nix / Build i686-linux.default (push) Has been cancelled
Nix / Build i686-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-darwin.default (push) Has been cancelled
Nix / Build x86_64-darwin.release-package (push) Has been cancelled
Nix / Build x86_64-darwin.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.default (push) Has been cancelled
Nix / Build x86_64-linux.proof-proverif (push) Has been cancelled
Nix / Build x86_64-linux.release-package (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build aarch64-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Has been cancelled
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.94 to 1.0.95.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.94...1.0.95)

---
updated-dependencies:
- dependency-name: anyhow
  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-12-25 10:47:12 +01:00
Karolin Varner
e3d16966c9 Add documentation and tests for the build_crypto_server module (#568)
Some checks failed
Nix / Run Nix checks on i686-linux (push) Waiting to run
Nix / Build x86_64-darwin.default (push) Blocked by required conditions
Nix / Build x86_64-darwin.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Has been cancelled
QC / Rust Format (push) Has been cancelled
QC / cargo-bench (push) Has been cancelled
QC / mandoc (push) Has been cancelled
QC / cargo-audit (push) Has been cancelled
Regressions / multi-peer (push) Has been cancelled
Regressions / boot-race (push) Has been cancelled
2024-12-21 17:02:02 +01:00
Philipp Dresselmann
a5e6af4b49 chore(docs): Add docstrings for the build_crypto_server module 2024-12-21 00:35:26 +01:00
Karolin Varner
24a71977f0 API Doc and a few tests for rosenpass::api (#566)
Some checks failed
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
Nix / Build i686-linux.rosenpass (push) Has been cancelled
Nix / Run Nix checks on i686-linux (push) Has been cancelled
Nix / Build x86_64-darwin.rosenpass (push) Has been cancelled
Nix / Build x86_64-darwin.rp (push) Has been cancelled
Nix / Run Nix checks on x86_64-darwin (push) Has been cancelled
Nix / Build x86_64-linux.proverif-patched (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass (push) Has been cancelled
Nix / Build aarch64-linux.rosenpass (push) Has been cancelled
Nix / Build aarch64-linux.rp (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-static (push) Has been cancelled
Nix / Build x86_64-linux.rp-static (push) Has been cancelled
Nix / Build x86_64-linux.whitepaper (push) Has been cancelled
Nix / Run Nix checks on x86_64-linux (push) Has been cancelled
Nix / Upload whitepaper x86_64-linux (push) Has been cancelled
QC / cargo-test (macos-13) (push) Has been cancelled
QC / cargo-test (ubuntu-latest) (push) Has been cancelled
QC / cargo-test-nix-devshell-x86_64-linux (push) Has been cancelled
2024-12-20 09:24:57 +01:00
Karolin Varner
5f0ac579d7 chore: Documentation and few tests for rosenpass::api 2024-12-19 19:42:09 +01:00
Karolin Varner
4df994b5f0 fix: Coverage reporting in API integration tests 2024-12-19 19:42:09 +01:00
Karolin Varner
e4e0a9e661 chore: Example on how to use to use the Rosenpass API 2024-12-19 19:42:09 +01:00
Karolin Varner
742e037936 chore: Smoketests for rosenpass-gen-ipc-msg-types 2024-12-19 19:42:09 +01:00
Karolin Varner
b5848af799 chore: Smoketests for rp command (#565)
Some checks are pending
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
2024-12-19 15:11:08 +01:00
Karolin Varner
4982e40084 chore: Smoketests for rp 2024-12-19 15:00:08 +01:00
Karolin Varner
c1ae3268c6 Add a missing cleanup step to the coverage script (#564) 2024-12-19 14:59:51 +01:00
Paul Spooren
524ec68f3f Add a docstring example for mio/uds_send_fd (#563)
Some checks are pending
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
2024-12-19 13:07:13 +01:00
Philipp Dresselmann
184603aa2c chore: Add a missing cleanup step to the coverage script
Looks like `cargo llvm-cov` doesn't clean up the entire `target/llvm-cov-target` directory tree, which means running the coverage script more than once fails as `mv` refuses to overwrite the leftover doctest binaries from a previous run.
2024-12-19 12:36:32 +01:00
Philipp Dresselmann
ec6706ffeb chore(docs): Add a docstring example for uds_send_fd 2024-12-19 11:42:46 +01:00
Paul Spooren
7571670e71 docs(wireguard-broker): add docs and examples (#550) 2024-12-19 09:51:51 +01:00
David Niehues
0d7dd99d96 test(wireguard-broker): Add smoketest and doc-tests for wiregaurd broker 2024-12-19 09:34:40 +01:00
David Niehues
c78a9cb777 docs(wireguard-broker): add docs and examples 2024-12-19 09:34:15 +01:00
Paul Spooren
dd0db53e8b chore(doc): Docs for rosenpass::{config, cli} (#560)
Some checks are pending
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
2024-12-18 23:11:45 +01:00
Paul Spooren
422acf9891 Docs and unit tests for app_server.rs (#552) 2024-12-18 23:11:25 +01:00
Paul Spooren
55d7f8b1c1 Avoid unnecessarily copying the doctest binaries (#558) 2024-12-18 22:44:24 +01:00
Paul Spooren
199ff63a06 Add docstring examples and unit tests for the LengthPrefixEncoder module (#500)
Some checks are pending
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
2024-12-18 22:01:53 +01:00
Karolin Varner
47b556e317 chore(doc): Docs for rosenpass::{config, cli} 2024-12-18 20:48:12 +01:00
Philipp Dresselmann
f87e2cb31b chore(doc): Fix module descriptions for length_prefix_encoding
There's a more complete module description for the encoder and decoder now. Both versions get appended by rustdoc, which looks wrong and shouldn't be necessary.
2024-12-18 16:24:50 +01:00
Philipp Dresselmann
58e1c8fbff chore(coverage): Add unit tests for the LengthPrefixEncoder
There's some redundancy here with the docstring examples/tests, but that's entirely on purpose:

Unfortunately, it seems that the coverage tool has trouble recognizing calls from within the docstring examples. It's an unstable feature - maybe that's why?

Even with these tests, the tool still doesn't properly detect everything. Regardless, function coverage is 100% when running the coverage tool locally.
2024-12-18 16:24:50 +01:00
Philipp Dresselmann
c89c7d7acf chore(doc): Add docstring examples for the LengthPrefixEncoder 2024-12-18 16:24:50 +01:00
Philipp Dresselmann
a5b876f119 chore(doc): Add a module summary for LengthPrefixEncoder 2024-12-18 16:23:13 +01:00
Philipp Dresselmann
c2f50f47b3 chore(doc): Update docstrings for LengthPrefixEncoder
This is more consistent with the LengthPrefixDecoder documentation.
2024-12-18 16:23:13 +01:00
Paul Spooren
53168dc62d Add documentation, doc-tests and examples to the secret-memory crate. (#531) 2024-12-18 16:18:11 +01:00
David Niehues
2cfe703118 docu(secret-memeory): improve comment in example for Secret 2024-12-18 16:15:35 +01:00
David Niehues
a2d7c3aaa6 chore(secret-memory): fix typos 2024-12-18 16:15:35 +01:00
David Niehues
1aa111570e style(secret-memory): improve style in doc-tests around using the the ?-operator 2024-12-18 16:15:35 +01:00
David Niehues
a91d61f9f0 docs(secret-memory): fix warnings when generating the documentation 2024-12-18 16:15:35 +01:00
David Niehues
ff7827c24e test(fix-doctest): fix doctests where a function si wrapped around a doctest but the function is never called 2024-12-18 16:15:35 +01:00
David Niehues
255e377d29 test(coverage): add unit tests to improve coverage in public.rs and secret.rs 2024-12-18 16:15:35 +01:00
David Niehues
50505d81cc test: fix doctest in alloc/mod.rs to make it work on macos 2024-12-18 16:15:35 +01:00
David Niehues
10484cc6d4 docs(doctests+coverage): add documentation and doctests for all modules of secret-memory except for alloc 2024-12-18 16:15:35 +01:00
David Niehues
d27e602f43 docu(doctest+coverage): add documentation, doc-tests and examples to the alloc module 2024-12-18 16:15:35 +01:00
Philipp Dresselmann
73f6b33dbb chore: Avoid unnecessarily copying the doctest binaries
The doctest binaries can take up ~3GB for a debug build. There's no reason to waste that much disk space and copying them is slower than moving, too. They're only used by `grcov` right now, so they needn't be preserved.
2024-12-18 16:05:34 +01:00
Paul Spooren
a279dfc0b1 docs+doctest(to): Add tests, examples and documentation to the to-crate (#546) 2024-12-18 14:30:38 +01:00
Karolin Varner
caf2f6bfec chore: Remove unused warning in api integration test 2024-12-18 14:28:51 +01:00
Karolin Varner
d398ad369e fix: Disable asserts that rely on timing characteristics during coverage testing 2024-12-18 14:28:35 +01:00
Karolin Varner
00696321ff chore: Final improvements on the to crate API doc 2024-12-18 14:28:24 +01:00
Paul Spooren
d807a1bca7 Add examples and docstring improvements for mio/uds_recv_fd (#551)
Some checks are pending
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
2024-12-18 12:29:20 +01:00
Karolin Varner
4daf97b2ee style(ciphers): improve style in doc-tests around using the the ?-operator in the ciphers crate (#549) 2024-12-18 11:23:59 +01:00
Karolin Varner
b394e302ab chore(tests): start using unused test output (#547) 2024-12-18 11:22:38 +01:00
Paul Spooren
198bc2d5f2 chore(tests): start using unused test output
Resolve a warning of unused `output` variable.

Fixes: 0745019 docs(cli): Create commented config file

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-12-18 11:22:16 +01:00
Paul Spooren
fc2f535eae docs(util): add docs and examples for the remaining util crate (#545) 2024-12-18 11:16:00 +01:00
Paul Spooren
302e249f08 docs(constant-time): add docs, examples and safety notices (#544) 2024-12-18 10:58:35 +01:00
dependabot[bot]
d8fe3eba5f build(deps): bump clap_complete from 4.5.38 to 4.5.40
Bumps [clap_complete](https://github.com/clap-rs/clap) from 4.5.38 to 4.5.40.
- [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.38...clap_complete-v4.5.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 10:29:44 +01:00
David Niehues
61b8b28e86 style(ciphers): improve style in doc-tests around using the the ?-operator in the ciphers crate 2024-12-17 11:57:54 +01:00
Amin Faez
26f77924f8 docs(constant-time): add docs, examples and safety notices 2024-12-17 11:56:33 +01:00
Amin Faez
2e0e2cfa0c docs(util): add docs and examples for the remaining util crate 2024-12-17 11:55:23 +01:00
Philipp Dresselmann
a537eb3e1b chore(docs): Adjust docstrings for the mio module 2024-12-16 22:29:01 +01:00
Philipp Dresselmann
ea233bf137 chore(docs): Add an example for the UnixListenerExt trait 2024-12-16 22:28:53 +01:00
Philipp Dresselmann
db8796ab40 chore(docs): Add an example for the uds_recv_fd module 2024-12-16 20:54:08 +01:00
David Niehues
0353c82729 docs+doctest(to): Add tests, examples and documentation to the to-crate 2024-12-16 17:47:44 +01:00
Philipp Dresselmann
51d4dede15 chore(doc): Add a link to the MIO utils module summary 2024-12-16 17:02:43 +01:00
85 changed files with 3933 additions and 273 deletions

16
Cargo.lock generated
View File

@@ -109,9 +109,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
dependencies = [
"backtrace",
]
@@ -403,9 +403,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.38"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9"
dependencies = [
"clap 4.5.23",
]
@@ -2114,18 +2114,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.216"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.216"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -49,10 +49,10 @@ typenum = "1.17.0"
log = { version = "0.4.22" }
clap = { version = "4.5.23", features = ["derive"] }
clap_mangen = "0.2.24"
clap_complete = "4.5.38"
serde = { version = "1.0.216", features = ["derive"] }
clap_complete = "4.5.40"
serde = { version = "1.0.217", features = ["derive"] }
arbitrary = { version = "1.4.1", features = ["derive"] }
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
anyhow = { version = "1.0.95", features = ["backtrace", "std"] }
mio = { version = "1.0.3", features = ["net", "os-poll"] }
oqs-sys = { version = "0.9.1", default-features = false, features = [
'classic_mceliece',

View File

@@ -13,7 +13,6 @@ pub use hash::KEY_LEN;
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
///
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
/// # fn do_doc_test() -> Result<(), Box<dyn std::error::Error>> {
/// // create use once hash domain for the protocol identifier
/// let mut hash_domain = HashDomain::zero();
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
@@ -31,10 +30,7 @@ pub use hash::KEY_LEN;
/// let new_key_identifier = "my_new_key_identifier".as_bytes();
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
///
/// # Ok(())
/// # }
/// # do_doc_test().unwrap();
///
/// # Ok::<(), anyhow::Error>(())
///```
///

View File

@@ -20,3 +20,6 @@ memsec = { workspace = true }
[dev-dependencies]
rand = "0.8.5"
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -2,14 +2,29 @@
use core::ptr;
/// Little endian memcmp version of quinier/memsec
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
/// Little endian memcmp version of [quinier/memsec](https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30)
///
/// # Panic & Safety
///
/// Both input arrays must be at least of the indicated length.
///
/// See [std::ptr::read_volatile] on safety.
///
/// # Examples
/// ```
/// let a = [1, 2, 3, 4];
/// let b = [1, 2, 3, 4];
/// let c = [1, 2, 2, 5];
/// let d = [1, 2, 2, 4];
///
/// unsafe {
/// use rosenpass_constant_time::memcmp_le;
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
/// assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
/// assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
/// }
/// ```
#[inline(never)]
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
let mut res = 0;
@@ -77,3 +92,23 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) }
}
#[cfg(test)]
mod tests {
use crate::compare::memcmp_le;
#[test]
fn memcmp_le_test() {
let a = [1, 2, 3, 4];
let b = [1, 2, 3, 4];
let c = [1, 2, 2, 5];
let d = [1, 2, 2, 4];
unsafe {
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
}
}
}

View File

@@ -6,8 +6,16 @@ use core::hint::black_box;
/// and increment that integer.
///
/// # Leaks
/// TODO: mention here if this function leaks any information, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
/// This function may leak timing information in the following ways:
///
/// - The function execution time is linearly proportional to the input length
/// - The number of carry operations that occur may affect timing slightly
/// - Memory access patterns are sequential and predictable
///
/// The carry operation timing variation is mitigated through the use of black_box,
/// but the linear scaling with input size is inherent to the operation.
/// These timing characteristics are generally considered acceptable for most
/// cryptographic counter implementations.
///
/// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see

View File

@@ -7,6 +7,32 @@
//! ## TODO
//! Figure out methodology to ensure that code is actually constant time, see
//! <https://github.com/rosenpass/rosenpass/issues/232>
//!
//! # Examples
//!
//! ```rust
//! use rosenpass_constant_time::{memcmp, compare};
//!
//! let a = [1, 2, 3, 4];
//! let b = [1, 2, 3, 4];
//! let c = [1, 2, 3, 5];
//!
//! // Compare for equality
//! assert!(memcmp(&a, &b));
//! assert!(!memcmp(&a, &c));
//!
//! // Compare lexicographically
//! assert_eq!(compare(&a, &c), -1); // a < c
//! assert_eq!(compare(&c, &a), 1); // c > a
//! assert_eq!(compare(&a, &b), 0); // a == b
//! ```
//!
//! # Security Notes
//!
//! While these functions aim to be constant-time, they may leak timing information in some cases:
//!
//! - Length mismatches between inputs are immediately detectable
//! - Execution time scales linearly with input size
mod compare;
mod increment;
@@ -14,6 +40,7 @@ mod memcmp;
mod xor;
pub use compare::compare;
pub use compare::memcmp_le;
pub use increment::increment;
pub use memcmp::memcmp;
pub use xor::xor;

View File

@@ -113,9 +113,10 @@ mod tests {
// Pearson correlation
let correlation = cv / (sd_x * sd_y);
println!("correlation: {:.6?}", correlation);
#[cfg(not(coverage))]
assert!(
correlation.abs() < 0.01,
"execution time correlates with result"
)
);
}
}

View File

@@ -5,12 +5,23 @@ use rosenpass_to::{with_destination, To};
/// Xors the source into the destination
///
/// Performs a constant-time XOR operation between two byte slices
///
/// Takes a source slice and XORs it with the destination slice in-place using the
/// rosenpass_to trait for destination management.
///
/// # Panics
/// If source and destination are of different sizes.
///
/// # Leaks
/// TODO: mention here if this function leaks any information, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
/// This function may leak timing information in the following ways:
///
/// - The function execution time is linearly proportional to the input length
/// - Length mismatches between source and destination are immediately detectable via panic
/// - Memory access patterns follow a predictable sequential pattern
///
/// These leaks are generally considered acceptable in most cryptographic contexts
/// as they don't reveal information about the actual content being XORed.
///
/// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see

View File

@@ -23,7 +23,8 @@ main() {
exc cargo llvm-cov --all-features --workspace --doctests --branch
exc cp -rv target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/doctestbins
exc rm -rf target/llvm-cov-target/debug/deps/doctestbins
exc mv -v target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/
exc rm -rf "${OUTPUT_DIR}"
exc mkdir -p "${OUTPUT_DIR}"
exc grcov target/llvm-cov-target/ --llvm -s . --branch \

View File

@@ -50,6 +50,8 @@ rustPlatform.buildRustPackage {
cargoBuildOptions = [ "--package" package ];
cargoTestOptions = [ "--package" package ];
buildFeatures = [ "experiment_api" ];
doCheck = true;
cargoLock = {

View File

@@ -26,6 +26,10 @@ required-features = ["experiment_api", "internal_testing"]
name = "api-integration-tests-api-setup"
required-features = ["experiment_api", "internal_testing"]
[[test]]
name = "gen-ipc-msg-types"
required-features = ["experiment_api", "internal_testing", "internal_bin_gen_ipc_msg_types"]
[[bench]]
name = "handshake"
harness = false
@@ -91,3 +95,6 @@ experiment_api = [
internal_signal_handling_for_coverage_reports = ["signal-hook"]
internal_testing = []
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -8,210 +8,250 @@ use super::{
};
pub trait ByteSliceRefExt: ByteSlice {
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
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()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and
/// [RefMakerRawMsgTypeExt::parse_request_msg_type]
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker().parse_request_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_prefix], and
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker()
.from_prefix()?
.parse_request_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_suffix], and
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker()
.from_suffix()?
.parse_request_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker().parse_response_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_prefix], and
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker()
.from_prefix()?
.parse_response_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_suffix], and
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker()
.from_suffix()?
.parse_response_msg_type()
}
/// Shorthand for the use of [RequestRef::parse] in chaining.
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse(self)
}
/// Shorthand for the use of [RequestRef::parse_from_prefix] in chaining.
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse_from_prefix(self)
}
/// Shorthand for the use of [RequestRef::parse_from_suffix] in chaining.
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse_from_suffix(self)
}
/// Shorthand for the use of [ResponseRef::parse] in chaining.
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse(self)
}
/// Shorthand for the use of [ResponseRef::parse_from_prefix] in chaining.
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse_from_prefix(self)
}
/// Shorthand for the use of [ResponseRef::parse_from_suffix] in chaining.
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse_from_suffix(self)
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn supply_keypair_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn supply_keypair_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_listen_socket_request_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_listen_socket_request_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_listen_socket_response(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_listen_socket_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_listen_socket_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_psk_broker_request_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_psk_broker_request_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_psk_broker_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_psk_broker_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {

View File

@@ -4,16 +4,41 @@ use rosenpass_util::zerocopy::RefMaker;
use super::RawMsgType;
/// Trait implemented by all the Rosenpass API message types.
///
/// Implemented by the message as including the message envelope; e.g.
/// [crate::api::PingRequest] but not by [crate::api::PingRequestPayload].
pub trait Message {
/// The payload this API message contains. E.g. this is [crate::api::PingRequestPayload] for [[crate::api::PingRequest].
type Payload;
/// Either [crate::api::RequestMsgType] or [crate::api::ResponseMsgType]
type MessageClass: Into<RawMsgType>;
/// The specific message type in the [Self::MessageClass].
/// E.g. this is [crate::api::RequestMsgType::Ping] for [crate::api::PingRequest]
const MESSAGE_TYPE: Self::MessageClass;
/// Wraps the payload into the envelope
///
/// # Examples
///
/// See [crate::api::PingRequest::from_payload]
fn from_payload(payload: Self::Payload) -> Self;
/// Initialize the message;
/// just sets the message type [crate::api::Envelope::msg_type].
///
/// # Examples
///
/// See [crate::api::PingRequest::init]
fn init(&mut self);
/// Initialize the message from a raw buffer: Zeroize the buffer and then call [Self::init].
///
/// # Examples
///
/// See [crate::api::PingRequest::setup]
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
}
/// Additional convenience functions for working with [rosenpass_util::zerocopy::RefMaker]
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
}
@@ -23,6 +48,27 @@ where
B: ByteSliceMut,
T: Message,
{
/// Initialize the message using [Message::setup].
///
/// # Examples
///
/// ```
/// use rosenpass::api::{
/// PingRequest, ZerocopyResponseMakerSetupMessageExt, PING_REQUEST,
/// };
/// use rosenpass_util::zerocopy::RefMaker;
/// use std::mem::size_of;
///
/// let mut buf = [0u8; { size_of::<PingRequest>() }];
///
/// let rm = RefMaker::<&mut [u8], PingRequest>::new(&mut buf);
/// let msg: zerocopy::Ref<_, PingRequest> = rm.setup_msg()?;
///
/// let t = msg.msg_type; // Deal with unaligned read
/// assert_eq!(t, PING_REQUEST);
///
/// Ok::<(), anyhow::Error>(())
/// ```
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
T::setup(self.into_buf())
}

View File

@@ -35,10 +35,15 @@ const ADD_PSK_BROKER_REQUEST: RawMsgType =
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
/// Message properties global to the message type
pub trait MessageAttributes {
/// Get the size of the message
///
/// # Exampleds
fn message_size(&self) -> usize;
}
/// API request message types as an enum
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum RequestMsgType {
Ping,
@@ -47,6 +52,7 @@ pub enum RequestMsgType {
AddPskBroker,
}
/// API response messages types as an enum
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum ResponseMsgType {
Ping,
@@ -131,8 +137,17 @@ impl From<ResponseMsgType> for RawMsgType {
}
}
/// Extension trait for [RawMsgType].
///
/// We are using an extension trait rather than just using methods
/// because [RawMsgType] is a type alias, so we can not define methods
/// on it.
pub trait RawMsgTypeExt {
/// Try to convert this to a [RequestMsgType]; alias for the appropriate [TryFrom]
/// implementation
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
/// Try to convert this to a [ResponseMsgType]; alias for the appropriate [TryFrom]
/// implementation
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
}
@@ -146,8 +161,11 @@ impl RawMsgTypeExt for RawMsgType {
}
}
/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
pub trait RefMakerRawMsgTypeExt {
/// Parse a request message type from bytes
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
/// Parse a response message type from bytes
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
}

View File

@@ -1,3 +1,7 @@
//! Boring, repetitive code related to message parsing for the API.
//!
//! Most of this should be automatically generated though some derive macro at some point.
mod byte_slice_ext;
mod message_trait;
mod message_type;

View File

@@ -3,11 +3,14 @@ use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
/// Size required to fit any message in binary form
/// Size required to fit any request message in binary form
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
/// Size required to fit any response message in binary form
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
/// Maximum number of file descriptors that can be sent in a request.
pub const MAX_REQUEST_FDS: usize = 2;
/// Message envelope for API messages
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct Envelope<M: AsBytes + FromBytes> {
@@ -17,9 +20,12 @@ pub struct Envelope<M: AsBytes + FromBytes> {
pub payload: M,
}
/// Message envelope for API requests
pub type RequestEnvelope<M> = Envelope<M>;
/// Message envelope for API responses
pub type ResponseEnvelope<M> = Envelope<M>;
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct PingRequestPayload {
@@ -27,9 +33,11 @@ pub struct PingRequestPayload {
pub echo: [u8; 256],
}
#[allow(missing_docs)]
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
impl PingRequest {
#[allow(missing_docs)]
pub fn new(echo: [u8; 256]) -> Self {
Self::from_payload(PingRequestPayload { echo })
}
@@ -58,6 +66,7 @@ impl Message for PingRequest {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct PingResponsePayload {
@@ -65,9 +74,11 @@ pub struct PingResponsePayload {
pub echo: [u8; 256],
}
#[allow(missing_docs)]
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
impl PingResponse {
#[allow(missing_docs)]
pub fn new(echo: [u8; 256]) -> Self {
Self::from_payload(PingResponsePayload { echo })
}
@@ -96,10 +107,12 @@ impl Message for PingResponse {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct SupplyKeypairRequestPayload {}
#[allow(missing_docs)]
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
impl Default for SupplyKeypairRequest {
@@ -109,6 +122,7 @@ impl Default for SupplyKeypairRequest {
}
impl SupplyKeypairRequest {
#[allow(missing_docs)]
pub fn new() -> Self {
Self::from_payload(SupplyKeypairRequestPayload {})
}
@@ -137,25 +151,35 @@ impl Message for SupplyKeypairRequest {
}
}
#[allow(missing_docs)]
pub mod supply_keypair_response_status {
#[allow(missing_docs)]
pub const OK: u128 = 0;
#[allow(missing_docs)]
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
// TODO: This is not actually part of the API. Remove.
/// TODO: This is not actually part of the API. Remove.
#[allow(missing_docs)]
pub const INTERNAL_ERROR: u128 = 2;
#[allow(missing_docs)]
pub const INVALID_REQUEST: u128 = 3;
/// TODO: Deprectaed, remove
#[allow(missing_docs)]
pub const IO_ERROR: u128 = 4;
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct SupplyKeypairResponsePayload {
#[allow(missing_docs)]
pub status: u128,
}
#[allow(missing_docs)]
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
impl SupplyKeypairResponse {
#[allow(missing_docs)]
pub fn new(status: u128) -> Self {
Self::from_payload(SupplyKeypairResponsePayload { status })
}
@@ -184,10 +208,12 @@ impl Message for SupplyKeypairResponse {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddListenSocketRequestPayload {}
#[allow(missing_docs)]
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
impl Default for AddListenSocketRequest {
@@ -197,6 +223,7 @@ impl Default for AddListenSocketRequest {
}
impl AddListenSocketRequest {
#[allow(missing_docs)]
pub fn new() -> Self {
Self::from_payload(AddListenSocketRequestPayload {})
}
@@ -225,21 +252,28 @@ impl Message for AddListenSocketRequest {
}
}
#[allow(missing_docs)]
pub mod add_listen_socket_response_status {
#[allow(missing_docs)]
pub const OK: u128 = 0;
#[allow(missing_docs)]
pub const INVALID_REQUEST: u128 = 1;
#[allow(missing_docs)]
pub const INTERNAL_ERROR: u128 = 2;
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddListenSocketResponsePayload {
pub status: u128,
}
#[allow(missing_docs)]
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
impl AddListenSocketResponse {
#[allow(missing_docs)]
pub fn new(status: u128) -> Self {
Self::from_payload(AddListenSocketResponsePayload { status })
}
@@ -268,19 +302,23 @@ impl Message for AddListenSocketResponse {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddPskBrokerRequestPayload {}
#[allow(missing_docs)]
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
impl Default for AddPskBrokerRequest {
#[allow(missing_docs)]
fn default() -> Self {
Self::new()
}
}
impl AddPskBrokerRequest {
#[allow(missing_docs)]
pub fn new() -> Self {
Self::from_payload(AddPskBrokerRequestPayload {})
}
@@ -309,21 +347,28 @@ impl Message for AddPskBrokerRequest {
}
}
#[allow(missing_docs)]
pub mod add_psk_broker_response_status {
#[allow(missing_docs)]
pub const OK: u128 = 0;
#[allow(missing_docs)]
pub const INVALID_REQUEST: u128 = 1;
#[allow(missing_docs)]
pub const INTERNAL_ERROR: u128 = 2;
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddPskBrokerResponsePayload {
pub status: u128,
}
#[allow(missing_docs)]
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
impl AddPskBrokerResponse {
#[allow(missing_docs)]
pub fn new(status: u128) -> Self {
Self::from_payload(AddPskBrokerResponsePayload { status })
}

View File

@@ -4,24 +4,63 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
/// Helper for producing API message request references, [RequestRef].
///
/// This is to [RequestRef] as [rosenpass_util::zerocopy::RefMaker] is to
/// [zerocopy::Ref].
struct RequestRefMaker<B> {
buf: B,
msg_type: RequestMsgType,
}
impl<B: ByteSlice> RequestRef<B> {
/// Produce a [RequestRef] from a raw message buffer,
/// reading the type from the buffer
///
/// # Examples
///
/// ```
/// use zerocopy::AsBytes;
///
/// use rosenpass::api::{PingRequest, RequestRef, RequestMsgType};
///
/// let msg = PingRequest::new([0u8; 256]);
///
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
/// let typ = msg.msg_type;
/// assert_eq!(typ, rosenpass::api::PING_REQUEST);
///
/// let buf = msg.as_bytes();
/// let msg_ref = RequestRef::parse(buf)?;
/// assert!(matches!(msg_ref, RequestRef::Ping(_)));
///
/// assert_eq!(msg_ref.message_type(), RequestMsgType::Ping);
///
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn parse(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.from_prefix()?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.from_suffix()?.parse()
}
/// Get the message type [Self] contains
///
/// # Examples
///
/// See [Self::parse]
pub fn message_type(&self) -> RequestMsgType {
match self {
Self::Ping(_) => RequestMsgType::Ping,
@@ -110,6 +149,7 @@ impl<B: ByteSlice> RequestRefMaker<B> {
}
}
/// Reference to a API message response, typed as an enum.
pub enum RequestRef<B> {
Ping(Ref<B, PingRequest>),
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
@@ -121,6 +161,11 @@ impl<B> RequestRef<B>
where
B: ByteSlice,
{
/// Access the byte data of this reference
///
/// # Examples
///
/// See [Self::parse].
pub fn bytes(&self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes(),
@@ -135,6 +180,7 @@ impl<B> RequestRef<B>
where
B: ByteSliceMut,
{
/// Access the byte data of this reference; mutably
pub fn bytes_mut(&mut self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes_mut(),

View File

@@ -6,23 +6,29 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{Message, PingRequest, PingResponse};
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
/// Extension trait for [Message]s that are requests messages
pub trait RequestMsg: Sized + Message {
/// The response message belonging to this request message
type ResponseMsg: ResponseMsg;
/// Construct a response make for this particular message
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
buf.zk_ref_maker()
}
/// Setup a response maker (through [Message::setup]) for this request message type
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).setup_msg()
}
/// Setup a response maker from a buffer prefix (through [Message::setup]) for this request message type
fn setup_response_from_prefix<B: ByteSliceMut>(
buf: B,
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
}
/// Setup a response maker from a buffer suffix (through [Message::setup]) for this request message type
fn setup_response_from_suffix<B: ByteSliceMut>(
buf: B,
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
@@ -30,6 +36,7 @@ pub trait RequestMsg: Sized + Message {
}
}
/// Extension trait for [Message]s that are response messages
pub trait ResponseMsg: Message {
type RequestMsg: RequestMsg;
}
@@ -66,20 +73,25 @@ impl ResponseMsg for super::AddPskBrokerResponse {
type RequestMsg = super::AddPskBrokerRequest;
}
/// Request and response for the [crate::api::RequestMsgType::Ping] message type
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
/// Request and response for the [crate::api::RequestMsgType::SupplyKeypair] message type
pub type SupplyKeypairPair<B1, B2> = (
Ref<B1, super::SupplyKeypairRequest>,
Ref<B2, super::SupplyKeypairResponse>,
);
/// Request and response for the [crate::api::RequestMsgType::AddListenSocket] message type
pub type AddListenSocketPair<B1, B2> = (
Ref<B1, super::AddListenSocketRequest>,
Ref<B2, super::AddListenSocketResponse>,
);
/// Request and response for the [crate::api::RequestMsgType::AddPskBroker] message type
pub type AddPskBrokerPair<B1, B2> = (
Ref<B1, super::AddPskBrokerRequest>,
Ref<B2, super::AddPskBrokerResponse>,
);
/// A pair of references to messages; request and response each.
pub enum RequestResponsePair<B1, B2> {
Ping(PingPair<B1, B2>),
SupplyKeypair(SupplyKeypairPair<B1, B2>),
@@ -116,6 +128,7 @@ where
B1: ByteSlice,
B2: ByteSlice,
{
/// Returns a tuple to both the request and the response message
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
match self {
Self::Ping((req, res)) => {
@@ -141,10 +154,12 @@ where
}
}
/// Returns the request message
pub fn request(&self) -> RequestRef<&[u8]> {
self.both().0
}
/// Returns the response message
pub fn response(&self) -> ResponseRef<&[u8]> {
self.both().1
}
@@ -155,6 +170,7 @@ where
B1: ByteSliceMut,
B2: ByteSliceMut,
{
/// Returns a mutable tuple to both the request and the response message
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
match self {
Self::Ping((req, res)) => {
@@ -180,10 +196,12 @@ where
}
}
/// Returns the request message, mutably
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
self.both_mut().0
}
/// Returns the response message, mutably
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
self.both_mut().1
}

View File

@@ -5,24 +5,66 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
/// Helper for producing API message response references, [ResponseRef].
///
/// This is to [ResponseRef] as [rosenpass_util::zerocopy::RefMaker] is to
/// [zerocopy::Ref].
struct ResponseRefMaker<B> {
/// Buffer we are referencing
buf: B,
/// Message type we are producing
msg_type: ResponseMsgType,
}
impl<B: ByteSlice> ResponseRef<B> {
/// Produce a [ResponseRef] from a raw message buffer,
/// reading the type from the buffer
///
/// # Examples
///
/// ```
/// use zerocopy::AsBytes;
///
/// use rosenpass::api::{PingResponse, ResponseRef, ResponseMsgType};
/// // Produce the original PingResponse
/// let msg = PingResponse::new([0u8; 256]);
///
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
/// let typ = msg.msg_type;
/// assert_eq!(typ, rosenpass::api::PING_RESPONSE);
///
/// // Parse as a message type
/// let buf = msg.as_bytes();
/// let msg_ref = ResponseRef::parse(buf)?;
/// assert!(matches!(msg_ref, ResponseRef::Ping(_)));
///
/// // Buffers and message types of course match what we expect
/// assert_eq!(msg_ref.message_type(), ResponseMsgType::Ping);
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn parse(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
}
/// Get the message type [Self] contains
///
/// # Examples
///
/// See [Self::parse]
pub fn message_type(&self) -> ResponseMsgType {
match self {
Self::Ping(_) => ResponseMsgType::Ping,
@@ -111,6 +153,7 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
}
}
/// Reference to a API message response, typed.
pub enum ResponseRef<B> {
Ping(Ref<B, PingResponse>),
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
@@ -122,6 +165,11 @@ impl<B> ResponseRef<B>
where
B: ByteSlice,
{
/// Access the byte data of this reference
///
/// # Examples
///
/// See [Self::parse].
pub fn bytes(&self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes(),
@@ -136,6 +184,7 @@ impl<B> ResponseRef<B>
where
B: ByteSliceMut,
{
/// Access the byte data of this reference; mutably
pub fn bytes_mut(&mut self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes_mut(),

View File

@@ -2,10 +2,21 @@ use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, Req
use std::{collections::VecDeque, os::fd::OwnedFd};
use zerocopy::{ByteSlice, ByteSliceMut};
/// The rosenpass API implementation functions.
///
/// Implemented by [crate::api::ApiHandler].
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
pub trait Server {
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
///
/// It merely takes a buffer and returns that same buffer.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn ping(
&mut self,
req: &PingRequest,
@@ -54,6 +65,10 @@ pub trait Server {
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
/// backed file descriptor if the server keys are not backed by a file system file.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn supply_keypair(
&mut self,
req: &super::SupplyKeypairRequest,
@@ -80,8 +95,13 @@ pub trait Server {
///
/// # Description
///
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform key
/// key exchanges using the Rosenpass protocol.
/// cryptographic key exchanges via the Rosenpass protocol.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn add_listen_socket(
&mut self,
req: &super::AddListenSocketRequest,
@@ -89,6 +109,31 @@ pub trait Server {
res: &mut super::AddListenSocketResponse,
) -> anyhow::Result<()>;
/// Supply a new PSK broker listen socket through file descriptor passing via the API
///
/// This implements the handler for the [crate::api::RequestMsgType::AddPskBroker] API message.
///
/// # File descriptors
///
/// 1. The listen socket; must be backed by a unix domain stream socket
///
/// # API Return Status
///
/// 1. [crate::api::add_psk_broker_response_status::OK] - Indicates success
/// 2. [crate::api::add_psk_broker_response_status::INVALID_REQUEST] Malformed request; could be:
/// - Missing file descriptors for public key
/// - Invalid file descriptor type
/// 3. [crate::api::add_psk_broker_response_status::INTERNAL_ERROR] Some other, non-fatal error
/// occured. Check the logs on log
///
/// # Description
///
/// This endpoint allows you to supply a UDP listen socket; it will be used to transmit
/// cryptographic keys exchanged to WireGuard.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn add_psk_broker(
&mut self,
req: &super::AddPskBrokerRequest,
@@ -96,6 +141,11 @@ pub trait Server {
res: &mut super::AddPskBrokerResponse,
) -> anyhow::Result<()>;
/// Similar to [Self::handle_message], but takes a [RequestResponsePair]
/// instead of taking to separate byte buffers.
///
/// I.e. this function uses the explicit type tag encoded in [RequestResponsePair]
/// rather than reading the type tag from the request buffer.
fn dispatch<ReqBuf, ResBuf>(
&mut self,
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
@@ -117,6 +167,14 @@ pub trait Server {
}
}
/// Called by [crate::api::mio::MioConnection] when a new API request was received.
///
/// The parameters are:
///
/// - `req` A buffer containing the request
/// - `res_fds` A list of file descriptors received during the API call (i.e. this is used
/// with unix socket file descriptor passing)
/// - `res` The buffer to store the response in.
fn handle_message<ReqBuf, ResBuf>(
&mut self,
req: ReqBuf,

View File

@@ -6,6 +6,7 @@ use crate::config::Rosenpass as RosenpassConfig;
use super::config::ApiConfig;
/// Additional command line arguments for the API
#[cfg(feature = "experiment_api")]
#[derive(Args, Debug)]
pub struct ApiCli {
@@ -27,10 +28,14 @@ pub struct ApiCli {
}
impl ApiCli {
/// Copy the parameters set here into the [RosenpassConfig].
/// Forwards to [Self::apply_to_api_config]:
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
self.apply_to_api_config(&mut cfg.api)
}
/// Fills the values from [ApiConfig::listen_path], [ApiConfig::listen_fd], and
/// [ApiConfig::stream_fd] with the values from [Self]
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);

View File

@@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// Configuration options for the Rosenpass API
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
pub struct ApiConfig {
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
/// connections on
@@ -23,6 +24,10 @@ pub struct ApiConfig {
}
impl ApiConfig {
/// Construct appropriate [UnixListener]s for each of the API
/// listeners and connections configured in [Self] and invoke
/// [AppServer::add_api_listener] for each to add them to the
/// [AppServer].
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)?)?;
@@ -39,10 +44,12 @@ impl ApiConfig {
Ok(())
}
/// Sum of all the API sources configured in here
pub fn count_api_sources(&self) -> usize {
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
}
/// Checks if [Self::count_api_sources] is greater than zero
pub fn has_api_sources(&self) -> bool {
self.count_api_sources() > 0
}

View File

@@ -53,6 +53,9 @@ struct MioConnectionBuffers {
}
#[derive(Debug)]
/// Represents a single connection with an API client.
/// Includes the necessary buffers, the [ApiHandler],
/// and the [UnixStream] that is used for communication.
pub struct MioConnection {
io: UnixStream,
mio_token: mio::Token,
@@ -62,6 +65,8 @@ pub struct MioConnection {
}
impl MioConnection {
/// Construct a new [Self] for the given app server from the unix socket stream
/// to communicate on.
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
let mio_token = app_server.mio_token_dispenser.dispense();
app_server
@@ -88,6 +93,8 @@ impl MioConnection {
})
}
/// Checks if this unix stream should be closed by the enclosing
/// structure
pub fn should_close(&self) -> bool {
let exhausted = self
.buffers
@@ -97,22 +104,30 @@ impl MioConnection {
self.invalid_read && exhausted
}
/// Close and deregister this particular API connection
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
app_server.mio_poll.registry().deregister(&mut self.io)?;
Ok(())
}
/// Retrieve the mio token
pub fn mio_token(&self) -> mio::Token {
self.mio_token
}
}
/// We require references to both [MioConnection] and to the [AppServer] that contains it.
pub trait MioConnectionContext {
/// Reference to the [MioConnection] we are focusing on
fn mio_connection(&self) -> &MioConnection;
/// Reference to the [AppServer] that contains the [Self::mio_connection]
fn app_server(&self) -> &AppServer;
/// Mutable reference to the [MioConnection] we are focusing on
fn mio_connection_mut(&mut self) -> &mut MioConnection;
/// Mutable reference to the [AppServer] that contains the [Self::mio_connection]
fn app_server_mut(&mut self) -> &mut AppServer;
/// Called by [AppServer::poll] regularly to process any incoming (and outgoing) API messages
fn poll(&mut self) -> anyhow::Result<()> {
macro_rules! short {
($e:expr) => {
@@ -133,6 +148,7 @@ pub trait MioConnectionContext {
Ok(())
}
/// Called by [Self::poll] to process incoming messages
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
self.with_buffers_stolen(|this, bufs| {
// Acquire request & response. Caller is responsible to make sure
@@ -156,6 +172,7 @@ pub trait MioConnectionContext {
})
}
/// Called by [Self::poll] to write data in the send buffer to the unix stream
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
if self.write_buf_mut().exhausted() {
return Ok(Some(()));
@@ -194,6 +211,7 @@ pub trait MioConnectionContext {
}
}
/// Called by [Self::poll] to check for messages to receive
fn recv(&mut self) -> anyhow::Result<Option<()>> {
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
return Ok(None);
@@ -257,10 +275,12 @@ pub trait MioConnectionContext {
}
}
/// Forwards to [MioConnection::mio_token]
fn mio_token(&self) -> mio::Token {
self.mio_connection().mio_token()
}
/// Forwards to [MioConnection::should_close]
fn should_close(&self) -> bool {
self.mio_connection().should_close()
}
@@ -299,6 +319,7 @@ trait MioConnectionContextPrivate: MioConnectionContext {
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
/// Every [MioConnectionContext] is also a [ApiHandlerContext]
impl<T> ApiHandlerContext for T
where
T: ?Sized + MioConnectionContext,

View File

@@ -10,41 +10,59 @@ use crate::app_server::{AppServer, AppServerIoSource};
use super::{MioConnection, MioConnectionContext};
/// This is in essence a unix listener for API connections.
///
/// It contains a number of [UnixListener]s and the associated [MioConnection]s encapsulating [mio::net::UnixListener]s.
#[derive(Default, Debug)]
pub struct MioManager {
listeners: Vec<UnixListener>,
connections: Vec<Option<MioConnection>>,
}
/// Points at a particular source of IO events inside [MioManager]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum MioManagerIoSource {
// Source of IO events is the Nth unix socket listener (see [MioManager::listeners])
Listener(usize),
// Source of IO events is the Nth unix socket listener (see [MioManager::connections])
Connection(usize),
}
impl MioManager {
/// Construct an empty [Self]
pub fn new() -> Self {
Self::default()
}
}
/// Focus in on a particular [MioConnection] inside a [MioManager]
///
/// This is mainly used to implement [MioConnectionContext].
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
/// [MioConnectionContext] to access the [MioManager] instance and [AppServer]
ctx: &'a mut T,
/// Index of the connection referenced to by [Self]
conn_idx: usize,
}
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
/// Produce a MioConnectionContext from the [MioConnectionContext] and the connection index
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
Self { ctx, conn_idx }
}
}
pub trait MioManagerContext {
/// Reference to the [MioManager]
fn mio_manager(&self) -> &MioManager;
/// Reference to the [MioManager], mutably
fn mio_manager_mut(&mut self) -> &mut MioManager;
/// Reference to the [AppServer] this [MioManager] is associated with
fn app_server(&self) -> &AppServer;
/// Mutable reference to the [AppServer] this [MioManager] is associated with
fn app_server_mut(&mut self) -> &mut AppServer;
/// Add a new [UnixListener] to listen for API connections on
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
let srv = self.app_server_mut();
let mio_token = srv.mio_token_dispenser.dispense();
@@ -64,6 +82,7 @@ pub trait MioManagerContext {
Ok(())
}
/// Add a new connection to an API client
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
let connection = MioConnection::new(self.app_server_mut(), connection)?;
let mio_token = connection.mio_token();
@@ -84,6 +103,7 @@ pub trait MioManagerContext {
Ok(())
}
/// Poll a particular [MioManagerIoSource] in this [MioManager]
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
use MioManagerIoSource as S;
match io_source {
@@ -93,12 +113,14 @@ pub trait MioManagerContext {
Ok(())
}
/// Check for new connections and poll all the [MioConnectionContext]s managed by [Self]
fn poll(&mut self) -> anyhow::Result<()> {
self.accept_connections()?;
self.poll_connections()?;
Ok(())
}
/// Check all the [UnixListener]s managed by this [MioManager] for new connections
fn accept_connections(&mut self) -> io::Result<()> {
for idx in 0..self.mio_manager_mut().listeners.len() {
self.accept_from(idx)?;
@@ -106,6 +128,7 @@ pub trait MioManagerContext {
Ok(())
}
/// Check a particular [UnixListener] managed by this for new connections.
fn accept_from(&mut self, idx: usize) -> 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
@@ -122,6 +145,7 @@ pub trait MioManagerContext {
Ok(())
}
/// Call [MioConnectionContext::poll] on all the [MioConnection]s in This
fn poll_connections(&mut self) -> anyhow::Result<()> {
for idx in 0..self.mio_manager().connections.len() {
self.poll_particular_connection(idx)?;
@@ -129,6 +153,7 @@ pub trait MioManagerContext {
Ok(())
}
/// Call [MioConnectionContext::poll] on a particular connection
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
if self.mio_manager().connections[idx].is_none() {
return Ok(());

View File

@@ -1,4 +1,10 @@
//! The bulk code relating to the Rosenpass unix socket API
//!
//! # Examples
//!
#![doc = "```ignore"]
#![doc = include_str!("../../tests/api-integration-tests-api-setup.rs")]
#![doc = "```"]
mod api_handler;
mod boilerplate;

View File

@@ -1,3 +1,8 @@
//! Contains the code used to parse command line parameters for rosenpass.
//!
//! [CliArgs::run] is called by the rosenpass main function and contains the
//! bulk of our boostrapping code while the main function just sets up the basic environment
use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
@@ -31,15 +36,25 @@ use {
std::thread,
};
/// enum representing a choice of interface to a WireGuard broker
/// How to reach a WireGuard PSK Broker
#[derive(Debug)]
pub enum BrokerInterface {
/// The PSK Broker is listening on a unix socket at the given path
Socket(PathBuf),
/// The PSK Broker broker is already connected to this process; a
/// unix socket stream can be reached at the given file descriptor.
///
/// This is generally used with file descriptor passing.
FileDescriptor(i32),
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
/// and hand one end of the socketpair to the broker process via file
/// descriptor passing to the subprocess
SocketPair,
}
/// struct holding all CLI arguments for `clap` crate to parse
/// Command line arguments to the Rosenpass binary.
///
/// Used for parsing with [clap].
#[derive(Parser, Debug)]
#[command(author, version, about, long_about, arg_required_else_help = true)]
pub struct CliArgs {
@@ -80,6 +95,7 @@ pub struct CliArgs {
#[arg(short, long, group = "psk-broker-specs")]
psk_broker_spawn: bool,
/// The subcommand to be invoked
#[command(subcommand)]
pub command: Option<CliCommand>,
@@ -98,6 +114,10 @@ pub struct CliArgs {
}
impl CliArgs {
/// Apply the command line parameters to the Rosenpass configuration struct
///
/// Generally the flow of control here is that all the command line parameters
/// are merged into the configuration file to avoid much code duplication.
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_config(_cfg)?;
@@ -123,9 +143,11 @@ impl CliArgs {
None
}
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(feature = "experiment_api")]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
if let Some(path_ref) = self.psk_broker_path.as_ref() {
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
@@ -138,9 +160,10 @@ impl CliArgs {
}
}
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(not(feature = "experiment_api"))]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
None
}
@@ -244,15 +267,17 @@ pub enum CliCommand {
}
impl CliArgs {
/// Runs the command specified via CLI
/// Run Rosenpass with the given command line parameters
///
/// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
/// This contains the bulk of our startup logic with
/// the main function just setting up the basic environment
/// and then calling this function.
pub fn run(
self,
broker_interface: Option<BrokerInterface>,
test_helpers: Option<AppServerTest>,
) -> anyhow::Result<()> {
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
use CliCommand::*;
match &self.command {
Some(GenConfig { config_file, force }) => {
@@ -403,6 +428,7 @@ impl CliArgs {
Ok(())
}
/// Used by [Self::run] to start the Rosenpass key exchange server
fn event_loop(
config: config::Rosenpass,
broker_interface: Option<BrokerInterface>,
@@ -470,6 +496,19 @@ impl CliArgs {
srv.event_loop()
}
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(feature = "experiment_api")]
fn create_broker(
broker_interface: Option<BrokerInterface>,
@@ -485,6 +524,19 @@ impl CliArgs {
}
}
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(not(feature = "experiment_api"))]
fn create_broker(
_broker_interface: Option<BrokerInterface>,
@@ -492,6 +544,10 @@ impl CliArgs {
Ok(Box::new(NativeUnixBroker::new()))
}
/// Used by [Self::create_broker] if the `experiment_api` is configured
/// to set up the connection with the PSK broker process as configured
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
#[cfg(feature = "experiment_api")]
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
// Connect to the psk broker unix socket if one was specified
@@ -549,7 +605,7 @@ impl CliArgs {
}
/// generate secret and public keys, store in files according to the paths passed as arguments
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;

View File

@@ -4,8 +4,9 @@
//! [`Rosenpass`] which holds such a configuration.
//!
//! ## TODO
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
use crate::protocol::{SPk, SSk};
use rosenpass_util::file::LoadValue;
use std::{
@@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
}
}
#[derive(Debug, Serialize, Deserialize)]
/// Configuration for the Rosenpass key exchange
///
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Rosenpass {
// TODO: Raise error if secret key or public key alone is set during deserialization
// SEE: https://github.com/serde-rs/serde/issues/2793
@@ -46,7 +50,10 @@ pub struct Rosenpass {
/// list of [`SocketAddr`] to listen on
///
/// Examples:
/// - `0.0.0.0:123`
///
/// - `0.0.0.0:123` Listen on any interface using IPv4, port 123
/// - `[::1]:1234` Listen on IPv6 localhost, port 1234
/// - `[::]:4476` Listen on any IPv4 or IPv6 interface, port 4476
pub listen: Vec<SocketAddr>,
/// log verbosity
@@ -68,6 +75,7 @@ pub struct Rosenpass {
pub config_file_path: PathBuf,
}
/// Public key and secret key locations.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Keypair {
/// path to the public key file
@@ -78,6 +86,7 @@ pub struct Keypair {
}
impl Keypair {
/// Construct a keypair from its fields
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
let public_key = public_key.as_ref().to_path_buf();
let secret_key = secret_key.as_ref().to_path_buf();
@@ -88,62 +97,72 @@ impl Keypair {
}
}
/// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
/// Level of verbosity for [crate::app_server::AppServer]
///
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
/// for details.
///
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Verbosity {
Quiet,
Verbose,
}
/// ## TODO
/// - examples
/// - documentation
/// Configuration data for a single Rosenpass peer
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer {
/// path to the public key of the peer
pub public_key: PathBuf,
/// ## TODO
/// - documentation
/// The hostname and port to connect to
///
/// Can be a
///
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
pub endpoint: Option<String>,
/// path to the pre-shared key with the peer
/// path to the pre-shared key shared with the peer
///
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
pub pre_shared_key: Option<PathBuf>,
/// ## TODO
/// - documentation
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
/// to the given file and write a notification to standard out to let the calling application
/// know that a new key was exchanged
#[serde(default)]
pub key_out: Option<PathBuf>,
/// ## TODO
/// - documentation
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
/// Information for supplying exchanged keys directly to WireGuard
#[serde(flatten)]
pub wg: Option<WireGuard>,
}
/// ## TODO
/// - documentation
/// Information for supplying exchanged keys directly to WireGuard
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireGuard {
/// ## TODO
/// - documentation
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
/// key exchange
pub device: String,
/// ## TODO
/// - documentation
/// WireGuard public key of the peer to supply with pre-shared keys
pub peer: String,
/// ## TODO
/// - documentation
/// Extra parameters passed to the `wg` command
#[serde(default)]
pub extra_params: Vec<String>,
}
impl Default for Rosenpass {
/// Generate an empty configuration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
fn default() -> Self {
Self::empty()
}
@@ -156,8 +175,15 @@ impl Rosenpass {
/// checked whether they even exist.
///
/// ## TODO
///
/// - consider using a different algorithm to determine home directory the below one may
/// behave unexpectedly on Windows
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
// read file and deserialize
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
@@ -185,7 +211,13 @@ impl Rosenpass {
Ok(config)
}
/// Write a config to a file
/// Encode a configuration object as toml and write it to a file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
let serialized_config =
toml::to_string_pretty(&self).expect("unable to serialize the default config");
@@ -194,6 +226,12 @@ impl Rosenpass {
}
/// Commit the configuration to where it came from, overwriting the original file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn commit(&self) -> anyhow::Result<()> {
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
@@ -201,13 +239,21 @@ impl Rosenpass {
self.store(&self.config_file_path)
}
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
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
/// Check that the configuration is sound, ensuring
/// for instance that the referenced files exist
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(ref keypair) = self.keypair {
// check the public key file exists
@@ -284,6 +330,21 @@ impl Rosenpass {
Ok(())
}
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
/// and the configuration values.
///
/// This was introduced when we introduced a unix-socket API feature allowing the server
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
/// enabled in the configuration.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
pub fn check_usefullness(&self) -> anyhow::Result<()> {
#[cfg(not(feature = "experiment_api"))]
ensure!(self.keypair.is_some(), "Server keypair missing.");
@@ -299,15 +360,38 @@ impl Rosenpass {
Ok(())
}
/// Produce an empty confuguration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn empty() -> Self {
Self::new(None)
}
/// Produce configuration from the keypair
///
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
Self::new(Some(Keypair::new(pk, sk)))
}
/// Creates a new configuration
/// Initialize a minimal configuration with the [Self::keypair] field supplied
/// as a parameter
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn new(keypair: Option<Keypair>) -> Self {
Self {
keypair,
@@ -321,6 +405,14 @@ impl Rosenpass {
}
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
///
/// I.e. listen on any interface.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
#[doc = "```"]
pub fn add_if_any(&mut self, port: u16) {
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
@@ -333,8 +425,20 @@ impl Rosenpass {
self.listen.push(ipv6_any);
}
/// from chaotic args
/// Quest: the grammar is undecideable, what do we do here?
/// Parser for the old, IP style grammar.
///
/// See out manual page rosenpass-exchange(1) on details about the grammar.
///
/// This function parses the grammar and turns it into an instance of the configuration
/// struct.
///
/// TODO: the grammar is undecidable, what do we do here?
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
#[doc = "```"]
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
let mut config = Self::new(Some(Keypair::new("", "")));
@@ -525,11 +629,13 @@ impl Rosenpass {
}
impl Default for Verbosity {
/// Self::Quiet
fn default() -> Self {
Self::Quiet
}
}
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
secret_key = "/path/to/rp-secret-key"
listen = []
@@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
mod test {
use super::*;
use std::{borrow::Borrow, net::IpAddr};
use std::borrow::Borrow;
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
toml::from_str(s.borrow())
@@ -664,37 +770,6 @@ mod test {
Ok(())
}
#[test]
fn test_simple_cli_parse() {
let args = split_str(
"public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out",
);
let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
)
}
#[test]
fn test_cli_parse_multiple_peers() {
let args = split_str(

View File

@@ -8,29 +8,99 @@ use thiserror::Error;
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
#[derive(Debug, Clone)]
/// A pair of matching public/secret keys used to launch the crypto server.
///
/// # Examples
///
/// Decomposing a key pair into its individual components, then recreating it:
///
/// ```rust
/// use rosenpass::protocol::Keypair;
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let random_pair = Keypair::random();
/// let random_copy = random_pair.clone();
/// let (sk_copy, pk_copy) = random_copy.into_parts();
///
/// // Re-assemble the key pair from the original secret/public key
/// // Note that it doesn't have to be the exact same keys;
/// // you could just as easily use a completely different pair here
/// let reconstructed_pair = Keypair::from_parts((sk_copy, pk_copy));
///
/// assert_eq!(random_pair.sk.secret(), reconstructed_pair.sk.secret());
/// assert_eq!(random_pair.pk, reconstructed_pair.pk);
/// ```
pub struct Keypair {
/// Secret key matching the crypto server's public key.
pub sk: SSk,
/// Public key identifying the crypto server instance.
pub pk: SPk,
}
// TODO: We need a named tuple derive
impl Keypair {
/// Creates a new key pair from the given secret/public key components.
///
/// # Example
///
/// ```rust
/// use rosenpass::protocol::{Keypair, SSk, SPk};
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let random_sk = SSk::random();
/// let random_pk = SPk::random();
/// let random_pair = Keypair::new(random_sk.clone(), random_pk.clone());
///
/// assert_eq!(random_sk.secret(), random_pair.sk.secret());
/// assert_eq!(random_pk, random_pair.pk);
/// ```
pub fn new(sk: SSk, pk: SPk) -> Self {
Self { sk, pk }
}
/// Creates a new "empty" key pair. All bytes are initialized to zero.
///
/// See [SSk:zero()][crate::protocol::SSk::zero] and [SPk:zero()][crate::protocol::SPk::zero], respectively.
///
/// # Example
///
/// ```rust
/// use rosenpass::protocol::{Keypair, SSk, SPk};
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let zero_sk = SSk::zero();
/// let zero_pk = SPk::zero();
/// let zero_pair = Keypair::zero();
///
/// assert_eq!(zero_sk.secret(), zero_pair.sk.secret());
/// assert_eq!(zero_pk, zero_pair.pk);
/// ```
pub fn zero() -> Self {
Self::new(SSk::zero(), SPk::zero())
}
/// Creates a new (securely-)random key pair. The mechanism is described in [rosenpass_secret_memory::Secret].
///
/// See [SSk:random()][crate::protocol::SSk::random] and [SPk:random()][crate::protocol::SPk::random], respectively.
pub fn random() -> Self {
Self::new(SSk::random(), SPk::random())
}
/// Creates a new key pair from the given public/secret key components.
pub fn from_parts(parts: (SSk, SPk)) -> Self {
Self::new(parts.0, parts.1)
}
/// Deconstructs the key pair, yielding the individual public/secret key components.
pub fn into_parts(self) -> (SSk, SPk) {
(self.sk, self.pk)
}
@@ -38,25 +108,77 @@ impl Keypair {
#[derive(Error, Debug)]
#[error("PSK already set in BuildCryptoServer")]
/// Error indicating that the PSK is already set.
/// Unused in the current version of the protocol.
pub struct PskAlreadySet;
#[derive(Error, Debug)]
#[error("Keypair already set in BuildCryptoServer")]
/// Error type indicating that the public/secret key pair has already been set.
pub struct KeypairAlreadySet;
#[derive(Error, Debug)]
#[error("Can not construct CryptoServer: Missing keypair")]
/// Error type indicating that no public/secret key pair has been provided.
pub struct MissingKeypair;
#[derive(Debug, Default)]
/// Builder for setting up a [CryptoServer] (with deferred initialization).
///
/// There are multiple ways of creating a crypto server:
///
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][crate::protocol::CryptoServer::new])
/// 2. Provide the key pair at a later time (using [BuildCryptoServer::empty])
///
/// With BuildCryptoServer, you can gradually configure parameters as they become available.
/// This may be useful when they depend on runtime conditions or have to be fetched asynchronously.
/// It's possible to use the builder multiple times; it then serves as a "blueprint" for new
/// instances, several of which may be spawned with the same base configuration (or variations thereof).
///
/// Note that the server won't actually launch without a key pair (expect a [MissingKeypair] error).
/// The setup will be much simplified if one is provided, at the cost of some flexibility.
/// It's however possible to defer this step in case your application requires it.
///
/// For additional details or examples, see [AppServer::crypto_site][crate::app_server::AppServer::crypto_site] and [ConstructionSite][rosenpass_util::build::ConstructionSite].
///
/// # Example
///
/// ```rust
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let keypair = Keypair::random();
/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random() };
/// let peer2 = PeerParams { psk: None, pk: SPk::random() };
///
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]);
/// builder.add_peer(peer2.psk.clone(), peer2.pk);
///
/// let server = builder.build().expect("build failed");
/// assert_eq!(server.peers.len(), 2);
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
/// assert_eq!(server.spkm, keypair.pk);
/// ```
pub struct BuildCryptoServer {
/// The key pair (secret/public key) identifying the crypto server instance.
pub keypair: Option<Keypair>,
/// A list of network peers that should be registered when launching the server.
pub peers: Vec<PeerParams>,
}
impl Build<CryptoServer> for BuildCryptoServer {
type Error = anyhow::Error;
/// Creates a crypto server, adding all peers that have previously been registered.
///
/// You must provide a key pair at the time of instantiation.
/// If the list of peers is outdated, building the server will fail.
///
/// In this case, make sure to remove or re-add any peers that may have changed.
fn build(self) -> Result<CryptoServer, Self::Error> {
let Some(Keypair { sk, pk }) = self.keypair else {
return Err(MissingKeypair)?;
@@ -74,20 +196,32 @@ impl Build<CryptoServer> for BuildCryptoServer {
}
#[derive(Debug)]
/// Cryptographic key(s) identifying the connected [peer][crate::protocol::Peer] ("client")
/// for a given session that is being managed by the crypto server.
///
/// Each peer must be identified by a [public key (SPk)][crate::protocol::SPk].
/// Optionally, a [symmetric key (SymKey)][crate::protocol::SymKey]
/// can be provided when setting up the connection.
/// For more information on the intended usage and security considerations, see [Peer::psk][crate::protocol::Peer::psk] and [Peer::spkt][crate::protocol::Peer::spkt].
pub struct PeerParams {
/// Pre-shared (symmetric) encryption keys that should be used with this peer.
pub psk: Option<SymKey>,
/// Public key identifying the peer.
pub pk: SPk,
}
impl BuildCryptoServer {
/// Creates a new builder instance using the given key pair and peer list.
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
Self { keypair, peers }
}
/// Creates an "incomplete" builder instance, without assigning a key pair.
pub fn empty() -> Self {
Self::new(None, Vec::new())
}
/// Creates a builder instance from the given key pair and peer list components.
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
Self {
keypair: parts.0,
@@ -95,32 +229,165 @@ impl BuildCryptoServer {
}
}
/// Deconstructs the current builder instance, taking ownership of its key pair and peer list.
///
/// Replaces all parameters with their default values, which allows extracting them
/// while leaving the builder in a reusable state.
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
(self.keypair.take(), self.peers.swap_with_default())
}
/// Deconstructs the builder instance, yielding the assigned key pair and peer list.
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
self.take_parts()
}
/// Creates a new builder instance, assigning the given keypair to it.
///
/// Note that only one key pair can be assigned (expect [KeypairAlreadySet] on failure).
///
/// # Examples
///
/// ## Adding key pairs to an existing builder
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
///
/// // Deferred initialization: Create builder first, add the key pair later
/// let mut builder = BuildCryptoServer::empty();
/// // Do something with the builder ...
///
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
/// // Now we've got a key pair that should be added to the configuration
/// let keypair = Keypair::random();
/// builder.with_keypair(keypair.clone()).expect("build with key pair failed");
///
/// // New server instances can now make use of the assigned key pair
/// let server = builder.build().expect("build failed");
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
/// assert_eq!(server.spkm, keypair.pk);
/// ```
///
/// ## Basic error handling: Re-assigning key pairs
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, KeypairAlreadySet};
///
/// // In this case, we'll create a functional builder from its various components
/// // These could be salvaged from another builder, or obtained from disk/network (etc.)
/// let keypair = Keypair::random();
/// let mut builder = BuildCryptoServer::from_parts((Some(keypair.clone()), Vec::new()));
///
/// // The builder has already been assigned a key pair, so this won't work
/// let err = builder.with_keypair(keypair).expect_err("should fail to reassign key pair");
/// assert!(matches!(err, KeypairAlreadySet));
/// ```
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
self.keypair.insert(keypair).discard_result();
Ok(self)
}
/// Creates a new builder instance, adding a new entry to the list of registered peers.
///
/// # Example
///
/// Adding peers to an existing builder:
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
///
/// // Deferred initialization: Create builder first, add some peers later
/// let keypair_option = Some(Keypair::random());
/// let mut builder = BuildCryptoServer::new(keypair_option, Vec::new());
/// assert!(builder.peers.is_empty());
///
/// // Do something with the builder ...
///
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
/// // Now we've found a peer that should be added to the configuration
/// let pre_shared_key = SymKey::random();
/// let public_key = SPk::random();
/// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone());
///
/// // New server instances will then start with the peer being registered already
/// let server = builder.build().expect("build failed");
/// assert_eq!(server.peers.len(), 1);
/// let peer = &server.peers[0];
/// let peer_psk = Some(peer.psk.clone()).expect("PSK is None");
/// assert_eq!(peer.spkt, public_key);
/// assert_eq!(peer_psk.secret(), pre_shared_key.secret());
/// ```
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self {
// TODO: Check here already whether peer was already added
self.peers.push(PeerParams { psk, pk });
self
}
/// Add a new entry to the list of registered peers, with or without a pre-shared key.
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr {
let id = PeerPtr(self.peers.len());
self.with_added_peer(psk, pk);
id
}
/// Creates a new builder, taking ownership of another instance's key pair and peer list.
/// Allows duplicating the current set of launch parameters, which can then be used to
/// start multiple servers with the exact same configuration (or variants using it as a base).
///
/// # Example
///
/// Extracting the server configuration from a builder:
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
///
/// let keypair = Keypair::random();
/// let peer_pk = SPk::random();
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]);
/// builder.add_peer(None, peer_pk);
///
/// // Extract configuration parameters from the decomissioned builder
/// let (keypair_option, peers) = builder.take_parts();
/// let extracted_keypair = keypair_option.unwrap();
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
/// assert_eq!(extracted_keypair.pk, keypair.pk);
/// assert_eq!(peers.len(), 1);
///
/// // Now we can create a new builder with the same configuration
/// let parts = (Some(extracted_keypair), peers);
/// let mut reassembled_builder = BuildCryptoServer::from_parts(parts);
/// let new_builder = reassembled_builder.emancipate();
///
/// // Do something with the new builder ...
///
/// // ... and now, deconstruct this one as well - still using the same parts
/// let (keypair_option, peers) = new_builder.into_parts();
/// let extracted_keypair = keypair_option.unwrap();
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
/// assert_eq!(extracted_keypair.pk, keypair.pk);
/// assert_eq!(peers.len(), 1);
/// ```
pub fn emancipate(&mut self) -> Self {
Self::from_parts(self.take_parts())
}

View File

@@ -39,7 +39,7 @@ impl Drop for KillChild {
// system is a bit broken; there is probably a few functions that just restart on EINTR
// so the signal is absorbed
loop {
rustix::process::kill_process(pid, Term).discard_result();
kill_process(pid, Term).discard_result();
if self.0.try_wait().unwrap().is_some() {
break;
}

View File

@@ -22,8 +22,17 @@ struct KillChild(std::process::Child);
impl Drop for KillChild {
fn drop(&mut self) {
self.0.kill().discard_result();
self.0.wait().discard_result()
use rustix::process::{kill_process, Pid, Signal::Term};
let pid = Pid::from_child(&self.0);
// We seriously need to start handling signals with signalfd, our current signal handling
// system is a bit broken; there is probably a few functions that just restart on EINTR
// so the signal is absorbed
loop {
kill_process(pid, Term).discard_result();
if self.0.try_wait().unwrap().is_some() {
break;
}
}
}
}

View File

@@ -0,0 +1,10 @@
use rosenpass::config::Rosenpass;
#[test]
fn config_Rosenpass_add_if_any_example() {
let mut v = Rosenpass::empty();
v.add_if_any(4000);
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
}

View File

@@ -0,0 +1,18 @@
use rosenpass::config::{Keypair, Rosenpass};
#[test]
fn example_config_rosenpass_new() {
let (sk, pk) = ("./example.sk", "./example.pk");
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
assert_eq!(Rosenpass::empty(), Rosenpass::default());
assert_eq!(
Rosenpass::from_sk_pk(sk, pk),
Rosenpass::new(Some(Keypair::new(pk, sk)))
);
let mut v = Rosenpass::empty();
v.keypair = Some(Keypair::new(pk, sk));
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
}

View File

@@ -0,0 +1,36 @@
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
};
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
#[test]
fn parse_simple() {
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out";
let argv = argv.split(' ').map(|s| s.to_string()).collect();
let config = Rosenpass::parse_args(argv).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
);
}

View File

@@ -0,0 +1,42 @@
use std::path::PathBuf;
use rosenpass::config::{Rosenpass, Verbosity};
#[test]
fn example_config_rosenpass_store() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = tmpdir.path().join("config.toml");
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
// Can not commit config, path not known
assert!(c.commit().is_err());
// We can store it to an explicit path though
c.store(&cfg)?;
// Storing does not set commitment path
assert!(c.commit().is_err());
// We can reload the config now and the configurations
// are equal if we adjust the commitment path
let mut c2 = Rosenpass::load(&cfg)?;
c.config_file_path = PathBuf::from(&cfg);
assert_eq!(c, c2);
// And this loaded config can now be committed
c2.verbosity = Verbosity::Verbose;
c2.commit()?;
// And the changes actually made it to disk
let c3 = Rosenpass::load(cfg)?;
assert_eq!(c2, c3);
assert_ne!(c, c3);
Ok(())
}

View File

@@ -0,0 +1,37 @@
use std::fs;
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
#[test]
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
// Empty validates OK
assert!(Rosenpass::empty().validate().is_ok());
// Missing secret key does not pass usefulness
assert!(Rosenpass::empty().check_usefullness().is_err());
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
// Missing secret key does not validate
assert!(cfg.validate().is_err());
// But passes usefulness (the configuration is useful but invalid)
assert!(cfg.check_usefullness().is_ok());
// Providing empty key files does not help
fs::write(&sk, b"")?;
fs::write(&pk, b"")?;
assert!(cfg.validate().is_err());
// But after providing proper key files, the configuration validates
generate_and_save_keypair(sk, pk)?;
assert!(cfg.validate().is_ok());
Ok(())
}

View File

@@ -0,0 +1,15 @@
use std::{borrow::Borrow, process::Command};
#[test]
fn test_gen_ipc_msg_types() -> anyhow::Result<()> {
let out = Command::new(env!("CARGO_BIN_EXE_rosenpass-gen-ipc-msg-types")).output()?;
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout)?;
// Smoke tests only
assert!(stdout.contains("type RawMsgType = u128;"));
assert!(stdout.contains("const SUPPLY_KEYPAIR_RESPONSE : RawMsgType = RawMsgType::from_le_bytes(hex!(\"f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9\"));"));
Ok(())
}

View File

@@ -160,6 +160,9 @@ fn check_example_config() {
.output()
.expect("EXAMPLE_CONFIG not valid");
let stderr = String::from_utf8_lossy(&output.stderr);
assert_eq!(stderr, "");
fs::copy(
tmp_dir.path().join("rp-public-key"),
tmp_dir.path().join("rp-peer-public-key"),

View File

@@ -2,11 +2,9 @@
use std::{
borrow::{Borrow, BorrowMut},
collections::VecDeque,
fmt::{Debug, Write},
ops::{DerefMut, RangeBounds},
ops::DerefMut,
};
use rand::distributions::uniform::SampleBorrow;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_util::result::OkExt;
@@ -28,48 +26,53 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
sim.poll_loop(150)?; // Poll 75 times
let transcript = sim.transcript;
let completions: Vec<_> = transcript
let _completions: Vec<_> = transcript
.iter()
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
.collect();
#[cfg(not(coverage))]
assert!(
!completions.is_empty(),
!_completions.is_empty(),
"\
Should have performed a successful key exchanged!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions[0].0 < 20.0,
_completions[0].0 < 20.0,
"\
First key exchange should happen in under twenty seconds!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions.len() >= 3,
_completions.len() >= 3,
"\
Should have at least two renegotiations!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
(110.0..175.0).contains(&completions[1].0),
(110.0..175.0).contains(&_completions[1].0),
"\
First renegotiation should happen in between two and three minutes!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
#[cfg(not(coverage))]
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
");
Ok(())
@@ -106,48 +109,53 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
}
let transcript = sim.transcript;
let completions: Vec<_> = transcript
let _completions: Vec<_> = transcript
.iter()
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
.collect();
#[cfg(not(coverage))]
assert!(
!completions.is_empty(),
!_completions.is_empty(),
"\
Should have performed a successful key exchanged!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions[0].0 < 10.0,
_completions[0].0 < 10.0,
"\
First key exchange should happen in under twenty seconds!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
completions.len() >= 3,
_completions.len() >= 3,
"\
Should have at least two renegotiations!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
#[cfg(not(coverage))]
assert!(
(110.0..175.0).contains(&completions[1].0),
(110.0..175.0).contains(&_completions[1].0),
"\
First renegotiation should happen in between two and three minutes!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
"
);
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
#[cfg(not(coverage))]
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
Transcript: {transcript:?}\n\
Completions: {completions:?}\
Completions: {_completions:?}\
");
Ok(())

50
rp/tests/smoketest.rs Normal file
View File

@@ -0,0 +1,50 @@
use std::process::Command;
#[test]
fn smoketest() -> anyhow::Result<()> {
let tmpdir = tempfile::tempdir()?;
let secret = tmpdir.path().join("server.secret");
let public = tmpdir.path().join("server.public");
let invalid = tmpdir.path().join("invalid.secret");
let toml = tmpdir.path().join("config.toml");
let invalid_config = r#"
verbose = false
private_keys_dir = "invliad"
[[peers]]
public_keys_dir = "invliad"
"#;
// Generate keys
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
.args(["genkey", secret.to_str().unwrap()])
.spawn()?
.wait()?;
assert!(status.success());
// Derive Public keys
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
.args(["pubkey", secret.to_str().unwrap(), public.to_str().unwrap()])
.spawn()?
.wait()?;
assert!(status.success());
// Can not exchange keys using exchange with invalid keys
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
.args(["exchange", invalid.to_str().unwrap()])
.output()?;
assert!(!out.status.success());
std::fs::write(toml, invalid_config)?;
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
.args([
"exchange-config",
tmpdir.path().join("invalid_config").to_str().unwrap(),
])
.output()?;
assert!(!out.status.success());
Ok(())
}

View File

@@ -1,3 +1,9 @@
//! This module provides a wrapper [MallocAllocator] around the memsec allocator in
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memsec allocator
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
//!
//! The module also provides the [MallocVec] and [MallocBox] types.
use std::fmt;
use std::ptr::NonNull;
@@ -6,31 +12,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MallocAllocatorContents;
/// Memory allocation using using the memsec crate
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
#[derive(Copy, Clone, Default)]
pub struct MallocAllocator {
_dummy_private_data: MallocAllocatorContents,
}
/// A box backed by the memsec allocator
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
/// from the [memsec] crate.
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
/// A vector backed by the memsec allocator
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
/// from the [memsec] crate.
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
/// Try to allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box_try, MallocBox};
/// let data: u8 = 42;
/// let malloc_box: MallocBox<u8> = malloc_box_try(data)?;
/// # assert_eq!(*malloc_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
}
/// Allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box, MallocBox};
/// let data: u8 = 42;
/// let malloc_box: MallocBox<u8> = malloc_box(data);
/// # assert_eq!(*malloc_box, 42u8);
/// ```
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
MallocBox::<T>::new_in(x, MallocAllocator::new())
}
/// Allocate a [MallocVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_vec, MallocVec};
/// let mut malloc_vec: MallocVec<u8> = malloc_vec();
/// malloc_vec.push(0u8);
/// malloc_vec.push(1u8);
/// malloc_vec.push(2u8);
/// # let mut element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn malloc_vec<T>() -> MallocVec<T> {
MallocVec::<T>::new_in(MallocAllocator::new())
}
impl MallocAllocator {
/// Creates a new [MallocAllocator].
pub fn new() -> Self {
Self {
_dummy_private_data: MallocAllocatorContents,
@@ -94,6 +147,9 @@ mod test {
malloc_allocation_impl::<8>(&alloc);
malloc_allocation_impl::<64>(&alloc);
malloc_allocation_impl::<999>(&alloc);
// Also test the debug-print for good measure
let _ = format!("{:?}", alloc);
}
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {

View File

@@ -1,3 +1,9 @@
//! This module provides a wrapper [MemfdSecAllocator] around the memfdsec allocator in
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memfdsec allocator
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
//!
//! The module also provides the [MemfdSecVec] and [MemfdSecBox] types.
#![cfg(target_os = "linux")]
use std::fmt;
use std::ptr::NonNull;
@@ -7,31 +13,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MemfdSecAllocatorContents;
/// Memory allocation using using the memsec crate
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
#[derive(Copy, Clone, Default)]
pub struct MemfdSecAllocator {
_dummy_private_data: MemfdSecAllocatorContents,
}
/// A box backed by the memsec allocator
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
/// from the [memsec] crate.
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
/// A vector backed by the memsec allocator
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
/// from the [memsec] crate.
pub type MemfdSecVec<T> = allocator_api2::vec::Vec<T, MemfdSecAllocator>;
/// Try to allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box_try, MemfdSecBox};
/// let data: u8 = 42;
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box_try(data)?;
/// # assert_eq!(*memfdsec_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
}
/// Allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box, MemfdSecBox};
/// let data: u8 = 42;
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box(data);
/// # assert_eq!(*memfdsec_box, 42u8);
/// ```
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
}
/// Allocate a [MemfdSecVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_vec, MemfdSecVec};
/// let mut memfdsec_vec: MemfdSecVec<u8> = memfdsec_vec();
/// memfdsec_vec.push(0u8);
/// memfdsec_vec.push(1u8);
/// memfdsec_vec.push(2u8);
/// # let mut element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
}
impl MemfdSecAllocator {
/// Create a new [MemfdSecAllocator].
pub fn new() -> Self {
Self {
_dummy_private_data: MemfdSecAllocatorContents,
@@ -95,6 +148,9 @@ mod test {
memfdsec_allocation_impl::<8>(&alloc);
memfdsec_allocation_impl::<64>(&alloc);
memfdsec_allocation_impl::<999>(&alloc);
// Also test the debug-print for good measure
let _ = format!("{:?}", alloc);
}
fn memfdsec_allocation_impl<const N: usize>(alloc: &MemfdSecAllocator) {

View File

@@ -1,2 +1,6 @@
//! This module provides wrappers around the memfdsec and the memsec allocators from the
//! [memsec] crate. The wrappers implement the [Allocator](allocator_api2::alloc::Allocator) trait
//! and can thus be used as a drop in replacement wherever ever this trait is required.
pub mod malloc;
pub mod memfdsec;

View File

@@ -1,3 +1,12 @@
//! This module provides a [SecretAllocator](SecretAlloc) that allocates memory with extra
//! protections that make it more difficult for threat actors to access secrets that they aren't
//! authorized to access. At the moment the `memsec` and `memfdsec` allocators from the
//! [memsec] crate are supported for this purpose.
//!
//! [SecretAlloc] implements the [Allocator] trait and can thus be used as a drop in replacement
//! wherever ever this trait is required.
//!
//! The module also provides the [SecretVec] and [SecretBox] types.
pub mod memsec;
use std::sync::OnceLock;
@@ -8,15 +17,50 @@ use memsec::malloc::MallocAllocator;
#[cfg(target_os = "linux")]
use memsec::memfdsec::MemfdSecAllocator;
/// Globally configures which [SecretAllocType] to use as default for
/// [SecretAllocators](SecretAlloc).
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
/// Sets the secret allocation type to use.
/// Intended usage at startup before secret allocation
/// takes place
/// Sets the secret allocation type to use by default for [SecretAllocators](SecretAlloc).
/// It is intended that this function is called at startup before a secret allocation
/// takes place.
///
/// # Example
/// ```rust
/// # use std::alloc::Layout;
/// # use allocator_api2::alloc::Allocator;
/// # use rosenpass_secret_memory::alloc::{set_secret_alloc_type, SecretAlloc, SecretAllocType};
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// let secret_alloc = SecretAlloc::default();
/// unsafe {
/// let memory = secret_alloc.allocate(Layout::from_size_align_unchecked(128, 32))?;
/// }
/// # Ok::<(), anyhow::Error>(())
/// ```
/// ```
pub fn set_secret_alloc_type(alloc_type: SecretAllocType) {
ALLOC_TYPE.set(alloc_type).unwrap();
}
/// Initializes type of allocator to be sued with `alloc_type` if it is not initialized yet. Returns
/// the current [SecretAllocType] afterward.
///
/// # Example
/// ```rust
/// # use std::alloc::Layout;
/// # use allocator_api2::alloc::Allocator;
/// # use rosenpass_secret_memory::alloc::{get_or_init_secret_alloc_type, set_secret_alloc_type,
/// # SecretAlloc, SecretAllocType};
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// #[cfg(target_os = "linux")] {
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMemfdSec);
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
/// }
/// #[cfg(not(target_os = "linux"))] {
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
/// }
///```
pub fn get_or_init_secret_alloc_type(alloc_type: SecretAllocType) -> SecretAllocType {
*ALLOC_TYPE.get_or_init(|| alloc_type)
}
@@ -28,6 +72,7 @@ pub enum SecretAllocType {
MemsecMemfdSec,
}
/// An [Allocator] that uses a [SecretAllocType] for allocation.
pub struct SecretAlloc {
alloc_type: SecretAllocType,
}
@@ -68,19 +113,72 @@ unsafe impl Allocator for SecretAlloc {
}
}
/// A [allocator_api2::boxed::Box] that is backed by [SecretAlloc].
pub type SecretBox<T> = allocator_api2::boxed::Box<T, SecretAlloc>;
/// A vector backed by the memsec allocator
/// A [allocator_api2::vec::Vec] that is backed by [SecretAlloc].
pub type SecretVec<T> = allocator_api2::vec::Vec<T, SecretAlloc>;
/// Try to allocate a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_box_try, SecretBox};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let data: u8 = 42;
/// let secret_box: SecretBox<u8> = secret_box_try(data)?;
/// # assert_eq!(*secret_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn secret_box_try<T>(x: T) -> Result<SecretBox<T>, AllocError> {
SecretBox::<T>::try_new_in(x, SecretAlloc::default())
}
/// Allocates a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_box, SecretBox};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let data: u8 = 42;
/// let secret_box: SecretBox<u8> = secret_box(data);
/// # assert_eq!(*secret_box, 42u8);
/// ```
pub fn secret_box<T>(x: T) -> SecretBox<T> {
SecretBox::<T>::new_in(x, SecretAlloc::default())
}
/// Allocate a [SecretVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_vec, SecretVec};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let mut secret_vec: SecretVec<u8> = secret_vec();
/// secret_vec.push(0u8);
/// secret_vec.push(1u8);
/// secret_vec.push(2u8);
/// # let mut element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn secret_vec<T>() -> SecretVec<T> {
SecretVec::<T>::new_in(SecretAlloc::default())
}

View File

@@ -1,6 +1,31 @@
//! This module provides a helper for creating debug prints for byte slices.
//! See [debug_crypto_array] for more details.
use std::fmt;
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter].
/// # Example
///
/// ```rust
/// use std::fmt::{Debug, Formatter};
/// use rosenpass_secret_memory::debug::debug_crypto_array;
///
/// struct U8Wrapper {
/// pub u_eigt: Vec<u8>
/// }
/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7];
/// debug_crypto_array(self.u_eigt.as_slice(), f)
/// }
/// }
/// // Short byte slices are printed completely.
/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]};
/// assert_eq!(format!("{:?}", cafe), "[{}]=145376");
/// // For longer byte slices, only the first 32 and last 32 bytes are printed.
/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()};
/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\
/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
/// ```
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {

View File

@@ -1,8 +1,46 @@
//! Objects that implement this Trait provide a way to store their data in way that respects the
//! confidentiality of its data. Specifically, an object implementing this Trait guarantees
//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility
//! equivalent to [rosenpass_util::file::Visibility::Secret].
use std::path::Path;
/// Objects that implement this Trait provide a standard method to be stored securely. The trait can
/// be implemented as follows for example:
/// # Example
/// ```rust
/// use std::io::Write;
/// use std::path::Path;
/// use rosenpass_secret_memory::file::StoreSecret;
///
/// use rosenpass_util::file::{fopen_w, Visibility};
///
/// struct MyWeirdI32 {
/// _priv_i32: [u8; 4],
/// }
///
/// impl StoreSecret for MyWeirdI32 {
/// type Error = std::io::Error;
///
/// fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
///
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
/// }
/// ```
pub trait StoreSecret {
type Error;
/// Stores the object securely. In particular, it ensures that the visibility is equivalent to
/// [rosenpass_util::file::Visibility::Secret].
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
/// Stores the object. No requirement on the visibility is given, but it is common to store
/// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public].
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

View File

@@ -1,3 +1,38 @@
//! This library provides functionality for working with secret data and protecting it in
//! memory from illegitimate access.
//!
//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators
//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait.
//! We refer to the documentation of these modules for more details on their appropriate usage.
//!
//! The [policy] module then provides functionality for specifying which of the allocators from
//! the [alloc] module should be used.
//!
//! Once this configuration is made [Secret] can be used to store sensitive data in memory
//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways*
//! zeroized before it is released. Because allocations of the protected memory are expensive to do,
//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows:
//! # Exmaple
//! ```rust
//! use zeroize::Zeroize;
//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret};
//! secret_policy_try_use_memfd_secrets();
//! let mut my_secret: Secret<32> = Secret::random();
//! my_secret.zeroize();
//! ```
//!
//! # Futher functionality
//! In addition to this core functionality, this library provides some more smaller tools.
//!
//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to
//! that of [Secret].
//! 2. The [debug] module provides functionality to easily create debug output for objects that are
//! backed by byte arrays or slices, like for example [Secret].
//! 3. The [mod@file] module provides functionality to store [Secrets](crate::Secret)
//! and [Public] in files such that the file's [Visibility](rosenpass_util::file::Visibility)
//! corresponds to the confidentiality of the data.
//! 4. The [rand] module provides a simple way of generating randomness.
pub mod debug;
pub mod file;
pub mod rand;

View File

@@ -1,3 +1,12 @@
//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for
//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires
//! that an allocator is chosen before [Secret](crate::Secret) is used the first time.
//! This module provides functionality for just that.
/// This function sets the `memfdsec` allocator as the default in case it is supported by
/// the target and uses the `memsec` allocator otherwise.
///
/// At the time of writing, the `memfdsec` allocator is just supported on linux targets.
pub fn secret_policy_try_use_memfd_secrets() {
let alloc_type = {
#[cfg(target_os = "linux")]
@@ -22,6 +31,8 @@ pub fn secret_policy_try_use_memfd_secrets() {
log::info!("Secrets will be allocated using {:?}", alloc_type);
}
/// This functions sets the `memfdsec` allocator as the default. At the time of writing
/// this is only supported on Linux targets.
#[cfg(target_os = "linux")]
pub fn secret_policy_use_only_memfd_secrets() {
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
@@ -34,6 +45,7 @@ pub fn secret_policy_use_only_memfd_secrets() {
log::info!("Secrets will be allocated using {:?}", alloc_type);
}
/// This function sets the `memsec` allocator as the default. It is supported on all targets.
pub fn secret_policy_use_only_malloc_secrets() {
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
assert_eq!(

View File

@@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut};
use std::path::Path;
/// Contains information in the form of a byte array that may be known to the
/// public
/// public.
///
/// # Example
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{Public};
///
/// let mut my_public_data: Public<32> = Public::random();
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_public_data.randomize();
/// // A Public can be overwritten with zeros.
/// my_public_data.zeroize();
/// // If a Public is printed as Debug, its content is printed byte for byte.
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
/// ```
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
@@ -24,75 +38,84 @@ pub struct Public<const N: usize> {
}
impl<const N: usize> Public<N> {
/// Create a new [Public] from a byte slice
/// Create a new [Public] from a byte slice.
pub fn from_slice(value: &[u8]) -> Self {
copy_slice(value).to_this(Self::zero)
}
/// Create a new [Public] from a byte array
/// Create a new [Public] from a byte array.
pub fn new(value: [u8; N]) -> Self {
Self { value }
}
/// Create a zero initialized [Public]
/// Create a zero initialized [Public].
pub fn zero() -> Self {
Self { value: [0u8; N] }
}
/// Create a random initialized [Public]
/// Create a random initialized [Public].
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Randomize all bytes in an existing [Public]
/// Randomize all bytes in an existing [Public].
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
}
impl<const N: usize> Randomize for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.value.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&self.value, fmt)
}
}
impl<const N: usize> Deref for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Target = [u8; N];
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> DerefMut for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
&mut self.value
}
@@ -101,6 +124,7 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
@@ -111,6 +135,7 @@ impl<const N: usize> LoadValue for Public<N> {
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
}
impl<const N: usize> LoadValueB64 for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
impl<const N: usize> StoreValueB64 for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
let mut f = [0u8; F];
@@ -155,8 +183,10 @@ impl<const N: usize> StoreValueB64 for Public<N> {
}
impl<const N: usize> StoreValueB64Writer for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
mut writer: W,
@@ -172,89 +202,117 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
}
}
/// A [Box] around a [Public] so that the latter one can be allocated on the heap.
///
/// # Example
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{Public, PublicBox};
///
/// let mut my_public_data: Public<32> = Public::random();
/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)};
///
/// // Now we can practically handle it just as we would handle the Public itself:
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_public_data.randomize();
/// // A Public can be overwritten with zeros.
/// my_public_data.zeroize();
/// // If a Public is printed as Debug, its content is printed byte for byte.
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
/// ```
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct PublicBox<const N: usize> {
/// The inner [Box] around the [Public].
pub inner: Box<Public<N>>,
}
impl<const N: usize> PublicBox<N> {
/// Create a new [PublicBox] from a byte slice
/// 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
/// 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]
/// Create a zero initialized [PublicBox].
pub fn zero() -> Self {
Self {
inner: Box::new(Public::zero()),
}
}
/// Create a random initialized [PublicBox]
/// Create a random initialized [PublicBox].
pub fn random() -> Self {
Self {
inner: Box::new(Public::random()),
}
}
/// Randomize all bytes in an existing [PublicBox]
/// Randomize all bytes in an existing [PublicBox].
pub fn randomize(&mut self) {
self.inner.randomize()
}
}
impl<const N: usize> Randomize for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
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> {
// No extra documentation here because the Trait already provides a good documentation.
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&**self, fmt)
}
}
impl<const N: usize> Deref for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Target = [u8; N];
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &[u8; N] {
self.inner.deref()
}
}
impl<const N: usize> DerefMut for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
self.inner.deref_mut()
}
}
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
self.deref()
}
}
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
impl<const N: usize> LoadValue for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
// No extra documentation here because the Trait already provides a good documentation.
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())?;
@@ -263,22 +321,26 @@ impl<const N: usize> LoadValue for PublicBox<N> {
}
impl<const N: usize> StoreValue for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store(path)
}
}
impl<const N: usize> LoadValueB64 for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
// This is implemented separately from Public to avoid allocating too much stack memory.
// No extra documentation here because the Trait already provides a good documentation.
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
// 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();
@@ -295,16 +357,20 @@ impl<const N: usize> LoadValueB64 for PublicBox<N> {
}
impl<const N: usize> StoreValueB64 for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
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> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
writer: W,
@@ -320,6 +386,7 @@ mod tests {
#[allow(clippy::module_inception)]
mod tests {
use crate::{Public, PublicBox};
use rand::Fill;
use rosenpass_util::{
b64::b64_encode,
file::{
@@ -449,5 +516,112 @@ mod tests {
fn test_public_box_load_store_base64() {
run_base64_load_store_test::<PublicBox<N>>();
}
/// Test the debug print function for [Public]
#[test]
fn test_debug_public() {
let p: Public<32> = Public::zero();
let _ = format!("{:?}", p);
}
/// Test that [Public] is correctly borrowed to a u8 array.
#[test]
fn test_borrow_public_sized() {
let p: Public<32> = Public::zero();
let borrowed: &[u8; 32] = &p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 array.
#[test]
fn test_borrow_public_sized_mut() {
let mut p: Public<32> = Public::zero();
let borrowed: &mut [u8; 32] = &mut p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a u8 slice.
#[test]
fn test_borrow_public_unsized() {
use std::borrow::Borrow;
let p: Public<32> = Public::zero();
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
#[test]
fn test_borrow_public_unsized_mut() {
use std::borrow::BorrowMut;
let mut p: Public<32> = Public::zero();
let borrowed: &mut [u8] = p.borrow_mut();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly created from a slice.
#[test]
fn test_public_box_from_slice() {
let my_slice: &[u8; 32] = &[0; 32];
let p: PublicBox<32> = PublicBox::from_slice(my_slice);
assert_eq!(p.deref(), my_slice);
}
/// Test that [PublicBox] can correctly be created with its [PublicBox::new] function.
#[test]
fn test_public_box_new() {
let pb = PublicBox::new([42; 32]);
assert_eq!(pb.deref(), &[42; 32]);
}
/// Test the randomize functionality of [PublicBox].
#[test]
fn test_public_box_randomize() {
let mut pb: PublicBox<32> = PublicBox::zero();
pb.randomize();
pb.try_fill(&mut crate::rand::rng()).unwrap();
// Can't really assert anything here until we have can predict the randomness
// by derandomizing the RNG for tests.
}
/// Test the [Debug] print of [PublicBox]
#[test]
fn test_public_box_debug() {
let pb: PublicBox<32> = PublicBox::new([42; 32]);
let _ = format!("{:?}", pb);
}
/// Test that [PublicBox] is correctly borrowed to a u8 array.
#[test]
fn test_borrow_public_box_sized() {
let p: PublicBox<32> = PublicBox::zero();
let borrowed: &[u8; 32] = &p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly borrowed to a mutable u8 array.
#[test]
fn test_borrow_public_box_sized_mut() {
let mut p: PublicBox<32> = PublicBox::zero();
let borrowed: &mut [u8; 32] = &mut p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly borrowed to a u8 slice.
#[test]
fn test_borrow_public_box_unsized() {
use std::borrow::Borrow;
let p: PublicBox<32> = PublicBox::zero();
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
#[test]
fn test_borrow_public_box_unsized_mut() {
use std::borrow::BorrowMut;
let mut p: PublicBox<32> = PublicBox::zero();
let borrowed: &mut [u8] = p.borrow_mut();
assert_eq!(borrowed, &[0; 32]);
}
}
}

View File

@@ -1,5 +1,9 @@
//! This module provides functionality for generating random numbers using the [rand] crate.
/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate.
pub type Rng = rand::rngs::ThreadRng;
/// Get the default [Rng].
pub fn rng() -> Rng {
rand::thread_rng()
}

View File

@@ -27,6 +27,7 @@ thread_local! {
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
}
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
where
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
@@ -47,37 +48,47 @@ where
.unwrap_or_else(|| f(None))
}
// Wrapper around SecretBox that applies automatic zeroization
/// Wrapper around SecretBox that applies automatic zeroization.
#[derive(Debug)]
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
impl<T: Zeroize> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement
/// [Zeroize].
fn new(boxed: T) -> Self {
ZeroizingSecretBox(Some(secret_box(boxed)))
}
}
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] from a [SecretBox] for the type `T`,
/// which must implement [Zeroize] but does not have to be [Sized].
fn from_secret_box(inner: SecretBox<T>) -> Self {
Self(Some(inner))
}
/// Consumes the [ZeroizingSecretBox] and returns the content in a [SecretBox] for the type `T`,
/// which must implement [Zeroize] but does not have to be [Sized].
fn take(mut self) -> SecretBox<T> {
self.0.take().unwrap()
}
}
// Indicate that a Secret is always zeroized when it is dropped.
impl<T: Zeroize + ?Sized> ZeroizeOnDrop for ZeroizingSecretBox<T> {}
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
fn zeroize(&mut self) {
if let Some(inner) = &mut self.0 {
let inner: &mut SecretBox<T> = inner; // type annotation
let inner: &mut SecretBox<T> = inner; // Type annotation.
inner.zeroize()
}
}
}
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
/// Releases the memory of this [ZeroizingSecretBox]. In contrast to usual implementations
/// of [Drop], we zeroize the memory before dropping it. This fulfills the promises we make
/// by implementing [ZeroizeOnDrop].
fn drop(&mut self) {
self.zeroize()
}
@@ -86,29 +97,32 @@ impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
type Target = T;
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &T {
self.0.as_ref().unwrap()
}
}
impl<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut T {
self.0.as_mut().unwrap()
}
}
/// Pool that stores secret memory allocations
/// Pool that stores secret memory allocations.
///
/// Allocation of secret memory is expensive. Thus, this struct provides a
/// pool of secret memory, readily available to yield protected, slices of
/// memory.
#[derive(Debug)] // TODO check on Debug derive, is that clever
struct SecretMemoryPool {
/// A pool to reuse secret memory
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
}
impl SecretMemoryPool {
/// Create a new [SecretMemoryPool]
/// Create a new [SecretMemoryPool].
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
@@ -116,7 +130,7 @@ impl SecretMemoryPool {
}
}
/// Return secret back to the pool for future re-use
/// Return secret back to the pool for future re-use.
pub fn release<const N: usize>(&mut self, mut sec: ZeroizingSecretBox<[u8; N]>) {
sec.zeroize();
@@ -134,7 +148,7 @@ impl SecretMemoryPool {
/// Take protected memory from the pool, allocating new one if no suitable
/// chunk is found in the inventory.
///
/// The secret is guaranteed to be full of nullbytes
/// The secret is guaranteed to be full of nullbytes.
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
let entry = self.pool.entry(N).or_default();
let inner = match entry.pop() {
@@ -145,22 +159,50 @@ impl SecretMemoryPool {
}
}
/// Storage for secret data
/// [Secret] stores its memory in a way that makes it difficult for threat actors to access it
/// without permission. This is achieved through the following mechanisms:
/// 1. Data in a [Secret] is stored in memory that is allocated and managed by memory allocators
/// that make it more difficult for threat actors to access the memory. Specifically, the
/// allocators from [memsec] are supported.
/// 2. Memory that is allocated for a [Secret] is zeroized before it is used for anything else.
///
/// In order to use a [Secret], we have to decide on the secure allocator to use beforehand
/// calling either
/// [secret_policy_use_only_malloc_secrets](crate::secret_policy_use_only_malloc_secrets),
/// [secret_policy_use_only_memfd_secrets](crate::secret_policy_use_only_memfd_secrets)
/// or [secret_policy_try_use_memfd_secrets](crate::secret_policy_try_use_memfd_secrets).
///
/// You can use a [Secret] as follows:
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
///
/// // We have to define the security policy before using Secrets.
/// secret_policy_use_only_malloc_secrets();
/// let mut my_secret: Secret<32> = Secret::zero();
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_secret.randomize();
/// // In case I accidentally print my secret in a debug, it's still not leaked:
/// assert_eq!(format!("{:?}", my_secret), "<SECRET>");
/// // If you need to, you can zeroize a [Secret] at any time it's necessary:
/// my_secret.zeroize();
/// ```
pub struct Secret<const N: usize> {
storage: Option<ZeroizingSecretBox<[u8; N]>>,
}
impl<const N: usize> Secret<N> {
/// Create a new [Secret] from a byte-slice.
pub fn from_slice(slice: &[u8]) -> Self {
let mut new_self = Self::zero();
new_self.secret_mut().copy_from_slice(slice);
new_self
}
/// Returns a new [Secret] that is zero initialized
/// Returns a new [Secret] that is zero initialized.
pub fn zero() -> Self {
// Using [SecretMemoryPool] here because this operation is expensive,
// yet it is used in hot loops
// yet it is used in hot loops.
let buf = with_secret_memory_pool(|pool| {
pool.map(|p| p.take())
.unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N]))
@@ -169,28 +211,30 @@ impl<const N: usize> Secret<N> {
Self { storage: Some(buf) }
}
/// Returns a new [Secret] that is randomized
/// Returns a new [Secret] that is randomized.
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Sets all data an existing secret to random bytes
/// Sets all data of an existing [Secret] to random bytes.
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
/// Borrows the data
/// Borrows the data.
pub fn secret(&self) -> &[u8; N] {
self.storage.as_ref().unwrap()
}
/// Borrows the data mutably
/// Borrows the data mutably.
pub fn secret_mut(&mut self) -> &mut [u8; N] {
self.storage.as_mut().unwrap()
}
}
impl<const N: usize> Randomize for Secret<N> {
/// Tries to fill this [Secret] with random data. The [Secret] is first zeroized
/// to make sure that the barriers from the zeroize crate take effect.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
// Zeroize self first just to make sure the barriers from the zeroize create take
// effect to prevent the compiler from optimizing this away.
@@ -200,8 +244,10 @@ impl<const N: usize> Randomize for Secret<N> {
}
}
// Indicate that a [Secret] is always zeroized when it is dropped.
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
impl<const N: usize> Zeroize for Secret<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn zeroize(&mut self) {
if let Some(inner) = &mut self.storage {
inner.zeroize()
@@ -209,7 +255,14 @@ impl<const N: usize> Zeroize for Secret<N> {
}
}
// Our implementation of Drop gives back the allocated secret memory to the secret memory pool.
impl<const N: usize> Drop for Secret<N> {
/// Release the memory of this [Secret]. In contrast to usual implementations to [Drop] we
/// do the following:
/// 1. The memory of this [Secret] gets zeroized as required by [ZeroizeOnDrop].
/// 2. The memory is returned to a memory pool of specially secure memory to be reused.
///
/// This behaviour fulfills the promises we make by implementing [ZeroizeOnDrop].
fn drop(&mut self) {
with_secret_memory_pool(|pool| {
if let Some((pool, secret)) = pool.zip(self.storage.take()) {
@@ -240,6 +293,7 @@ impl<const N: usize> fmt::Debug for Secret<N> {
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
@@ -253,6 +307,7 @@ impl<const N: usize> LoadValue for Secret<N> {
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut f: Secret<F> = Secret::random();
let mut v = Self::random();
@@ -272,6 +327,7 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
impl<const N: usize> StoreValueB64 for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
@@ -291,6 +347,7 @@ impl<const N: usize> StoreValueB64 for Secret<N> {
impl<const N: usize> StoreValueB64Writer for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
let mut f: Secret<F> = Secret::random();
let encoded_str = b64_encode(self.secret(), f.secret_mut())
@@ -307,11 +364,13 @@ impl<const N: usize> StoreValueB64Writer for Secret<N> {
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
fopen_w(path, Visibility::Secret)?.write_all(self.secret())?;
Ok(())
}
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
fopen_w(path, Visibility::Public)?.write_all(self.secret())?;
Ok(())
@@ -320,7 +379,10 @@ impl<const N: usize> StoreSecret for Secret<N> {
#[cfg(test)]
mod test {
use crate::test_spawn_process_provided_policies;
use crate::{
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
test_spawn_process_provided_policies,
};
use super::*;
use std::{fs, os::unix::fs::PermissionsExt};
@@ -328,7 +390,7 @@ mod test {
procspawn::enable_test_support!();
/// check that we can alloc using the magic pool
/// Check that we can alloc using the magic pool.
#[test]
fn secret_memory_pool_take() {
test_spawn_process_provided_policies!({
@@ -339,7 +401,7 @@ mod test {
});
}
/// check that a secret lives, even if its [SecretMemoryPool] is deleted
/// Check that a secret lives, even if its [SecretMemoryPool] is deleted.
#[test]
fn secret_memory_pool_drop() {
test_spawn_process_provided_policies!({
@@ -351,7 +413,7 @@ mod test {
});
}
/// check that a secret can be reborn, freshly initialized with zero
/// Check that a secret can be reborn, freshly initialized with zero.
#[test]
fn secret_memory_pool_release() {
test_spawn_process_provided_policies!({
@@ -372,7 +434,7 @@ mod test {
});
}
/// test loading a secret from an example file, and then storing it again in a different file
/// Test loading a secret from an example file, and then storing it again in a different file.
#[test]
fn test_secret_load_store() {
test_spawn_process_provided_policies!({
@@ -409,7 +471,8 @@ mod test {
});
}
/// test loading a base64 encoded secret from an example file, and then storing it again in a different file
/// Test loading a base64 encoded secret from an example file, and then storing it again in
/// a different file.
#[test]
fn test_secret_load_store_base64() {
test_spawn_process_provided_policies!({
@@ -463,4 +526,33 @@ mod test {
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
});
}
/// Test the creation of a [ZeroizingSecretBox] using its [new](ZeroizingSecretBox::new)
/// function.
#[test]
fn test_zeroizing_secret_box_new() {
struct DummyZeroizing {
inner_dummy: [u8; 32],
}
impl Zeroize for DummyZeroizing {
fn zeroize(&mut self) {
self.inner_dummy = [0; 32];
}
}
let dummy = DummyZeroizing {
inner_dummy: [42; 32],
};
secret_policy_use_only_malloc_secrets();
let mut zsb: ZeroizingSecretBox<DummyZeroizing> = ZeroizingSecretBox::new(dummy);
zsb.zeroize();
assert_eq!(zsb.inner_dummy, [0; 32]);
}
/// Test the debug print of [Secret].
#[test]
fn test_debug_secret() {
secret_policy_use_only_malloc_secrets();
let my_secret: Secret<32> = Secret::zero();
assert_eq!(format!("{:?}", my_secret), "<SECRET>");
}
}

View File

@@ -17,13 +17,14 @@ use rosenpass_to::{to, with_destination, To};
use std::ops::BitXorAssign;
// Destination functions return some value that implements the To trait.
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
// Unfortunately dealing with lifetimes is a bit more finicky than it would
// be without destination parameters
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where
T: BitXorAssign + Clone,
{
// Custom implementations of the to trait can be created, but the easiest
// way to create them is to use the provided helper functions like with_destination.
with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) {

View File

@@ -1,4 +1,5 @@
//! Functions with destination copying data between slices and arrays.
//! Functions that make it easy to copy data between arrays and slices using functions with
//! destinations. See the specific functions for examples and more explanations.
use crate::{with_destination, To};
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
/// # Panics
///
/// This function will panic if the two slices have different lengths.
///
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::copy_slice;
/// let to_function = copy_slice(&[0; 16]);
/// let mut dst = [255; 16];
/// to_function.to(&mut dst);
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
where
T: Copy,
@@ -23,6 +35,19 @@ where
/// # Panics
///
/// This function will panic if destination is shorter than origin.
///
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::copy_slice_least_src;
/// let to_function = copy_slice_least_src(&[0; 16]);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation the first half of `dst` will hold the same data as the original slice.
/// assert!(dst[0..16].iter().all(|b| *b == 0));
/// // The second half will have remained the same
/// assert!(dst[16..32].iter().all(|b| *b == 255));
/// ```
pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
where
T: Copy,
@@ -34,6 +59,18 @@ where
/// destination.
///
/// Copies as much data as is present in the shorter slice.
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::copy_slice_least;
/// let to_function = copy_slice_least(&[0; 16]);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation the first half of `dst` will hold the same data as the original slice.
/// assert!(dst[0..16].iter().all(|b| *b == 0));
/// // The second half will have remained the same.
/// assert!(dst[16..32].iter().all(|b| *b == 255));
/// ```
pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
where
T: Copy,
@@ -47,6 +84,24 @@ where
/// Function with destination that attempts to copy data from origin into the destination.
///
/// Will return None if the slices are of different lengths.
/// # Example
/// ```
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::try_copy_slice;
/// let to_function = try_copy_slice(&[0; 16]);
/// let mut dst = [255; 32];
/// let result = to_function.to(&mut dst);
/// // This will return None because the slices do not have the same length.
/// assert!(result.is_none());
///
/// let to_function = try_copy_slice(&[0; 16]);
/// let mut dst = [255; 16];
/// let result = to_function.to(&mut dst);
/// // This time it works:
/// assert!(result.is_some());
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where
T: Copy,
@@ -62,6 +117,26 @@ where
/// Destination may be longer than origin.
///
/// Will return None if the destination is shorter than origin.
/// # Example
/// ```rust
/// use rosenpass_to::To;
/// # use crate::rosenpass_to::ops::try_copy_slice_least_src;
/// let to_function = try_copy_slice_least_src(&[0; 16]);
/// let mut dst = [255; 15];
/// let result = to_function.to(&mut dst);
/// // This will return None because the destination is to short.
/// assert!(result.is_none());
///
/// let to_function = try_copy_slice_least_src(&[0; 16]);
/// let mut dst = [255; 32];
/// let result = to_function.to(&mut dst);
/// // This time it works:
/// assert!(result.is_some());
/// // After the operation, the first half of `dst` will hold the same data as the original slice.
/// assert!(dst[0..16].iter().all(|b| *b == 0));
/// // The second half will have remained the same.
/// assert!(dst[16..32].iter().all(|b| *b == 255));
/// ```
pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where
T: Copy,
@@ -72,6 +147,18 @@ where
}
/// Function with destination that copies all data between two array references.
///
/// # Example
/// ```rust
/// use rosenpass_to::ops::copy_array;
/// use rosenpass_to::To;
/// let my_arr: [u8; 32] = [0; 32];
/// let to_function = copy_array(&my_arr);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
where
T: Copy,

View File

@@ -1,6 +1,11 @@
//! This module provides the [Beside] struct. In the context of functions with targets,
//! [Beside] structures the destination value and the return value unmistakably and offers useful
//! helper functions to work with them.
use crate::CondenseBeside;
/// Named tuple holding the return value and the output from a function with destinations.
/// Named tuple holding the return value and the destination from a function with destinations.
/// See the respective functions for usage examples.
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
pub struct Beside<Val, Ret>(pub Val, pub Ret);
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
&mut self.1
}
/// Perform beside condensation. See [CondenseBeside]
/// Perform beside condensation. See [CondenseBeside] for more details.
///
/// # Example
/// ```
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
(val, ret)
}
}
#[cfg(test)]
mod tests {
use crate::Beside;
#[test]
fn from_tuple() {
let tuple = (21u8, 42u16);
let beside: Beside<u8, u16> = Beside::from(tuple);
assert_eq!(beside.dest(), &21u8);
assert_eq!(beside.ret(), &42u16);
}
#[test]
fn from_beside() {
let beside: Beside<u8, u16> = Beside(21u8, 42u16);
type U8u16 = (u8, u16);
let tuple = U8u16::from(beside);
assert_eq!(tuple.0, 21u8);
assert_eq!(tuple.1, 42u16);
}
}

View File

@@ -1,4 +1,11 @@
/// Beside condensation.
//! This module provides condensation for values that stand side by side,
//! which is often useful when working with destination parameters. See [CondenseBeside]
//! for more details.
/// Condenses two values that stand beside each other into one value.
/// For example, a blanked implementation for [Result<(), Error>](Result) is provided. If
/// `condense(val)` is called on such an object, a [Result<Val, Error>](Result) will
/// be returned, if `val` is of type `Val`.
///
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
@@ -6,6 +13,19 @@
///
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
/// condense trait.
///
/// # Example
/// As an example implementation, we take a look at the blanket implementation for [Option]
/// ```ignore
/// impl<Val> CondenseBeside<Val> for Option<()> {
/// type Condensed = Option<Val>;
///
/// /// Replaces the empty tuple inside this [Option] with `ret`.
/// fn condense(self, ret: Val) -> Option<Val> {
/// self.map(|()| ret)
/// }
/// }
/// ```
pub trait CondenseBeside<Val> {
/// The type that results from condensation.
type Condensed;
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
impl<Val> CondenseBeside<Val> for () {
type Condensed = Val;
/// Replaces this empty tuple with `ret`.
fn condense(self, ret: Val) -> Val {
ret
}
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
type Condensed = Result<Val, Error>;
/// Replaces the empty tuple inside this [Result] with `ret`.
fn condense(self, ret: Val) -> Result<Val, Error> {
self.map(|()| ret)
}
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
impl<Val> CondenseBeside<Val> for Option<()> {
type Condensed = Option<Val>;
/// Replaces the empty tuple inside this [Option] with `ret`.
fn condense(self, ret: Val) -> Option<Val> {
self.map(|()| ret)
}

View File

@@ -1,5 +1,28 @@
/// Helper performing explicit unsized coercion.
/// Used by the [to](crate::to()) function.
//! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
//! types. See [DstCoercion] for more details.
/// Helper Trait for performing explicit coercion from [Sized] types to
/// [?Sized][core::marker::Sized] types. It's used by the [to](crate::to()) function.
///
/// We provide blanket implementations for any [Sized] type and for any array of [Sized] types.
///
/// # Example
/// It can be used as follows:
/// ```
/// # use rosenpass_to::DstCoercion;
/// // Consider a sized type like this example:
/// struct SizedStruct {
/// x: u32
/// }
/// // Then we can coerce it to be unsized:
/// let mut sized = SizedStruct { x: 42 };
/// assert_eq!(42, sized.coerce_dest().x);
///
/// // Analogously, we can coerce arrays to slices:
/// let mut sized_array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
/// let un_sized: &[i32] = sized_array.coerce_dest();
/// assert_eq!(un_sized, un_sized);
/// ```
pub trait DstCoercion<Dst: ?Sized> {
/// Performs an explicit coercion to the destination type.
fn coerce_dest(&mut self) -> &mut Dst;

View File

@@ -6,11 +6,16 @@
//! - `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
//! 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`).
//! - `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`).
pub mod beside;
pub mod condense;

View File

@@ -1,9 +1,23 @@
//! This module provides the [To::to] function which allows to use functions with destination in
//! a manner akin to that of a variable assignment. See [To::to] for more details.
use crate::{DstCoercion, To};
/// Alias for [To::to] moving the destination to the left.
///
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
/// the variable to assign to on the left and the generating function on the right.
///
/// # Example
/// ```rust
/// // Using the to function to have data flowing from the right to the left,
/// // performing something akin to a variable assignment.
/// use rosenpass_to::ops::copy_slice_least;
/// # use rosenpass_to::to;
/// let mut dst = b" ".to_vec();
/// to(&mut dst[..], copy_slice_least(b"Hello World"));
/// assert_eq!(&dst[..], b"Hello World");
/// ```
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
where
Coercable: ?Sized + DstCoercion<Dst>,

View File

@@ -1,12 +1,46 @@
//! Module that contains the [To] crate which is the container used to
//! implement the core functionality of this crate.
use crate::{Beside, CondenseBeside};
use std::borrow::BorrowMut;
/// The To trait is the core of the to crate; most functions with destinations will either return
/// an object that is an instance of this trait or they will return `-> impl To<Destination,
/// Return_value`.
/// an object that is an instance of this trait, or they will return `-> impl To<Destination,
/// Return_value>`.
///
/// A quick way to implement a function with destination is to use the
/// [with_destination(|param: &mut Type| ...)] higher order function.
/// [with_destination(|param: &mut Type| ...)](crate::with_destination) higher order function.
///
/// # Example
/// Below, we provide a very simple example for how the Trait can be implemented. More examples for
/// how this Trait is best implemented can be found in the overall [crate documentation](crate).
/// ```
/// use rosenpass_to::To;
///
/// // This is a simple wrapper around a String that can be written into a byte array using to.
/// struct StringToBytes {
/// inner: String
/// }
///
/// impl To<[u8], Result<(), String>> for StringToBytes {
/// fn to(self, out: &mut [u8]) -> Result<(), String> {
/// let bytes = self.inner.as_bytes();
/// if bytes.len() > out.len() {
/// return Err("out is to short".to_string());
/// }
/// for i in 0..bytes.len() {
/// (*out)[i] = bytes[i];
/// }
/// Ok(())
/// }
/// }
///
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
/// let mut buffer: [u8; 10] = [0; 10];
/// let result = string_to_bytes.to(&mut buffer);
/// assert_eq!(buffer, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
/// assert!(result.is_ok());
/// ```
pub trait To<Dst: ?Sized, Ret>: Sized {
/// Writes self to the destination `out` and returns a value of type `Ret`.
///
@@ -19,6 +53,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
///
/// # Example
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
/// [self.to_this_beside]. We refer to the overall [crate documentation](crate)
/// for more examples and general explanations.
/// ```
/// # use rosenpass_to::To;
/// use rosenpass_to::Beside;
/// # struct StringToBytes {
/// # inner: String
/// # }
///
/// # impl To<[u8], Result<(), String>> for StringToBytes {
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
/// # let bytes = self.inner.as_bytes();
/// # if bytes.len() > out.len() {
/// # return Err("out is to short".to_string());
/// # }
/// # for i in 0..bytes.len() {
/// # (*out)[i] = bytes[i];
/// # }
/// # Ok(())
/// # }
/// # }
/// // StringToBytes is taken from the overall Trait example.
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
/// let Beside(dst, result) = string_to_bytes.to_this_beside(|| [0; 10]);
/// assert_eq!(dst, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
/// assert!(result.is_ok());
/// ```
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
where
Val: BorrowMut<Dst>,
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// Generate a destination on the fly using default.
///
/// Uses [Default] to create a value,
/// calls [crate::to()] to evaluate the function and finally
/// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
///
/// # Example
/// Below, we provide a simple example for the usage of [to_value_beside](To::to_value_beside).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
/// let Beside(dst, ret) = copy_array(&[42u8; 16]).to_value_beside();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn to_value_beside(self) -> Beside<Dst, Ret>
where
Dst: Sized + Default,
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// when the Destination is unsized.
///
/// This could be the case when the destination is an `[u8]` for instance.
///
/// # Example
/// Below, we provide a simple example for the usage of [collect_beside](To::collect_beside).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
///
/// let Beside(dst, ret) = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn collect_beside<Val>(self) -> Beside<Val, Ret>
where
Val: Default + BorrowMut<Dst>,
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
/// # Example
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
/// [Self::to_this]. We refer to the overall [crate documentation](crate)
/// for more examples and general explanations.
/// ```
/// # use rosenpass_to::To;
/// use rosenpass_to::Beside;
/// # struct StringToBytes {
/// # inner: String
/// # }
///
/// # impl To<[u8], Result<(), String>> for StringToBytes {
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
/// # let bytes = self.inner.as_bytes();
/// # if bytes.len() > out.len() {
/// # return Err("out is to short".to_string());
/// # }
/// # for i in 0..bytes.len() {
/// # (*out)[i] = bytes[i];
/// # }
/// # Ok(())
/// # }
/// # }
/// // StringToBytes is taken from the overall Trait example.
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
/// let result = string_to_bytes.to_this_beside(|| [0; 10]).condense();
/// assert!(result.is_ok());
/// assert_eq!(result.unwrap(), [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
///
/// ```
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
where
Ret: CondenseBeside<Val>,
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
///
/// # Example
/// Below, we provide a simple example for the usage of [to_value](To::to_value).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
/// let dst = copy_array(&[42u8; 16]).to_value_beside().condense();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
where
Dst: Sized + Default,
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
///
/// # Example
/// Below, we provide a simple example for the usage of [collect](To::collect).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
///
/// let dst = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>().condense();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
where
Val: Default + BorrowMut<Dst>,

View File

@@ -1,15 +1,19 @@
//! The module provides the [with_destination] function, which makes it easy to create
//! a [To] from a lambda function. See [with_destination] and the [crate documentation](crate)
//! for more details and examples.
use crate::To;
use std::marker::PhantomData;
/// A struct that wraps a closure and implements the `To` trait
/// A struct that wraps a closure and implements the `To` trait.
///
/// This allows passing closures that operate on a destination type `Dst`
/// and return `Ret`.
/// and return `Ret`. It is only internally used to implement [with_destination].
///
/// # Type Parameters
/// * `Dst` - The destination type the closure operates on
/// * `Ret` - The return type of the closure
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
/// * `Dst` - The destination type the closure operates on.
/// * `Ret` - The return type of the closure.
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`.
struct ToClosure<Dst, Ret, Fun>
where
Dst: ?Sized,
@@ -17,11 +21,11 @@ where
{
/// The function to call.
fun: Fun,
/// Phantom data to hold the destination type
/// Phantom data to hold the destination type.
_val: PhantomData<Box<Dst>>,
}
/// Implementation of the `To` trait for ToClosure
/// Implementation of the `To` trait for ToClosure.
///
/// This enables calling the wrapped closure with a destination reference.
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
@@ -33,6 +37,7 @@ where
///
/// # Arguments
/// * `out` - Mutable reference to the destination
/// See the tutorial in [readme.md] for examples and more explanations.
fn to(self, out: &mut Dst) -> Ret {
(self.fun)(out)
}
@@ -48,7 +53,24 @@ where
/// * `Ret` - The return type of the closure
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
///
/// See the tutorial in [readme.me]..
/// See the tutorial in the [crate documentation](crate) for more examples and more explanations.
/// # Example
/// ```
/// # use rosenpass_to::with_destination;
/// use crate::rosenpass_to::To;
/// let my_origin_data: [u8; 16]= [2; 16];
/// let times_two = with_destination( move |dst: &mut [u8; 16]| {
/// for (dst, org) in dst.iter_mut().zip(my_origin_data.iter()) {
/// *dst = dst.clone() * org;
/// }
/// });
/// let mut dst: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// times_two.to(&mut dst);
/// for i in 0..16 {
/// assert_eq!(dst[i], (2 * i) as u8);
/// }
///
/// ```
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
where
Dst: ?Sized,

View File

@@ -78,8 +78,8 @@ pub trait Build<T>: Sized {
/// A type that can be incrementally built from a type that can [Build] it
///
/// This is similar to an option, where [Self::Void] is [std::Option::None],
/// [Self::Product] is [std::Option::Some], except that there is a third
/// This is similar to an option, where [Self::Void] is [std::option::Option::None],
/// [Self::Product] is [std::option::Option::Some], except that there is a third
/// intermediate state [Self::Builder] that represents a Some/Product value
/// in the process of being made.
///
@@ -508,9 +508,9 @@ where
matches!(self, Self::Void)
}
/// Returns `true` if the construction site is [`InProgress`].
/// Returns `true` if the construction site is in the [`Builder`] phase.
///
/// [`InProgress`]: ConstructionSite::InProgress
/// [`Builder`]: ConstructionSite::Builder
///
/// # Examples
///
@@ -541,9 +541,10 @@ where
matches!(self, Self::Builder(..))
}
/// Returns `true` if the construction site is [`Done`].
/// Returns `true` if the construction site is in the [`Product`] phase and
/// is therefore done.
///
/// [`Done`]: ConstructionSite::Done
/// [`Product`]: ConstructionSite::Product
///
/// # Examples
///

View File

@@ -1,4 +1,4 @@
/// A collection of control flow utility macros
//! A collection of control flow utility macros
#[macro_export]
/// A simple for loop to repeat a $body a number of times
@@ -33,7 +33,7 @@ macro_rules! repeat {
/// 0
/// }
/// assert_eq!(test_fn(), 0);
///
/// fn test_fn2() -> i32 {
/// return_unless!(false, 1);
/// 0
@@ -65,7 +65,7 @@ macro_rules! return_unless {
/// 0
/// }
/// assert_eq!(test_fn(), 1);
///
/// fn test_fn2() -> i32 {
/// return_if!(false, 1);
/// 0
@@ -98,7 +98,7 @@ macro_rules! return_if {
/// sum += 1;
/// }
/// assert_eq!(sum, 5);
///
/// let mut sum = 0;
/// 'one: for _ in 0..10 {
/// for j in 0..20 {
@@ -134,7 +134,7 @@ macro_rules! break_if {
/// sum += 1;
/// }
/// assert_eq!(sum, 9);
///
/// let mut sum = 0;
/// 'one: for i in 0..10 {
/// continue_if!(i == 5, 'one);

View File

@@ -89,6 +89,30 @@ pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
///
/// Will panic if the given file descriptor is negative of or larger than
/// the file descriptor numbers permitted by the operating system.
///
/// # Example
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # use std::os::unix::io::{AsRawFd, FromRawFd};
/// # use std::os::fd::IntoRawFd;
/// # use rustix::fd::AsFd;
/// # use rosenpass_util::fd::mask_fd;
///
/// // Open a temporary file
/// let fd = tempfile::tempfile().unwrap().into_raw_fd();
/// assert!(fd >= 0);
///
/// // Mask the file descriptor
/// mask_fd(fd).unwrap();
///
/// // Verify the file descriptor now points to `/dev/null`
/// // Reading from `/dev/null` always returns 0 bytes
/// let mut replaced_file = unsafe { File::from_raw_fd(fd) };
/// let mut buffer = [0u8; 4];
/// let bytes_read = replaced_file.read(&mut buffer).unwrap();
/// assert_eq!(bytes_read, 0);
/// ```
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
@@ -286,14 +310,14 @@ where
}
}
/// Distinguish different socket address familys; e.g. IP and unix sockets
/// Distinguish different socket address families; e.g. IP and unix sockets
#[cfg(target_os = "linux")]
pub trait GetSocketDomain {
/// Error type returned by operations in this trait
type Error;
/// Retrieve the socket domain (address family)
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>;
/// Alias for [socket_domain]
/// Alias for [Self::socket_domain]
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
self.socket_domain()
}
@@ -320,9 +344,67 @@ where
pub trait GetUnixSocketType {
/// Error type returned by operations in this trait
type Error;
/// Check if the socket is a unix stream socket
/// Checks whether the socket is a Unix stream socket.
///
/// # Returns
/// - `Ok(true)` if the socket is a Unix stream socket.
/// - `Ok(false)` if the socket is not a Unix stream socket.
/// - `Err(Self::Error)` if there is an error while performing the check.
///
/// # Examples
/// ```
/// # use std::fs::File;
/// # use std::os::fd::{AsFd, BorrowedFd};
/// # use std::os::unix::net::UnixListener;
/// # use tempfile::NamedTempFile;
/// # use rosenpass_util::fd::GetUnixSocketType;
/// let f = {
/// // Generate a temp file and take its path
/// // Remove the temp file
/// // Create a unix socket on the temp path that is not unused
/// let temp_file = NamedTempFile::new().unwrap();
/// let socket_path = temp_file.path().to_owned();
/// std::fs::remove_file(&socket_path).unwrap();
/// UnixListener::bind(socket_path).unwrap()
/// };
/// assert!(matches!(f.as_fd().is_unix_stream_socket(), Ok(true)));
/// ```
fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
/// Returns Ok(()) only if the underlying socket is a unix stream socket
/// # Examples
/// ```
/// # use std::fs::File;
/// # use std::os::fd::{AsFd, BorrowedFd};
/// # use std::os::unix::net::{UnixDatagram, UnixListener};
/// # use tempfile::NamedTempFile;
/// # use rosenpass_util::fd::GetUnixSocketType;
/// let f = {
/// // Generate a temp file and take its path
/// // Remove the temp file
/// // Create a unix socket on the temp path that is not unused
/// let temp_file = NamedTempFile::new().unwrap();
/// let socket_path = temp_file.path().to_owned();
/// std::fs::remove_file(&socket_path).unwrap();
/// UnixListener::bind(socket_path).unwrap()
/// };
/// assert!(matches!(f.as_fd().demand_unix_stream_socket(), Ok(())));
/// // Error if the FD is a file
/// let temp_file = NamedTempFile::new().unwrap();
/// assert_eq!(temp_file.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
/// "Socket operation on non-socket (os error 88)"
/// );
/// // Error if the FD is a Unix stream with a wrong mode (e.g. Datagram)
/// let f = {
/// let temp_file = NamedTempFile::new().unwrap();
/// let socket_path = temp_file.path().to_owned();
/// std::fs::remove_file(&socket_path).unwrap();
/// UnixDatagram::bind(socket_path).unwrap()
/// };
/// assert_eq!(f.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
/// "Expected unix socket in stream mode, but mode is SocketType(2)"
/// );
/// ```
fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
}
@@ -352,16 +434,65 @@ where
#[cfg(target_os = "linux")]
/// Distinguish between different network socket protocols (e.g. tcp, udp)
pub trait GetSocketProtocol {
/// Retrieve the socket protocol
/// Retrieves the socket's protocol.
///
/// # Returns
/// - `Ok(Some(Protocol))`: The protocol of the socket if available.
/// - `Ok(None)`: If the protocol information is unavailable.
/// - `Err(rustix::io::Errno)`: If an error occurs while retrieving the protocol.
///
/// # Examples
/// ```
/// # use std::net::UdpSocket;
/// # use std::os::fd::{AsFd, AsRawFd};
/// # use rosenpass_util::fd::GetSocketProtocol;
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
/// assert_eq!(socket.as_fd().socket_protocol().unwrap().unwrap(), rustix::net::ipproto::UDP);
/// # Ok::<(), std::io::Error>(())
/// ```
fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
/// Check if the socket is a udp socket
///
/// # Examples
/// ```
/// # use std::net::UdpSocket;
/// # use std::net::TcpListener;
/// # use std::os::fd::{AsFd, AsRawFd};
/// # use rosenpass_util::fd::GetSocketProtocol;
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
/// assert!(socket.as_fd().is_udp_socket().unwrap());
///
/// let socket = TcpListener::bind("127.0.0.1:0")?;
/// assert!(!socket.as_fd().is_udp_socket().unwrap());
/// # Ok::<(), std::io::Error>(())
/// ```
fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
self.socket_protocol()?
.map(|p| p == rustix::net::ipproto::UDP)
.unwrap_or(false)
.ok()
}
/// Return Ok(()) only if the socket is a udp socket
/// Ensures that the socket is a UDP socket, returning an error otherwise.
///
/// # Returns
/// - `Ok(())` if the socket is a UDP socket.
/// - `Err(anyhow::Error)` if the socket is not a UDP socket or if an error occurs retrieving the socket protocol.
///
/// # Examples
/// ```
/// # use std::net::UdpSocket;
/// # use std::net::TcpListener;
/// # use std::os::fd::{AsFd, AsRawFd};
/// # use rosenpass_util::fd::GetSocketProtocol;
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
/// assert!(matches!(socket.as_fd().demand_udp_socket(), Ok(())));
///
/// let socket = TcpListener::bind("127.0.0.1:0")?;
/// assert_eq!(socket.as_fd().demand_udp_socket().unwrap_err().to_string(),
/// "Not a udp socket, instead socket protocol is: Protocol(6)");
/// # Ok::<(), std::io::Error>(())
/// ```
fn demand_udp_socket(&self) -> anyhow::Result<()> {
match self.socket_protocol() {
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),

View File

@@ -244,7 +244,6 @@
use std::{borrow::Borrow, io};
use anyhow::ensure;
use zerocopy::AsBytes;
/// Generic trait for accessing [std::io::Error::kind]
///

View File

@@ -1,3 +1,12 @@
//! Utilities for encoding length-prefixed messages that can be transmitted via I/O streams.
//!
//! Messages are prefixed with an unsigned 64-bit little-endian length header, followed by the
//! message payload. Each [`LengthPrefixEncoder`] maintains internal buffers and additional state for as-yet
//! incomplete messages.
//!
//! It also performs sanity checks and handles typical error conditions that may be encountered
//! when writing structured data to an output sink (such as stdout or an active socket connection).
use std::{
borrow::{Borrow, BorrowMut},
cmp::min,
@@ -9,31 +18,32 @@ use zeroize::Zeroize;
use crate::{io::IoResultKindHintExt, result::ensure_or};
/// Size of the length prefix header in bytes - equal to the size of a u64
/// Size in bytes of the message header carrying length information.
/// Currently, HEADER_SIZE is always 8 bytes and encodes a 64-bit little-endian number.
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of buffer bounds")]
/// Error type indicating that a write position is beyond the boundaries of the allocated buffer
/// Error indicating that the given offset exceeds the bounds of the allocated buffer.
pub struct PositionOutOfBufferBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of message bounds")]
/// Error type indicating that a write position is beyond the boundaries of the message
/// Error indicating that the given offset exceeds the bounds of the message.
pub struct PositionOutOfMessageBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of header bounds")]
/// Error type indicating that a write position is beyond the boundaries of the header
/// Error indicating that the given offset exceeds the bounds of the header.
pub struct PositionOutOfHeaderBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Message length is bigger than buffer length")]
/// Error type indicating that the message length is larger than the available buffer space
/// Error indicating that the message size is larger than the available buffer space.
pub struct MessageTooLarge;
#[derive(Error, Debug, Clone, Copy)]
/// Error type for message length sanity checks
/// Error enum representing sanity check failures related to the message size.
pub enum MessageLenSanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")]
@@ -44,7 +54,7 @@ pub enum MessageLenSanityError {
}
#[derive(Error, Debug, Clone, Copy)]
/// Error type for position bounds checking
/// Error enum representing sanity check failures related to out-of-bounds memory access.
pub enum PositionSanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")]
@@ -55,7 +65,7 @@ pub enum PositionSanityError {
}
#[derive(Error, Debug, Clone, Copy)]
/// Error type combining all sanity check errors
/// Error enum representing sanity check failures of any kind.
pub enum SanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")]
@@ -101,7 +111,7 @@ impl From<PositionSanityError> for SanityError {
}
}
/// Result of a write operation on an IO stream
/// Return type for `WriteToIo` operations, containing the number of bytes written and a completion flag.
pub struct WriteToIoReturn {
/// Number of bytes successfully written in this operation
pub bytes_written: usize,
@@ -110,7 +120,55 @@ pub struct WriteToIoReturn {
}
#[derive(Clone, Copy, Debug)]
/// Length-prefixed encoder that adds a length header to data before writing
/// An encoder for length-prefixed messages.
///
/// # Examples
///
/// ## Writing to output streams
///
/// Simplified usage example:
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
/// use rosenpass_util::length_prefix_encoding::encoder::HEADER_SIZE;
///
/// let message = String::from("hello world");
/// let mut encoder = LengthPrefixEncoder::from_message(message.as_bytes());
///
/// let mut output = Vec::new();
/// encoder.write_all_to_stdio(&mut output).expect("failed to write_all");
///
/// assert_eq!(output.len(), message.len() + HEADER_SIZE);
///
/// let (header, body) = output.split_at(HEADER_SIZE);
/// let length = u64::from_le_bytes(header.try_into().unwrap());
///
/// assert_eq!(length as usize, message.len());
/// assert_eq!(body, message.as_bytes());
/// ```
///
/// For more examples, see also:
///
/// * [Self::write_all_to_stdio]
/// * [Self::write_to_stdio]
///
///
/// ## Basic error handling
///
/// Creating an encoder with invalid parameters triggers one of the various sanity checks:
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, MessageLenSanityError};
///
/// let message_size = 32;
/// let message = vec![0u8; message_size];
///
/// // The sanity check prevents an unsafe out-of-bounds access here
/// let err = LengthPrefixEncoder::from_short_message(message, 2 * message_size)
/// .expect_err("OOB access should fail");
/// assert!(matches!(err, MessageLenSanityError::MessageTooLarge(_)));
/// ```
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
buf: Buf,
header: [u8; HEADER_SIZE],
@@ -135,6 +193,10 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
/// Creates a new encoder using part of the buffer as a message
///
/// # Example
///
/// See [Basic error handling](#basic-error-handling)
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
let mut r = Self::from_message(msg);
r.set_message_len(len)?;
@@ -149,12 +211,39 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
/// Consumes the encoder and returns the underlying buffer
///
/// # Example
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
///
/// let msg = String::from("hello world");
/// let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
/// let msg_buffer = encoder.into_buffer();
/// assert_eq!(msg_buffer, msg.as_bytes());
/// ```
pub fn into_buffer(self) -> Buf {
let Self { buf, .. } = self;
buf
}
/// Consumes the encoder and returns buffer, message length and write position
///
/// # Example
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
///
/// let msg = String::from("hello world");
/// let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
/// assert!(encoder.encoded_message_bytes() > msg.len());
/// assert!(!encoder.exhausted());
///
/// let (msg_buffer, msg_length, write_offset) = encoder.into_parts();
/// assert_eq!(msg_buffer, msg.as_bytes());
/// assert_eq!(write_offset, 0);
/// assert_eq!(msg_length, msg.len());
/// ```
pub fn into_parts(self) -> (Buf, usize, usize) {
let len = self.message_len();
let pos = self.writing_position();
@@ -169,6 +258,25 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
/// Writes the full message to an IO writer, retrying on interrupts
///
/// # Example
///
/// ```rust
/// # use std::io::Cursor;
/// # use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, HEADER_SIZE};
/// let msg = String::from("message in a bottle");
/// let prefixed_msg_size = msg.len() + HEADER_SIZE;
///
/// let mut encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
///
/// // Fast-forward - behaves as if the HEADER had already been written; only the message remains
/// encoder
/// .set_header_offset(HEADER_SIZE)
/// .expect("failed to move cursor");
/// let mut sink = Cursor::new(vec![0; prefixed_msg_size + 1]);
/// encoder.write_all_to_stdio(&mut sink).expect("write failed");
/// assert_eq!(&sink.get_ref()[0..msg.len()], msg.as_bytes());
/// ```
pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
use io::ErrorKind as K;
loop {
@@ -185,7 +293,45 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
}
/// Writes the next chunk of data to an IO writer and returns number of bytes written and completion status
/// Attempts to write the next chunk of data to an IO writer, returning the number of bytes written and completion flag
///
/// # Example
///
/// ```rust
/// # use std::io::Cursor;
/// # use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, WriteToIoReturn, HEADER_SIZE};
/// let msg = String::from("Hello world");
/// let prefixed_msg_size = msg.len() + HEADER_SIZE;
///
/// let mut encoder = LengthPrefixEncoder::from_parts(msg.as_bytes(), msg.len(), 0).unwrap();
/// assert_eq!(encoder.encoded_message_bytes(), prefixed_msg_size);
/// assert!(!encoder.exhausted());
///
/// let mut dummy_stdout = Cursor::new(vec![0; prefixed_msg_size + 1]);
///
/// loop {
/// let result: WriteToIoReturn = encoder
/// .write_to_stdio(&mut dummy_stdout)
/// .expect("write failed");
/// if dummy_stdout.position() as usize >= prefixed_msg_size {
/// // The entire message should've been written (and the encoder state reflect this)
/// assert!(result.done);
/// assert_eq!(result.bytes_written, msg.len());
/// assert_eq!(encoder.header_written(), (msg.len() as u64).to_le_bytes());
/// assert_eq!(encoder.message_written(), msg.as_bytes());
/// break;
/// }
/// }
/// let buffer_bytes = dummy_stdout.get_ref();
/// match String::from_utf8(buffer_bytes.to_vec()) {
/// Ok(buffer_str) => assert_eq!(&buffer_str[HEADER_SIZE..prefixed_msg_size], msg),
/// Err(err) => println!("Error converting buffer to String: {:?}", err),
/// }
/// assert_eq!(
/// &dummy_stdout.get_ref()[HEADER_SIZE..prefixed_msg_size],
/// msg.as_bytes()
/// );
/// ```
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
if self.exhausted() {
return Ok(WriteToIoReturn {
@@ -438,3 +584,150 @@ impl<Buf: BorrowMut<[u8]>> Zeroize for LengthPrefixEncoder<Buf> {
self.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lpe_error_conversion_upcast_valid() {
let len_error = MessageTooLarge;
let len_sanity_error: MessageLenSanityError = len_error.into();
let sanity_error: SanityError = len_error.into();
assert!(matches!(sanity_error, SanityError::MessageTooLarge(_)));
let sanity_error: SanityError = len_sanity_error.into();
assert!(matches!(sanity_error, SanityError::MessageTooLarge(_)));
let pos_error = PositionOutOfBufferBounds;
let pos_sanity_error: PositionSanityError = pos_error.into();
let sanity_error: SanityError = pos_error.into();
assert!(matches!(
sanity_error,
SanityError::PositionOutOfBufferBounds(_)
));
let sanity_error: SanityError = pos_sanity_error.into();
assert!(matches!(
sanity_error,
SanityError::PositionOutOfBufferBounds(_)
));
}
#[test]
fn test_lpe_error_conversion_downcast_invalid() {
let pos_error = PositionOutOfBufferBounds;
let sanity_error = SanityError::PositionOutOfBufferBounds(pos_error.into());
match MessageLenSanityError::try_from(sanity_error) {
Ok(_) => panic!("Conversion should always fail (incompatible enum variant)"),
Err(err) => assert!(matches!(err, PositionOutOfBufferBounds)),
}
}
#[test]
fn test_write_to_stdio_cursor() {
use std::io::Cursor;
let msg = String::from("Hello world");
let prefixed_msg_size = msg.len() + HEADER_SIZE;
let mut encoder = LengthPrefixEncoder::from_parts(msg.as_bytes(), msg.len(), 0).unwrap();
assert_eq!(encoder.encoded_message_bytes(), prefixed_msg_size);
assert!(!encoder.exhausted());
let mut dummy_stdout = Cursor::new(vec![0; prefixed_msg_size + 1]);
loop {
let result: WriteToIoReturn = encoder
.write_to_stdio(&mut dummy_stdout)
.expect("write failed");
if dummy_stdout.position() as usize >= prefixed_msg_size {
// The entire message should've been written (and the encoder state reflect this)
assert!(result.done);
assert_eq!(result.bytes_written, msg.len());
assert_eq!(encoder.header_written(), (msg.len() as u64).to_le_bytes());
assert_eq!(encoder.message_written(), msg.as_bytes());
break;
}
}
let buffer_bytes = dummy_stdout.get_ref();
match String::from_utf8(buffer_bytes.to_vec()) {
Ok(buffer_str) => assert_eq!(&buffer_str[HEADER_SIZE..prefixed_msg_size], msg),
Err(err) => println!("Error converting buffer to String: {:?}", err),
}
assert_eq!(
&dummy_stdout.get_ref()[HEADER_SIZE..prefixed_msg_size],
msg.as_bytes()
);
}
#[test]
fn test_write_offset_header() {
use std::io::Cursor;
let mut msg = Vec::<u8>::new();
msg.extend_from_slice(b"cats");
msg.extend_from_slice(b" and dogs");
let msg_len = msg.len();
let prefixed_msg_size = msg_len + HEADER_SIZE;
msg.extend_from_slice(b" and other animals"); // To be discarded
let mut encoder = LengthPrefixEncoder::from_short_message(msg.clone(), msg_len).unwrap();
// Only the short message should have been stored (and the unused part discarded)
assert_eq!(encoder.message_mut(), b"cats and dogs");
assert_eq!(encoder.message_written_mut(), []);
assert_eq!(encoder.message_left_mut(), b"cats and dogs");
assert_eq!(encoder.buf_mut(), &msg);
// Fast-forward as if the header had already been sent - only the message remains
encoder
.set_header_offset(HEADER_SIZE)
.expect("failed to move cursor");
let mut sink = Cursor::new(vec![0; prefixed_msg_size + 1]);
encoder.write_all_to_stdio(&mut sink).expect("write failed");
assert_eq!(&sink.get_ref()[0..msg_len], &msg[0..msg_len]);
assert_eq!(encoder.message_mut(), b"cats and dogs");
assert_eq!(encoder.message_written_mut(), b"cats and dogs");
assert_eq!(encoder.message_left_mut(), []);
assert_eq!(encoder.buf_mut(), &msg);
}
#[test]
fn test_some_assembly_required() {
let msg = String::from("hello world");
let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
assert!(encoder.encoded_message_bytes() > msg.len());
assert!(!encoder.exhausted());
let (msg_buffer, msg_length, write_offset) = encoder.into_parts();
assert_eq!(msg_buffer, msg.as_bytes());
assert_eq!(write_offset, 0);
assert_eq!(msg_length, msg.len());
}
#[test]
fn test_restart_write_reset() {
let msg = String::from("hello world");
let mut encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
assert_eq!(encoder.writing_position(), 0);
encoder.set_writing_position(4).unwrap();
assert_eq!(encoder.writing_position(), 4);
encoder.restart_write();
assert_eq!(encoder.writing_position(), 0);
}
#[test]
fn test_zeroize_state() {
use zeroize::Zeroize;
let mut msg = Vec::<u8>::new();
msg.extend_from_slice(b"test");
let mut encoder = LengthPrefixEncoder::from_message(msg.clone());
assert_eq!(encoder.message(), msg);
encoder.zeroize();
assert_eq!(encoder.message(), []);
}
}

View File

@@ -1,4 +1,2 @@
/// Module that handles decoding functionality
pub mod decoder;
/// Module that handles encoding functionality
pub mod encoder;

View File

@@ -22,7 +22,7 @@ pub mod io;
pub mod length_prefix_encoding;
/// Memory manipulation and allocation utilities.
pub mod mem;
/// MIO integration utilities.
/// [MIO (Metal I/O)](https://docs.rs/crate/mio/) integration utilities.
pub mod mio;
/// Extended Option type functionality.
pub mod option;

View File

@@ -1,10 +1,33 @@
//!
//! This module provides functions for copying data, concatenating byte arrays,
//! and various traits and types that help manage values, including preventing
//! drops, discarding results, and swapping values.
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?
/// Concatenate multiple byte slices into a fixed-size byte array.
///
/// # Panics
///
/// Panics if the concatenated length does not match the declared length.
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::cat;
/// let arr = cat!(6; b"abc", b"def");
/// assert_eq!(&arr, b"abcdef");
///
/// let err = std::panic::catch_unwind(|| cat!(5; b"abc", b"def"));
/// assert!(matches!(err, Err(_)));
///
/// let err = std::panic::catch_unwind(|| cat!(7; b"abc", b"def"));
/// assert!(matches!(err, Err(_)));
/// ```
#[macro_export]
macro_rules! cat {
($len:expr; $($toks:expr),+) => {{
@@ -22,12 +45,37 @@ macro_rules! cat {
}
// TODO: consistent inout ordering
/// Copy all bytes from `src` to `dst`. The lengths must match.
/// Copy bytes from `src` to `dst`, requiring equal lengths.
///
/// # Panics
///
/// Panics if lengths differ.
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::mem::cpy;
/// let src = [1, 2, 3];
/// let mut dst = [0; 3];
/// cpy(&src, &mut dst);
/// assert_eq!(dst, [1, 2, 3]);
/// ```
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
dst.borrow_mut().copy_from_slice(src.borrow());
}
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length,
/// copy as many bytes as possible.
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::mem::cpy_min;
/// let src = [1, 2, 3, 4];
/// let mut dst = [0; 2];
/// cpy_min(&src, &mut dst);
/// assert_eq!(dst, [1, 2]);
/// ```
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
let src = src.borrow();
let dst = dst.borrow_mut();
@@ -35,20 +83,30 @@ pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d
dst[..len].copy_from_slice(&src[..len]);
}
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying
/// variable is freed
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::mem::Forgetting;
/// let f = Forgetting::new(String::from("hello"));
/// assert_eq!(&*f, "hello");
/// let val = f.extract();
/// assert_eq!(val, "hello");
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)]
pub struct Forgetting<T> {
value: Option<T>,
}
impl<T> Forgetting<T> {
/// Creates a new `Forgetting<T>` instance containing the given value.
/// Create a new `Forgetting` wrapping `value`.
pub fn new(value: T) -> Self {
let value = Some(value);
Self { value }
Self { value: Some(value) }
}
/// Extracts and returns the contained value, consuming self.
/// Consume and return the inner value.
pub fn extract(mut self) -> T {
let mut value = None;
swap(&mut value, &mut self.value);
@@ -97,6 +155,13 @@ impl<T> Drop for Forgetting<T> {
}
/// A trait that provides a method to discard a value without explicitly handling its results.
///
/// # Examples
///
/// ```rust
/// # use rosenpass_util::mem::DiscardResultExt;
/// let result: () = (|| { return 42 })().discard_result(); // Just discard
/// ```
pub trait DiscardResultExt {
/// Consumes and discards a value without doing anything with it.
fn discard_result(self);
@@ -107,8 +172,16 @@ impl<T> DiscardResultExt for T {
}
/// Trait that provides a method to explicitly forget values.
///
/// # Examples
///
/// ```rust
/// # use rosenpass_util::mem::ForgetExt;
/// let s = String::from("no drop");
/// s.forget(); // destructor not run
/// ```
pub trait ForgetExt {
/// Consumes and forgets a value, preventing its destructor from running.
/// Forget the value.
fn forget(self);
}
@@ -119,10 +192,23 @@ impl<T> ForgetExt for T {
}
/// Extension trait that provides methods for swapping values.
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::mem::SwapWithExt;
/// let mut x = 10;
/// let mut y = x.swap_with(20);
/// assert_eq!(x, 20);
/// assert_eq!(y, 10);
/// y.swap_with_mut(&mut x);
/// assert_eq!(x, 10);
/// assert_eq!(y, 20);
/// ```
pub trait SwapWithExt {
/// Takes ownership of `other` and swaps its value with `self`, returning the original value.
/// Swap values and return the old value of `self`.
fn swap_with(&mut self, other: Self) -> Self;
/// Swaps the values between `self` and `other` in place.
/// Swap values in place with another mutable reference.
fn swap_with_mut(&mut self, other: &mut Self);
}
@@ -138,8 +224,18 @@ impl<T> SwapWithExt for T {
}
/// Extension trait that provides methods for swapping values with default values.
///
/// # Examples
///
/// ```rust
/// # use rosenpass_util::mem::SwapWithDefaultExt;
/// let mut s = String::from("abc");
/// let old = s.swap_with_default();
/// assert_eq!(old, "abc");
/// assert_eq!(s, "");
/// ```
pub trait SwapWithDefaultExt {
/// Takes the current value and replaces it with the default value, returning the original.
/// Swap with `Self::default()`.
fn swap_with_default(&mut self) -> Self;
}
@@ -150,6 +246,26 @@ impl<T: Default> SwapWithDefaultExt for T {
}
/// Extension trait that provides a method to explicitly move values.
///
/// # Examples
///
/// ```rust
/// # use std::rc::Rc;
/// use rosenpass_util::mem::MoveExt;
/// let val = 42;
/// let another_val = val.move_here();
/// assert_eq!(another_val, 42);
/// // val is now inaccessible
///
/// let value = Rc::new(42);
/// let clone = Rc::clone(&value);
///
/// assert_eq!(Rc::strong_count(&value), 2);
///
/// clone.move_here(); // this will drop the second reference
///
/// assert_eq!(Rc::strong_count(&value), 1);
/// ```
pub trait MoveExt {
/// Deliberately move the value
///
@@ -163,3 +279,29 @@ impl<T: Sized> MoveExt for T {
self
}
}
#[cfg(test)]
mod test_forgetting {
use crate::mem::Forgetting;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc;
#[test]
fn test_forgetting() {
let drop_was_called = Arc::new(AtomicBool::new(false));
struct SetFlagOnDrop(Arc<AtomicBool>);
impl Drop for SetFlagOnDrop {
fn drop(&mut self) {
self.0.store(true, SeqCst);
}
}
drop(SetFlagOnDrop(drop_was_called.clone()));
assert!(drop_was_called.load(SeqCst));
// reset flag and use Forgetting
drop_was_called.store(false, SeqCst);
let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone()));
drop(forgetting);
assert_eq!(drop_was_called.load(SeqCst), false);
}
}

View File

@@ -6,7 +6,7 @@ use crate::{
result::OkExt,
};
/// Module containing I/O interest flags for Unix operations
/// Module containing I/O interest flags for Unix operations (see also: [mio::Interest])
pub mod interest {
use mio::Interest;
@@ -20,9 +20,48 @@ pub mod interest {
pub const RW: Interest = R.add(W);
}
/// Extension trait providing additional functionality for Unix listener
/// Extension trait providing additional functionality for a Unix listener
///
/// # Example
///
/// ```rust
/// use mio::net::{UnixListener, UnixStream};
/// use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt};
///
/// use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
/// use std::path::Path;
///
/// // This would be the UDS created by an external source
/// let socket_path = "/tmp/rp_mio_uds_test_socket";
/// if Path::new(socket_path).exists() {
/// std::fs::remove_file(socket_path).expect("Failed to remove existing socket");
/// }
///
/// // An extended MIO listener can then be created by claiming the existing socket
/// // Note that the original descriptor is not reused, but copied before claiming it here
/// let listener = UnixListener::bind(socket_path).unwrap();
/// let listener_fd: RawFd = listener.as_raw_fd();
/// let ext_listener = <UnixListener as UnixListenerExt>
/// ::claim_fd(listener_fd).expect("Failed to claim_fd for ext_listener socket");
///
/// // Similarly, "client" connections can be established by claiming existing sockets
/// // Note that in this case, the file descriptor will be reused (safety implications!)
/// let stream = UnixStream::connect(socket_path).unwrap();
/// let stream_fd = stream.into_raw_fd();
/// let ext_stream = <UnixStream as UnixStreamExt>
/// ::claim_fd_inplace(stream_fd).expect("Failed to claim_fd_inplace for ext_stream socket");
///
/// // Handle accepted connections...
/// ext_listener.accept().expect("Failed to accept incoming connection");
///
/// // Send or receive messages ...
///
/// // Cleanup, shutdown etc. goes here ...
/// std::fs::remove_file(socket_path).unwrap();
/// ```
pub trait UnixListenerExt: Sized {
/// Creates a new Unix listener by claiming ownership of a raw file descriptor
/// (see [fd::claim_fd](crate::fd::claim_fd))
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
}
@@ -36,15 +75,17 @@ impl UnixListenerExt for UnixListener {
}
}
/// Extension trait providing additional functionality for Unix streams
/// Extension trait providing additional functionality for a Unix stream
pub trait UnixStreamExt: Sized {
/// Creates a new Unix stream from an owned file descriptor
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor and creates a new Unix stream
/// (see [fd::claim_fd](crate::fd::claim_fd))
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream
/// (see [fd::claim_fd_inplace](crate::fd::claim_fd_inplace))
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
}

View File

@@ -12,6 +12,58 @@ use crate::fd::{claim_fd_inplace, IntoStdioErr};
/// A wrapper around a socket that combines reading from the socket with tracking
/// received file descriptors. Limits the maximum number of file descriptors that
/// can be received in a single read operation via the `MAX_FDS` parameter.
///
/// # Example
///
/// ```rust
/// use std::collections::VecDeque;
/// use std::io::Cursor;
/// use std::io::Read;
/// use std::os::fd::AsRawFd;
/// use std::os::fd::OwnedFd;
///
/// use mio::net::UnixStream;
/// use rosenpass_util::mio::ReadWithFileDescriptors;
/// use rosenpass_util::io::TryIoResultKindHintExt;
///
/// const MAX_REQUEST_FDS : usize = 2; // Limit to 2 descriptors per read operation
/// let mut read_fd_buffer = VecDeque::<OwnedFd>::new(); // File descriptor queue
///
/// // In this case, the unused writable end of the connection can be ignored
/// let (io_stream, _) = UnixStream::pair().expect("failed to create socket pair");
///
/// // Wait until the output stream is writable...
///
/// // Wrap the socket to start tracking received file descriptors
/// let mut fd_passing_sock = ReadWithFileDescriptors::<MAX_REQUEST_FDS, UnixStream, _, _>::new(
/// &io_stream,
/// &mut read_fd_buffer,
/// );
////
/// // Simulated reads; the actual operations will depend on the protocol (implementation details)
/// let mut recv_buffer = Vec::<u8>::new();
/// let bytes_read = fd_passing_sock.read(&mut recv_buffer[..]).expect("error reading from socket");
/// assert_eq!(bytes_read, 0);
/// assert_eq!(&recv_buffer[..bytes_read], []);
///
/// // Alternatively, it's possible to use the try_io_err_kind_hint utility provided by this crate
/// match fd_passing_sock.read(&mut recv_buffer).try_io_err_kind_hint() {
/// Err(_) => {
/// // Handle errors here ...
/// }
/// Ok(result) => {
/// // Process messages here ...
/// assert_eq!(0, result); // Nothing to read in this example
/// }
/// };
///
/// // The wrapped components can still be accessed
/// assert_eq!(fd_passing_sock.socket().as_raw_fd(), io_stream.as_raw_fd());
/// let (socket, fd_queue) = fd_passing_sock.into_parts();
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
///
/// // Shutdown, cleanup, etc. goes here ...
/// ```
pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
where
Sock: FdPassingExt,

View File

@@ -11,6 +11,43 @@ use uds::UnixStreamExt as FdPassingExt;
use crate::{repeat, return_if};
/// A structure that facilitates writing data and file descriptors to a Unix domain socket
///
/// # Example
///
/// ```rust
/// use std::io::{Read, Write};
/// use std::net::UdpSocket;
/// use std::os::fd::{AsFd, AsRawFd};
///
/// use mio::net::UnixStream;
/// use rosenpass_util::mio::WriteWithFileDescriptors;
///
/// // Create socket descriptors that should be sent (not limited to UDP sockets)
/// let peer_endpoint = "[::1]:0";
/// let peer_socket = UdpSocket::bind(peer_endpoint).expect("bind failed");
/// let peer_socket_fd = peer_socket.as_fd();
/// let mut fds_to_send = vec![&peer_socket_fd].into();
///
/// // Create writable end (must be an Unix Domain Socket)
/// // In this case, the readable end of the connection can be ignored
/// let (mut dummy_sink, io_stream) = UnixStream::pair().expect("failed to create socket pair");
/// let mut writable_stream = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(
/// &io_stream, &mut fds_to_send);
///
/// // Send data and file descriptors (note that at least one byte should be written)
/// writable_stream.write(&[0xffu8; 42]).expect("failed to write");
/// // Discard data; the dummy_sink is only required to keep the connection alive here
/// let mut recv_buffer = Vec::<u8>::new();
/// dummy_sink.read(&mut recv_buffer[..]).expect("error reading from socket");
/// writable_stream.flush().expect("failed to flush"); // Currently a NOOP
///
/// // The wrapped components can still be accessed
/// let (socket, fds) = writable_stream.into_parts();
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
/// assert!(fds_to_send.is_empty(), "Failed to send file descriptors");
///
/// // Shutdown, cleanup, etc. goes here ...
/// ```
pub struct WriteWithFileDescriptors<Sock, Fd, BorrowSock, BorrowFds>
where
Sock: FdPassingExt,

View File

@@ -1,6 +1,24 @@
use std::convert::Infallible;
/// Try block basically…returns a result and allows the use of the question mark operator inside
///
/// # Examples
/// ```rust
/// # use anyhow::Result;
/// # use rosenpass_util::attempt;
/// let result: Result<i32> = attempt!({
/// let x = 42;
/// Ok(x)
/// });
///
/// assert_eq!(result.unwrap(), 42);
///
/// let error_result: Result<()> = attempt!({
/// Err(anyhow::anyhow!("some error"))
/// });
///
/// assert!(error_result.is_err());
/// ```
#[macro_export]
macro_rules! attempt {
($block:expr) => {
@@ -9,6 +27,19 @@ macro_rules! attempt {
}
/// Trait for the ok operation, which provides a way to convert a value into a Result
/// # Examples
/// ```rust
/// # use rosenpass_util::result::OkExt;
/// let value: i32 = 42;
/// let result: Result<i32, &str> = value.ok();
///
/// assert_eq!(result, Ok(42));
///
/// let value = "hello";
/// let result: Result<&str, &str> = value.ok();
///
/// assert_eq!(result, Ok("hello"));
/// ```
pub trait OkExt<E>: Sized {
/// Wraps a value in a Result::Ok variant
fn ok(self) -> Result<Self, E>;
@@ -26,6 +57,11 @@ impl<T, E> OkExt<E> for T {
/// the function will not panic.
///
/// Implementations must not panic.
/// # Examples
/// ```
/// # use rosenpass_util::result::GuaranteedValue;
/// let x:u32 = 10u8.try_into().guaranteed();
/// ```
pub trait GuaranteedValue {
/// The value type that will be returned by guaranteed()
type Value;

View File

@@ -3,7 +3,23 @@ use typenum::int::{NInt, PInt, Z0};
use typenum::marker_traits as markers;
use typenum::uint::{UInt, UTerm};
/// Convenience macro to convert type numbers to constant integers
/// Convenience macro to convert [`typenum`] type numbers to constant integers.
///
/// This macro takes a [`typenum`] type-level integer (like `U5`, `P3`, or `N7`)
/// and converts it into its equivalent constant integer value at compile time.
/// By default, it converts to a suitable unsigned integer type, but you can
/// specify a target type explicitly using `typenum2const!(Type as i32)`,
/// for example.
///
/// # Examples
///
/// ```rust
/// # use typenum::consts::U10;
/// # use rosenpass_util::typenum2const;
///
/// const TEN: u32 = typenum2const!(U10 as u32);
/// assert_eq!(TEN, 10);
/// ```
#[macro_export]
macro_rules! typenum2const {
($val:ty) => {
@@ -14,35 +30,80 @@ macro_rules! typenum2const {
};
}
/// Trait implemented by constant integers to facilitate conversion to constant integers
/// A trait implemented by type-level integers to facilitate their conversion
/// into constant values.
///
/// Types from the [`typenum`] crate (like `U5`, `P3`, or `N7`) can implement
/// `IntoConst` to produce a compile-time constant integer of the specified
/// type. This trait is part of the underlying mechanism used by the
/// [`crate::typenum2const`] macro.
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::typenum2const;
/// use typenum::consts::U42;
/// use rosenpass_util::typenum::IntoConst;
///
/// // Directly using IntoConst:
/// const VALUE: u64 = <U42 as IntoConst<u64>>::VALUE;
/// assert_eq!(VALUE, 42);
///
/// // Or via the macro:
/// const VALUE_MACRO: u64 = typenum2const!(U42 as u64);
/// assert_eq!(VALUE_MACRO, 42);
/// ```
pub trait IntoConst<T> {
/// The constant value after conversion
/// The constant value after conversion.
const VALUE: T;
}
#[allow(dead_code)]
/// Internal struct for applying a negative sign to an unsigned type-level integer during conversion.
///
/// This is part of the implementation detail for signed conversions. It uses
/// [`AssociatedUnsigned`] to determine the underlying unsigned type and negates its value.
struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
#[allow(dead_code)]
/// Internal struct for applying a positive sign to an unsigned type-level integer during conversion.
///
/// This is used as part of converting a positive signed type-level integer to its runtime integer
/// value, ensuring that the correct unsigned representation is known via [`AssociatedUnsigned`].
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>
/// Internal struct representing a left-shift operation on a type-level integer.
///
/// Used as part of compile-time computations. `SHIFT` determines how many bits the value will be
/// shifted to the left.
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param);
#[allow(dead_code)]
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
/// Internal struct representing an addition operation between two type-level integers.
///
/// `ConstAdd` is another building block for compile-time arithmetic on type-level integers before
/// their conversion to runtime constants.
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs);
/// Assigns an unsigned type to a signed type
/// Associates an unsigned type with a signed type, enabling conversions between signed and unsigned
/// representations of compile-time integers.
///
/// This trait is used internally to facilitate the conversion of signed [`typenum`] integers by
/// referencing their underlying unsigned representation.
trait AssociatedUnsigned {
/// The associated unsigned type.
type Type;
}
/// Internal macro implementing the [`IntoConst`] trait for a given mapping from a type-level integer
/// to a concrete integer type.
macro_rules! impl_into_const {
( $from:ty as $to:ty := $impl:expr) => {
impl IntoConst<$to> for $from {
@@ -51,6 +112,10 @@ macro_rules! impl_into_const {
};
}
/// Internal macro implementing common `IntoConst` logic for various numeric types.
///
/// It sets up `Z0`, `B0`, `B1`, `UTerm`, and also provides default implementations for
/// `ConstLshift` and `ConstAdd`.
macro_rules! impl_numeric_into_const_common {
($type:ty) => {
impl_into_const! { Z0 as $type := 0 }
@@ -73,6 +138,10 @@ macro_rules! impl_numeric_into_const_common {
};
}
/// Internal macro implementing `IntoConst` for unsigned integer types.
///
/// It sets up conversions for multiple unsigned integer target types and
/// provides the positive sign application implementation.
macro_rules! impl_numeric_into_const_unsigned {
($($to_list:ty),*) => {
$( impl_numeric_into_const_unsigned! { @impl $to_list } )*
@@ -91,6 +160,9 @@ macro_rules! impl_numeric_into_const_unsigned {
};
}
/// Internal macro implementing `IntoConst` for signed integer types.
///
/// It uses their associated unsigned types to handle positive and negative conversions correctly.
macro_rules! impl_numeric_into_const_signed {
($($to_list:ty : $unsigned_list:ty),*) => {
$( impl_numeric_into_const_signed! { @impl $to_list : $unsigned_list} )*
@@ -110,9 +182,8 @@ macro_rules! impl_numeric_into_const_signed {
impl<Param: IntoConst<$unsigned>> IntoConst<$type> for ConstApplyNegSign<$type, Param> {
const VALUE : $type =
if Param::VALUE == (1 as $unsigned).rotate_right(1) {
// Handle the negative value without an associated positive value (e.g. -128
// for i8)
< $type >::MIN
// Handling negative values at boundaries, such as i8::MIN
<$type>::MIN
} else {
-(Param::VALUE as $type)
};
@@ -122,10 +193,10 @@ macro_rules! impl_numeric_into_const_signed {
impl_into_const! { B0 as bool := false }
impl_into_const! { B1 as bool := true }
impl_numeric_into_const_unsigned! { usize, u8, u16, u32, u64, u128 }
impl_numeric_into_const_signed! { isize : usize, i8 : u8, i16 : u16, i32 : u32, i64 : u64, i128 : u128 }
// Unsigned type numbers to const integers
impl<Ret, Rest, Bit> IntoConst<Ret> for UInt<Rest, Bit>
where
Rest: IntoConst<Ret>,
@@ -133,26 +204,28 @@ where
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: IntoConst<Ret>,
{
/// Converts an unsigned [`UInt`] typenum into its corresponding constant integer by
/// decomposing it into shifts and additions on its subparts.
const VALUE: Ret = <ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit> as IntoConst<Ret>>::VALUE;
}
// Signed type numbers with positive sign to const integers
impl<Ret, Unsigned> IntoConst<Ret> for PInt<Unsigned>
where
Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyPosSign<Ret, Unsigned>: IntoConst<Ret>,
{
/// Converts a positive signed [`PInt`] typenum into its corresponding constant integer.
const VALUE: Ret = <ConstApplyPosSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}
// Signed type numbers with negative sign to const integers
impl<Ret, Unsigned> IntoConst<Ret> for NInt<Unsigned>
where
Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyNegSign<Ret, Unsigned>: IntoConst<Ret>,
{
/// Converts a negative signed [`NInt`] typenum into its corresponding constant integer.
const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}

View File

@@ -1,2 +1,23 @@
//!
//! This module provides an extension trait,
//! [`ZeroizedExt`](crate::zeroize::ZeroizedExt), for all types implementing the
//! `zeroize::Zeroize` trait.
//! It introduces the [`zeroized`](crate::zeroize::ZeroizedExt::zeroized)
//! method, which zeroizes a value in place and returns it, making it convenient
//! for chaining operations and ensuring sensitive data is securely erased.
//!
//! # Examples
//!
//! ```rust
//! use zeroize::Zeroize;
//! use rosenpass_util::zeroize::ZeroizedExt;
//!
//! let mut value = String::from("hello");
//! value.zeroize(); // Zeroizes in place
//! assert_eq!(value, "");
//!
//! assert_eq!(String::from("hello").zeroized(), "");
//! ```
mod zeroized_ext;
pub use zeroized_ext::*;

View File

@@ -1,3 +1,41 @@
//! Client implementation for the WireGuard broker protocol.
//!
//! This module provides a client implementation that communicates with a WireGuard broker server
//! using a binary protocol. The client handles serialization and deserialization of messages,
//! error handling, and the core interaction flow.
//!
//! # Examples
//!
//! ```
//! use rosenpass_wireguard_broker::api::client::{BrokerClient, BrokerClientIo};
//! #[derive(Debug)]
//! struct MyIo;
//!
//! impl BrokerClientIo for MyIo {
//! type SendError = std::io::Error;
//! type RecvError = std::io::Error;
//!
//! fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
//! // Implement sending logic
//! Ok(())
//! }
//!
//! fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
//! // Implement receiving logic
//! Ok(None)
//! }
//! }
//!
//! // Create client with custom IO implementation
//! let mut client = BrokerClient::new(MyIo);
//! assert!(client.poll_response().unwrap().is_none());
//! ```
//!
//! # Protocol
//!
//! The client implements a simple request-response protocol for setting WireGuard pre-shared keys.
//! Messages are serialized using a binary format defined in the [`crate::api::msgs`] module.
use std::{borrow::BorrowMut, fmt::Debug};
use crate::{
@@ -13,10 +51,13 @@ use super::{
msgs::{Envelope, SetPskResponse},
};
/// Error type for polling responses from the broker server.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientPollResponseError<RecvError> {
/// An IO error occurred while receiving the response
#[error(transparent)]
IoError(RecvError),
/// The received message was invalid or malformed
#[error("Invalid message.")]
InvalidMessage,
}
@@ -28,34 +69,52 @@ impl<RecvError> From<msgs::InvalidMessageTypeError> for BrokerClientPollResponse
}
}
/// Helper function that wraps a receive error into a `BrokerClientPollResponseError::IoError`
fn io_poller<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::IoError(e)
}
/// Helper function that returns a `BrokerClientPollResponseError::InvalidMessage` error
fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::InvalidMessage
}
/// Error type for setting pre-shared keys through the broker client.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientSetPskError<SendError> {
/// Error encoding or decoding the message
#[error("Error with encoding/decoding message")]
MsgError,
/// Error in the broker configuration
#[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr),
/// IO error while sending the request
#[error(transparent)]
IoError(SendError),
/// Interface name exceeds maximum length
#[error("Interface name out of bounds")]
IfaceOutOfBounds,
}
/// Trait defining the IO operations required by the broker client.
///
/// Implementors must provide methods for sending and receiving binary messages.
pub trait BrokerClientIo {
/// Error type returned by send operations
type SendError;
/// Error type returned by receive operations
type RecvError;
/// Send a binary message
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>;
/// Receive a binary message, returning None if no message is available
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>;
}
/// Client for interacting with a WireGuard broker server.
///
/// The client handles the protocol-level communication with the server,
/// including message serialization and response handling.
#[derive(Debug)]
pub struct BrokerClient<Io>
where
@@ -68,18 +127,37 @@ impl<Io> BrokerClient<Io>
where
Io: BrokerClientIo + Debug,
{
/// Creates a new `BrokerClient` with the given IO implementation.
pub fn new(io: Io) -> Self {
Self { io }
}
/// Returns a reference to the underlying IO implementation.
pub fn io(&self) -> &Io {
&self.io
}
/// Returns a mutable reference to the underlying IO implementation.
pub fn io_mut(&mut self) -> &mut Io {
&mut self.io
}
/// Polls for a response from the broker server.
///
/// This method attempts to receive and parse a SetPsk response message from the server.
/// If no message is available, returns `Ok(None)`. If a message is received, it is
/// parsed and validated before being returned as `Ok(Some(result))`.
///
/// # Returns
/// - `Ok(Some(result))` if a valid response was received
/// - `Ok(None)` if no message was available
/// - `Err(BrokerClientPollResponseError)` if an error occurred during receiving or parsing
///
/// # Errors
/// Returns an error if:
/// - An IO error occurs while receiving the message
/// - The received message is invalid or malformed
/// - The message type is incorrect
pub fn poll_response(
&mut self,
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
@@ -147,3 +225,112 @@ where
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use msgs::{MsgType, SetPskError, SetPskResponseReturnCode};
// Mock IO implementation for testing
#[derive(Debug)]
struct MockIo {
recv_data: Vec<u8>,
}
impl MockIo {
fn new() -> Self {
Self {
recv_data: Vec::new(),
}
}
fn set_recv_data(&mut self, data: Option<Vec<u8>>) {
self.recv_data = data.unwrap_or_default();
}
}
impl BrokerClientIo for MockIo {
type SendError = std::io::Error;
type RecvError = std::io::Error;
fn send_msg(&mut self, _buf: &[u8]) -> Result<(), Self::SendError> {
Ok(())
}
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
if self.recv_data.is_empty() {
Ok(None)
} else {
Ok(Some(&self.recv_data))
}
}
}
fn create_response_msg(return_code: u8) -> Vec<u8> {
let mut msg = vec![
MsgType::SetPsk as u8, // msg_type
0,
0,
0, // reserved bytes
];
msg.push(return_code); // return_code
msg
}
#[test]
fn test_poll_response_no_message() {
let io = MockIo::new();
let mut client = BrokerClient::new(io);
assert_eq!(client.poll_response().unwrap(), None);
}
#[test]
fn test_poll_response_success() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(
SetPskResponseReturnCode::Success as u8,
)));
let mut client = BrokerClient::new(io);
assert_eq!(client.poll_response().unwrap(), Some(Ok(())));
}
#[test]
fn test_poll_response_no_such_peer() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(
SetPskResponseReturnCode::NoSuchPeer as u8,
)));
let mut client = BrokerClient::new(io);
assert_eq!(
client.poll_response().unwrap(),
Some(Err(SetPskError::NoSuchPeer))
);
}
#[test]
fn test_poll_response_invalid_message_type() {
let mut io = MockIo::new();
io.set_recv_data(Some(vec![0xFF, 0, 0, 0, 0])); // Invalid message type
let mut client = BrokerClient::new(io);
assert!(matches!(
client.poll_response(),
Err(BrokerClientPollResponseError::InvalidMessage)
));
}
#[test]
fn test_poll_response_invalid_return_code() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(0xFF))); // Invalid return code
let mut client = BrokerClient::new(io);
assert!(matches!(
client.poll_response(),
Err(BrokerClientPollResponseError::InvalidMessage)
));
}
}

View File

@@ -1,3 +1,7 @@
//! This module provides [NetworkBrokerConfig] for configuring a
//! [BrokerServer](crate::api::server::BrokerServer) and tooling to serialize and deserialize these
//! configurations.
use crate::{SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
use derive_builder::Builder;
use rosenpass_secret_memory::{Public, Secret};
@@ -5,13 +9,19 @@ use rosenpass_secret_memory::{Public, Secret};
#[derive(Builder, Debug)]
#[builder(pattern = "mutable")]
//TODO: Use generics for iface, add additional params
/// Specifies a configuration for a [BrokerServer](crate::api::server::BrokerServer).
pub struct NetworkBrokerConfig<'a> {
/// The interface for the [BrokerServer](crate::api::server::BrokerServer).
pub iface: &'a str,
/// The peer identifier for the [BrokerServer](crate::api::server::BrokerServer).
pub peer_id: &'a Public<WG_PEER_LEN>,
/// The pre-shared key for the [BrokerServer](crate::api::server::BrokerServer) and the
/// interface.
pub psk: &'a Secret<WG_KEY_LEN>,
}
impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
/// Transforms a [NetworkBrokerConfig] into a [SerializedBrokerConfig] meant for serialization.
fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> {
Self {
interface: src.iface.as_bytes(),
@@ -22,15 +32,23 @@ impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
}
}
/// Error variants that can occur when loading a [NetworkBrokerConfig] from a
/// [SerializedBrokerConfig].
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum NetworkBrokerConfigErr {
/// Error indicating that the interface specification could not be read correctly.
#[error("Interface")]
Interface,
Interface, // TODO, give this a better name.
}
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
type Error = NetworkBrokerConfigErr;
/// Tries to load a [NetworkBrokerConfig] from a [SerializedBrokerConfig].
///
/// # Errors
/// Returns a [NetworkBrokerConfigErr::Interface]-error when the interface description
/// can not be parsed correctly.
fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> {
let iface =
std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?;

View File

@@ -1,3 +1,9 @@
//! This module implements the binary WireGuard broker protocol in the form of the [client::BrokerClient]
//! and the [server::BrokerServer].
//!
//! Specifically, The protocol enables the client to tell the server to set a pre-shared key for a
//! wireguard interface.
pub mod client;
pub mod config;
pub mod msgs;

View File

@@ -1,11 +1,19 @@
//! This module defines message formats for messages in the Wireguard Broker protocol as well as
//! helper structures like errors and conversion functions.
use std::str::{from_utf8, Utf8Error};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
/// The number of bytes reserved for overhead when packaging data.
pub const ENVELOPE_OVERHEAD: usize = 1 + 3;
/// The buffer size for request messages.
pub const REQUEST_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 32 + 32 + 1 + 255;
/// The buffer size for responses.
pub const RESPONSE_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 1;
/// Envelope for messages being passed around.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Envelope<M: AsBytes + FromBytes> {
@@ -17,25 +25,43 @@ pub struct Envelope<M: AsBytes + FromBytes> {
pub payload: M,
}
/// Message format for requests to set a pre-shared key.
/// # Example
///
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct SetPskRequest {
/// The pre-shared key.
pub psk: [u8; 32],
/// The identifier of the peer.
pub peer_id: [u8; 32],
/// The size for the interface
pub iface_size: u8, // TODO: We should have variable length strings in lenses
/// The buffer for the interface.
pub iface_buf: [u8; 255],
}
impl SetPskRequest {
/// Gets the interface specification as byte slice.
pub fn iface_bin(&self) -> &[u8] {
let len = self.iface_size as usize;
&self.iface_buf[..len]
}
/// Gets the interface specification as a `&str`.
///
/// # Errors
/// Returns a [Utf8Error] if the interface specification isn't utf8 encoded.
pub fn iface(&self) -> Result<&str, Utf8Error> {
from_utf8(self.iface_bin())
}
/// Sets the interface specification to `iface`. No check is made whether `iface` is correctly
/// encoded as utf8.
///
/// # Result
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
/// [Some(())](Some).
pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> {
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256
@@ -47,17 +73,24 @@ impl SetPskRequest {
Some(())
}
/// Sets the interface specification to `iface`.
///
/// # Result
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
/// [Some(())](Some).
pub fn set_iface(&mut self, iface: &str) -> Option<()> {
self.set_iface_bin(iface.as_bytes())
}
}
/// Message format for response to the set pre-shared key operation.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct SetPskResponse {
pub return_code: u8,
}
/// Error type for the errors that can occur when setting a pre-shared key.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum SetPskError {
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
@@ -70,6 +103,12 @@ pub enum SetPskError {
pub type SetPskResult = Result<(), SetPskError>;
/// The return codes and their meanings for the set psk response operation.
///
/// [SetPskResponseReturnCode] is represented by by a single `u8` as required by the protocol.
///
/// # Example
/// See [SetPskResponseReturnCode::try_from] for an example.
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum SetPskResponseReturnCode {
@@ -85,6 +124,19 @@ pub struct InvalidSetPskResponseError;
impl TryFrom<u8> for SetPskResponseReturnCode {
type Error = InvalidSetPskResponseError;
/// Parse a [u8] as a [MsgType].
///
/// # Example
/// ```
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidSetPskResponseError, SetPskResponseReturnCode};
/// let return_code: u8 = 0x00; // Usually specifically set or comes out of a message.
/// let res = SetPskResponseReturnCode::try_from(return_code);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), SetPskResponseReturnCode::Success);
/// # Ok::<(), InvalidSetPskResponseError>(())
/// ```
/// # Errors
/// Returns a [InvalidSetPskResponseError] if `value` does not correspond to a known return code.
fn try_from(value: u8) -> Result<Self, Self::Error> {
use SetPskResponseReturnCode::*;
match value {
@@ -98,6 +150,9 @@ impl TryFrom<u8> for SetPskResponseReturnCode {
}
impl From<SetPskResponseReturnCode> for SetPskResult {
/// A [SetPskResult] can directly be deduced from a [SetPskResponseReturnCode].
/// An [Ok] type is only returned if `value` is [SetPskResponseReturnCode::Success].
/// Otherwise, an appropriate variant of [SetPskError] will be returned.
fn from(value: SetPskResponseReturnCode) -> Self {
use SetPskError as E;
use SetPskResponseReturnCode as C;
@@ -111,6 +166,7 @@ impl From<SetPskResponseReturnCode> for SetPskResult {
}
impl From<SetPskResult> for SetPskResponseReturnCode {
/// A [SetPskResponseReturnCode] can directly be deduced from a [SetPskResult].
fn from(value: SetPskResult) -> Self {
use SetPskError as E;
use SetPskResponseReturnCode as C;
@@ -123,18 +179,45 @@ impl From<SetPskResult> for SetPskResponseReturnCode {
}
}
/// The types of messages supported by this crate. At the time of writing, this is only
/// the message to set a pre-shared key.
///
/// [MsgType] is represented by a single `u8` as required by the protocol.
///
/// # Example
/// It is usually used like this:
/// ```
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidMessageTypeError, MsgType};
/// let typ: u8 = 0x01; // Usually specifically set or comes out of a message.
/// let typ = MsgType::try_from(typ)?;
/// let MsgType::SetPsk = typ; // Assert type.
/// # Ok::<(), InvalidMessageTypeError>(())
/// ```
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum MsgType {
SetPsk = 0x01,
}
/// Error indicating that an invalid [MsgType] was used.
/// This error is returned by [MsgType::try_from].
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct InvalidMessageTypeError;
impl TryFrom<u8> for MsgType {
type Error = InvalidMessageTypeError;
/// Parse a [u8] as a [MsgType].
///
/// # Example
/// ```rust
/// use rosenpass_wireguard_broker::api::msgs::MsgType;
/// let msg_type = MsgType::try_from(0x01);
/// assert!(msg_type.is_ok());
/// assert_eq!(msg_type.unwrap(), MsgType::SetPsk);
/// ```
/// # Errors
/// Returns an [InvalidMessageTypeError] if `value` does not correspond to a valid [MsgType].
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(MsgType::SetPsk),

View File

@@ -1,3 +1,10 @@
//! Server implementation for the WireGuard broker protocol.
//!
//! This module provides a server implementation that communicates with WireGuard broker clients
//! using a binary protocol. The server handles serialization and deserialization of messages,
//! error handling, and the core interaction flow.
//! Specifically, it handles requests to set a pre-shared key for a wireguard interface.
use std::borrow::BorrowMut;
use rosenpass_secret_memory::{Public, Secret};
@@ -7,12 +14,16 @@ use crate::WireGuardBroker;
use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr};
/// Error variants for the [BrokerServer].
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerServerError {
/// Indicates that an unknown request type was encountered.
#[error("No such request type: {}", .0)]
NoSuchRequestType(u8),
/// Indicates that an invalid message was sent.
#[error("Invalid message received.")]
InvalidMessage,
/// Indicates an error when configuration the network broker.
#[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr),
}
@@ -24,11 +35,18 @@ impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
}
}
/// The broker server. It requires an inner [WireGuardBroker] and an error type such
/// that the [msgs::SetPskError] implements [From] for the error type.
/// # Type Parameters
/// - `Err`: The used error type. Must be chosen such that [msgs::SetPskError] implements
/// [`From<Err>`](From)
///- `Inner`: A [WireGuardBroker]-type parametrized with `Err`.
pub struct BrokerServer<Err, Inner>
where
Inner: WireGuardBroker<Error = Err>,
msgs::SetPskError: From<Err>,
{
/// The inner [WireGuardBroker].
inner: Inner,
}
@@ -38,10 +56,17 @@ where
msgs::SetPskError: From<Err>,
Err: std::fmt::Debug,
{
/// Creates a new [BrokerServer] from a [WireGuardBroker].
pub fn new(inner: Inner) -> Self {
Self { inner }
}
/// Processes a message (at the moment only setting the pre-shared key is supported)
/// and takes the appropriate actions.
///
/// # Errors
/// - [BrokerServerError::InvalidMessage] if the message is not properly formatted or refers to
/// an unsupported message type.
pub fn handle_message(
&mut self,
req: &[u8],
@@ -53,22 +78,28 @@ where
let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type
let req = zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req)
.ok_or(BrokerServerError::InvalidMessage)?;
let mut res = zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res)
.ok_or(BrokerServerError::InvalidMessage)?;
let req =
zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req).ok_or(InvalidMessage)?;
let mut res =
zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res).ok_or(InvalidMessage)?;
res.msg_type = msgs::MsgType::SetPsk as u8;
self.handle_set_psk(&req.payload, &mut res.payload)?;
Ok(res.bytes().len())
}
/// Sets the pre-shared key for the interface identified in `req` to the pre-shared key
/// specified in `req`.
///
/// # Errors
/// - [InvalidMessage](BrokerServerError::InvalidMessage) if the `iface` specified in `req` is
/// longer than 255 bytes or not correctly encoded in utf8.
fn handle_set_psk(
&mut self,
req: &SetPskRequest,
res: &mut SetPskResponse,
) -> Result<(), BrokerServerError> {
// Using unwrap here since lenses can not return fixed-size arrays
// Using unwrap here since lenses can not return fixed-size arrays.
// TODO: Slices should give access to fixed size arrays
let peer_id = Public::from_slice(&req.peer_id);
let psk = Secret::from_slice(&req.psk);
@@ -95,3 +126,60 @@ where
Ok(())
}
}
// We can only include this test if this feature is enabled, because otherwise
// brokers::netlink::SetPskError is not defined.
#[cfg(all(feature = "experiment_api", target_os = "linux"))]
#[cfg(test)]
mod tests {
use crate::api::msgs;
use crate::api::msgs::{Envelope, SetPskRequest};
use crate::api::server::BrokerServer;
use crate::brokers::netlink::SetPskError;
use crate::{SerializedBrokerConfig, WireGuardBroker};
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
use zerocopy::AsBytes;
#[derive(Debug, Clone)]
struct MockWireGuardBroker {
psk: Secret<32>,
}
impl WireGuardBroker for MockWireGuardBroker {
type Error = SetPskError;
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error> {
self.psk = config.psk.clone();
Ok(())
}
}
#[test]
fn test_broker_server() {
secret_policy_use_only_malloc_secrets();
let mock_broker = MockWireGuardBroker {
psk: Secret::zero(),
};
let mut broker_server = BrokerServer::new(mock_broker);
let mut iface_buf: [u8; 255] = [0; 255];
// These are the utf encoded bytes of the string "wg0".
iface_buf[0] = 119;
iface_buf[1] = 103;
iface_buf[2] = 48;
let psk_req = SetPskRequest {
psk: [0; 32],
peer_id: [0; 32],
iface_size: 3,
iface_buf,
};
let req: Envelope<SetPskRequest> = Envelope {
msg_type: 0x01, // The only valid value.
reserved: [0, 0, 0],
payload: psk_req,
};
let mut res: [u8; msgs::RESPONSE_MSG_BUFFER_SIZE] = [0; msgs::RESPONSE_MSG_BUFFER_SIZE];
broker_server
.handle_message(req.as_bytes(), &mut res)
.unwrap();
}
}

View File

@@ -1,3 +1,12 @@
//! This module defines the Unix socket broker that interacts with the Linux-specific
//! WireGuard broker through a privileged process.
//!
//! It manages communication using length-prefixed
//! messages that are read from standard-input.
//! On each input message the process responds through its standard-output
//!
//! The functionality is only supported on Linux systems.
fn main() {
#[cfg(target_os = "linux")]
linux::main().unwrap();
@@ -8,20 +17,33 @@ fn main() {
#[cfg(target_os = "linux")]
pub mod linux {
//! Linux-specific implementation for the broker that communicates with the WireGuard broker.
use std::io::{stdin, stdout, Read, Write};
use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer;
use rosenpass_wireguard_broker::brokers::netlink as wg;
/// Represents errors that can occur during WireGuard broker operations
#[derive(thiserror::Error, Debug)]
pub enum BrokerAppError {
/// Wraps standard I/O errors that may occur during broker operations
#[error(transparent)]
IoError(#[from] std::io::Error),
/// Wraps WireGuard connection errors
#[error(transparent)]
WgConnectError(#[from] wg::ConnectError),
/// Wraps errors that occur when setting WireGuard Pre-Shared Keys (PSK)
#[error(transparent)]
WgSetPskError(#[from] wg::SetPskError),
/// Indicates that a received message exceeds the maximum allowed size
///
/// # Arguments
/// * `u64` - The size of the oversized message in bytes
#[error("Oversized message {}; something about the request is fatally wrong", .0)]
OversizedMessage(u64),
}

View File

@@ -1,3 +1,6 @@
//! Provides an asynchronous Unix socket handler for managing connections between clients
//! and privileged WireGuard broker processes.
use std::process::Stdio;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -12,6 +15,7 @@ use clap::{ArgGroup, Parser};
use rosenpass_util::fd::claim_fd;
use rosenpass_wireguard_broker::api::msgs;
/// Command-line arguments for configuring the socket handler
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(group(
@@ -45,11 +49,13 @@ struct Args {
command: Vec<String>,
}
/// Represents a request to the broker with a channel for receiving the response
struct BrokerRequest {
reply_to: oneshot::Sender<BrokerResponse>,
request: Vec<u8>,
}
/// Contains the broker's response data
struct BrokerResponse {
response: Vec<u8>,
}
@@ -87,6 +93,7 @@ async fn main() -> Result<()> {
}
}
/// Manages communication with the privileged broker process
async fn direct_broker_process(
mut queue: mpsc::Receiver<BrokerRequest>,
cmd: Vec<String>,
@@ -131,6 +138,7 @@ async fn direct_broker_process(
}
}
/// Accepts and handles incoming client connections
async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListener) -> Result<()> {
loop {
let (stream, _addr) = sock.accept().await?;
@@ -145,6 +153,7 @@ async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListen
// NOTE: If loop can ever terminate we need to join the spawned tasks
}
/// Handles individual client connections and message processing
async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> {
let mut req_buf = Vec::new();

View File

@@ -1,3 +1,52 @@
//! Asynchronous WireGuard PSK broker client using mio for non-blocking I/O.
//!
//! This module provides a client implementation that communicates with a WireGuard broker
//! through Unix domain sockets using non-blocking I/O operations. It's designed to be used
//! in event-driven applications using the mio event framework.
//!
//! # Examples
//!
//! ```no_run
//! # use mio::net::UnixStream;
//! # use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
//! # use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerMio};
//! # use mio::{Events, Interest, Poll, Token};
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::api::config::NetworkBrokerConfig;
//! # use rosenpass_wireguard_broker::SerializedBrokerConfig;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let socket = UnixStream::connect("/path/to/broker.sock")?;
//! let mut client = MioBrokerClient::new(socket);
//!
//! // Set up mio polling
//! let mut poll = Poll::new()?;
//! let mut events = Events::with_capacity(128);
//! client.register(&poll.registry(), Token(0))?;
//!
//! // Prepare PSK configuration
//! let network_config = NetworkBrokerConfig {
//! iface: "wg0",
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! };
//!
//! // Convert to serialized format and send
//! let config: SerializedBrokerConfig = network_config.into();
//! client.set_psk(config)?;
//!
//! // Process responses in event loop
//! loop {
//! poll.poll(&mut events, None)?;
//! for event in &events {
//! if event.token() == Token(0) {
//! client.process_poll()?;
//! }
//! }
//! }
//! # Ok(())
//! # }
//! ```
use anyhow::{bail, Context};
use mio::Interest;
use rosenpass_secret_memory::Secret;
@@ -13,12 +62,44 @@ use crate::api::client::{
};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerMio};
/// WireGuard broker client using mio for non-blocking I/O operations.
///
/// This client communicates with a WireGuard broker through a Unix domain socket,
/// using length-prefixed messages for communication. It supports both the basic
/// `WireGuardBroker` operations and non-blocking I/O through the
/// `WireguardBrokerMio` trait.
///
/// # Examples
///
/// ```no_run
/// use mio::net::UnixStream;
/// use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
/// use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig};
/// use rosenpass_secret_memory::{Public, Secret};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let socket = UnixStream::connect("/path/to/broker.sock")?;
/// let mut client = MioBrokerClient::new(socket);
///
/// // Set a PSK
/// let config = SerializedBrokerConfig {
/// interface: "wg0".as_bytes(),
/// peer_id: &Public::zero(), // Replace with actual peer ID
/// psk: &Secret::zero(), // Replace with actual PSK
/// additional_params: &[],
/// };
///
/// client.set_psk(config)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct MioBrokerClient {
inner: BrokerClient<MioBrokerClientIo>,
mio_token: Option<mio::Token>,
}
/// A buffer wrapper that provides secure memory for sensitive data.
#[derive(Debug)]
struct SecretBuffer<const N: usize>(pub Secret<N>);
@@ -43,6 +124,10 @@ impl<const N: usize> BorrowMut<[u8]> for SecretBuffer<N> {
type ReadBuffer = LengthPrefixDecoder<SecretBuffer<4096>>;
type WriteBuffer = LengthPrefixEncoder<SecretBuffer<4096>>;
/// I/O implementation for the broker client using non-blocking operations.
///
/// This type handles the low-level details of sending and receiving length-prefixed
/// messages over a Unix domain socket.
#[derive(Debug)]
struct MioBrokerClientIo {
socket: mio::net::UnixStream,
@@ -51,6 +136,10 @@ struct MioBrokerClientIo {
}
impl MioBrokerClient {
/// Creates a new client from a Unix domain socket.
///
/// The socket should be connected to a WireGuard broker server that speaks
/// the same protocol.
pub fn new(socket: mio::net::UnixStream) -> Self {
let read_buffer = LengthPrefixDecoder::new(SecretBuffer::new());
let write_buffer = LengthPrefixEncoder::from_buffer(SecretBuffer::new());
@@ -66,6 +155,10 @@ impl MioBrokerClient {
}
}
/// Polls for and processes any pending responses from the broker.
///
/// This method should be called when the socket becomes readable according
/// to mio events.
fn poll(&mut self) -> anyhow::Result<()> {
self.inner.io_mut().flush()?;

View File

@@ -1,3 +1,35 @@
//! Native Unix implementation of the WireGuard PSK broker using the `wg` command-line tool.
//!
//! This module provides an implementation that works on Unix systems by executing the `wg`
//! command-line tool to set pre-shared keys. It requires the `wg` tool to be installed and
//! accessible in the system PATH.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBroker, NativeUnixBrokerConfigBase};
//! use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerCfg, WG_KEY_LEN, WG_PEER_LEN};
//!
//! # fn main() -> Result<(), anyhow::Error> {
//! // Create a broker instance
//! let mut broker = NativeUnixBroker::new();
//!
//! // Create configuration
//! let config = NativeUnixBrokerConfigBase {
//! interface: "wg0".to_string(),
//! peer_id: Public::zero(), // Replace with actual peer ID
//! extra_params: Vec::new(),
//! };
//!
//! // Set PSK using the broker
//! let psk = Secret::<WG_KEY_LEN>::zero(); // Replace with actual PSK
//! let serialized_config = config.create_config(&psk);
//! broker.set_psk(serialized_config)?;
//! # Ok(())
//! # }
//! ```
use std::fmt::Debug;
use std::process::{Command, Stdio};
use std::thread;
@@ -12,9 +44,21 @@ use rosenpass_util::{b64::B64Display, file::StoreValueB64Writer};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerCfg, WireguardBrokerMio};
use crate::{WG_KEY_LEN, WG_PEER_LEN};
/// Maximum size of a base64-encoded WireGuard key in bytes
const MAX_B64_KEY_SIZE: usize = WG_KEY_LEN * 5 / 3;
/// Maximum size of a base64-encoded WireGuard peer ID in bytes
const MAX_B64_PEER_ID_SIZE: usize = WG_PEER_LEN * 5 / 3;
/// A WireGuard broker implementation that uses the native `wg` command-line tool.
///
/// This broker executes the `wg` command to set pre-shared keys. It supports both synchronous
/// operations through the `WireGuardBroker` trait and asynchronous operations through the
/// `WireguardBrokerMio` trait.
///
/// # Requirements
///
/// - The `wg` command-line tool must be installed and in the system PATH
/// - The user running the broker must have sufficient permissions to execute `wg` commands
#[derive(Debug)]
pub struct NativeUnixBroker {
mio_token: Option<mio::Token>,
@@ -110,16 +154,63 @@ impl WireguardBrokerMio for NativeUnixBroker {
}
}
/// Base configuration for the native Unix WireGuard broker.
///
/// This configuration type is used to store persistent broker settings and create
/// serialized configurations for individual PSK operations.
///
/// # Examples
///
/// ```
/// use rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBrokerConfigBase;
/// use rosenpass_secret_memory::Public;
/// use rosenpass_wireguard_broker::WG_PEER_LEN;
///
/// let config = NativeUnixBrokerConfigBase {
/// interface: "wg0".to_string(),
/// peer_id: Public::zero(),
/// extra_params: Vec::new(),
/// };
/// ```
#[derive(Debug, Builder)]
#[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfigBase {
/// Name of the WireGuard interface (e.g., "wg0")
pub interface: String,
/// Public key of the peer
pub peer_id: Public<WG_PEER_LEN>,
/// Additional parameters to pass to the wg command
#[builder(private)]
pub extra_params: Vec<u8>,
}
impl NativeUnixBrokerConfigBaseBuilder {
/// Sets the peer ID from a base64-encoded string.
///
/// # Arguments
///
/// * `peer_id` - Base64-encoded peer public key
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBrokerConfigBaseBuilder};
/// let mut peer_cfg = NativeUnixBrokerConfigBaseBuilder::default();
/// // set peer id to [48;32] encoded as base64
/// peer_cfg.peer_id_b64("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=")?;
/// peer_cfg.interface("wg0".to_string());
/// peer_cfg.extra_params_ser(&vec![])?;
/// let peer_cfg = peer_cfg.build()?;
/// assert_eq!(peer_cfg.peer_id.value, [48u8;32]);
///
/// let error = NativeUnixBrokerConfigBaseBuilder::default()
/// .peer_id_b64("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA") // invalid base64 encoding
/// .err().unwrap();
/// assert_eq!(error.to_string(), "Failed to parse peer id b64");
/// # Ok(())
/// # }
/// ```
pub fn peer_id_b64(
&mut self,
peer_id: &str,
@@ -133,6 +224,29 @@ impl NativeUnixBrokerConfigBaseBuilder {
Ok(self.peer_id(peer_id_b64))
}
/// Sets additional parameters for the wg command.
///
/// Note: This function cannot fail as `Vec<String>` is always serializable.
///
/// # Examples
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBrokerConfigBaseBuilder;
///
/// let mut peer_cfg = NativeUnixBrokerConfigBaseBuilder::default();
/// // Set typical wireguard parameters
/// peer_cfg.interface("wg0".to_string());
/// peer_cfg.peer_id_b64("Zm9v")?;
/// peer_cfg.extra_params_ser(&vec![
/// "persistent-keepalive".to_string(),
/// "25".to_string(),
/// "allowed-ips".to_string(),
/// "10.0.0.2/32".to_string(),
/// ])?;
/// let peer_cfg = peer_cfg.build()?;
/// # Ok(())
/// # }
/// ```
pub fn extra_params_ser(
&mut self,
extra_params: &Vec<String>,
@@ -157,12 +271,17 @@ impl WireguardBrokerCfg for NativeUnixBrokerConfigBase {
}
}
/// Runtime configuration for a single PSK operation.
#[derive(Debug, Builder)]
#[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfig<'a> {
/// WireGuard interface name
pub interface: &'a str,
/// Public key of the peer
pub peer_id: &'a Public<WG_PEER_LEN>,
/// Pre-shared key to set
pub psk: &'a Secret<WG_KEY_LEN>,
/// Additional wg command parameters
pub extra_params: Vec<String>,
}

View File

@@ -1,4 +1,31 @@
#![cfg(target_os = "linux")]
//! Linux-specific WireGuard PSK broker implementation using netlink.
//!
//! This module provides direct kernel communication through netlink sockets for managing
//! WireGuard pre-shared keys. It's more efficient than the command-line implementation
//! but only available on Linux systems.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
//! use rosenpass_wireguard_broker::brokers::netlink::NetlinkWireGuardBroker;
//! # use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
//! # secret_policy_use_only_malloc_secrets();
//!
//! let mut broker = NetlinkWireGuardBroker::new()?;
//!
//! let config = SerializedBrokerConfig {
//! interface: "wg0".as_bytes(),
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! additional_params: &[],
//! };
//!
//! broker.set_psk(config)?;
//! # Ok::<(), anyhow::Error>(())
//! ```
use std::fmt::Debug;
@@ -8,12 +35,14 @@ use crate::api::config::NetworkBrokerConfig;
use crate::api::msgs;
use crate::{SerializedBrokerConfig, WireGuardBroker};
/// Error that can occur when connecting to the WireGuard netlink interface.
#[derive(thiserror::Error, Debug)]
pub enum ConnectError {
#[error(transparent)]
ConnectError(#[from] wg::err::ConnectError),
}
/// Errors that can occur during netlink operations.
#[derive(thiserror::Error, Debug)]
pub enum NetlinkError {
#[error(transparent)]
@@ -22,6 +51,7 @@ pub enum NetlinkError {
GetDevice(#[from] wg::err::GetDeviceError),
}
/// Errors that can occur when setting a pre-shared key.
#[derive(thiserror::Error, Debug)]
pub enum SetPskError {
#[error("The indicated wireguard interface does not exist")]
@@ -32,18 +62,41 @@ pub enum SetPskError {
NetlinkError(#[from] NetlinkError),
}
/// # Example
/// ```
/// # use wireguard_uapi::err::NlError;
/// # use wireguard_uapi::linux::err::SetDeviceError;
/// use rosenpass_wireguard_broker::brokers::netlink::SetPskError;
/// let set_device_error: SetDeviceError = SetDeviceError::NlError(NlError::Msg("test-error".to_string()));
/// let set_psk_error: SetPskError = set_device_error.into();
/// ```
impl From<wg::err::SetDeviceError> for SetPskError {
fn from(err: wg::err::SetDeviceError) -> Self {
NetlinkError::from(err).into()
}
}
/// # Example
/// ```
/// # use wireguard_uapi::err::NlError;
/// # use wireguard_uapi::linux::err::GetDeviceError;
/// # use rosenpass_wireguard_broker::brokers::netlink::SetPskError;
/// let get_device_error: GetDeviceError = GetDeviceError::NlError(NlError::Msg("test-error".to_string()));
/// let set_psk_error: SetPskError = get_device_error.into();
/// ```
impl From<wg::err::GetDeviceError> for SetPskError {
fn from(err: wg::err::GetDeviceError) -> Self {
NetlinkError::from(err).into()
}
}
/// # Example
/// ```
/// use rosenpass_wireguard_broker::api::msgs::SetPskError as SetPskMsgsError;
/// use rosenpass_wireguard_broker::brokers::netlink::SetPskError as SetPskNetlinkError;
/// let set_psk_nlink_error: SetPskNetlinkError = SetPskNetlinkError::NoSuchInterface;
/// let set_psk_msgs_error = SetPskMsgsError::from(set_psk_nlink_error);
/// ```
use msgs::SetPskError as SetPskMsgsError;
use SetPskError as SetPskNetlinkError;
impl From<SetPskNetlinkError> for SetPskMsgsError {
@@ -55,11 +108,33 @@ impl From<SetPskNetlinkError> for SetPskMsgsError {
}
}
/// WireGuard broker implementation using Linux netlink sockets.
///
/// This implementation communicates directly with the kernel through netlink sockets,
/// providing better performance than command-line based implementations.
///
/// # Examples
///
/// ```
/// use rosenpass_wireguard_broker::brokers::netlink::NetlinkWireGuardBroker;
/// use rosenpass_wireguard_broker::WireGuardBroker;
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let mut broker = NetlinkWireGuardBroker::new()?;
/// # Ok(())
/// # }
/// ```
///
/// # Platform Support
///
/// This implementation is only available on Linux systems and requires appropriate
/// permissions to use netlink sockets.
pub struct NetlinkWireGuardBroker {
sock: wg::WgSocket,
}
impl NetlinkWireGuardBroker {
/// Opens a netlink socket to the WireGuard kernel module
/// and returns a new netlink-based WireGuard broker.
pub fn new() -> Result<Self, ConnectError> {
let sock = wg::WgSocket::connect()?;
Ok(Self { sock })
@@ -112,3 +187,27 @@ impl WireGuardBroker for NetlinkWireGuardBroker {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Public, Secret};
#[test]
fn smoke_test() -> Result<(), Box<dyn std::error::Error>> {
secret_policy_use_only_malloc_secrets();
let result = NetlinkWireGuardBroker::new();
assert!(result.is_ok());
let mut broker = result.unwrap();
let peer_id = Public::zero();
let psk = Secret::zero();
let config: SerializedBrokerConfig = NetworkBrokerConfig {
iface: "wg0",
peer_id: &peer_id,
psk: &psk,
}
.into();
let result = broker.set_psk(config);
assert!(result.is_err());
Ok(())
}
}

View File

@@ -1,38 +1,106 @@
//! A broker interface for managing WireGuard pre-shared keys (PSK).
//!
//! This crate provides traits and implementations for interacting with WireGuard interfaces
//! to set pre-shared keys for peers. It supports different backend implementations including:
//! - Native Unix command-line interface
//! - Linux netlink interface
//! - Custom Unix socket protocol
//!
//! # Examples
//!
//! ```no_run
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
//! # use std::error::Error;
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! # rosenpass_secret_memory::policy::secret_policy_try_use_memfd_secrets();
//! # let interface = "wg0";
//! # let peer_id = Public::<WG_PEER_LEN>::zero();
//! # let psk = Secret::<WG_KEY_LEN>::zero();
//!
//! // Create a native Unix broker
//! let mut broker = rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBroker::new();
//!
//! // Configure and set PSK
//! let config = SerializedBrokerConfig {
//! interface: interface.as_bytes(),
//! peer_id: &peer_id,
//! psk: &psk,
//! additional_params: &[],
//! };
//!
//! broker.set_psk(config)?;
//! # Ok(())
//! # }
//! ```
use rosenpass_secret_memory::{Public, Secret};
use std::fmt::Debug;
/// Length of a WireGuard key in bytes
pub const WG_KEY_LEN: usize = 32;
/// Length of a WireGuard peer ID in bytes
pub const WG_PEER_LEN: usize = 32;
/// Core trait for WireGuard PSK brokers.
///
/// This trait defines the basic interface for setting pre-shared keys (PSK) on WireGuard interfaces.
/// Implementations handle the actual communication with WireGuard, whether through command-line tools,
/// netlink, or other mechanisms.
pub trait WireGuardBroker: Debug {
/// The error type returned by broker operations
type Error;
/// Set a pre-shared key for a WireGuard peer
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error>;
}
/// Configuration trait for WireGuard PSK brokers.
///
/// This trait allows creation of broker configurations from a PSK and implementation-specific
/// configuration data.
pub trait WireguardBrokerCfg: Debug {
/// Creates a serialized broker configuration from this config and a specific PSK
fn create_config<'a>(&'a self, psk: &'a Secret<WG_KEY_LEN>) -> SerializedBrokerConfig<'a>;
}
/// Serialized configuration for WireGuard PSK operations.
#[derive(Debug)]
pub struct SerializedBrokerConfig<'a> {
/// The WireGuard interface name as UTF-8 bytes
pub interface: &'a [u8],
/// The public key of the peer
pub peer_id: &'a Public<WG_PEER_LEN>,
/// The pre-shared key to set
pub psk: &'a Secret<WG_KEY_LEN>,
/// Additional implementation-specific parameters
pub additional_params: &'a [u8],
}
/// Extension trait for mio integration with WireGuard brokers.
///
/// This trait extends the basic `WireGuardBroker` functionality with asynchronous I/O
/// operations using the mio event framework.
pub trait WireguardBrokerMio: WireGuardBroker {
/// The error type for mio operations
type MioError;
/// Register interested events for mio::Registry
/// Register the broker with a mio Registry for event notifications
fn register(
&mut self,
registry: &mio::Registry,
token: mio::Token,
) -> Result<(), Self::MioError>;
/// Get the mio token associated with this broker, if any
fn mio_token(&self) -> Option<mio::Token>;
/// Run after a mio::poll operation
/// Process events after a mio poll operation
fn process_poll(&mut self) -> Result<(), Self::MioError>;
/// Unregister the broker from a mio Registry
fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError>;
}