Compare commits

...

45 Commits

Author SHA1 Message Date
Karolin Varner
ed5d7b4fa4 WIP support for the Hermit microkernel.
A whole bunch of close-to-the-metal crates are yielding compiler errors
upon compilation with --target x86_64-unknown-hermit. MIO is not.

Using hermit might necessitate relying on threads for IO instead of
using MIO.

https://github.com/hermit-os/kernel/issues/1043
2024-01-27 23:17:10 +01:00
Karolin Varner
97a2a30678 feat: First version of broker based WireGuard PSK interface
This allows us to run with minimal priviledges in the Rosenpass process itself
2024-01-27 21:48:36 +01:00
Karolin Varner
9690085156 chore: Cargo fmt 2024-01-27 21:38:13 +01:00
Karolin Varner
ca972e8b70 feat: Remove libsodium 2024-01-27 21:38:13 +01:00
Karolin Varner
2fa0a2a72a feat: Use core::hint::black_box in rosenpass_constant_time::xor 2024-01-27 21:38:13 +01:00
Karolin Varner
b6203683fc feat: Migrate away from sodium blake2b towards the rust crypto implementation 2024-01-27 21:38:13 +01:00
Karolin Varner
e0f75ab97e feat: Use xchacha implementation from rust crypto instead of sodium 2024-01-27 21:38:13 +01:00
Karolin Varner
0789c60602 feat: Use chacha implementation from rust crypto instead of sodium 2024-01-27 21:38:13 +01:00
Karolin Varner
e42f90b048 chore: Add helper to turn typenums into const values 2024-01-27 21:38:13 +01:00
Emil Engler
29917fd7a6 doc: Fix keygen/gen-keys misspell
Fixes #166
2024-01-21 20:54:29 +01:00
wucke13
62aa9b4351 fix: second round of clippy lints
Clippy would not automatically apply these fixes, so they were applied
by hand.
2024-01-03 18:43:05 +01:00
wucke13
26cb4a587f fix: apply clippy lints 2024-01-03 18:43:05 +01:00
wucke13
1c14be38dd fix: make benches work again
Somehow in the past while splitting into many crates, we broke the bench
setup. This commit both fixes it, and adds a CI job that ensures it is
still working to avoid such silent failure in the future. The benchmarks
are not actually run, they would take forever on the slow GitHub Actions
runners, but they are at least compiled.
2024-01-03 18:43:05 +01:00
Karolin Varner
30cb0e9801 chore: Remove references to libsodium from secret-memory 2024-01-03 18:43:05 +01:00
Karolin Varner
9824db4f09 fix: Migrate away from lazy_static in favor of thread_local
The new secret memory pool was causing CI failures in the fuzzing code,
due to the fuzzer compiling its binaries with memory sanitizer support.

https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html

Using lazy_static was – intentionally – introducing a memory leak, but the
LeakSanitizer detected this and raised an error.

Now by using thread_local we are calling the destructors and so – while still being a
memory leak in practice – the LeakSanitizer no longer detects this behaviour as an error.

Alternatively we could have used a known-leaks list with the leak-sanitizer, but this would have increased the complexity of the build setup.

Finally, this was likely triggered with the migration to memsec, because libsodium circumvents the malloc/free calls,
relying on direct calls to MMAP.
2024-01-03 18:43:05 +01:00
Karolin Varner
e3b72487db fix: Make sure all tests are run during CI runs
Had to fix the tests in util/src/result.rs.
2024-01-03 18:43:05 +01:00
Karolin Varner
85c447052e feat: Migrate to memsec 2024-01-03 18:43:05 +01:00
James Brownlee
b2a64ed17a feat: add INITIATOR_TEST and RESPONDER_TEST macros
Added INITIATOR_TEST and RESPONDER_TEST macros to the identity hiding
mpv file that can be used to selectively test the anonymity of the
initiator or the responder.
2024-01-03 18:35:54 +01:00
James Brownlee
91da0dfd2d feat: identity hiding in two stage process
Changed identity hiding test to work as a two stage process where
participants with fresh secure secret keys communicate with each other
and other compromised participants. Then the attacker is asked to
identify the difference between two of the secure participants as on of
them acts as a responder.
2024-01-03 18:35:54 +01:00
James Brownlee
4a170b1983 feat: add inital identity hiding code to proverif 2024-01-03 18:35:54 +01:00
wucke13
7c83e244f9 fix: fix Rust code in markdown files
This applies the novel format_rustcode.sh script to the markdown files in the
repo, to maintain a consistent style across code examples.
2023-12-22 17:57:32 +01:00
alankritdabral_2
eb76179dc4 feat: add format_rustcode.sh script
This script makes it possible to check formatting of rust code found in the various markdown files in the repo. It is also added as a job to the QC CI workflow.
2023-12-22 17:57:32 +01:00
wucke13
d84efa7422 Merge pull request #197 from guhitb/main
Add backwards compatibility for keygen command
2023-12-21 11:28:25 +01:00
user
61ef5b92bb fix: add deprecated keygen command
This allows users to use the old keygen command, while being informed
about its deprecation.
2023-12-20 16:03:47 +01:00
wucke13
184cff0e5e Merge pull request #196 from rosenpass/dev/fix-65
fix: remove OSFONTDIR var from whitepaper build
2023-12-03 14:01:25 +01:00
wucke13
9819148b6f fix: remove OSFONTDIR var from whitepaper build
Fixes #65. I checked with `pdffonts` that the whitepaper still has all fonts embedded.
2023-12-03 13:27:47 +01:00
Morgan Hill
3a0ebd2cbc feat: Add fuzzing for libsodium allocator 2023-12-02 14:14:05 +01:00
Karolin Varner
1eefb5f263 fix: Guaranteed results typo 2023-12-02 12:21:41 +01:00
Karolin Varner
d45e24e9b6 feat: Move lenses into library 2023-12-02 12:21:41 +01:00
Karolin Varner
972e82b35f chore: Move kems out of rosenpass crate 2023-12-02 10:42:13 +01:00
Karolin Varner
101c9bf4b3 feat: Add an internal library for guaranteed results
This is helpful for functions that have to return a result to
implement some interface but that do not actually need to return
a result value.
2023-12-02 10:42:13 +01:00
Marei (peiTeX)
955d57ea49 fix output of authorlist to support unlimited authors 2023-12-01 20:25:58 +01:00
Karolin Varner
838f700a74 chore: Upgrade dependencies 2023-12-01 18:43:32 +01:00
Karolin Varner
5448cdc565 feat: Use the rand crate for random values instead of sodium 2023-12-01 18:37:33 +01:00
Karolin Varner
77cd8a9fd1 feat: Move prftree into ciphers crate
- Use a new nomenclature for these functions based on the idea of a hash
  domain (as in domain separation); this makes much more sence
- Remove the ciphers::hash export; we did not even export a hash
  function in the purest sence of the word. This gets us around the
  difficulty of figuring out what we should call the underlying
  primitive
2023-12-01 18:36:46 +01:00
Karolin Varner
0f89ab7976 chore: Shorten fuzzing runtime to make sure the CI finishes quickly 2023-12-01 18:30:16 +01:00
Karolin Varner
70fa9bd6d7 feat: Wrap sodium_malloc as a custom allocator
This lets us get rid of quite a few unsafe blocks.
2023-12-01 18:29:53 +01:00
Karolin Varner
85a61808de feat: Use the zeroize crate for zeroization 2023-12-01 18:11:05 +01:00
Karolin Varner
cf132bca11 chore: Move rest of coloring.rs into secret-memory crate
Also removes the StoreSecret trait from cli.rs as it was
redundant.
2023-12-01 18:11:05 +01:00
Karolin Varner
7bda010a9b chore: Move Public and debug_crypto_array into secret-memory crate 2023-12-01 18:11:05 +01:00
Olaf Pichler
36089fd37f Added example for additional PSK 2023-12-01 15:44:42 +01:00
Olaf Pichler
31d43accd5 #172 removed exchange_command 2023-12-01 15:44:42 +01:00
Olaf Pichler
205c301012 Added indications that file paths are used 2023-12-01 15:44:42 +01:00
Olaf Pichler
d014095469 Added indication that exchange_command is not used 2023-12-01 15:44:42 +01:00
Olaf Pichler
7cece82119 added WireGuard config example to gen-config 2023-12-01 15:44:42 +01:00
89 changed files with 4068 additions and 1809 deletions

View File

@@ -25,6 +25,34 @@ jobs:
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
rustfmt:
name: Rust Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Rust Formatting Script
run: bash format_rust_code.sh --mode check
cargo-bench:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install libsodium
run: sudo apt-get install -y libsodium-dev
# liboqs requires quite a lot of stack memory, thus we adjust
# the default stack size picked for new threads (which is used
# by `cargo test`) to be _big enough_. Setting it to 8 MiB
- run: RUST_MIN_STACK=8388608 cargo bench --no-run --workspace
cargo-audit: cargo-audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -93,7 +121,7 @@ jobs:
# liboqs requires quite a lot of stack memory, thus we adjust # liboqs requires quite a lot of stack memory, thus we adjust
# the default stack size picked for new threads (which is used # the default stack size picked for new threads (which is used
# by `cargo test`) to be _big enough_. Setting it to 8 MiB # by `cargo test`) to be _big enough_. Setting it to 8 MiB
- run: RUST_MIN_STACK=8388608 cargo test - run: RUST_MIN_STACK=8388608 cargo test --workspace
cargo-test-nix-devshell-x86_64-linux: cargo-test-nix-devshell-x86_64-linux:
runs-on: runs-on:
@@ -116,7 +144,7 @@ jobs:
with: with:
name: rosenpass name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix develop --command cargo test - run: nix develop --command cargo test --workspace
cargo-fuzz: cargo-fuzz:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -141,8 +169,10 @@ jobs:
run: cargo install cargo-fuzz run: cargo install cargo-fuzz
- name: Run fuzzing - name: Run fuzzing
run: | run: |
cargo fuzz run fuzz_aead_enc_into -- -max_total_time=60 cargo fuzz run fuzz_aead_enc_into -- -max_total_time=5
cargo fuzz run fuzz_blake2b -- -max_total_time=60 cargo fuzz run fuzz_blake2b -- -max_total_time=5
cargo fuzz run fuzz_handle_msg -- -max_total_time=60 cargo fuzz run fuzz_handle_msg -- -max_total_time=5
cargo fuzz run fuzz_kyber_encaps -- -max_total_time=60 ulimit -s 8192000 && RUST_MIN_STACK=33554432000 && cargo fuzz run fuzz_kyber_encaps -- -max_total_time=5
cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=60 cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=5
cargo fuzz run fuzz_box_secret_alloc -- -max_total_time=5
cargo fuzz run fuzz_vec_secret_alloc -- -max_total_time=5

800
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,16 +3,21 @@ resolver = "2"
members = [ members = [
"rosenpass", "rosenpass",
"cipher-traits",
"ciphers", "ciphers",
"util", "util",
"constant-time", "constant-time",
"sodium", "oqs",
"to", "to",
"fuzz", "fuzz",
"secret-memory",
"lenses",
"wireguard-broker",
] ]
default-members = [ default-members = [
"rosenpass" "rosenpass",
"wireguard-broker"
] ]
[workspace.metadata.release] [workspace.metadata.release]
@@ -23,28 +28,37 @@ tag-prefix = ""
rosenpass = { path = "rosenpass" } rosenpass = { path = "rosenpass" }
rosenpass-util = { path = "util" } rosenpass-util = { path = "util" }
rosenpass-constant-time = { path = "constant-time" } rosenpass-constant-time = { path = "constant-time" }
rosenpass-sodium = { path = "sodium" } rosenpass-cipher-traits = { path = "cipher-traits" }
rosenpass-ciphers = { path = "ciphers" } rosenpass-ciphers = { path = "ciphers" }
rosenpass-to = { path = "to" } rosenpass-to = { path = "to" }
rosenpass-secret-memory = { path = "secret-memory" }
rosenpass-oqs = { path = "oqs" }
rosenpass-lenses = { path = "lenses" }
rosenpass-wireguard-broker = { path = "wireguard-broker" }
criterion = "0.4.0" criterion = "0.4.0"
test_bin = "0.4.0" test_bin = "0.4.0"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
stacker = "0.1.15" stacker = "0.1.15"
doc-comment = "0.3.3" doc-comment = "0.3.3"
base64 = "0.21.1" base64 = "0.21.5"
zeroize = "1.7.0" zeroize = "1.7.0"
memoffset = "0.9.0" memoffset = "0.9.0"
lazy_static = "1.4.0" thiserror = "1.0.50"
thiserror = "1.0.40" paste = "1.0.14"
paste = "1.0.12" env_logger = "0.10.1"
env_logger = "0.10.0" toml = "0.7.8"
toml = "0.7.4"
static_assertions = "1.1.0" static_assertions = "1.1.0"
log = { version = "0.4.17" } allocator-api2 = "0.2.14"
clap = { version = "4.3.0", features = ["derive"] } allocator-api2-tests = "0.2.14"
serde = { version = "1.0.163", features = ["derive"] } rand = "0.8.5"
wireguard-uapi = "3.0.0"
typenum = "1.17.0"
log = { version = "0.4.20" }
clap = { version = "4.4.10", features = ["derive"] }
serde = { version = "1.0.193", features = ["derive"] }
arbitrary = { version = "1.3.2", features = ["derive"] } arbitrary = { version = "1.3.2", features = ["derive"] }
anyhow = { version = "1.0.71", features = ["backtrace"] } anyhow = { version = "1.0.75", features = ["backtrace", "std"] }
mio = { version = "0.8.6", features = ["net", "os-poll"] } mio = { version = "0.8.9", features = ["net"] }
libsodium-sys-stable= { version = "1.19.28", features = ["use-pkg-config"] } oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] } blake2 = "0.10.6"
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ "std", "heapless" ] }

View File

@@ -0,0 +1,121 @@
/*
This identity hiding process tests whether the rosenpass protocol is able to protect the identity of an initiator or responder.
The participants in the test are trusted initiators, trusted responders and compromised initiators and responders.
The test consists of two phases. In the first phase all of the participants can communicate with each other using the rosenpass protocol.
An attacker observes the first phase and is able to intercept and modify messages and choose participants to communicate with each other
In the second phase if the anonymity of an initiator is being tested then one of two trusted initiators is chosen.
The chosen initiator communicates directly with a trusted responder.
If an attacker can determine which initiator was chosen then the anonymity of the initiator has been compromised.
Otherwise the protocol has successfully protected the initiators identity.
If the anonymity of a responder is being tested then one of two trusted responders is chosen instead.
Then an initiator communicates directly with the chosen responder.
If an attacker can determine which responder was chosen then the anonymity of the responder is compromised.
Otherwise the protocol successfully protects the identity of a responder.
The Proverif code treats the public key as synonymous with identity.
In the above test when a responder or initiator is chosen what is actually chosen is the public/private key pair to use for communication.
Traditionally when a responder or initiator is chosen they would be chosen randomly.
The way Proverif makes a "choice" is by simulating multiple processes, one process per choice
Then the processes are compared and if an association between a public key and a process can be made the test fails.
As the choice is at least as bad as choosing the worst possible option the credibility of the test is maintained.
The drawback is that Proverif is only able to tell if the identity can be brute forced but misses any probabilistic associations.
As usual Proverif also assumes perfect encryption and in particular assumes encryption cannot be linked to identity.
One of the tradeoffs made here is that the choice function in Proverif is slow but this is in favour of being able to write more precise tests.
Another issue is the choice function does not work with queries so a test needs to be run for each set of assumptions.
In this case the test uses secure rng and a fresh secure biscuit key.
*/
#include "config.mpv"
#define CHAINING_KEY_EVENTS 1
#define MESSAGE_TRANSMISSION_EVENTS 1
#define SESSION_START_EVENTS 0
#define RANDOMIZED_CALL_IDS 0
#undef FULL_MODEL
#undef SIMPLE_MODEL
#define SIMPLE_MODEL 1
#include "prelude/basic.mpv"
#include "crypto/key.mpv"
#include "rosenpass/oracles.mpv"
#include "crypto/kem.mpv"
#define INITIATOR_TEST
#define NEW_TRUSTED_SEED(name) \
new MCAT(name, _secret_seed):seed_prec; \
name <- make_trusted_seed(MCAT(name, _secret_seed)); \
free D:channel [private].
free secure_biscuit_no:Atom [private].
free secure_sidi,secure_sidr:SessionId [private].
free secure_psk:key [private].
free initiator1, initiator2:kem_sk_prec.
free responder1, responder2:kem_sk_prec.
let secure_init_hello(initiator: kem_sk_tmpl, sidi : SessionId, psk: key_tmpl, responder: kem_sk_tmpl) =
NEW_TRUSTED_SEED(seski_trusted_seed)
NEW_TRUSTED_SEED(ssptr_trusted_seed)
Oinitiator_inner(sidi, initiator, psk, responder, seski_trusted_seed, ssptr_trusted_seed, D).
let secure_resp_hello(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, sidr:SessionId, sidi:SessionId, biscuit_no:Atom, psk:key_tmpl) =
in(D, Envelope(k, IH2b(InitHello(=sidi, epki, sctr, pidiC, auth))));
ih <- InitHello(sidi, epki, sctr, pidiC, auth);
NEW_TRUSTED_SEED(septi_trusted_seed)
NEW_TRUSTED_SEED(sspti_trusted_seed)
Oinit_hello_inner(sidr, biscuit_no, responder, psk, initiator, septi_trusted_seed, sspti_trusted_seed, ih, D).
let secure_init_conf(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, psk:key_tmpl, sidi:SessionId, sidr:SessionId) =
in(D, Envelope(k3, IC2b(InitConf(=sidi, =sidr, biscuit, auth3))));
ic <- InitConf(sidi,sidr,biscuit, auth3);
NEW_TRUSTED_SEED(seski_trusted_seed)
NEW_TRUSTED_SEED(ssptr_trusted_seed)
Oinit_conf_inner(initiator, psk, responder, ic).
let secure_communication(initiator: kem_sk_tmpl, responder:kem_sk_tmpl) =
secure_key <- prepare_key(secure_psk);
(!secure_init_hello(initiator, secure_sidi, secure_key, responder))
| !secure_resp_hello(initiator, responder, secure_sidr, secure_sidi, secure_biscuit_no, secure_key)
| !(secure_init_conf(initiator, responder, secure_key, secure_sidi, secure_sidr)).
let pipeChannel(D:channel, C:channel) =
in(D, b:bits);
out(C, b).
fun kem_private(kem_pk): kem_sk
reduc forall sk_tmpl:kem_sk;
kem_private(kem_pub(sk_tmpl)) = sk_tmpl[private].
let secretCommunication() =
#ifdef INITIATOR_TEST
initiator_pk <- choice[setup_kem_pk(make_trusted_kem_sk(initiator1)), setup_kem_pk(make_trusted_kem_sk(initiator2))];
initiator_seed <- prepare_kem_sk(kem_private(initiator_pk));
#else
initiator_seed <- prepare_kem_sk(trusted_kem_sk(initiator1));
#endif
#ifdef RESPONDER_TEST
responder_pk <- choice[setup_kem_pk(make_trusted_kem_sk(responder1)), setup_kem_pk(make_trusted_kem_sk(responder2))];
responder_seed <- prepare_kem_sk(kem_private(responder_pk));
#else
responder_seed <- prepare_kem_sk(trusted_kem_sk(responder1));
#endif
secure_communication(initiator_seed, responder_seed) | !pipeChannel(D, C).
let reveal_pks() =
out(C, setup_kem_pk(make_trusted_kem_sk(responder1)));
out(C, setup_kem_pk(make_trusted_kem_sk(responder2)));
out(C, setup_kem_pk(make_trusted_kem_sk(initiator1)));
out(C, setup_kem_pk(make_trusted_kem_sk(initiator2))).
let rosenpass_main2() =
REP(INITIATOR_BOUND, Oinitiator)
| REP(RESPONDER_BOUND, Oinit_hello)
| REP(RESPONDER_BOUND, Oinit_conf).
let identity_hiding_main() =
0 | reveal_pks() | rosenpass_main2() | phase 1; secretCommunication().
let main = identity_hiding_main.

View File

@@ -47,14 +47,16 @@ CK_EV( event OskOinit_conf(key, key). )
MTX_EV( event ICRjct(InitConf_t, key, kem_sk, kem_pk). ) MTX_EV( event ICRjct(InitConf_t, key, kem_sk, kem_pk). )
SES_EV( event ResponderSession(InitConf_t, key). ) SES_EV( event ResponderSession(InitConf_t, key). )
event ConsumeBiscuit(Atom, kem_sk, kem_pk, Atom). event ConsumeBiscuit(Atom, kem_sk, kem_pk, Atom).
let Oinit_conf() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic)); let Oinit_conf_inner(Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t) =
#if RANDOMIZED_CALL_IDS #if RANDOMIZED_CALL_IDS
new call:Atom; new call:Atom;
#else #else
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic); call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
#endif #endif
SETUP_HANDSHAKE_STATE() SETUP_HANDSHAKE_STATE()
eski <- kem_sk0; eski <- kem_sk0;
epki <- kem_pk0; epki <- kem_pk0;
let try_ = ( let try_ = (
@@ -72,6 +74,10 @@ let Oinit_conf() =
0 0
#endif #endif
). ).
let Oinit_conf() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
Oinit_conf_inner(Ssskm, Spsk, Sspkt, ic).
restriction biscuit_no:Atom, sskm:kem_sk, spkr:kem_pk, ad1:Atom, ad2:Atom; restriction biscuit_no:Atom, sskm:kem_sk, spkr:kem_pk, ad1:Atom, ad2:Atom;
event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad1)) && event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad2)) event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad1)) && event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad2))
@@ -85,8 +91,8 @@ CK_EV( event OskOresp_hello(key, key, key). )
MTX_EV( event RHRjct(RespHello_t, key, kem_sk, kem_pk). ) MTX_EV( event RHRjct(RespHello_t, key, kem_sk, kem_pk). )
MTX_EV( event ICSent(RespHello_t, InitConf_t, key, kem_sk, kem_pk). ) MTX_EV( event ICSent(RespHello_t, InitConf_t, key, kem_sk, kem_pk). )
SES_EV( event InitiatorSession(RespHello_t, key). ) SES_EV( event InitiatorSession(RespHello_t, key). )
let Oresp_hello(HS_DECL_ARGS) = let Oresp_hello(HS_DECL_ARGS, C_in:channel) =
in(C, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth))); in(C_in, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth)));
rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth); rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth);
/* try */ let ic = ( /* try */ let ic = (
ck_ini <- ck; ck_ini <- ck;
@@ -98,7 +104,7 @@ let Oresp_hello(HS_DECL_ARGS) =
SES_EV( event InitiatorSession(rh, osk); ) SES_EV( event InitiatorSession(rh, osk); )
ic ic
/* success */ ) in ( /* success */ ) in (
out(C, ic) out(C_in, Envelope(create_mac(spkt, IC2b(ic)), IC2b(ic)))
/* fail */ ) else ( /* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS #if MESSAGE_TRANSMISSION_EVENTS
event RHRjct(rh, psk, sski, spkr) event RHRjct(rh, psk, sski, spkr)
@@ -116,8 +122,8 @@ MTX_EV( event IHRjct(InitHello_t, key, kem_sk, kem_pk). )
MTX_EV( event RHSent(InitHello_t, RespHello_t, key, kem_sk, kem_pk). ) MTX_EV( event RHSent(InitHello_t, RespHello_t, key, kem_sk, kem_pk). )
event ConsumeSidr(SessionId, Atom). event ConsumeSidr(SessionId, Atom).
event ConsumeBn(Atom, kem_sk, kem_pk, Atom). event ConsumeBn(Atom, kem_sk, kem_pk, Atom).
let Oinit_hello() =
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih)); let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt: kem_sk_tmpl, Septi: seed_tmpl, Sspti: seed_tmpl, ih: InitHello_t, C_out:channel) =
#if RANDOMIZED_CALL_IDS #if RANDOMIZED_CALL_IDS
new call:Atom; new call:Atom;
#else #else
@@ -125,14 +131,19 @@ let Oinit_hello() =
#endif #endif
// TODO: This is ugly // TODO: This is ugly
let InitHello(sidi, epki, sctr, pidiC, auth) = ih in let InitHello(sidi, epki, sctr, pidiC, auth) = ih in
SETUP_HANDSHAKE_STATE() SETUP_HANDSHAKE_STATE()
eski <- kem_sk0; eski <- kem_sk0;
epti <- rng_key(setup_seed(Septi)); // RHR4
spti <- rng_key(setup_seed(Sspti)); // RHR5
event ConsumeBn(biscuit_no, sskm, spkt, call); event ConsumeBn(biscuit_no, sskm, spkt, call);
event ConsumeSidr(sidr, call); event ConsumeSidr(sidr, call);
epti <- rng_key(setup_seed(Septi)); // RHR4
spti <- rng_key(setup_seed(Sspti)); // RHR5
event ConsumeSeed(Epti, setup_seed(Septi), call); event ConsumeSeed(Epti, setup_seed(Septi), call);
event ConsumeSeed(Spti, setup_seed(Sspti), call); event ConsumeSeed(Spti, setup_seed(Sspti), call);
let rh = ( let rh = (
INITHELLO_CONSUME() INITHELLO_CONSUME()
ck_ini <- ck; ck_ini <- ck;
@@ -141,7 +152,8 @@ let Oinit_hello() =
MTX_EV( event RHSent(ih, rh, psk, sskr, spki); ) MTX_EV( event RHSent(ih, rh, psk, sskr, spki); )
rh rh
/* success */ ) in ( /* success */ ) in (
out(C, rh) out(C_out, Envelope(create_mac(spkt, RH2b(rh)), RH2b(rh)))
/* fail */ ) else ( /* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS #if MESSAGE_TRANSMISSION_EVENTS
event IHRjct(ih, psk, sskr, spki) event IHRjct(ih, psk, sskr, spki)
@@ -150,6 +162,10 @@ let Oinit_hello() =
#endif #endif
). ).
let Oinit_hello() =
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
Oinit_hello_inner(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih, C).
restriction sid:SessionId, ad1:Atom, ad2:Atom; restriction sid:SessionId, ad1:Atom, ad2:Atom;
event(ConsumeSidr(sid, ad1)) && event(ConsumeSidr(sid, ad2)) event(ConsumeSidr(sid, ad1)) && event(ConsumeSidr(sid, ad2))
==> ad1 = ad2. ==> ad1 = ad2.
@@ -167,26 +183,34 @@ CK_EV( event OskOinitiator_ck(key). )
CK_EV( event OskOinitiator(key, key, kem_sk, kem_pk, key). ) CK_EV( event OskOinitiator(key, key, kem_sk, kem_pk, key). )
MTX_EV( event IHSent(InitHello_t, key, kem_sk, kem_pk). ) MTX_EV( event IHSent(InitHello_t, key, kem_sk, kem_pk). )
event ConsumeSidi(SessionId, Atom). event ConsumeSidi(SessionId, Atom).
let Oinitiator_inner(sidi: SessionId, Ssskm: kem_sk_tmpl, Spsk: key_tmpl, Sspkt: kem_sk_tmpl, Seski: seed_tmpl, Ssptr: seed_tmpl, C_out:channel) =
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
#endif
SETUP_HANDSHAKE_STATE()
sidr <- sid0;
RNG_KEM_PAIR(eski, epki, Seski) // IHI3
sptr <- rng_key(setup_seed(Ssptr)); // IHI5
event ConsumeSidi(sidi, call);
event ConsumeSeed(Sptr, setup_seed(Ssptr), call);
event ConsumeSeed(Eski, setup_seed(Seski), call);
INITHELLO_PRODUCE()
CK_EV( event OskOinitiator_ck(ck); )
CK_EV( event OskOinitiator(ck, psk, sski, spkr, sptr); )
MTX_EV( event IHSent(ih, psk, sski, spkr); )
out(C_out, Envelope(create_mac(spkt, IH2b(ih)), IH2b(ih)));
Oresp_hello(HS_PASS_ARGS, C_out).
let Oinitiator() = let Oinitiator() =
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr)); in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
#if RANDOMIZED_CALL_IDS Oinitiator_inner(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr, C).
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
#endif
SETUP_HANDSHAKE_STATE()
RNG_KEM_PAIR(eski, epki, Seski) // IHI3
sidr <- sid0;
sptr <- rng_key(setup_seed(Ssptr)); // IHI5
event ConsumeSidi(sidi, call);
event ConsumeSeed(Sptr, setup_seed(Ssptr), call);
event ConsumeSeed(Eski, setup_seed(Seski), call);
INITHELLO_PRODUCE()
CK_EV( event OskOinitiator_ck(ck); )
CK_EV( event OskOinitiator(ck, psk, sski, spkr, sptr); )
MTX_EV( event IHSent(ih, psk, sski, spkr); )
out(C, ih);
Oresp_hello(HS_PASS_ARGS).
restriction sid:SessionId, ad1:Atom, ad2:Atom; restriction sid:SessionId, ad1:Atom, ad2:Atom;
event(ConsumeSidi(sid, ad1)) && event(ConsumeSidi(sid, ad2)) event(ConsumeSidi(sid, ad1)) && event(ConsumeSidi(sid, ad2))

View File

@@ -2,6 +2,12 @@
#include "crypto/kem.mpv" #include "crypto/kem.mpv"
#include "rosenpass/handshake_state.mpv" #include "rosenpass/handshake_state.mpv"
fun Envelope(
key,
bits
): bits [data].
letfun create_mac(pk:kem_pk, payload:bits) = lprf2(MAC, kem_pk2b(pk), payload).
type InitHello_t. type InitHello_t.
fun InitHello( fun InitHello(
SessionId, // sidi SessionId, // sidi
@@ -11,6 +17,8 @@ fun InitHello(
bits // auth bits // auth
) : InitHello_t [data]. ) : InitHello_t [data].
fun IH2b(InitHello_t) : bitstring [typeConverter].
#define INITHELLO_PRODUCE() \ #define INITHELLO_PRODUCE() \
ck <- lprf1(CK_INIT, kem_pk2b(spkr)); /* IHI1 */ \ ck <- lprf1(CK_INIT, kem_pk2b(spkr)); /* IHI1 */ \
/* not handled here */ /* IHI2 */ \ /* not handled here */ /* IHI2 */ \
@@ -41,7 +49,9 @@ fun RespHello(
bits // auth bits // auth
) : RespHello_t [data]. ) : RespHello_t [data].
#define RESPHELLO_PRODUCE() \ fun RH2b(RespHello_t) : bitstring [typeConverter].
#define RESPHELLO_PRODUCE() \
/* not handled here */ /* RHR1 */ \ /* not handled here */ /* RHR1 */ \
MIX2(sid2b(sidr), sid2b(sidi)) /* RHR3 */ \ MIX2(sid2b(sidr), sid2b(sidi)) /* RHR3 */ \
ENCAPS_AND_MIX(ecti, epki, epti) /* RHR4 */ \ ENCAPS_AND_MIX(ecti, epki, epti) /* RHR4 */ \
@@ -67,6 +77,8 @@ fun InitConf(
bits // auth bits // auth
) : InitConf_t [data]. ) : InitConf_t [data].
fun IC2b(InitConf_t) : bitstring [typeConverter].
#define INITCONF_PRODUCE() \ #define INITCONF_PRODUCE() \
MIX2(sid2b(sidi), sid2b(sidr)) /* ICI3 */ \ MIX2(sid2b(sidi), sid2b(sidr)) /* ICI3 */ \
ENCRYPT_AND_MIX(auth, empty) /* ICI4 */ \ ENCRYPT_AND_MIX(auth, empty) /* ICI4 */ \

12
cipher-traits/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "rosenpass-cipher-traits"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal traits for cryptographic primitives"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]

View File

@@ -1,5 +1,5 @@
# Rosenpass internal libsodium bindings # Rosenpass internal libsodium bindings
Rosenpass internal library providing bindings to libsodium. Rosenpass internal library providing traits for cryptographic primitives.
This is an internal library; not guarantee is made about its API at this point in time. This is an internal library; not guarantee is made about its API at this point in time.

47
cipher-traits/src/kem.rs Normal file
View File

@@ -0,0 +1,47 @@
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
//!
//! KEMs are the interface provided by almost all post-quantum
//! secure key exchange mechanisms.
//!
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
//!
//! encapsulation.
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
use std::result::Result;
/// Key Encapsulation Mechanism
///
/// The KEM interface defines three operations: Key generation, key encapsulation and key
/// decapsulation.
pub trait Kem {
type Error;
/// Secrete Key length
const SK_LEN: usize;
/// Public Key length
const PK_LEN: usize;
/// Ciphertext length
const CT_LEN: usize;
/// Shared Secret length
const SHK_LEN: usize;
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
///
/// `keygen() -> sk, pk`
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), Self::Error>;
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
///
/// `encaps(pk) -> shk, ct`
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), Self::Error>;
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
/// (`shk`)
///
/// `decaps(sk, ct) -> shk`
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), Self::Error>;
}

2
cipher-traits/src/lib.rs Normal file
View File

@@ -0,0 +1,2 @@
mod kem;
pub use kem::Kem;

View File

@@ -11,8 +11,12 @@ readme = "readme.md"
[dependencies] [dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-to = { workspace = true } rosenpass-to = { workspace = true }
rosenpass-constant-time = { workspace = true } rosenpass-constant-time = { workspace = true }
rosenpass-secret-memory = { workspace = true }
rosenpass-oqs = { workspace = true }
rosenpass-util = { workspace = true }
static_assertions = { workspace = true } static_assertions = { workspace = true }
zeroize = { workspace = true } zeroize = { workspace = true }
chacha20poly1305 = { workspace = true }
blake2 = { workspace = true }

109
ciphers/src/hash_domain.rs Normal file
View File

@@ -0,0 +1,109 @@
use anyhow::Result;
use rosenpass_secret_memory::Secret;
use rosenpass_to::To;
use crate::subtle::incorrect_hmac_blake2b as hash;
pub use hash::KEY_LEN;
// TODO Use a proper Dec interface
#[derive(Clone, Debug)]
pub struct HashDomain([u8; KEY_LEN]);
#[derive(Clone, Debug)]
pub struct HashDomainNamespace([u8; KEY_LEN]);
#[derive(Clone, Debug)]
pub struct SecretHashDomain(Secret<KEY_LEN>);
#[derive(Clone, Debug)]
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
impl HashDomain {
pub fn zero() -> Self {
Self([0u8; KEY_LEN])
}
pub fn dup(self) -> HashDomainNamespace {
HashDomainNamespace(self.0)
}
pub fn turn_secret(self) -> SecretHashDomain {
SecretHashDomain(Secret::from_slice(&self.0))
}
// TODO: Protocol! Use domain separation to ensure that
pub fn mix(self, v: &[u8]) -> Result<Self> {
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
pub fn into_value(self) -> [u8; KEY_LEN] {
self.0
}
}
impl HashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
Ok(HashDomain(
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
))
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
}
impl SecretHashDomain {
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
let mut r = SecretHashDomain(Secret::zero());
hash::hash(k, d).to(r.0.secret_mut())?;
Ok(r)
}
pub fn zero() -> Self {
Self(Secret::zero())
}
pub fn dup(self) -> SecretHashDomainNamespace {
SecretHashDomainNamespace(self.0)
}
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
Self(k)
}
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v.secret())
}
pub fn into_secret(self) -> Secret<KEY_LEN> {
self.0
}
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
hash::hash(v, dst).to(self.0.secret_mut())
}
}
impl SecretHashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
}
// TODO: This entire API is not very nice; we need this for biscuits, but
// it might be better to extract a special "biscuit"
// labeled subkey and reinitialize the chain with this
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
self.0
}
}

View File

@@ -5,24 +5,23 @@ pub mod subtle;
pub const KEY_LEN: usize = 32; pub const KEY_LEN: usize = 32;
const_assert!(KEY_LEN == aead::KEY_LEN); const_assert!(KEY_LEN == aead::KEY_LEN);
const_assert!(KEY_LEN == xaead::KEY_LEN); const_assert!(KEY_LEN == xaead::KEY_LEN);
const_assert!(KEY_LEN == hash::KEY_LEN); const_assert!(KEY_LEN == hash_domain::KEY_LEN);
/// Authenticated encryption with associated data /// Authenticated encryption with associated data
pub mod aead { pub mod aead {
pub use rosenpass_sodium::aead::chacha20poly1305_ietf::{ pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
} }
/// Authenticated encryption with associated data with a constant nonce /// Authenticated encryption with associated data with a constant nonce
pub mod xaead { pub mod xaead {
pub use rosenpass_sodium::aead::xchacha20poly1305_ietf::{ pub use crate::subtle::xchacha20poly1305_ietf::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN, decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
}; };
} }
pub mod hash { pub mod hash_domain;
pub use crate::subtle::incorrect_hmac_blake2b::{
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN, pub mod kem {
}; pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
} }

View File

@@ -0,0 +1,42 @@
use zeroize::Zeroizing;
use blake2::digest::crypto_common::generic_array::GenericArray;
use blake2::digest::crypto_common::typenum::U32;
use blake2::digest::crypto_common::KeySizeUser;
use blake2::digest::{FixedOutput, Mac, OutputSizeUser};
use blake2::Blake2bMac;
use rosenpass_to::{ops::copy_slice, with_destination, To};
use rosenpass_util::typenum2const;
type Impl = Blake2bMac<U32>;
type KeyLen = <Impl as KeySizeUser>::KeySize;
type OutLen = <Impl as OutputSizeUser>::OutputSize;
const KEY_LEN: usize = typenum2const! { KeyLen };
const OUT_LEN: usize = typenum2const! { OutLen };
pub const KEY_MIN: usize = KEY_LEN;
pub const KEY_MAX: usize = KEY_LEN;
pub const OUT_MIN: usize = OUT_LEN;
pub const OUT_MAX: usize = OUT_LEN;
#[inline]
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
with_destination(|out: &mut [u8]| {
let mut h = Impl::new_from_slice(key)?;
h.update(data);
// Jesus christ, blake2 crate, your usage of GenericArray might be nice and fancy
// but it introduces a ton of complexity. This cost me half an hour just to figure
// out the right way to use the imports while allowing for zeroization.
// An API based on slices might actually be simpler.
let mut tmp = Zeroizing::new([0u8; OUT_LEN]);
let mut tmp = GenericArray::from_mut_slice(tmp.as_mut());
h.finalize_into(&mut tmp);
copy_slice(tmp.as_ref()).to(out);
Ok(())
})
}

View File

@@ -0,0 +1,43 @@
use rosenpass_to::ops::copy_slice;
use rosenpass_to::To;
use rosenpass_util::typenum2const;
use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
let nonce = GenericArray::from_slice(nonce);
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
copy_slice(plaintext).to(ct);
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(&nonce, ad, ct)?;
copy_slice(&mac_value[..]).to(mac);
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
let nonce = GenericArray::from_slice(nonce);
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
let tag = GenericArray::from_slice(mac);
copy_slice(ct).to(plaintext);
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(&nonce, ad, plaintext, tag)?;
Ok(())
}

View File

@@ -1,9 +1,11 @@
use anyhow::ensure; use anyhow::ensure;
use rosenpass_constant_time::xor;
use rosenpass_sodium::hash::blake2b;
use rosenpass_to::{ops::copy_slice, with_destination, To};
use zeroize::Zeroizing; use zeroize::Zeroizing;
use rosenpass_constant_time::xor;
use rosenpass_to::{ops::copy_slice, with_destination, To};
use crate::subtle::blake2b;
pub const KEY_LEN: usize = 32; pub const KEY_LEN: usize = 32;
pub const KEY_MIN: usize = KEY_LEN; pub const KEY_MIN: usize = KEY_LEN;
pub const KEY_MAX: usize = KEY_LEN; pub const KEY_MAX: usize = KEY_LEN;

View File

@@ -1 +1,4 @@
pub mod blake2b;
pub mod chacha20poly1305_ietf;
pub mod incorrect_hmac_blake2b; pub mod incorrect_hmac_blake2b;
pub mod xchacha20poly1305_ietf;

View File

@@ -0,0 +1,45 @@
use rosenpass_to::ops::copy_slice;
use rosenpass_to::To;
use rosenpass_util::typenum2const;
use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
let nonce = GenericArray::from_slice(nonce);
let (n, ct_mac) = ciphertext.split_at_mut(NONCE_LEN);
let (ct, mac) = ct_mac.split_at_mut(ct_mac.len() - TAG_LEN);
copy_slice(nonce).to(n);
copy_slice(plaintext).to(ct);
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(&nonce, ad, ct)?;
copy_slice(&mac_value[..]).to(mac);
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
let (n, ct_mac) = ciphertext.split_at(NONCE_LEN);
let (ct, mac) = ct_mac.split_at(ct_mac.len() - TAG_LEN);
let nonce = GenericArray::from_slice(n);
let tag = GenericArray::from_slice(mac);
copy_slice(ct).to(plaintext);
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(&nonce, ad, plaintext, tag)?;
Ok(())
}

View File

@@ -1,3 +1,5 @@
use core::hint::black_box;
use rosenpass_to::{with_destination, To}; use rosenpass_to::{with_destination, To};
/// Xors the source into the destination /// Xors the source into the destination
@@ -16,11 +18,61 @@ use rosenpass_to::{with_destination, To};
/// ///
/// If source and destination are of different sizes. /// If source and destination are of different sizes.
#[inline] #[inline]
pub fn xor<'a>(src: &'a [u8]) -> impl To<[u8], ()> + 'a { pub fn xor(src: &[u8]) -> impl To<[u8], ()> + '_ {
with_destination(|dst: &mut [u8]| { with_destination(|dst: &mut [u8]| {
assert!(src.len() == dst.len()); assert!(black_box(src.len()) == black_box(dst.len()));
for (dv, sv) in dst.iter_mut().zip(src.iter()) { for (dv, sv) in dst.iter_mut().zip(src.iter()) {
*dv ^= *sv; *black_box(dv) ^= black_box(*sv);
} }
}) })
} }
#[inline]
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
a == b
}
#[inline]
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
a.cmp(b) as i32
}
/// Interpret the given slice as a little-endian unsigned integer
/// and increment that integer.
///
/// # Examples
///
/// ```
/// use rosenpass_constant_time::increment as inc;
/// use rosenpass_to::To;
///
/// fn testcase(v: &[u8], correct: &[u8]) {
/// let mut v = v.to_owned();
/// inc(&mut v);
/// assert_eq!(&v, correct);
/// }
///
/// testcase(b"", b"");
/// testcase(b"\x00", b"\x01");
/// testcase(b"\x01", b"\x02");
/// testcase(b"\xfe", b"\xff");
/// testcase(b"\xff", b"\x00");
/// testcase(b"\x00\x00", b"\x01\x00");
/// testcase(b"\x01\x00", b"\x02\x00");
/// testcase(b"\xfe\x00", b"\xff\x00");
/// testcase(b"\xff\x00", b"\x00\x01");
/// testcase(b"\x00\x00\x00\x00\x00\x00", b"\x01\x00\x00\x00\x00\x00");
/// testcase(b"\x00\xa3\x00\x77\x00\x00", b"\x01\xa3\x00\x77\x00\x00");
/// testcase(b"\xff\xa3\x00\x77\x00\x00", b"\x00\xa4\x00\x77\x00\x00");
/// testcase(b"\xff\xff\xff\x77\x00\x00", b"\x00\x00\x00\x78\x00\x00");
/// ```
#[inline]
pub fn increment(v: &mut [u8]) {
let mut carry = 1u8;
for val in v.iter_mut() {
let (v, c) = black_box(*val).overflowing_add(black_box(carry));
*black_box(val) = v;
*black_box(&mut carry) = black_box(black_box(c) as u8);
}
}

View File

@@ -23,7 +23,7 @@ If you are not specifically tasked with developing post-quantum secure systems,
you probably do not need this tool. you probably do not need this tool.
.Ss COMMANDS .Ss COMMANDS
.Bl -tag -width Ds .Bl -tag -width Ds
.It Ar keygen private-key <file-path> public-key <file-path> .It Ar gen-keys --secret-key <file-path> --public-key <file-path>
Generate a keypair to use in the exchange command later. Generate a keypair to use in the exchange command later.
Send the public-key file to your communication partner and keep the private-key Send the public-key file to your communication partner and keep the private-key
file secret! file secret!

View File

@@ -291,7 +291,6 @@
]; ];
buildPhase = '' buildPhase = ''
export HOME=$(mktemp -d) export HOME=$(mktemp -d)
export OSFONTDIR="$(kpsewhich --var-value TEXMF)/fonts/{opentype/public/nunito,truetype/google/noto}"
latexmk -r tex/CI.rc latexmk -r tex/CI.rc
''; '';
installPhase = '' installPhase = ''

115
format_rust_code.sh Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# Parse command line options
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
mode="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Check if mode is specified
if [ -z "$mode" ]; then
echo "Please specify the mode using --mode option. Valid modes are 'check' and 'fix'."
exit 1
fi
# Find all Markdown files in the current directory and its subdirectories
mapfile -t md_files < <(find . -type f -name "*.md")
count=0
# Iterate through each Markdown file
for file in "${md_files[@]}"; do
# Use awk to extract Rust code blocks enclosed within triple backticks
rust_code_blocks=$(awk '/```rust/{flag=1; next}/```/{flag=0} flag' "$file")
# Count the number of Rust code blocks
num_fences=$(awk '/```rust/{f=1} f{if(/```/){f=0; count++}} END{print count}' "$file")
if [ -n "$rust_code_blocks" ]; then
echo "Processing Rust code in $file"
# Iterate through each Rust code block
for ((i=1; i <= num_fences ; i++)); do
# Extract individual Rust code block using awk
current_rust_block=$(awk -v i="$i" '/```rust/{f=1; if (++count == i) next} f&&/```/{f=0;next} f' "$file")
# Variable to check if we have added the main function
add_main=0
# Check if the Rust code block is already inside a function
if ! echo "$current_rust_block" | grep -q "fn main()"; then
# If not, wrap it in a main function
current_rust_block=$'fn main() {\n'"$current_rust_block"$'\n}'
add_main=1
fi
if [ "$mode" == "check" ]; then
# Apply changes to the Rust code block
formatted_rust_code=$(echo "$current_rust_block" | rustfmt)
# Use rustfmt to format the Rust code block, remove first and last lines, and remove the first 4 spaces if added main function
if [ "$add_main" == 1 ]; then
formatted_rust_code=$(echo "$formatted_rust_code" | sed '1d;$d' | sed 's/^ //')
current_rust_block=$(echo "$current_rust_block" | sed '1d;')
current_rust_block=$(echo "$current_rust_block" | sed '$d')
fi
if [ "$formatted_rust_code" == "$current_rust_block" ]; then
echo "No changes needed in Rust code block $i in $file"
else
echo -e "\nChanges needed in Rust code block $i in $file:\n"
echo "$formatted_rust_code"
count=+1
fi
elif [ "$mode" == "fix" ]; then
# Replace current_rust_block with formatted_rust_code in the file
formatted_rust_code=$(echo "$current_rust_block" | rustfmt)
# Use rustfmt to format the Rust code block, remove first and last lines, and remove the first 4 spaces if added main function
if [ "$add_main" == 1 ]; then
formatted_rust_code=$(echo "$formatted_rust_code" | sed '1d;$d' | sed 's/^ //')
current_rust_block=$(echo "$current_rust_block" | sed '1d;')
current_rust_block=$(echo "$current_rust_block" | sed '$d')
fi
# Check if the formatted code is the same as the current Rust code block
if [ "$formatted_rust_code" == "$current_rust_block" ]; then
echo "No changes needed in Rust code block $i in $file"
else
echo "Formatting Rust code block $i in $file"
# Replace current_rust_block with formatted_rust_code in the file
# Use awk to find the line number of the pattern
start_line=$(grep -n "^\`\`\`rust" "$file" | sed -n "${i}p" | cut -d: -f1)
end_line=$(grep -n "^\`\`\`" "$file" | awk -F: -v start_line="$start_line" '$1 > start_line {print $1; exit;}')
if [ -n "$start_line" ] && [ -n "$end_line" ]; then
# Print lines before the Rust code block
head -n "$((start_line - 1))" "$file"
# Print the formatted Rust code block
echo "\`\`\`rust"
echo "$formatted_rust_code"
echo "\`\`\`"
# Print lines after the Rust code block
tail -n +"$((end_line + 1))" "$file"
else
# Rust code block not found or end line not found
cat "$file"
fi > tmpfile && mv tmpfile "$file"
fi
else
echo "Unknown mode: $mode. Valid modes are 'check' and 'fix'."
exit 1
fi
done
fi
done
# CI failure if changes are needed
if [ $count -gt 0 ]; then
echo "CI failed: Changes needed in Rust code blocks."
exit 1
fi

View File

@@ -11,8 +11,9 @@ cargo-fuzz = true
arbitrary = { workspace = true } arbitrary = { workspace = true }
libfuzzer-sys = { workspace = true } libfuzzer-sys = { workspace = true }
stacker = { workspace = true } stacker = { workspace = true }
rosenpass-sodium = { workspace = true } rosenpass-secret-memory = { workspace = true }
rosenpass-ciphers = { workspace = true } rosenpass-ciphers = { workspace = true }
rosenpass-cipher-traits = { workspace = true }
rosenpass-to = { workspace = true } rosenpass-to = { workspace = true }
rosenpass = { workspace = true } rosenpass = { workspace = true }
@@ -45,3 +46,15 @@ name = "fuzz_kyber_encaps"
path = "fuzz_targets/kyber_encaps.rs" path = "fuzz_targets/kyber_encaps.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "fuzz_box_secret_alloc"
path = "fuzz_targets/box_secret_alloc.rs"
test = false
doc = false
[[bin]]
name = "fuzz_vec_secret_alloc"
path = "fuzz_targets/vec_secret_alloc.rs"
test = false
doc = false

View File

@@ -5,7 +5,6 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass_ciphers::aead; use rosenpass_ciphers::aead;
use rosenpass_sodium::init as sodium_init;
#[derive(arbitrary::Arbitrary, Debug)] #[derive(arbitrary::Arbitrary, Debug)]
pub struct Input { pub struct Input {
@@ -16,8 +15,6 @@ pub struct Input {
} }
fuzz_target!(|input: Input| { fuzz_target!(|input: Input| {
sodium_init().unwrap();
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16); let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
ciphertext.resize(input.plaintext.len() + 16, 0); ciphertext.resize(input.plaintext.len() + 16, 0);

View File

@@ -4,7 +4,7 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass_sodium::{hash::blake2b, init as sodium_init}; use rosenpass_ciphers::subtle::blake2b;
use rosenpass_to::To; use rosenpass_to::To;
#[derive(arbitrary::Arbitrary, Debug)] #[derive(arbitrary::Arbitrary, Debug)]
@@ -14,8 +14,6 @@ pub struct Blake2b {
} }
fuzz_target!(|input: Blake2b| { fuzz_target!(|input: Blake2b| {
sodium_init().unwrap();
let mut out = [0u8; 32]; let mut out = [0u8; 32];
blake2b::hash(&input.key, &input.data).to(&mut out).unwrap(); blake2b::hash(&input.key, &input.data).to(&mut out).unwrap();

View File

@@ -0,0 +1,8 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use rosenpass_secret_memory::alloc::secret_box;
fuzz_target!(|data: &[u8]| {
let _ = secret_box(data);
});

View File

@@ -3,13 +3,10 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass::coloring::Secret;
use rosenpass::protocol::CryptoServer; use rosenpass::protocol::CryptoServer;
use rosenpass_sodium::init as sodium_init; use rosenpass_secret_memory::Secret;
fuzz_target!(|rx_buf: &[u8]| { fuzz_target!(|rx_buf: &[u8]| {
sodium_init().unwrap();
let sk = Secret::from_slice(&[0; 13568]); let sk = Secret::from_slice(&[0; 13568]);
let pk = Secret::from_slice(&[0; 524160]); let pk = Secret::from_slice(&[0; 524160]);

View File

@@ -4,7 +4,8 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass::pqkem::{EphemeralKEM, KEM}; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::EphemeralKem;
#[derive(arbitrary::Arbitrary, Debug)] #[derive(arbitrary::Arbitrary, Debug)]
pub struct Input { pub struct Input {
@@ -15,5 +16,5 @@ fuzz_target!(|input: Input| {
let mut ciphertext = [0u8; 768]; let mut ciphertext = [0u8; 768];
let mut shared_secret = [0u8; 32]; let mut shared_secret = [0u8; 32];
EphemeralKEM::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap(); EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
}); });

View File

@@ -3,12 +3,13 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass::pqkem::{StaticKEM, KEM}; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
fuzz_target!(|input: &[u8]| { fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
let mut ciphertext = [0u8; 188]; let mut ciphertext = [0u8; 188];
let mut shared_secret = [0u8; 32]; let mut shared_secret = [0u8; 32];
// We expect errors while fuzzing therefore we do not check the result. // We expect errors while fuzzing therefore we do not check the result.
let _ = StaticKEM::encaps(&mut shared_secret, &mut ciphertext, input); let _ = StaticKem::encaps(&mut shared_secret, &mut ciphertext, &input);
}); });

View File

@@ -0,0 +1,9 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use rosenpass_secret_memory::alloc::secret_vec;
fuzz_target!(|data: &[u8]| {
let mut vec = secret_vec();
vec.extend_from_slice(data);
});

16
lenses/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "rosenpass-lenses"
version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal library for parsing binary data securely"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
paste = { workspace = true }
thiserror = { workspace = true }

3
lenses/readme.md Normal file
View File

@@ -0,0 +1,3 @@
# Rosenpass internal binary parsing library
This is an internal library; no guarantee is made about its API at this point in time.

206
lenses/src/lib.rs Normal file
View File

@@ -0,0 +1,206 @@
use std::result::Result;
/// Common trait shared by all Lenses
pub trait LenseView {
const LEN: usize;
}
/// Error during lense creation
#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
pub enum LenseError {
#[error("buffer size mismatch")]
BufferSizeMismatch,
}
pub type LenseResult<T> = Result<T, LenseError>;
impl LenseError {
pub fn ensure_exact_buffer_size(len: usize, required: usize) -> LenseResult<()> {
(len == required)
.then_some(())
.ok_or(LenseError::BufferSizeMismatch)
}
pub fn ensure_sufficient_buffer_size(len: usize, required: usize) -> LenseResult<()> {
(len >= required)
.then_some(())
.ok_or(LenseError::BufferSizeMismatch)
}
}
/// A macro to create data lenses.
#[macro_export]
macro_rules! lense(
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = lense!(maybe_docstring_link $len)]
/// bytes long
pub fn $field(&self) -> &__ContainerType::Output {
&self.0[$offset .. $offset + $len]
}
/// The bytes until the
#[doc = lense!(maybe_docstring_link Self::$field)]
/// field
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
&self.0[0 .. $offset]
}
// if the tail exits, consume it as well
$(
lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
)?
}
};
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = lense!(maybe_docstring_link $len)]
/// bytes long
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
&mut self.0[$offset .. $offset + $len]
}
// if the tail exits, consume it as well
$(
lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
)?
}
};
// switch that yields literals unchanged, but creates docstring links to
// constants
// TODO the doc string link doesn't work if $x is taken from a generic,
(maybe_docstring_link $x:literal) => (stringify!($x));
(maybe_docstring_link $x:expr) => (stringify!([$x]));
// struct name < optional generics > := optional doc string field name : field length, ...
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
/// A data lense to manipulate byte slices.
///
//// # Fields
///
$(
/// - `
#[doc = stringify!($field)]
/// `:
#[doc = lense!(maybe_docstring_link $len)]
/// bytes
)+
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
__ContainerType,
// The phantom data is required, since all generics declared on a
// type need to be used on the type.
// https://doc.rust-lang.org/stable/error_codes/E0392.html
$( $( ::core::marker::PhantomData<$generic> ),+ )?
);
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
$(
/// Size in bytes of the field `
#[doc = !($field)]
/// `
pub const fn [< $field _len >]() -> usize{
$len
}
)+
/// Verify that `len` exactly holds [Self]
pub fn check_size(len: usize) -> ::rosenpass_lenses::LenseResult<()> {
::rosenpass_lenses::LenseError::ensure_exact_buffer_size(len, $( $len + )+ 0)
}
}
// read-only accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
{
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
}
// mutable accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
{
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
/// View into all bytes belonging to this Lense
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
&mut self.0[0..Self::LEN]
}
}
// lense trait, allowing us to know the implementing lenses size
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
/// Number of bytes required to store this type in binary format
const LEN: usize = $( $len + )+ 0;
}
/// Extension trait to allow checked creation of a lense over
/// some byte slice that contains a
#[doc = lense!(maybe_docstring_link $type)]
pub trait [< $type Ext >] {
type __ContainerType;
/// Create a lense to the byte slice
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
/// Create a lense to the byte slice, automatically truncating oversized buffers
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
}
impl<'a> [< $type Ext >] for &'a [u8] {
type __ContainerType = &'a [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
let required_size = $( $len + )+ 0;
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
[< $type Ext >]::[< $type:snake >](&self[..required_size])
}
}
impl<'a> [< $type Ext >] for &'a mut [u8] {
type __ContainerType = &'a mut [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
let required_size = $( $len + )+ 0;
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
}
}
});
);

16
oqs/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "rosenpass-oqs"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal bindings to liboqs"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
rosenpass-cipher-traits = { workspace = true }
rosenpass-util = { workspace = true }
oqs-sys = { workspace = true }
paste = { workspace = true }

5
oqs/readme.md Normal file
View File

@@ -0,0 +1,5 @@
# Rosenpass internal liboqs bindings
Rosenpass internal library providing bindings to liboqs.
This is an internal library; not guarantee is made about its API at this point in time.

80
oqs/src/kem_macro.rs Normal file
View File

@@ -0,0 +1,80 @@
macro_rules! oqs_kem {
($name:ident) => { ::paste::paste!{
mod [< $name:snake >] {
use rosenpass_cipher_traits::Kem;
use rosenpass_util::result::Guaranteed;
pub enum [< $name:camel >] {}
/// # Panic & Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
/// slices only identified using raw pointers. It must be ensured that the raw
/// pointers point into byte slices of sufficient length, to avoid UB through
/// overwriting of arbitrary data. This is ensured through assertions in the
/// implementation.
///
/// __Note__: This requirement is stricter than necessary, it would suffice
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl Kem for [< $name:camel >] {
type Error = ::std::convert::Infallible;
const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Guaranteed<()> {
assert_eq!(sk.len(), Self::SK_LEN);
assert_eq!(pk.len(), Self::PK_LEN);
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
pk.as_mut_ptr(),
sk.as_mut_ptr()
);
}
Ok(())
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
assert_eq!(shk.len(), Self::SHK_LEN);
assert_eq!(ct.len(), Self::CT_LEN);
assert_eq!(pk.len(), Self::PK_LEN);
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
ct.as_mut_ptr(),
shk.as_mut_ptr(),
pk.as_ptr()
);
}
Ok(())
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
assert_eq!(shk.len(), Self::SHK_LEN);
assert_eq!(sk.len(), Self::SK_LEN);
assert_eq!(ct.len(), Self::CT_LEN);
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
shk.as_mut_ptr(),
ct.as_ptr(),
sk.as_ptr()
);
}
Ok(())
}
}
}
pub use [< $name:snake >] :: [< $name:camel >];
}}
}

21
oqs/src/lib.rs Normal file
View File

@@ -0,0 +1,21 @@
macro_rules! oqs_call {
($name:path, $($args:expr),*) => {{
use oqs_sys::common::OQS_STATUS::*;
match $name($($args),*) {
OQS_SUCCESS => {}, // nop
OQS_EXTERNAL_LIB_ERROR_OPENSSL => {
panic!("OpenSSL error in liboqs' {}.", stringify!($name));
},
OQS_ERROR => {
panic!("Unknown error in liboqs' {}.", stringify!($name));
}
}
}};
($name:ident) => { oqs_call!($name, ) };
}
#[macro_use]
mod kem_macro;
oqs_kem!(kyber_512);
oqs_kem!(classic_mceliece_460896);

View File

@@ -177,7 +177,11 @@ version={4.0},
\titlehead{\centerline{\includegraphics[width=4cm]{RosenPass-Logo}}} \titlehead{\centerline{\includegraphics[width=4cm]{RosenPass-Logo}}}
\title{\inserttitle} \title{\inserttitle}
} }
\author{\csname insertauthor\endcsname} \ifx\csname insertauthor\endcsname\relax
\author{}
\else
\author{\parbox{\linewidth}{\centering\insertauthor}}
\fi
\subject{\csname insertsubject\endcsname} \subject{\csname insertsubject\endcsname}
\date{\vspace{-1cm}} \date{\vspace{-1cm}}
} }

View File

@@ -1,10 +1,10 @@
[package] [package]
name = "rosenpass" name = "rosenpass"
description = "Build post-quantum-secure VPNs with WireGuard!"
version = "0.2.1" version = "0.2.1"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"] authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Build post-quantum-secure VPNs with WireGuard!"
homepage = "https://rosenpass.eu/" homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass" repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md" readme = "readme.md"
@@ -16,15 +16,15 @@ harness = false
[dependencies] [dependencies]
rosenpass-util = { workspace = true } rosenpass-util = { workspace = true }
rosenpass-constant-time = { workspace = true } rosenpass-constant-time = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-ciphers = { workspace = true } rosenpass-ciphers = { workspace = true }
rosenpass-cipher-traits = { workspace = true }
rosenpass-to = { workspace = true } rosenpass-to = { workspace = true }
rosenpass-secret-memory = { workspace = true }
rosenpass-lenses = { workspace = true }
rosenpass-wireguard-broker = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
static_assertions = { workspace = true } static_assertions = { workspace = true }
memoffset = { workspace = true } memoffset = { workspace = true }
libsodium-sys-stable = { workspace = true }
oqs-sys = { workspace = true }
lazy_static = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
log = { workspace = true } log = { workspace = true }
@@ -33,6 +33,10 @@ serde = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
mio = { workspace = true } mio = { workspace = true }
rand = { workspace = true }
[target.'cfg(target_os = "hermit")'.dependencies]
hermit = { version = "0.8", features = ["pci", "pci-ids", "acpi", "fsgsbase", "tcp", "rtl8139"]}
[build-dependencies] [build-dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }

View File

@@ -1,10 +1,8 @@
use anyhow::Result; use anyhow::Result;
use rosenpass::pqkem::KEM; use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
use rosenpass::{
pqkem::StaticKEM, use rosenpass_cipher_traits::Kem;
protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey}, use rosenpass_ciphers::kem::StaticKem;
sodium::sodium_init,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
@@ -41,7 +39,7 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
fn keygen() -> Result<(SSk, SPk)> { fn keygen() -> Result<(SSk, SPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?; StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
Ok((sk, pk)) Ok((sk, pk))
} }
@@ -58,7 +56,6 @@ fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
} }
fn criterion_benchmark(c: &mut Criterion) { fn criterion_benchmark(c: &mut Criterion) {
sodium_init().unwrap();
let (mut a, mut b) = make_server_pair().unwrap(); let (mut a, mut b) = make_server_pair().unwrap();
c.bench_function("cca_secret_alloc", |bench| { c.bench_function("cca_secret_alloc", |bench| {
bench.iter(|| { bench.iter(|| {

View File

@@ -1,38 +1,25 @@
use anyhow::bail; use std::cell::{Cell, RefCell};
use std::io::{ErrorKind, Write};
use anyhow::Result; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, TcpStream};
use log::{debug, error, info, warn};
use mio::Interest;
use mio::Token;
use rosenpass_util::file::fopen_w;
use std::cell::Cell;
use std::io::Write;
use std::io::ErrorKind;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use std::net::SocketAddrV4;
use std::net::SocketAddrV6;
use std::net::ToSocketAddrs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use std::slice; use std::slice;
use std::thread;
use std::time::Duration; use std::time::Duration;
use crate::{ use anyhow::{bail, Result};
config::Verbosity, use log::{error, info, warn};
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing}, use mio::{Interest, Token};
};
use rosenpass_util::attempt; use rosenpass_secret_memory::Public;
use rosenpass_util::b64::{b64_writer, fmt_b64}; use rosenpass_util::b64::{b64_writer, fmt_b64};
use rosenpass_util::{attempt, file::fopen_w};
use rosenpass_wireguard_broker::api::mio_client::MioBrokerClient as PskBroker;
use rosenpass_wireguard_broker::WireGuardBroker;
use crate::config::Verbosity;
use crate::protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing};
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
fn ipv4_any_binding() -> SocketAddr { fn ipv4_any_binding() -> SocketAddr {
// addr, port // addr, port
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0)) SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
@@ -43,6 +30,19 @@ fn ipv6_any_binding() -> SocketAddr {
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0)) SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
} }
#[derive(Default)]
struct MioTokenDispenser {
counter: usize,
}
impl MioTokenDispenser {
fn get_token(&mut self) -> Token {
let r = self.counter;
self.counter += 1;
Token(r)
}
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct AppPeer { pub struct AppPeer {
pub outfile: Option<PathBuf>, pub outfile: Option<PathBuf>,
@@ -59,14 +59,24 @@ impl AppPeer {
} }
} }
#[derive(Default, Debug)] #[derive(Debug)]
pub struct WireguardOut { pub struct WireguardOut {
// impl KeyOutput // impl KeyOutput
pub dev: String, pub dev: String,
pub pk: String, pub pk: Public<32>,
pub extra_params: Vec<String>, pub extra_params: Vec<String>,
} }
impl Default for WireguardOut {
fn default() -> Self {
Self {
dev: Default::default(),
pk: Public::zero(),
extra_params: Default::default(),
}
}
}
/// Holds the state of the application, namely the external IO /// Holds the state of the application, namely the external IO
/// ///
/// Responsible for file IO, network IO /// Responsible for file IO, network IO
@@ -77,6 +87,7 @@ pub struct AppServer {
pub sockets: Vec<mio::net::UdpSocket>, pub sockets: Vec<mio::net::UdpSocket>,
pub events: mio::Events, pub events: mio::Events,
pub mio_poll: mio::Poll, pub mio_poll: mio::Poll,
pub psk_broker: RefCell<PskBroker>,
pub peers: Vec<AppPeer>, pub peers: Vec<AppPeer>,
pub verbosity: Verbosity, pub verbosity: Verbosity,
pub all_sockets_drained: bool, pub all_sockets_drained: bool,
@@ -341,11 +352,24 @@ impl AppServer {
sk: SSk, sk: SSk,
pk: SPk, pk: SPk,
addrs: Vec<SocketAddr>, addrs: Vec<SocketAddr>,
psk_broker_socket: TcpStream,
verbosity: Verbosity, verbosity: Verbosity,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
// setup mio // setup mio
let mio_poll = mio::Poll::new()?; let mio_poll = mio::Poll::new()?;
let events = mio::Events::with_capacity(8); let events = mio::Events::with_capacity(8);
let mut dispenser = MioTokenDispenser::default();
// Create the Wireguard broker connection
let psk_broker = {
let mut sock = mio::net::TcpStream::from_std(psk_broker_socket);
mio_poll.registry().register(
&mut sock,
dispenser.get_token(),
Interest::READABLE | Interest::WRITABLE,
)?;
PskBroker::new(sock)
};
// bind each SocketAddr to a socket // bind each SocketAddr to a socket
let maybe_sockets: Result<Vec<_>, _> = let maybe_sockets: Result<Vec<_>, _> =
@@ -430,6 +454,7 @@ impl AppServer {
Ok(Self { Ok(Self {
crypt: CryptoServer::new(sk, pk), crypt: CryptoServer::new(sk, pk),
peers: Vec::new(), peers: Vec::new(),
psk_broker: RefCell::new(psk_broker),
verbosity, verbosity,
sockets, sockets,
events, events,
@@ -624,31 +649,9 @@ impl AppServer {
} }
if let Some(owg) = ap.outwg.as_ref() { if let Some(owg) = ap.outwg.as_ref() {
let mut child = Command::new("wg") self.psk_broker
.arg("set") .borrow_mut()
.arg(&owg.dev) .set_psk(&owg.dev, owg.pk.value, *key.secret())?;
.arg("peer")
.arg(&owg.pk)
.arg("preshared-key")
.arg("/dev/stdin")
.stdin(Stdio::piped())
.args(&owg.extra_params)
.spawn()?;
b64_writer(child.stdin.take().unwrap()).write_all(key.secret())?;
thread::spawn(move || {
let status = child.wait();
if let Ok(status) = status {
if status.success() {
debug!("successfully passed psk to wg")
} else {
error!("could not pass psk to wg {:?}", status)
}
} else {
error!("wait failed: {:?}", status)
}
});
} }
Ok(()) Ok(())
@@ -706,9 +709,16 @@ impl AppServer {
// only poll if we drained all sockets before // only poll if we drained all sockets before
if self.all_sockets_drained { if self.all_sockets_drained {
self.mio_poll.poll(&mut self.events, Some(timeout))?; self.mio_poll
.poll(&mut self.events, Some(timeout))
.or_else(|e| match e.kind() {
ErrorKind::Interrupted | ErrorKind::WouldBlock => Ok(()),
_ => Err(e),
})?;
} }
self.psk_broker.get_mut().poll()?;
let mut would_block_count = 0; let mut would_block_count = 0;
for (sock_no, socket) in self.sockets.iter_mut().enumerate() { for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
match socket.recv_from(buf) { match socket.recv_from(buf) {

View File

@@ -1,16 +1,20 @@
use anyhow::{bail, ensure}; use std::io::{BufReader, Read};
use std::net::TcpStream;
use std::path::PathBuf;
use anyhow::{bail, ensure, Context};
use clap::Parser; use clap::Parser;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::file::StoreSecret;
use rosenpass_secret_memory::Public;
use rosenpass_util::b64::b64_reader;
use rosenpass_util::file::{LoadValue, LoadValueB64}; use rosenpass_util::file::{LoadValue, LoadValueB64};
use std::path::{Path, PathBuf};
use crate::app_server; use crate::app_server;
use crate::app_server::AppServer; use crate::app_server::AppServer;
use crate::{ use crate::protocol::{SPk, SSk, SymKey};
// app_server::{AppServer, LoadValue, LoadValueB64},
coloring::Secret,
pqkem::{StaticKEM, KEM},
protocol::{SPk, SSk, SymKey},
};
use super::config; use super::config;
@@ -64,6 +68,7 @@ pub enum Cli {
config_file: PathBuf, config_file: PathBuf,
/// Forcefully overwrite existing config file /// Forcefully overwrite existing config file
/// - [ ] Janepie
#[clap(short, long)] #[clap(short, long)]
force: bool, force: bool,
}, },
@@ -89,6 +94,15 @@ pub enum Cli {
force: bool, force: bool,
}, },
/// Deprecated - use gen-keys instead
#[allow(rustdoc::broken_intra_doc_links)]
#[allow(rustdoc::invalid_html_tags)]
Keygen {
// NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"!
/// public-key <PATH> private-key <PATH>
args: Vec<String>,
},
/// Validate a configuration /// Validate a configuration
Validate { config_files: Vec<PathBuf> }, Validate { config_files: Vec<PathBuf> },
@@ -121,6 +135,40 @@ impl Cli {
config::Rosenpass::example_config().store(config_file)?; config::Rosenpass::example_config().store(config_file)?;
} }
// Deprecated - use gen-keys instead
Keygen { args } => {
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
let mut public_key: Option<PathBuf> = None;
let mut secret_key: Option<PathBuf> = None;
// Manual arg parsing, since clap wants to prefix flags with "--"
let mut args = args.into_iter();
loop {
match (args.next().as_deref(), args.next()) {
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
secret_key = Some(opt.into());
}
(Some("public-key"), Some(opt)) => {
public_key = Some(opt.into());
}
(Some(flag), _) => {
bail!("Unknown option `{}`", flag);
}
(_, _) => break,
};
}
if secret_key.is_none() {
bail!("private-key is required");
}
if public_key.is_none() {
bail!("public-key is required");
}
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
}
GenKeys { GenKeys {
config_file, config_file,
public_key, public_key,
@@ -162,12 +210,7 @@ impl Cli {
} }
// generate the keys and store them in files // generate the keys and store them in files
let mut ssk = crate::protocol::SSk::random(); generate_and_save_keypair(skf, pkf)?;
let mut spk = crate::protocol::SPk::random();
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(skf)?;
spk.store_secret(pkf)?;
} }
ExchangeConfig { config_file } => { ExchangeConfig { config_file } => {
@@ -222,11 +265,14 @@ impl Cli {
let sk = SSk::load(&config.secret_key)?; let sk = SSk::load(&config.secret_key)?;
let pk = SPk::load(&config.public_key)?; let pk = SPk::load(&config.public_key)?;
// Spawn the psk broker and use socketpair(2) to connect with them
let psk_broker_socket = TcpStream::connect("127.0.0.1:8001")?;
// start an application server // start an application server
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new( let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
sk, sk,
pk, pk,
config.listen, config.listen,
psk_broker_socket,
config.verbosity, config.verbosity,
)?); )?);
@@ -236,11 +282,24 @@ impl Cli {
cfg_peer.pre_shared_key.map(SymKey::load_b64).transpose()?, cfg_peer.pre_shared_key.map(SymKey::load_b64).transpose()?,
SPk::load(&cfg_peer.public_key)?, SPk::load(&cfg_peer.public_key)?,
cfg_peer.key_out, cfg_peer.key_out,
cfg_peer.wg.map(|cfg| app_server::WireguardOut { cfg_peer
dev: cfg.device, .wg
pk: cfg.peer, .map(|cfg| -> anyhow::Result<_> {
extra_params: cfg.extra_params, let b64pk = &cfg.peer;
}), let mut pk = Public::zero();
b64_reader(BufReader::new(b64pk.as_bytes()))
.read_exact(&mut pk.value)
.with_context(|| {
format!("Could not decode base64 public key: '{b64pk}'")
})?;
Ok(app_server::WireguardOut {
pk,
dev: cfg.device,
extra_params: cfg.extra_params,
})
})
.transpose()?,
cfg_peer.endpoint.clone(), cfg_peer.endpoint.clone(),
)?; )?;
} }
@@ -249,13 +308,11 @@ impl Cli {
} }
} }
trait StoreSecret { /// generate secret and public keys, store in files according to the paths passed as arguments
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>; 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();
impl<const N: usize> StoreSecret for Secret<N> { StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> { ssk.store_secret(secret_key)?;
std::fs::write(path, self.secret())?; spk.store_secret(public_key)
Ok(())
}
} }

View File

@@ -1,443 +0,0 @@
//! Types types for dealing with (secret-) values
//!
//! These types use type level coloring to make accidential leackage of secrets extra hard. Both [Secret] and [Public] own their data, but the memory backing
//! [Secret] is special:
//! - as it is heap allocated, we can actively zeroize the memory before freeing it.
//! - guard pages before and after each allocation trap accidential sequential reads that creep towards our secrets
//! - the memory is mlocked, e.g. it is never swapped
use anyhow::Context;
use lazy_static::lazy_static;
use libsodium_sys as libsodium;
use rosenpass_util::{
b64::b64_reader,
file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd, StoreValue},
functional::mutating,
mem::cpy,
};
use std::result::Result;
use std::{
collections::HashMap,
convert::TryInto,
fmt,
ops::{Deref, DerefMut},
os::raw::c_void,
path::Path,
ptr::null_mut,
sync::Mutex,
};
// This might become a problem in library usage; it's effectively a memory
// leak which probably isn't a problem right now because most memory will
// be reused…
lazy_static! {
static ref SECRET_CACHE: Mutex<SecretMemoryPool> = Mutex::new(SecretMemoryPool::new());
}
/// 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.
///
/// Further information about the protection in place can be found in in the
/// [libsodium documentation](https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations)
#[derive(Debug)] // TODO check on Debug derive, is that clever
pub struct SecretMemoryPool {
pool: HashMap<usize, Vec<*mut c_void>>,
}
impl SecretMemoryPool {
/// Create a new [SecretMemoryPool]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let pool = HashMap::new();
Self { pool }
}
/// Return secrete back to the pool for future re-use
///
/// This consumes the [Secret], but its memory is re-used.
pub fn release<const N: usize>(&mut self, mut s: Secret<N>) {
unsafe {
self.release_by_ref(&mut s);
}
std::mem::forget(s);
}
/// Return secret back to the pool for future re-use, by slice
///
/// # Safety
///
/// After calling this function on a [Secret], the secret must never be
/// used again for anything.
unsafe fn release_by_ref<const N: usize>(&mut self, s: &mut Secret<N>) {
s.zeroize();
let Secret { ptr: secret } = s;
// don't call Secret::drop, that could cause a double free
self.pool.entry(N).or_default().push(*secret);
}
/// 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
///
/// # Safety
///
/// This function contains an unsafe call to [libsodium::sodium_malloc].
/// This call has no known safety invariants, thus nothing can go wrong™.
/// However, just like normal `malloc()` this can return a null ptr. Thus
/// the returned pointer is checked for null; causing the program to panic
/// if it is null.
pub fn take<const N: usize>(&mut self) -> Secret<N> {
let entry = self.pool.entry(N).or_default();
let secret = entry.pop().unwrap_or_else(|| {
let ptr = unsafe { libsodium::sodium_malloc(N) };
assert!(
!ptr.is_null(),
"libsodium::sodium_mallloc() returned a null ptr"
);
ptr
});
let mut s = Secret { ptr: secret };
s.zeroize();
s
}
}
impl Drop for SecretMemoryPool {
/// # Safety
///
/// The drop implementation frees the contained elements using
/// [libsodium::sodium_free]. This is safe as long as every `*mut c_void`
/// contained was initialized with a call to [libsodium::sodium_malloc]
fn drop(&mut self) {
for ptr in self.pool.drain().flat_map(|(_, x)| x.into_iter()) {
unsafe {
libsodium::sodium_free(ptr);
}
}
}
}
/// # Safety
///
/// No safety implications are known, since the `*mut c_void` in
/// is essentially used like a `&mut u8` [SecretMemoryPool].
unsafe impl Send for SecretMemoryPool {}
/// Store for a secret
///
/// Uses memory allocated with [libsodium::sodium_malloc],
/// esentially can do the same things as `[u8; N].as_mut_ptr()`.
pub struct Secret<const N: usize> {
ptr: *mut c_void,
}
impl<const N: usize> Clone for Secret<N> {
fn clone(&self) -> Self {
let mut new = Self::zero();
new.secret_mut().clone_from_slice(self.secret());
new
}
}
impl<const N: usize> Drop for Secret<N> {
fn drop(&mut self) {
self.zeroize();
// the invariant that the [Secret] is not used after the
// `release_by_ref` call is guaranteed, since this is a drop implementation
unsafe { SECRET_CACHE.lock().unwrap().release_by_ref(self) };
self.ptr = null_mut();
}
}
impl<const N: usize> Secret<N> {
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
pub fn zero() -> Self {
// Using [SecretMemoryPool] here because this operation is expensive,
// yet it is used in hot loops
let s = SECRET_CACHE.lock().unwrap().take();
assert_eq!(s.secret(), &[0u8; N]);
s
}
/// Returns a new [Secret] that is randomized
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Sets all data of an existing secret to null bytes
pub fn zeroize(&mut self) {
rosenpass_sodium::helpers::memzero(self.secret_mut());
}
/// Sets all data an existing secret to random bytes
pub fn randomize(&mut self) {
rosenpass_sodium::helpers::randombytes_buf(self.secret_mut());
}
/// Borrows the data
pub fn secret(&self) -> &[u8; N] {
// - calling `from_raw_parts` is safe, because `ptr` is initalized with
// as `N` byte allocation from the creation of `Secret` onwards. `ptr`
// stays valid over the full lifetime of `Secret`
//
// - calling uwnrap is safe, because we can guarantee that the slice has
// exactly the required size `N` to create an array of `N` elements.
let ptr = self.ptr as *const u8;
let slice = unsafe { std::slice::from_raw_parts(ptr, N) };
slice.try_into().unwrap()
}
/// Borrows the data mutably
pub fn secret_mut(&mut self) -> &mut [u8; N] {
// the same safety argument as for `secret()` holds
let ptr = self.ptr as *mut u8;
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, N) };
slice.try_into().unwrap()
}
}
/// The Debug implementation of [Secret] does not reveal the secret data,
/// instead a placeholder `<SECRET>` is used
impl<const N: usize> fmt::Debug for Secret<N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<SECRET>")
}
}
/// Contains information in the form of a byte array that may be known to the
/// public
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Public<const N: usize> {
pub value: [u8; N],
}
impl<const N: usize> Public<N> {
/// Create a new [Public] from a byte slice
pub fn from_slice(value: &[u8]) -> Self {
mutating(Self::zero(), |r| cpy(value, &mut r.value))
}
/// Create a new [Public] from a byte array
pub fn new(value: [u8; N]) -> Self {
Self { value }
}
/// Create a zero initialized [Public]
pub fn zero() -> Self {
Self { value: [0u8; N] }
}
/// Create a random initialized [Public]
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Randomize all bytes in an existing [Public]
pub fn randomize(&mut self) {
rosenpass_sodium::helpers::randombytes_buf(&mut self.value);
}
}
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {
for byte in &v[..32] {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
fmt.write_str("")?;
for byte in &v[v.len() - 32..] {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
} else {
for byte in v {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
}
Ok(())
}
impl<const N: usize> fmt::Debug for Public<N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&self.value, fmt)
}
}
impl<const N: usize> Deref for Public<N> {
type Target = [u8; N];
fn deref(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> DerefMut for Public<N> {
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
#[cfg(test)]
mod test {
use super::*;
/// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations
/// promises us that allocated memory is initialized with this magic byte
const SODIUM_MAGIC_BYTE: u8 = 0xdb;
/// must be called before any interaction with libsodium
fn init() {
unsafe { libsodium_sys::sodium_init() };
}
/// checks that whe can malloc with libsodium
#[test]
fn sodium_malloc() {
init();
const N: usize = 8;
let ptr = unsafe { libsodium_sys::sodium_malloc(N) };
let mem = unsafe { std::slice::from_raw_parts(ptr as *mut u8, N) };
assert_eq!(mem, &[SODIUM_MAGIC_BYTE; N])
}
/// checks that whe can free with libsodium
#[test]
fn sodium_free() {
init();
const N: usize = 8;
let ptr = unsafe { libsodium_sys::sodium_malloc(N) };
unsafe { libsodium_sys::sodium_free(ptr) }
}
/// check that we can alloc using the magic pool
#[test]
fn secret_memory_pool_take() {
init();
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: Secret<N> = pool.take();
assert_eq!(secret.secret(), &[0; N]);
}
/// check that a secrete lives, even if its [SecretMemoryPool] is deleted
#[test]
fn secret_memory_pool_drop() {
init();
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: Secret<N> = pool.take();
std::mem::drop(pool);
assert_eq!(secret.secret(), &[0; N]);
}
/// check that a secrete can be reborn, freshly initialized with zero
#[test]
fn secret_memory_pool_release() {
init();
const N: usize = 1;
let mut pool = SecretMemoryPool::new();
let mut secret: Secret<N> = pool.take();
let old_secret_ptr = secret.ptr;
secret.secret_mut()[0] = 0x13;
pool.release(secret);
// now check that we get the same ptr
let new_secret: Secret<N> = pool.take();
assert_eq!(old_secret_ptr, new_secret.ptr);
// and that the secret was zeroized
assert_eq!(new_secret.secret(), &[0; N]);
}
}
trait StoreSecret {
type Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}
impl<T: StoreValue> StoreSecret for T {
type Error = <T as StoreValue>::Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
self.store(path)
}
}
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
fopen_r(p)?
.read_exact_to_end(v.secret_mut())
.with_context(|| format!("Could not load file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
fn load_b64<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
use std::io::Read;
let mut v = Self::random();
let p = path.as_ref();
// This might leave some fragments of the secret on the stack;
// in practice this is likely not a problem because the stack likely
// will be overwritten by something else soon but this is not exactly
// guaranteed. It would be possible to remedy this, but since the secret
// data will linger in the Linux page cache anyways with the current
// implementation, going to great length to erase the secret here is
// not worth it right now.
b64_reader(&mut fopen_r(p)?)
.read_exact(v.secret_mut())
.with_context(|| format!("Could not load base64 file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, self.secret())?;
Ok(())
}
}
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
Ok(v)
}
}
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
}
}

View File

@@ -41,10 +41,6 @@ pub struct RosenpassPeer {
#[serde(default)] #[serde(default)]
pub key_out: Option<PathBuf>, pub key_out: Option<PathBuf>,
// TODO make sure failure does not crash but is logged
#[serde(default)]
pub exchange_command: Vec<String>,
// TODO make this field only available on binary builds, not on library builds // TODO make this field only available on binary builds, not on library builds
#[serde(flatten)] #[serde(flatten)]
pub wg: Option<WireGuard>, pub wg: Option<WireGuard>,
@@ -345,28 +341,20 @@ impl Rosenpass {
/// Generate an example configuration /// Generate an example configuration
pub fn example_config() -> Self { pub fn example_config() -> Self {
let peer = RosenpassPeer { let peer = RosenpassPeer {
public_key: "rp-peer-public-key".into(), public_key: "/path/to/rp-peer-public-key".into(),
endpoint: Some("my-peer.test:9999".into()), endpoint: Some("my-peer.test:9999".into()),
exchange_command: [ key_out: Some("/path/to/rp-key-out.txt".into()),
"wg", pre_shared_key: Some("additional pre shared key".into()),
"set", wg: Some(WireGuard {
"wg0", device: "wirgeguard device e.g. wg0".into(),
"peer", peer: "wireguard public key".into(),
"<PEER_ID>", extra_params: vec!["passed to".into(), "wg set".into()],
"preshared-key", }),
"/dev/stdin",
]
.into_iter()
.map(|x| x.to_string())
.collect(),
key_out: Some("rp-key-out".into()),
pre_shared_key: None,
wg: None,
}; };
Self { Self {
public_key: "rp-public-key".into(), public_key: "/path/to/rp-public-key".into(),
secret_key: "rp-secret-key".into(), secret_key: "/path/to/rp-secret-key".into(),
peers: vec![peer], peers: vec![peer],
..Self::new("", "") ..Self::new("", "")
} }
@@ -386,7 +374,7 @@ mod test {
use super::*; use super::*;
fn split_str(s: &str) -> Vec<String> { fn split_str(s: &str) -> Vec<String> {
s.split(" ").map(|s| s.to_string()).collect() s.split(' ').map(|s| s.to_string()).collect()
} }
#[test] #[test]

View File

@@ -0,0 +1,46 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use anyhow::Result;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
// TODO Use labels that can serve as identifiers
macro_rules! hash_domain_ns {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<HashDomain> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t)
}
}
}
macro_rules! hash_domain {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())
}
}
}
pub fn protocol() -> Result<HashDomain> {
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
}
hash_domain_ns!(protocol, mac, "mac");
hash_domain_ns!(protocol, cookie, "cookie");
hash_domain_ns!(protocol, peerid, "peer id");
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
hash_domain_ns!(protocol, ckinit, "chaining key init");
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
hash_domain!(_ckextract, mix, "mix");
hash_domain!(_ckextract, hs_enc, "handshake encryption");
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
hash_domain_ns!(_ckextract, _user, "user");
hash_domain_ns!(_user, _rp, "rosenpass.eu");
hash_domain!(_rp, osk, "wireguard psk");

View File

@@ -1,48 +0,0 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use crate::prftree::PrfTree;
use anyhow::Result;
use rosenpass_ciphers::KEY_LEN;
pub fn protocol() -> Result<PrfTree> {
PrfTree::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
}
// TODO Use labels that can serve as identifiers
macro_rules! prflabel {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<PrfTree> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t)
}
}
}
prflabel!(protocol, mac, "mac");
prflabel!(protocol, cookie, "cookie");
prflabel!(protocol, peerid, "peer id");
prflabel!(protocol, biscuit_ad, "biscuit additional data");
prflabel!(protocol, ckinit, "chaining key init");
prflabel!(protocol, _ckextract, "chaining key extract");
macro_rules! prflabel_leaf {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())
}
}
}
prflabel_leaf!(_ckextract, mix, "mix");
prflabel_leaf!(_ckextract, hs_enc, "handshake encryption");
prflabel_leaf!(_ckextract, ini_enc, "initiator handshake encryption");
prflabel_leaf!(_ckextract, res_enc, "responder handshake encryption");
prflabel!(_ckextract, _user, "user");
prflabel!(_user, _rp, "rosenpass.eu");
prflabel_leaf!(_rp, osk, "wireguard psk");

View File

@@ -1,56 +1,24 @@
pub mod coloring; use rosenpass_lenses::LenseError;
#[rustfmt::skip]
pub mod labeled_prf;
pub mod app_server; pub mod app_server;
pub mod cli; pub mod cli;
pub mod config; pub mod config;
pub mod hash_domains;
pub mod msgs; pub mod msgs;
pub mod pqkem;
pub mod prftree;
pub mod protocol; pub mod protocol;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum RosenpassError { pub enum RosenpassError {
#[error("error in OQS")] #[error("buffer size mismatch")]
Oqs, BufferSizeMismatch,
#[error("error from external library while calling OQS")]
OqsExternalLib,
#[error("buffer size mismatch, required {required_size} but found {actual_size}")]
BufferSizeMismatch {
required_size: usize,
actual_size: usize,
},
#[error("invalid message type")] #[error("invalid message type")]
InvalidMessageType(u8), InvalidMessageType(u8),
} }
impl RosenpassError { impl From<LenseError> for RosenpassError {
/// Helper function to check a buffer size fn from(value: LenseError) -> Self {
fn check_buffer_size(required_size: usize, actual_size: usize) -> Result<(), Self> { match value {
if required_size != actual_size { LenseError::BufferSizeMismatch => RosenpassError::BufferSizeMismatch,
Err(Self::BufferSizeMismatch {
required_size,
actual_size,
})
} else {
Ok(())
}
}
}
/// Extension trait to attach function calls to foreign types.
trait RosenpassMaybeError {
/// Checks whether something is an error or not
fn to_rg_error(&self) -> Result<(), RosenpassError>;
}
impl RosenpassMaybeError for oqs_sys::common::OQS_STATUS {
fn to_rg_error(&self) -> Result<(), RosenpassError> {
use oqs_sys::common::OQS_STATUS;
match self {
OQS_STATUS::OQS_SUCCESS => Ok(()),
OQS_STATUS::OQS_ERROR => Err(RosenpassError::Oqs),
OQS_STATUS::OQS_EXTERNAL_LIB_ERROR_OPENSSL => Err(RosenpassError::OqsExternalLib),
} }
} }
} }

View File

@@ -1,18 +1,16 @@
use log::error; use log::error;
use rosenpass::cli::Cli; use rosenpass::cli::Cli;
use rosenpass_util::attempt;
use std::process::exit; use std::process::exit;
#[cfg(target_os = "hermit")]
use hermit as _;
/// Catches errors, prints them through the logger, then exits /// Catches errors, prints them through the logger, then exits
pub fn main() { pub fn main() {
env_logger::init(); // default to displaying warning and error log messages only
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let res = attempt!({ match Cli::run() {
rosenpass_sodium::init()?;
Cli::run()
});
match res {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("{e}"); error!("{e}");

View File

@@ -9,14 +9,15 @@
//! //!
//! # Example //! # Example
//! //!
//! The following example uses the [`data_lense` macro](crate::data_lense) to create a lense that //! The following example uses the [`lense` macro](rosenpass_lenses::lense) to create a lense that
//! might be useful when dealing with UDP headers. //! might be useful when dealing with UDP headers.
//! //!
//! ``` //! ```
//! use rosenpass::{data_lense, RosenpassError, msgs::LenseView}; //! use rosenpass_lenses::{lense, LenseView};
//! use rosenpass::RosenpassError;
//! # fn main() -> Result<(), RosenpassError> { //! # fn main() -> Result<(), RosenpassError> {
//! //!
//! data_lense! {UdpDatagramHeader := //! lense! {UdpDatagramHeader :=
//! source_port: 2, //! source_port: 2,
//! dest_port: 2, //! dest_port: 2,
//! length: 2, //! length: 2,
@@ -44,219 +45,14 @@
//! ``` //! ```
use super::RosenpassError; use super::RosenpassError;
use crate::pqkem::*; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN}; use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_lenses::{lense, LenseView};
// Macro magic //////////////////////////////////////////////////////////////// // Macro magic ////////////////////////////////////////////////////////////////
/// A macro to create data lenses. Refer to the [`msgs` mod](crate::msgs) for lense! { Envelope<M> :=
/// an example and further elaboration
// TODO implement TryFrom<[u8]> and From<[u8; Self::len()]>
#[macro_export]
macro_rules! data_lense(
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = data_lense!(maybe_docstring_link $len)]
/// bytes long
pub fn $field(&self) -> &__ContainerType::Output {
&self.0[$offset .. $offset + $len]
}
/// The bytes until the
#[doc = data_lense!(maybe_docstring_link Self::$field)]
/// field
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
&self.0[0 .. $offset]
}
// if the tail exits, consume it as well
$(
data_lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
)?
}
};
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = data_lense!(maybe_docstring_link $len)]
/// bytes long
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
&mut self.0[$offset .. $offset + $len]
}
// if the tail exits, consume it as well
$(
data_lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
)?
}
};
// switch that yields literals unchanged, but creates docstring links to
// constants
// TODO the doc string link doesn't work if $x is taken from a generic,
(maybe_docstring_link $x:literal) => (stringify!($x));
(maybe_docstring_link $x:expr) => (stringify!([$x]));
// struct name < optional generics > := optional doc string field name : field length, ...
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
/// A data lense to manipulate byte slices.
///
//// # Fields
///
$(
/// - `
#[doc = stringify!($field)]
/// `:
#[doc = data_lense!(maybe_docstring_link $len)]
/// bytes
)+
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
__ContainerType,
// The phantom data is required, since all generics declared on a
// type need to be used on the type.
// https://doc.rust-lang.org/stable/error_codes/E0392.html
$( $( ::core::marker::PhantomData<$generic> ),+ )?
);
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
$(
/// Size in bytes of the field `
#[doc = !($field)]
/// `
pub const fn [< $field _len >]() -> usize{
$len
}
)+
/// Verify that `len` is sufficiently long to hold [Self]
pub fn check_size(len: usize) -> Result<(), RosenpassError>{
let required_size = $( $len + )+ 0;
let actual_size = len;
if required_size != actual_size {
Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
})
}else{
Ok(())
}
}
}
// read-only accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
{
data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
}
// mutable accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
{
data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
data_lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
/// View into all bytes belonging to this Lense
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
&mut self.0[0..Self::LEN]
}
}
// lense trait, allowing us to know the implementing lenses size
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
/// Number of bytes required to store this type in binary format
const LEN: usize = $( $len + )+ 0;
}
/// Extension trait to allow checked creation of a lense over
/// some byte slice that contains a
#[doc = data_lense!(maybe_docstring_link $type)]
pub trait [< $type Ext >] {
type __ContainerType;
/// Create a lense to the byte slice
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
/// Create a lense to the byte slice, automatically truncating oversized buffers
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
}
impl<'a> [< $type Ext >] for &'a [u8] {
type __ContainerType = &'a [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
let required_size = $( $len + )+ 0;
let actual_size = self.len();
if actual_size < required_size {
return Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
});
}
[< $type Ext >]::[< $type:snake >](&self[..required_size])
}
}
impl<'a> [< $type Ext >] for &'a mut [u8] {
type __ContainerType = &'a mut [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
let required_size = $( $len + )+ 0;
let actual_size = self.len();
if actual_size < required_size {
return Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
});
}
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
}
}
});
);
/// Common trait shared by all Lenses
pub trait LenseView {
const LEN: usize;
}
data_lense! { Envelope<M> :=
/// [MsgType] of this message /// [MsgType] of this message
msg_type: 1, msg_type: 1,
/// Reserved for future use /// Reserved for future use
@@ -270,35 +66,35 @@ data_lense! { Envelope<M> :=
cookie: 16 cookie: 16
} }
data_lense! { InitHello := lense! { InitHello :=
/// Randomly generated connection id /// Randomly generated connection id
sidi: 4, sidi: 4,
/// Kyber 512 Ephemeral Public Key /// Kyber 512 Ephemeral Public Key
epki: EphemeralKEM::PK_LEN, epki: EphemeralKem::PK_LEN,
/// Classic McEliece Ciphertext /// Classic McEliece Ciphertext
sctr: StaticKEM::CT_LEN, sctr: StaticKem::CT_LEN,
/// Encryped: 16 byte hash of McEliece initiator static key /// Encryped: 16 byte hash of McEliece initiator static key
pidic: aead::TAG_LEN + 32, pidic: aead::TAG_LEN + 32,
/// Encrypted TAI64N Time Stamp (against replay attacks) /// Encrypted TAI64N Time Stamp (against replay attacks)
auth: aead::TAG_LEN auth: aead::TAG_LEN
} }
data_lense! { RespHello := lense! { RespHello :=
/// Randomly generated connection id /// Randomly generated connection id
sidr: 4, sidr: 4,
/// Copied from InitHello /// Copied from InitHello
sidi: 4, sidi: 4,
/// Kyber 512 Ephemeral Ciphertext /// Kyber 512 Ephemeral Ciphertext
ecti: EphemeralKEM::CT_LEN, ecti: EphemeralKem::CT_LEN,
/// Classic McEliece Ciphertext /// Classic McEliece Ciphertext
scti: StaticKEM::CT_LEN, scti: StaticKem::CT_LEN,
/// Empty encrypted message (just an auth tag) /// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN, auth: aead::TAG_LEN,
/// Responders handshake state in encrypted form /// Responders handshake state in encrypted form
biscuit: BISCUIT_CT_LEN biscuit: BISCUIT_CT_LEN
} }
data_lense! { InitConf := lense! { InitConf :=
/// Copied from InitHello /// Copied from InitHello
sidi: 4, sidi: 4,
/// Copied from RespHello /// Copied from RespHello
@@ -309,7 +105,7 @@ data_lense! { InitConf :=
auth: aead::TAG_LEN auth: aead::TAG_LEN
} }
data_lense! { EmptyData := lense! { EmptyData :=
/// Copied from RespHello /// Copied from RespHello
sid: 4, sid: 4,
/// Nonce /// Nonce
@@ -318,7 +114,7 @@ data_lense! { EmptyData :=
auth: aead::TAG_LEN auth: aead::TAG_LEN
} }
data_lense! { Biscuit := lense! { Biscuit :=
/// H(spki) Ident ifies the initiator /// H(spki) Ident ifies the initiator
pidi: KEY_LEN, pidi: KEY_LEN,
/// The biscuit number (replay protection) /// The biscuit number (replay protection)
@@ -327,11 +123,11 @@ data_lense! { Biscuit :=
ck: KEY_LEN ck: KEY_LEN
} }
data_lense! { DataMsg := lense! { DataMsg :=
dummy: 4 dummy: 4
} }
data_lense! { CookieReply := lense! { CookieReply :=
dummy: 4 dummy: 4
} }

View File

@@ -1,168 +0,0 @@
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
//!
//! KEMs are the interface provided by almost all post-quantum
//! secure key exchange mechanisms.
//!
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
//!
//! encapsulation.
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
use crate::{RosenpassError, RosenpassMaybeError};
/// Key Encapsulation Mechanism
///
/// The KEM interface defines three operations: Key generation, key encapsulation and key
/// decapsulation.
pub trait KEM {
/// Secrete Key length
const SK_LEN: usize;
/// Public Key length
const PK_LEN: usize;
/// Ciphertext length
const CT_LEN: usize;
/// Shared Secret length
const SHK_LEN: usize;
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
///
/// `keygen() -> sk, pk`
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError>;
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
///
/// `encaps(pk) -> shk, ct`
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError>;
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
/// (`shk`)
///
/// `decaps(sk, ct) -> shk`
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError>;
}
/// A KEM that is secure against Chosen Ciphertext Attacks (CCA).
/// In the context of rosenpass this is used for static keys.
/// Uses [Classic McEliece](https://classic.mceliece.org/) 460896 from liboqs.
///
/// Classic McEliece is chosen because of its high security margin and its small
/// ciphertexts. The public keys are humongous, but (being static keys) the are never transmitted over
/// the wire so this is not a big problem.
pub struct StaticKEM;
/// # Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
/// slices only identified using raw pointers. It must be ensured that the raw
/// pointers point into byte slices of sufficient length, to avoid UB through
/// overwriting of arbitrary data. This is checked in the following code before
/// the unsafe calls, and an early return with an Err occurs if the byte slice
/// size does not match the required size.
///
/// __Note__: This requirement is stricter than necessary, it would suffice
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl KEM for StaticKEM {
const SK_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_secret_key as usize;
const PK_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_public_key as usize;
const CT_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_ciphertext as usize;
const SHK_LEN: usize =
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_shared_secret as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_keypair(pk.as_mut_ptr(), sk.as_mut_ptr())
.to_rg_error()
}
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_encaps(
ct.as_mut_ptr(),
shk.as_mut_ptr(),
pk.as_ptr(),
)
.to_rg_error()
}
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_decaps(
shk.as_mut_ptr(),
ct.as_ptr(),
sk.as_ptr(),
)
.to_rg_error()
}
}
}
/// Implements a KEM that is secure against Chosen Plaintext Attacks (CPA).
/// In the context of rosenpass this is used for ephemeral keys.
/// Currently the implementation uses
/// [Kyber 512](https://openquantumsafe.org/liboqs/algorithms/kem/kyber) from liboqs.
///
/// This is being used for ephemeral keys; since these are use-once the first post quantum
/// wireguard paper claimed that CPA security would be sufficient. Nonetheless we choose kyber
/// which provides CCA security since there are no publicly vetted KEMs out there which provide
/// only CPA security.
pub struct EphemeralKEM;
/// # Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
/// slices only identified using raw pointers. It must be ensured that the raw
/// pointers point into byte slices of sufficient length, to avoid UB through
/// overwriting of arbitrary data. This is checked in the following code before
/// the unsafe calls, and an early return with an Err occurs if the byte slice
/// size does not match the required size.
///
/// __Note__: This requirement is stricter than necessary, it would suffice
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl KEM for EphemeralKEM {
const SK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_secret_key as usize;
const PK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_public_key as usize;
const CT_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_ciphertext as usize;
const SHK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_shared_secret as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_kyber_512_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()).to_rg_error()
}
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_kyber_512_encaps(ct.as_mut_ptr(), shk.as_mut_ptr(), pk.as_ptr())
.to_rg_error()
}
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_kyber_512_decaps(shk.as_mut_ptr(), ct.as_ptr(), sk.as_ptr())
.to_rg_error()
}
}
}

View File

@@ -1,106 +0,0 @@
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
use crate::coloring::Secret;
use anyhow::Result;
use rosenpass_ciphers::{hash, KEY_LEN};
use rosenpass_to::To;
// TODO Use a proper Dec interface
#[derive(Clone, Debug)]
pub struct PrfTree([u8; KEY_LEN]);
#[derive(Clone, Debug)]
pub struct PrfTreeBranch([u8; KEY_LEN]);
#[derive(Clone, Debug)]
pub struct SecretPrfTree(Secret<KEY_LEN>);
#[derive(Clone, Debug)]
pub struct SecretPrfTreeBranch(Secret<KEY_LEN>);
impl PrfTree {
pub fn zero() -> Self {
Self([0u8; KEY_LEN])
}
pub fn dup(self) -> PrfTreeBranch {
PrfTreeBranch(self.0)
}
pub fn into_secret_prf_tree(self) -> SecretPrfTree {
SecretPrfTree(Secret::from_slice(&self.0))
}
// TODO: Protocol! Use domain separation to ensure that
pub fn mix(self, v: &[u8]) -> Result<Self> {
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(&self.0, v.secret())
}
pub fn into_value(self) -> [u8; KEY_LEN] {
self.0
}
}
impl PrfTreeBranch {
pub fn mix(&self, v: &[u8]) -> Result<PrfTree> {
Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(&self.0, v.secret())
}
}
impl SecretPrfTree {
pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result<SecretPrfTree> {
let mut r = SecretPrfTree(Secret::zero());
hash::hash(k, d).to(r.0.secret_mut())?;
Ok(r)
}
pub fn zero() -> Self {
Self(Secret::zero())
}
pub fn dup(self) -> SecretPrfTreeBranch {
SecretPrfTreeBranch(self.0)
}
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
Self(k)
}
pub fn mix(self, v: &[u8]) -> Result<SecretPrfTree> {
Self::prf_invoc(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
Self::prf_invoc(self.0.secret(), v.secret())
}
pub fn into_secret(self) -> Secret<KEY_LEN> {
self.0
}
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
hash::hash(v, dst).to(self.0.secret_mut())
}
}
impl SecretPrfTreeBranch {
pub fn mix(&self, v: &[u8]) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(self.0.secret(), v.secret())
}
// TODO: This entire API is not very nice; we need this for biscuits, but
// it might be better to extract a special "biscuit"
// labeled subkey and reinitialize the chain with this
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
self.0
}
}

View File

@@ -19,22 +19,20 @@
//! [CryptoServer]. //! [CryptoServer].
//! //!
//! ``` //! ```
//! use rosenpass_cipher_traits::Kem;
//! use rosenpass_ciphers::kem::StaticKem;
//! use rosenpass::{ //! use rosenpass::{
//! pqkem::{StaticKEM, KEM},
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey}, //! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
//! }; //! };
//! # fn main() -> anyhow::Result<()> { //! # fn main() -> anyhow::Result<()> {
//! //!
//! // always initialize libsodium before anything
//! rosenpass_sodium::init()?;
//!
//! // initialize secret and public key for peer a ... //! // initialize secret and public key for peer a ...
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero()); //! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
//! StaticKEM::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?; //! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
//! //!
//! // ... and for peer b //! // ... and for peer b
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero()); //! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
//! StaticKEM::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?; //! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?;
//! //!
//! // initialize server and a pre-shared key //! // initialize server and a pre-shared key
//! let psk = SymKey::random(); //! let psk = SymKey::random();
@@ -67,20 +65,24 @@
//! # } //! # }
//! ``` //! ```
use crate::{
coloring::*,
labeled_prf as lprf,
msgs::*,
pqkem::*,
prftree::{SecretPrfTree, SecretPrfTreeBranch},
};
use anyhow::{bail, ensure, Context, Result};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
use std::collections::hash_map::{ use std::collections::hash_map::{
Entry::{Occupied, Vacant}, Entry::{Occupied, Vacant},
HashMap, HashMap,
}; };
use std::convert::Infallible;
use anyhow::{bail, ensure, Context, Result};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace};
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_constant_time as constant_time;
use rosenpass_lenses::LenseView;
use rosenpass_secret_memory::{Public, Secret};
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
use crate::{hash_domains, msgs::*};
// CONSTANTS & SETTINGS ////////////////////////// // CONSTANTS & SETTINGS //////////////////////////
@@ -139,10 +141,10 @@ pub fn has_happened(ev: Timing, now: Timing) -> bool {
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS //// // DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
pub type SPk = Secret<{ StaticKEM::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap pub type SPk = Secret<{ StaticKem::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
pub type SSk = Secret<{ StaticKEM::SK_LEN }>; pub type SSk = Secret<{ StaticKem::SK_LEN }>;
pub type EPk = Public<{ EphemeralKEM::PK_LEN }>; pub type EPk = Public<{ EphemeralKem::PK_LEN }>;
pub type ESk = Secret<{ EphemeralKEM::SK_LEN }>; pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
pub type SymKey = Secret<KEY_LEN>; pub type SymKey = Secret<KEY_LEN>;
pub type SymHash = Public<KEY_LEN>; pub type SymHash = Public<KEY_LEN>;
@@ -233,7 +235,7 @@ pub struct HandshakeState {
/// Session ID of Responder /// Session ID of Responder
pub sidr: SessionId, pub sidr: SessionId,
/// Chaining Key /// Chaining Key
pub ck: SecretPrfTreeBranch, pub ck: SecretHashDomainNamespace, // TODO: We should probably add an abstr
} }
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
@@ -285,7 +287,7 @@ pub struct Session {
pub sidt: SessionId, pub sidt: SessionId,
pub handshake_role: HandshakeRole, pub handshake_role: HandshakeRole,
// Crypto // Crypto
pub ck: SecretPrfTreeBranch, pub ck: SecretHashDomainNamespace,
/// Key for Transmission ("transmission key mine") /// Key for Transmission ("transmission key mine")
pub txkm: SymKey, pub txkm: SymKey,
/// Key for Reception ("transmission key theirs") /// Key for Reception ("transmission key theirs")
@@ -460,7 +462,7 @@ impl CryptoServer {
#[rustfmt::skip] #[rustfmt::skip]
pub fn pidm(&self) -> Result<PeerId> { pub fn pidm(&self) -> Result<PeerId> {
Ok(Public::new( Ok(Public::new(
lprf::peerid()? hash_domains::peerid()?
.mix(self.spkm.secret())? .mix(self.spkm.secret())?
.into_value())) .into_value()))
} }
@@ -590,7 +592,7 @@ impl Peer {
#[rustfmt::skip] #[rustfmt::skip]
pub fn pidt(&self) -> Result<PeerId> { pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new( Ok(Public::new(
lprf::peerid()? hash_domains::peerid()?
.mix(self.spkt.secret())? .mix(self.spkt.secret())?
.into_value())) .into_value()))
} }
@@ -603,7 +605,7 @@ impl Session {
sidm: SessionId::zero(), sidm: SessionId::zero(),
sidt: SessionId::zero(), sidt: SessionId::zero(),
handshake_role: HandshakeRole::Initiator, handshake_role: HandshakeRole::Initiator,
ck: SecretPrfTree::zero().dup(), ck: SecretHashDomain::zero().dup(),
txkm: SymKey::zero(), txkm: SymKey::zero(),
txkt: SymKey::zero(), txkt: SymKey::zero(),
txnm: 0, txnm: 0,
@@ -1154,7 +1156,7 @@ impl IniHsPtr {
.min(ih.tx_count as f64), .min(ih.tx_count as f64),
) )
* RETRANSMIT_DELAY_JITTER * RETRANSMIT_DELAY_JITTER
* (rosenpass_sodium::helpers::rand_f64() + 1.0); // TODO: Replace with the rand crate * (rand::random::<f64>() + 1.0); // TODO: Replace with the rand crate
ih.tx_count += 1; ih.tx_count += 1;
Ok(()) Ok(())
} }
@@ -1174,7 +1176,7 @@ where
{ {
/// Calculate the message authentication code (`mac`) /// Calculate the message authentication code (`mac`)
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = lprf::mac()? let mac = hash_domains::mac()?
.mix(peer.get(srv).spkt.secret())? .mix(peer.get(srv).spkt.secret())?
.mix(self.until_mac())?; .mix(self.until_mac())?;
self.mac_mut() self.mac_mut()
@@ -1189,8 +1191,10 @@ where
{ {
/// Check the message authentication code /// Check the message authentication code
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> { pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?; let expected = hash_domains::mac()?
Ok(rosenpass_sodium::helpers::memcmp( .mix(srv.spkm.secret())?
.mix(self.until_mac())?;
Ok(constant_time::memcmp(
self.mac(), self.mac(),
&expected.into_value()[..16], &expected.into_value()[..16],
)) ))
@@ -1219,38 +1223,38 @@ impl HandshakeState {
Self { Self {
sidi: SessionId::zero(), sidi: SessionId::zero(),
sidr: SessionId::zero(), sidr: SessionId::zero(),
ck: SecretPrfTree::zero().dup(), ck: SecretHashDomain::zero().dup(),
} }
} }
pub fn erase(&mut self) { pub fn erase(&mut self) {
self.ck = SecretPrfTree::zero().dup(); self.ck = SecretHashDomain::zero().dup();
} }
pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> { pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> {
self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup(); self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup();
Ok(self) Ok(self)
} }
pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> { pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> {
self.ck = self.ck.mix(&lprf::mix()?)?.mix(a)?.dup(); self.ck = self.ck.mix(&hash_domains::mix()?)?.mix(a)?.dup();
Ok(self) Ok(self)
} }
pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> { pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret(); let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?; aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?;
self.mix(ct) self.mix(ct)
} }
pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> { pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret(); let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?; aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?;
self.mix(ct) self.mix(ct)
} }
// I loathe "error: constant expression depends on a generic parameter" // I loathe "error: constant expression depends on a generic parameter"
pub fn encaps_and_mix<T: KEM, const SHK_LEN: usize>( pub fn encaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
&mut self, &mut self,
ct: &mut [u8], ct: &mut [u8],
pk: &[u8], pk: &[u8],
@@ -1260,7 +1264,7 @@ impl HandshakeState {
self.mix(pk)?.mix(shk.secret())?.mix(ct) self.mix(pk)?.mix(shk.secret())?.mix(ct)
} }
pub fn decaps_and_mix<T: KEM, const SHK_LEN: usize>( pub fn decaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
&mut self, &mut self,
sk: &[u8], sk: &[u8],
pk: &[u8], pk: &[u8],
@@ -1290,14 +1294,14 @@ impl HandshakeState {
.copy_from_slice(self.ck.clone().danger_into_secret().secret()); .copy_from_slice(self.ck.clone().danger_into_secret().secret());
// calculate ad contents // calculate ad contents
let ad = lprf::biscuit_ad()? let ad = hash_domains::biscuit_ad()?
.mix(srv.spkm.secret())? .mix(srv.spkm.secret())?
.mix(self.sidi.as_slice())? .mix(self.sidi.as_slice())?
.mix(self.sidr.as_slice())? .mix(self.sidr.as_slice())?
.into_value(); .into_value();
// consume biscuit no // consume biscuit no
rosenpass_sodium::helpers::increment(&mut *srv.biscuit_ctr); constant_time::increment(&mut *srv.biscuit_ctr);
// The first bit of the nonce indicates which biscuit key was used // The first bit of the nonce indicates which biscuit key was used
// TODO: This is premature optimization. Remove! // TODO: This is premature optimization. Remove!
@@ -1325,7 +1329,7 @@ impl HandshakeState {
let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize); let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize);
// Calculate additional data fields // Calculate additional data fields
let ad = lprf::biscuit_ad()? let ad = hash_domains::biscuit_ad()?
.mix(srv.spkm.secret())? .mix(srv.spkm.secret())?
.mix(sidi.as_slice())? .mix(sidi.as_slice())?
.mix(sidr.as_slice())? .mix(sidr.as_slice())?
@@ -1343,7 +1347,7 @@ impl HandshakeState {
// Reconstruct the biscuit fields // Reconstruct the biscuit fields
let no = BiscuitId::from_slice(biscuit.biscuit_no()); let no = BiscuitId::from_slice(biscuit.biscuit_no());
let ck = SecretPrfTree::danger_from_secret(Secret::from_slice(biscuit.ck())).dup(); let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
let pid = PeerId::from_slice(biscuit.pidi()); let pid = PeerId::from_slice(biscuit.pidi());
// Reconstruct the handshake state // Reconstruct the handshake state
@@ -1360,8 +1364,7 @@ impl HandshakeState {
// indicates retransmission // indicates retransmission
// TODO: Handle retransmissions without involving the crypto code // TODO: Handle retransmissions without involving the crypto code
ensure!( ensure!(
rosenpass_sodium::helpers::compare(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) constant_time::compare(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) >= 0,
>= 0,
"Rejecting biscuit: Outdated biscuit number" "Rejecting biscuit: Outdated biscuit number"
); );
@@ -1370,8 +1373,8 @@ impl HandshakeState {
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> { pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> {
let HandshakeState { ck, sidi, sidr } = self; let HandshakeState { ck, sidi, sidr } = self;
let tki = ck.mix(&lprf::ini_enc()?)?.into_secret(); let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret();
let tkr = ck.mix(&lprf::res_enc()?)?.into_secret(); let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret();
let created_at = srv.timebase.now(); let created_at = srv.timebase.now();
let (ntx, nrx) = (0, 0); let (ntx, nrx) = (0, 0);
let (mysid, peersid, ktx, krx) = match role { let (mysid, peersid, ktx, krx) = match role {
@@ -1402,7 +1405,7 @@ impl CryptoServer {
.get(self) .get(self)
.as_ref() .as_ref()
.with_context(|| format!("No current session for peer {:?}", peer))?; .with_context(|| format!("No current session for peer {:?}", peer))?;
Ok(session.ck.mix(&lprf::osk()?)?.into_secret()) Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret())
} }
} }
@@ -1424,7 +1427,7 @@ impl CryptoServer {
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value); ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
// IHI3 // IHI3
EphemeralKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?; EphemeralKem::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
ih.epki_mut().copy_from_slice(&hs.epki.value); ih.epki_mut().copy_from_slice(&hs.epki.value);
// IHI4 // IHI4
@@ -1432,7 +1435,7 @@ impl CryptoServer {
// IHI5 // IHI5
hs.core hs.core
.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>( .encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
ih.sctr_mut(), ih.sctr_mut(),
peer.get(self).spkt.secret(), peer.get(self).spkt.secret(),
)?; )?;
@@ -1471,7 +1474,7 @@ impl CryptoServer {
core.mix(ih.sidi())?.mix(ih.epki())?; core.mix(ih.sidi())?.mix(ih.epki())?;
// IHR5 // IHR5
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>( core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(), self.sskm.secret(),
self.spkm.secret(), self.spkm.secret(),
ih.sctr(), ih.sctr(),
@@ -1501,10 +1504,10 @@ impl CryptoServer {
core.mix(rh.sidr())?.mix(rh.sidi())?; core.mix(rh.sidr())?.mix(rh.sidi())?;
// RHR4 // RHR4
core.encaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>(rh.ecti_mut(), ih.epki())?; core.encaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(rh.ecti_mut(), ih.epki())?;
// RHR5 // RHR5
core.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>( core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
rh.scti_mut(), rh.scti_mut(),
peer.get(self).spkt.secret(), peer.get(self).spkt.secret(),
)?; )?;
@@ -1569,14 +1572,14 @@ impl CryptoServer {
core.mix(rh.sidr())?.mix(rh.sidi())?; core.mix(rh.sidr())?.mix(rh.sidi())?;
// RHI4 // RHI4
core.decaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>( core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
hs!().eski.secret(), hs!().eski.secret(),
&*hs!().epki, &*hs!().epki,
rh.ecti(), rh.ecti(),
)?; )?;
// RHI5 // RHI5
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>( core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(), self.sskm.secret(),
self.spkm.secret(), self.spkm.secret(),
rh.scti(), rh.scti(),
@@ -1638,7 +1641,7 @@ impl CryptoServer {
core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?; core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?;
// ICR5 // ICR5
if rosenpass_sodium::helpers::compare(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 { if constant_time::compare(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 {
// ICR6 // ICR6
peer.get_mut(self).biscuit_used = biscuit_no; peer.get_mut(self).biscuit_used = biscuit_no;
@@ -1754,8 +1757,6 @@ mod test {
/// Through all this, the handshake should still successfully terminate; /// Through all this, the handshake should still successfully terminate;
/// i.e. an exchanged key must be produced in both servers. /// i.e. an exchanged key must be produced in both servers.
fn handles_incorrect_size_messages() { fn handles_incorrect_size_messages() {
rosenpass_sodium::init().unwrap();
stacker::grow(8 * 1024 * 1024, || { stacker::grow(8 * 1024 * 1024, || {
const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize; const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize;
type MsgBufPlus = Public<OVERSIZED_MESSAGE>; type MsgBufPlus = Public<OVERSIZED_MESSAGE>;
@@ -1767,14 +1768,10 @@ mod test {
// Process the entire handshake // Process the entire handshake
let mut msglen = Some(me.initiate_handshake(PEER0, &mut *resbuf).unwrap()); let mut msglen = Some(me.initiate_handshake(PEER0, &mut *resbuf).unwrap());
loop { while let Some(l) = msglen {
if let Some(l) = msglen { std::mem::swap(&mut me, &mut they);
std::mem::swap(&mut me, &mut they); std::mem::swap(&mut msgbuf, &mut resbuf);
std::mem::swap(&mut msgbuf, &mut resbuf); msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf);
msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf);
} else {
break;
}
} }
assert_eq!( assert_eq!(
@@ -1801,8 +1798,8 @@ mod test {
} }
let res = srv.handle_msg(&msgbuf[..l], resbuf); let res = srv.handle_msg(&msgbuf[..l], resbuf);
assert!(matches!(res, Err(_))); // handle_msg should raise an error assert!(res.is_err()); // handle_msg should raise an error
assert!(!resbuf.iter().find(|x| **x != 0).is_some()); // resbuf should not have been changed assert!(!resbuf.iter().any(|x| *x != 0)); // resbuf should not have been changed
} }
// Apply the proper handle_msg operation // Apply the proper handle_msg operation
@@ -1812,7 +1809,7 @@ mod test {
fn keygen() -> Result<(SSk, SPk)> { fn keygen() -> Result<(SSk, SPk)> {
// TODO: Copied from the benchmark; deduplicate // TODO: Copied from the benchmark; deduplicate
let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?; StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
Ok((sk, pk)) Ok((sk, pk))
} }

View File

@@ -30,11 +30,8 @@ fn generate_keys() {
fn find_udp_socket() -> u16 { fn find_udp_socket() -> u16 {
for port in 1025..=u16::MAX { for port in 1025..=u16::MAX {
match UdpSocket::bind(("127.0.0.1", port)) { if UdpSocket::bind(("127.0.0.1", port)).is_ok() {
Ok(_) => { return port;
return port;
}
_ => {}
} }
} }
panic!("no free UDP port found"); panic!("no free UDP port found");
@@ -54,9 +51,9 @@ fn check_exchange() {
for (secret_key_path, pub_key_path) in secret_key_paths.iter().zip(public_key_paths.iter()) { for (secret_key_path, pub_key_path) in secret_key_paths.iter().zip(public_key_paths.iter()) {
let output = test_bin::get_test_bin(BIN) let output = test_bin::get_test_bin(BIN)
.args(["gen-keys", "--secret-key"]) .args(["gen-keys", "--secret-key"])
.arg(&secret_key_path) .arg(secret_key_path)
.arg("--public-key") .arg("--public-key")
.arg(&pub_key_path) .arg(pub_key_path)
.output() .output()
.expect("Failed to start {BIN}"); .expect("Failed to start {BIN}");

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "1.74.1"

View File

@@ -1,17 +1,22 @@
[package] [package]
name = "rosenpass-sodium" name = "rosenpass-secret-memory"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0" version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Rosenpass internal bindings to libsodium" description = "Rosenpass internal utilities for storing secrets in memory"
homepage = "https://rosenpass.eu/" homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass" repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md" readme = "readme.md"
[dependencies] [dependencies]
rosenpass-util = { workspace = true }
rosenpass-to = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
libsodium-sys-stable = { workspace = true } rosenpass-to = { workspace = true }
rosenpass-util = { workspace = true }
zeroize = { workspace = true }
rand = { workspace = true }
allocator-api2 = { workspace = true }
log = { workspace = true } log = { workspace = true }
[dev-dependencies]
allocator-api2-tests = { workspace = true }

5
secret-memory/readme.md Normal file
View File

@@ -0,0 +1,5 @@
# Rosenpass secure memory library
Rosenpass internal library providing utilities for securely storing secret data in memory.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -0,0 +1,86 @@
use std::fmt;
use std::ptr::NonNull;
use allocator_api2::alloc::{AllocError, Allocator, Layout, Global};
#[derive(Copy, Clone, Default)]
struct MemsecAllocatorContents;
/// Memory allocation using using the memsec crate
#[derive(Copy, Clone, Default)]
pub struct MemsecAllocator {
global: Global
}
/// A box backed by the memsec allocator
pub type MemsecBox<T> = allocator_api2::boxed::Box<T, MemsecAllocator>;
/// A vector backed by the memsec allocator
pub type MemsecVec<T> = allocator_api2::vec::Vec<T, MemsecAllocator>;
pub fn memsec_box<T>(x: T) -> MemsecBox<T> {
MemsecBox::<T>::new_in(x, MemsecAllocator::new())
}
pub fn memsec_vec<T>() -> MemsecVec<T> {
MemsecVec::<T>::new_in(MemsecAllocator::new())
}
impl MemsecAllocator {
pub fn new() -> Self {
Self {
global: Global
}
}
}
unsafe impl Allocator for MemsecAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.global.allocate(layout)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
unsafe { self.global.deallocate(ptr, _layout) }
}
}
impl fmt::Debug for MemsecAllocator {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<memsec based Rust allocator>")
}
}
#[cfg(test)]
mod test {
use allocator_api2_tests::make_test;
use super::*;
make_test! { test_sizes(MemsecAllocator::new()) }
make_test! { test_vec(MemsecAllocator::new()) }
make_test! { test_many_boxes(MemsecAllocator::new()) }
#[test]
fn memsec_allocation() {
let alloc = MemsecAllocator::new();
memsec_allocation_impl::<0>(&alloc);
memsec_allocation_impl::<7>(&alloc);
memsec_allocation_impl::<8>(&alloc);
memsec_allocation_impl::<64>(&alloc);
memsec_allocation_impl::<999>(&alloc);
}
fn memsec_allocation_impl<const N: usize>(alloc: &MemsecAllocator) {
let layout = Layout::new::<[u8; N]>();
let mem = alloc.allocate(layout).unwrap();
// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations
// promises us that allocated memory is initialized with the magic byte 0xDB
// and memsec promises to provide a reimplementation of the libsodium mechanism;
// it uses the magic value 0xD0 though
assert_eq!(unsafe { mem.as_ref() }, &[0xD0u8; N]);
let mem = NonNull::new(mem.as_ptr() as *mut u8).unwrap();
unsafe { alloc.deallocate(mem, layout) };
}
}

View File

@@ -0,0 +1,6 @@
pub mod memsec;
pub use crate::alloc::memsec::{
memsec_box as secret_box, memsec_vec as secret_vec, MemsecAllocator as SecretAllocator,
MemsecBox as SecretBox, MemsecVec as SecretVec,
};

View File

@@ -0,0 +1,20 @@
use std::fmt;
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {
for byte in &v[..32] {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
fmt.write_str("")?;
for byte in &v[v.len() - 32..] {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
} else {
for byte in v {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
}
Ok(())
}

View File

@@ -0,0 +1,7 @@
use std::path::Path;
pub trait StoreSecret {
type Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

11
secret-memory/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod debug;
pub mod file;
pub mod rand;
pub mod alloc;
mod public;
pub use crate::public::Public;
mod secret;
pub use crate::secret::Secret;

112
secret-memory/src/public.rs Normal file
View File

@@ -0,0 +1,112 @@
use crate::debug::debug_crypto_array;
use rand::{Fill as Randomize, Rng};
use rosenpass_to::{ops::copy_slice, To};
use rosenpass_util::file::{fopen_r, LoadValue, ReadExactToEnd, StoreValue};
use rosenpass_util::functional::mutating;
use std::borrow::{Borrow, BorrowMut};
use std::fmt;
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
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Public<const N: usize> {
pub value: [u8; N],
}
impl<const N: usize> Public<N> {
/// 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
pub fn new(value: [u8; N]) -> Self {
Self { value }
}
/// Create a zero initialized [Public]
pub fn zero() -> Self {
Self { value: [0u8; N] }
}
/// Create a random initialized [Public]
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// 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> {
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> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&self.value, fmt)
}
}
impl<const N: usize> Deref for Public<N> {
type Target = [u8; N];
fn deref(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> DerefMut for Public<N> {
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
fn borrow(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
fn borrow_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8]> for Public<N> {
fn borrow(&self) -> &[u8] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
fn borrow_mut(&mut self) -> &mut [u8] {
&mut self.value
}
}
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
Ok(v)
}
}
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
}
}

View File

@@ -0,0 +1,5 @@
pub type Rng = rand::rngs::ThreadRng;
pub fn rng() -> Rng {
rand::thread_rng()
}

321
secret-memory/src/secret.rs Normal file
View File

@@ -0,0 +1,321 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use anyhow::Context;
use rand::{Fill as Randomize, Rng};
use zeroize::{Zeroize, ZeroizeOnDrop};
use rosenpass_util::b64::b64_reader;
use rosenpass_util::file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd};
use rosenpass_util::functional::mutating;
use crate::alloc::{secret_box, SecretBox, SecretVec};
use crate::file::StoreSecret;
// This might become a problem in library usage; it's effectively a memory
// leak which probably isn't a problem right now because most memory will
// be reused…
thread_local! {
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
}
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
where
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
{
// This acquires the SECRET_CACHE
SECRET_CACHE
.try_with(|cell| {
// And acquires the inner reference
cell.try_borrow_mut()
.as_deref_mut()
// To call the given function
.map(|pool| f(Some(pool)))
.ok()
})
.ok()
.flatten()
// Failing that, the given function is called with None
.unwrap_or_else(|| f(None))
}
// Wrapper around SecretBox that applies automatic zeroization
#[derive(Debug)]
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
impl<T: Zeroize> ZeroizingSecretBox<T> {
fn new(boxed: T) -> Self {
ZeroizingSecretBox(Some(secret_box(boxed)))
}
}
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
fn from_secret_box(inner: SecretBox<T>) -> Self {
Self(Some(inner))
}
fn take(mut self) -> SecretBox<T> {
self.0.take().unwrap()
}
}
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
inner.zeroize()
}
}
}
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
fn drop(&mut self) {
self.zeroize()
}
}
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
type Target = T;
fn deref(&self) -> &T {
self.0.as_ref().unwrap()
}
}
impl<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
fn deref_mut(&mut self) -> &mut T {
self.0.as_mut().unwrap()
}
}
/// 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 {
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
}
impl SecretMemoryPool {
/// Create a new [SecretMemoryPool]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
pool: HashMap::new(),
}
}
/// 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();
// This conversion sequence is weird but at least it guarantees
// that the heap allocation is preserved according to the docs
let sec: SecretVec<u8> = sec.take().into();
let sec: SecretBox<[u8]> = sec.into();
self.pool
.entry(N)
.or_default()
.push(ZeroizingSecretBox::from_secret_box(sec));
}
/// 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
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
let entry = self.pool.entry(N).or_default();
let inner = match entry.pop() {
None => secret_box([0u8; N]),
Some(sec) => sec.take().try_into().unwrap(),
};
ZeroizingSecretBox::from_secret_box(inner)
}
}
/// Storage for secret data
pub struct Secret<const N: usize> {
storage: Option<ZeroizingSecretBox<[u8; N]>>,
}
impl<const N: usize> Secret<N> {
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
pub fn zero() -> Self {
// Using [SecretMemoryPool] here because this operation is expensive,
// 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]))
});
Self { storage: Some(buf) }
}
/// 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
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
/// Borrows the data
pub fn secret(&self) -> &[u8; N] {
self.storage.as_ref().unwrap()
}
/// 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> {
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.
// We should at some point replace this with our own barriers.
self.zeroize();
self.secret_mut().try_fill(rng)
}
}
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
impl<const N: usize> Zeroize for Secret<N> {
fn zeroize(&mut self) {
if let Some(inner) = &mut self.storage {
inner.zeroize()
}
}
}
impl<const N: usize> Drop for Secret<N> {
fn drop(&mut self) {
with_secret_memory_pool(|pool| {
if let Some((pool, secret)) = pool.zip(self.storage.take()) {
pool.release(secret);
}
});
// This should be unnecessary: The pool has one item the inner secret which
// zeroizes itself on drop. Calling it should not do any harm though…
self.zeroize()
}
}
impl<const N: usize> Clone for Secret<N> {
fn clone(&self) -> Self {
Self::from_slice(self.secret())
}
}
/// The Debug implementation of [Secret] does not reveal the secret data,
/// instead a placeholder `<SECRET>` is used
impl<const N: usize> fmt::Debug for Secret<N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<SECRET>")
}
}
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
fopen_r(p)?
.read_exact_to_end(v.secret_mut())
.with_context(|| format!("Could not load file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
fn load_b64<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
use std::io::Read;
let mut v = Self::random();
let p = path.as_ref();
// This might leave some fragments of the secret on the stack;
// in practice this is likely not a problem because the stack likely
// will be overwritten by something else soon but this is not exactly
// guaranteed. It would be possible to remedy this, but since the secret
// data will linger in the Linux page cache anyways with the current
// implementation, going to great length to erase the secret here is
// not worth it right now.
b64_reader(&mut fopen_r(p)?)
.read_exact(v.secret_mut())
.with_context(|| format!("Could not load base64 file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, self.secret())?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
/// check that we can alloc using the magic pool
#[test]
fn secret_memory_pool_take() {
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: ZeroizingSecretBox<[u8; N]> = pool.take();
assert_eq!(secret.as_ref(), &[0; N]);
}
/// check that a secrete lives, even if its [SecretMemoryPool] is deleted
#[test]
fn secret_memory_pool_drop() {
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: ZeroizingSecretBox<[u8; N]> = pool.take();
std::mem::drop(pool);
assert_eq!(secret.as_ref(), &[0; N]);
}
/// check that a secrete can be reborn, freshly initialized with zero
#[test]
fn secret_memory_pool_release() {
const N: usize = 1;
let mut pool = SecretMemoryPool::new();
let mut secret: ZeroizingSecretBox<[u8; N]> = pool.take();
let old_secret_ptr = secret.as_ref().as_ptr();
secret.as_mut()[0] = 0x13;
pool.release(secret);
// now check that we get the same ptr
let new_secret: ZeroizingSecretBox<[u8; N]> = pool.take();
assert_eq!(old_secret_ptr, new_secret.as_ref().as_ptr());
// and that the secret was zeroized
assert_eq!(new_secret.as_ref(), &[0; N]);
}
}

View File

@@ -1,63 +0,0 @@
use libsodium_sys as libsodium;
use std::ffi::c_ulonglong;
use std::ptr::{null, null_mut};
pub const KEY_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize;
pub const TAG_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_ABYTES as usize;
pub const NONCE_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize;
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + TAG_LEN);
assert!(key.len() == KEY_LEN);
assert!(nonce.len() == NONCE_LEN);
let mut clen: u64 = 0;
sodium_call!(
crypto_aead_chacha20poly1305_ietf_encrypt,
ciphertext.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
null(), // nsec is not used
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(clen as usize == ciphertext.len());
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + TAG_LEN);
assert!(key.len() == KEY_LEN);
assert!(nonce.len() == NONCE_LEN);
let mut mlen: u64 = 0;
sodium_call!(
crypto_aead_chacha20poly1305_ietf_decrypt,
plaintext.as_mut_ptr(),
&mut mlen as *mut c_ulonglong,
null_mut(), // nsec is not used
ciphertext.as_ptr(),
ciphertext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(mlen as usize == plaintext.len());
Ok(())
}

View File

@@ -1,2 +0,0 @@
pub mod chacha20poly1305_ietf;
pub mod xchacha20poly1305_ietf;

View File

@@ -1,63 +0,0 @@
use libsodium_sys as libsodium;
use std::ffi::c_ulonglong;
use std::ptr::{null, null_mut};
pub const KEY_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize;
pub const TAG_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_ietf_ABYTES as usize;
pub const NONCE_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_NPUBBYTES as usize;
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + NONCE_LEN + TAG_LEN);
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
let (n, ct) = ciphertext.split_at_mut(NONCE_LEN);
n.copy_from_slice(nonce);
let mut clen: u64 = 0;
sodium_call!(
crypto_aead_xchacha20poly1305_ietf_encrypt,
ct.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
null(), // nsec is not used
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(clen as usize == ct.len());
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + NONCE_LEN + TAG_LEN);
assert!(key.len() == KEY_LEN);
let (n, ct) = ciphertext.split_at(NONCE_LEN);
let mut mlen: u64 = 0;
sodium_call!(
crypto_aead_xchacha20poly1305_ietf_decrypt,
plaintext.as_mut_ptr(),
&mut mlen as *mut c_ulonglong,
null_mut(), // nsec is not used
ct.as_ptr(),
ct.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
n.as_ptr(),
key.as_ptr()
)?;
assert!(mlen as usize == plaintext.len());
Ok(())
}

View File

@@ -1,31 +0,0 @@
use libsodium_sys as libsodium;
use rosenpass_to::{with_destination, To};
use std::ffi::c_ulonglong;
use std::ptr::null;
pub const KEY_MIN: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MIN as usize;
pub const KEY_MAX: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MAX as usize;
pub const OUT_MIN: usize = libsodium::crypto_generichash_blake2b_BYTES_MIN as usize;
pub const OUT_MAX: usize = libsodium::crypto_generichash_blake2b_BYTES_MAX as usize;
#[inline]
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
with_destination(|out: &mut [u8]| {
assert!(key.is_empty() || (KEY_MIN <= key.len() && key.len() <= KEY_MAX));
assert!(OUT_MIN <= out.len() && out.len() <= OUT_MAX);
let kptr = match key.len() {
// NULL key
0 => null(),
_ => key.as_ptr(),
};
sodium_call!(
crypto_generichash_blake2b,
out.as_mut_ptr(),
out.len(),
data.as_ptr(),
data.len() as c_ulonglong,
kptr,
key.len()
)
})
}

View File

@@ -1 +0,0 @@
pub mod blake2b;

View File

@@ -1,52 +0,0 @@
use libsodium_sys as libsodium;
use std::os::raw::c_void;
#[inline]
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
a.len() == b.len()
&& unsafe {
let r = libsodium::sodium_memcmp(
a.as_ptr() as *const c_void,
b.as_ptr() as *const c_void,
a.len(),
);
r == 0
}
}
#[inline]
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
unsafe { libsodium::sodium_compare(a.as_ptr(), b.as_ptr(), a.len()) }
}
#[inline]
pub fn increment(v: &mut [u8]) {
unsafe {
libsodium::sodium_increment(v.as_mut_ptr(), v.len());
}
}
#[inline]
pub fn randombytes_buf(buf: &mut [u8]) {
unsafe { libsodium::randombytes_buf(buf.as_mut_ptr() as *mut c_void, buf.len()) };
}
#[inline]
pub fn memzero(buf: &mut [u8]) {
unsafe { libsodium::sodium_memzero(buf.as_mut_ptr() as *mut c_void, buf.len()) };
}
// Choose a fully random u64
// TODO: Replace with ::rand::random
pub fn rand_u64() -> u64 {
let mut buf = [0u8; 8];
randombytes_buf(&mut buf);
u64::from_le_bytes(buf)
}
// Choose a random f64 in [0; 1] inclusive; quick and dirty
// TODO: Replace with ::rand::random
pub fn rand_f64() -> f64 {
(rand_u64() as f64) / (u64::MAX as f64)
}

View File

@@ -1,20 +0,0 @@
use libsodium_sys as libsodium;
macro_rules! sodium_call {
($name:ident, $($args:expr),*) => { ::rosenpass_util::attempt!({
anyhow::ensure!(unsafe{libsodium::$name($($args),*)} > -1,
"Error in libsodium's {}.", stringify!($name));
Ok(())
})};
($name:ident) => { sodium_call!($name, ) };
}
#[inline]
pub fn init() -> anyhow::Result<()> {
log::trace!("initializing libsodium");
sodium_call!(sodium_init)
}
pub mod aead;
pub mod hash;
pub mod helpers;

View File

@@ -12,15 +12,17 @@ The crate provides chained functions to simplify allocating the destination para
For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may. For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may.
```rust ```rust
use std::ops::BitXorAssign;
use rosenpass_to::{To, to, with_destination};
use rosenpass_to::ops::copy_array; use rosenpass_to::ops::copy_array;
use rosenpass_to::{to, with_destination, To};
use std::ops::BitXorAssign;
// Destination functions return some value that implements the To trait. // 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 // be without destination parameters
fn xor_slice<'a, T>(src: &'a[T]) -> impl To<[T], ()> + 'a fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where T: BitXorAssign + Clone { where
T: BitXorAssign + Clone,
{
// Custom implementations of the to trait can be created, but the easiest // Custom implementations of the to trait can be created, but the easiest
with_destination(move |dst: &mut [T]| { with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
@@ -65,7 +67,7 @@ assert_eq!(&dst[..], &flip01[..]);
// The builtin function copy_array supports to_value() since its // The builtin function copy_array supports to_value() since its
// destination parameter is a fixed size array, which can be allocated // destination parameter is a fixed size array, which can be allocated
// using default() // using default()
let dst : [u8; 4] = copy_array(flip01).to_value(); let dst: [u8; 4] = copy_array(flip01).to_value();
assert_eq!(&dst, flip01); assert_eq!(&dst, flip01);
``` ```
@@ -84,7 +86,9 @@ Functions declared like this are more cumbersome to use when the destination par
use std::ops::BitXorAssign; use std::ops::BitXorAssign;
fn xor_slice<T>(dst: &mut [T], src: &[T]) fn xor_slice<T>(dst: &mut [T], src: &[T])
where T: BitXorAssign + Clone { where
T: BitXorAssign + Clone,
{
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) { for (d, s) in dst.iter_mut().zip(src.iter()) {
*d ^= s.clone(); *d ^= s.clone();
@@ -114,8 +118,8 @@ assert_eq!(&dst[..], &flip01[..]);
There are a couple of ways to use a function with destination: There are a couple of ways to use a function with destination:
```rust ```rust
use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice_least}; use rosenpass_to::ops::{copy_array, copy_slice_least};
use rosenpass_to::{to, To};
let mut dst = b" ".to_vec(); let mut dst = b" ".to_vec();
@@ -129,7 +133,8 @@ copy_slice_least(b"This is fin").to(&mut dst[..]);
assert_eq!(&dst[..], b"This is fin"); assert_eq!(&dst[..], b"This is fin");
// You can allocate the destination variable on the fly using `.to_this(...)` // You can allocate the destination variable on the fly using `.to_this(...)`
let tmp = copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned()); let tmp =
copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned());
assert_eq!(&tmp[..], b"This is new---verwritten"); assert_eq!(&tmp[..], b"This is new---verwritten");
// You can allocate the destination variable on the fly `.collect(..)` if it implements default // You can allocate the destination variable on the fly `.collect(..)` if it implements default
@@ -147,8 +152,11 @@ assert_eq!(&tmp[..], b"Fixed");
The to crate provides basic functions with destination for copying data between slices and arrays. The to crate provides basic functions with destination for copying data between slices and arrays.
```rust ```rust
use rosenpass_to::ops::{
copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice,
try_copy_slice_least_src,
};
use rosenpass_to::{to, To}; use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice, try_copy_slice_least_src};
let mut dst = b" ".to_vec(); let mut dst = b" ".to_vec();
@@ -161,18 +169,33 @@ to(&mut dst[4..], copy_slice_least_src(b"!!!"));
assert_eq!(&dst[..], b"Hell!!!orld"); assert_eq!(&dst[..], b"Hell!!!orld");
// Copy a slice, copying as many bytes as possible // Copy a slice, copying as many bytes as possible
to(&mut dst[6..], copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); to(
&mut dst[6..],
copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"),
);
assert_eq!(&dst[..], b"Hell!!xxxxx"); assert_eq!(&dst[..], b"Hell!!xxxxx");
// Copy a slice, will return None and abort if the sizes do not much // Copy a slice, will return None and abort if the sizes do not much
assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World"))); assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World")));
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---------------------"))); assert_eq!(
None,
to(&mut dst[..], try_copy_slice(b"---------------------"))
);
assert_eq!(&dst[..], b"Hello World"); assert_eq!(&dst[..], b"Hello World");
// Copy a slice, will return None and abort if source is longer than destination // Copy a slice, will return None and abort if source is longer than destination
assert_eq!(Some(()), to(&mut dst[4..], try_copy_slice_least_src(b"!!!"))); assert_eq!(
assert_eq!(None, to(&mut dst[4..], try_copy_slice_least_src(b"-------------------------"))); Some(()),
to(&mut dst[4..], try_copy_slice_least_src(b"!!!"))
);
assert_eq!(
None,
to(
&mut dst[4..],
try_copy_slice_least_src(b"-------------------------")
)
);
assert_eq!(&dst[..], b"Hell!!!orld"); assert_eq!(&dst[..], b"Hell!!!orld");
// Copy fixed size arrays all at once // Copy fixed size arrays all at once
@@ -186,12 +209,14 @@ assert_eq!(&dst, b"Hello");
The easiest way to declare a function with destination is to use the with_destination function. The easiest way to declare a function with destination is to use the with_destination function.
```rust ```rust
use rosenpass_to::{To, to, with_destination};
use rosenpass_to::ops::copy_array; use rosenpass_to::ops::copy_array;
use rosenpass_to::{to, with_destination, To};
/// Copy the given slice to the start of a vector, reusing its memory if possible /// Copy the given slice to the start of a vector, reusing its memory if possible
fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To<Vec<T>, ()> + 'a fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To<Vec<T>, ()> + 'a
where T: Clone { where
T: Clone,
{
with_destination(move |dst: &mut Vec<T>| { with_destination(move |dst: &mut Vec<T>| {
dst.clear(); dst.clear();
dst.extend_from_slice(src); dst.extend_from_slice(src);
@@ -217,7 +242,9 @@ The same pattern can be implemented without `to`, at the cost of being slightly
```rust ```rust
/// Copy the given slice to the start of a vector, reusing its memory if possible /// Copy the given slice to the start of a vector, reusing its memory if possible
fn copy_to_vec<T>(dst: &mut Vec<T>, src: &[T]) fn copy_to_vec<T>(dst: &mut Vec<T>, src: &[T])
where T: Clone { where
T: Clone,
{
dst.clear(); dst.clear();
dst.extend_from_slice(src); dst.extend_from_slice(src);
} }
@@ -240,11 +267,11 @@ Alternative functions are returned, that return a `to::Beside` value, containing
destination variable and the return value. destination variable and the return value.
```rust ```rust
use std::cmp::{min, max}; use rosenpass_to::{to, with_destination, Beside, To};
use rosenpass_to::{To, to, with_destination, Beside}; use std::cmp::{max, min};
/// Copy an array of floats and calculate the average /// Copy an array of floats and calculate the average
pub fn copy_and_average<'a>(src: &'a[f64]) -> impl To<[f64], f64> + 'a { pub fn copy_and_average<'a>(src: &'a [f64]) -> impl To<[f64], f64> + 'a {
with_destination(move |dst: &mut [f64]| { with_destination(move |dst: &mut [f64]| {
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
let mut sum = 0f64; let mut sum = 0f64;
@@ -300,8 +327,8 @@ assert_eq!(tmp, Beside([42f64; 3], 42f64));
When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value. When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value.
```rust ```rust
use rosenpass_to::Beside;
use std::result::Result; use std::result::Result;
use rosenpass_to::{Beside};
assert_eq!((), Beside((), ()).condense()); assert_eq!((), Beside((), ()).condense());
@@ -318,8 +345,8 @@ assert_eq!(Err(()), Beside(42, err_unit).condense());
When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value: When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value:
```rust ```rust
use rosenpass_to::ops::try_copy_slice;
use rosenpass_to::To; use rosenpass_to::To;
use rosenpass_to::ops::try_copy_slice;;
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>(); let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>();
assert_eq!(tmp, Some(*b"Hello World")); assert_eq!(tmp, Some(*b"Hello World"));
@@ -337,8 +364,8 @@ assert_eq!(tmp, None);
The same naturally also works for Results, but the example is a bit harder to motivate: The same naturally also works for Results, but the example is a bit harder to motivate:
```rust ```rust
use rosenpass_to::{to, with_destination, To};
use std::result::Result; use std::result::Result;
use rosenpass_to::{to, To, with_destination};
#[derive(PartialEq, Eq, Debug, Default)] #[derive(PartialEq, Eq, Debug, Default)]
struct InvalidFloat; struct InvalidFloat;
@@ -380,8 +407,8 @@ Condensation is implemented through a trait called CondenseBeside ([local](Conde
If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules. If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules.
```rust ```rust
use rosenpass_to::{To, with_destination, Beside, CondenseBeside};
use rosenpass_to::ops::copy_slice; use rosenpass_to::ops::copy_slice;
use rosenpass_to::{with_destination, Beside, CondenseBeside, To};
#[derive(PartialEq, Eq, Debug, Default)] #[derive(PartialEq, Eq, Debug, Default)]
struct MyTuple<Left, Right>(Left, Right); struct MyTuple<Left, Right>(Left, Right);
@@ -396,7 +423,10 @@ impl<Val, Right> CondenseBeside<Val> for MyTuple<(), Right> {
} }
fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a
where T: Copy, U: 'a { where
T: Copy,
U: 'a,
{
with_destination(move |dst: &mut [T]| { with_destination(move |dst: &mut [T]| {
copy_slice(src).to(dst); copy_slice(src).to(dst);
something something
@@ -417,7 +447,7 @@ Using `with_destination(...)` is convenient, but since it uses closures it resul
Implementing the ToTrait manual is the right choice for library use cases. Implementing the ToTrait manual is the right choice for library use cases.
```rust ```rust
use rosenpass_to::{to, To, with_destination}; use rosenpass_to::{to, with_destination, To};
struct TryCopySliceSource<'a, T: Copy> { struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T], src: &'a [T],
@@ -425,17 +455,20 @@ struct TryCopySliceSource<'a, T: Copy> {
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> { impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> { fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len()) (self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src))
.then(|| dst.copy_from_slice(self.src))
} }
} }
fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T> fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T>
where T: Copy { where
T: Copy,
{
TryCopySliceSource { src } TryCopySliceSource { src }
} }
let mut dst = try_copy_slice(b"Hello World").collect::<[u8; 11]>().unwrap(); let mut dst = try_copy_slice(b"Hello World")
.collect::<[u8; 11]>()
.unwrap();
assert_eq!(&dst[..], b"Hello World"); assert_eq!(&dst[..], b"Hello World");
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
``` ```
@@ -445,8 +478,8 @@ assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation. Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation.
```rust ```rust
use rosenpass_to::{to, with_destination, To};
use std::borrow::Borrow; use std::borrow::Borrow;
use rosenpass_to::{to, To, with_destination};
struct TryCopySliceSource<'a, T: Copy> { struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T], src: &'a [T],
@@ -454,24 +487,24 @@ struct TryCopySliceSource<'a, T: Copy> {
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> { impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> { fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len()) (self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src))
.then(|| dst.copy_from_slice(self.src))
} }
} }
trait TryCopySliceExt<'a, T: Copy> { trait TryCopySliceExt<'a, T: Copy> {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>; fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>;
} }
impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref { impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> { fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> {
TryCopySliceSource { TryCopySliceSource { src: self.borrow() }
src: self.borrow()
}
} }
} }
let mut dst = b"Hello World".try_copy_slice().collect::<[u8; 11]>().unwrap(); let mut dst = b"Hello World"
.try_copy_slice()
.collect::<[u8; 11]>()
.unwrap();
assert_eq!(&dst[..], b"Hello World"); assert_eq!(&dst[..], b"Hello World");
assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice())); assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice()));
``` ```

View File

@@ -8,7 +8,7 @@ use crate::{with_destination, To};
/// # Panics /// # Panics
/// ///
/// This function will panic if the two slices have different lengths. /// This function will panic if the two slices have different lengths.
pub fn copy_slice<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
where where
T: Copy, T: Copy,
{ {
@@ -23,7 +23,7 @@ where
/// # Panics /// # Panics
/// ///
/// This function will panic if destination is shorter than origin. /// This function will panic if destination is shorter than origin.
pub fn copy_slice_least_src<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
where where
T: Copy, T: Copy,
{ {
@@ -34,7 +34,7 @@ where
/// destination. /// destination.
/// ///
/// Copies as much data as is present in the shorter slice. /// Copies as much data as is present in the shorter slice.
pub fn copy_slice_least<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
where where
T: Copy, T: Copy,
{ {
@@ -47,7 +47,7 @@ where
/// Function with destination that attempts to copy data from origin into the destination. /// Function with destination that attempts to copy data from origin into the destination.
/// ///
/// Will return None if the slices are of different lengths. /// Will return None if the slices are of different lengths.
pub fn try_copy_slice<'a, T>(origin: &'a [T]) -> impl To<[T], Option<()>> + 'a pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where where
T: Copy, T: Copy,
{ {
@@ -62,7 +62,7 @@ where
/// Destination may be longer than origin. /// Destination may be longer than origin.
/// ///
/// Will return None if the destination is shorter than origin. /// Will return None if the destination is shorter than origin.
pub fn try_copy_slice_least_src<'a, T>(origin: &'a [T]) -> impl To<[T], Option<()>> + 'a pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where where
T: Copy, T: Copy,
{ {
@@ -72,7 +72,7 @@ where
} }
/// Function with destination that copies all data between two array references. /// Function with destination that copies all data between two array references.
pub fn copy_array<'a, T, const N: usize>(origin: &'a [T; N]) -> impl To<[T; N], ()> + 'a pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
where where
T: Copy, T: Copy,
{ {

View File

@@ -14,3 +14,5 @@ readme = "readme.md"
[dependencies] [dependencies]
base64 = { workspace = true } base64 = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
typenum = { workspace = true }
static_assertions = { workspace = true }

0
util/src/fd.rs Normal file
View File

View File

@@ -6,21 +6,21 @@ use std::{fs::OpenOptions, path::Path};
/// Open a file writable /// Open a file writable
pub fn fopen_w<P: AsRef<Path>>(path: P) -> std::io::Result<File> { pub fn fopen_w<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
Ok(OpenOptions::new() OpenOptions::new()
.read(false) .read(false)
.write(true) .write(true)
.create(true) .create(true)
.truncate(true) .truncate(true)
.open(path)?) .open(path)
} }
/// Open a file readable /// Open a file readable
pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> { pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
Ok(OpenOptions::new() OpenOptions::new()
.read(true) .read(true)
.write(false) .write(false)
.create(false) .create(false)
.truncate(false) .truncate(false)
.open(path)?) .open(path)
} }
pub trait ReadExactToEnd { pub trait ReadExactToEnd {

View File

@@ -1,7 +1,11 @@
#![recursion_limit = "256"]
pub mod b64; pub mod b64;
pub mod fd;
pub mod file; pub mod file;
pub mod functional; pub mod functional;
pub mod mem; pub mod mem;
pub mod ord; pub mod ord;
pub mod result; pub mod result;
pub mod time; pub mod time;
pub mod typenum;

View File

@@ -1,3 +1,6 @@
use std::convert::Infallible;
use std::result::Result;
/// Try block basically…returns a result and allows the use of the question mark operator inside /// Try block basically…returns a result and allows the use of the question mark operator inside
#[macro_export] #[macro_export]
macro_rules! attempt { macro_rules! attempt {
@@ -5,3 +8,92 @@ macro_rules! attempt {
(|| -> ::anyhow::Result<_> { $block })() (|| -> ::anyhow::Result<_> { $block })()
}; };
} }
/// Trait for container types that guarantee successful unwrapping.
///
/// The `.guaranteed()` function can be used over unwrap to show that
/// the function will not panic.
///
/// Implementations must not panic.
pub trait GuaranteedValue {
type Value;
/// Extract the contained value while being panic-safe, like .unwrap()
///
/// # Panic Safety
///
/// Implementations of guaranteed() must not panic.
fn guaranteed(self) -> Self::Value;
}
/// A result type that never contains an error.
///
/// This is mostly useful in generic contexts.
///
/// # Examples
///
/// ```
/// use std::num::Wrapping;
/// use std::result::Result;
/// use std::convert::Infallible;
/// use std::ops::Add;
///
/// use rosenpass_util::result::{Guaranteed, GuaranteedValue};
///
/// trait FailableAddition: Sized {
/// type Error;
/// fn failable_addition(&self, other: &Self) -> Result<Self, Self::Error>;
/// }
///
/// #[derive(Copy, Clone, Debug, Eq, PartialEq)]
/// struct OverflowError;
///
/// impl<T> FailableAddition for Wrapping<T>
/// where for <'a> &'a Wrapping<T>: Add<Output = Wrapping<T>> {
/// type Error = Infallible;
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
/// Ok(self + other)
/// }
/// }
///
/// impl FailableAddition for u32 {
/// type Error = OverflowError;
/// fn failable_addition(&self, other: &Self) -> Result<Self, Self::Error> {
/// match self.checked_add(*other) {
/// Some(v) => Ok(v),
/// None => Err(OverflowError),
/// }
/// }
/// }
///
/// fn failable_multiply<T>(a: &T, b: u32)
/// -> Result<T, T::Error>
/// where
/// T: FailableAddition {
/// assert!(b >= 2); // Acceptable only because this is for demonstration purposes
/// let mut accu = a.failable_addition(a)?;
/// for _ in 2..b {
/// accu = accu.failable_addition(a)?;
/// }
/// Ok(accu)
/// }
///
/// // We can use .guaranteed() with Wrapping<u32>, since the operation uses
/// // the Infallible error type.
/// // We can also use unwrap which just happens to not raise an error.
/// assert_eq!(failable_multiply(&Wrapping(42u32), 3).guaranteed(), Wrapping(126));
/// assert_eq!(failable_multiply(&Wrapping(42u32), 3).unwrap(), Wrapping(126));
///
/// // We can not use .guaranteed() with u32, since there can be an error.
/// // We can however use unwrap(), which may panic
/// //assert_eq!(failable_multiply(&42u32, 3).guaranteed(), 126); // COMPILER ERROR
/// assert_eq!(failable_multiply(&42u32, 3).unwrap(), 126);
/// ```
pub type Guaranteed<T> = Result<T, Infallible>;
impl<T> GuaranteedValue for Guaranteed<T> {
type Value = T;
fn guaranteed(self) -> Self::Value {
self.unwrap()
}
}

341
util/src/typenum.rs Normal file
View File

@@ -0,0 +1,341 @@
use typenum::bit::{B0, B1};
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
#[macro_export]
macro_rules! typenum2const {
($val:ty) => {
typenum2const!($val as _)
};
($val:ty as $type:ty) => {
<$val as $crate::typenum::IntoConst<$type>>::VALUE
};
}
/// Trait implemented by constant integers to facilitate conversion to constant integers
pub trait IntoConst<T> {
const VALUE: T;
}
struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
struct ConstApplyPosSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param); // impl IntoConst<T>
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
/// Assigns an unsigned type to a signed type
trait AssociatedUnsigned {
type Type;
}
macro_rules! impl_into_const {
( $from:ty as $to:ty := $impl:expr) => {
impl IntoConst<$to> for $from {
const VALUE: $to = $impl;
}
};
}
macro_rules! impl_numeric_into_const_common {
($type:ty) => {
impl_into_const! { Z0 as $type := 0 }
impl_into_const! { B0 as $type := 0 }
impl_into_const! { B1 as $type := 1 }
impl_into_const! { UTerm as $type := 0 }
impl<Param: IntoConst<$type>, const SHIFT: i32> IntoConst<$type>
for ConstLshift<$type, Param, SHIFT>
{
const VALUE: $type = Param::VALUE << SHIFT;
}
impl<Lhs: IntoConst<$type>, Rhs: IntoConst<$type>> IntoConst<$type>
for ConstAdd<$type, Lhs, Rhs>
{
const VALUE: $type =
<Lhs as IntoConst<$type>>::VALUE + <Rhs as IntoConst<$type>>::VALUE;
}
};
}
macro_rules! impl_numeric_into_const_unsigned {
($($to_list:ty),*) => {
$( impl_numeric_into_const_unsigned! { @impl $to_list } )*
};
(@impl $type:ty) => {
impl_numeric_into_const_common!{ $type }
impl AssociatedUnsigned for $type {
type Type = $type;
}
impl<Param: IntoConst<$type>> IntoConst<$type> for ConstApplyPosSign<$type, Param> {
const VALUE : $type = Param::VALUE;
}
};
}
macro_rules! impl_numeric_into_const_signed {
($($to_list:ty : $unsigned_list:ty),*) => {
$( impl_numeric_into_const_signed! { @impl $to_list : $unsigned_list} )*
};
(@impl $type:ty : $unsigned:ty) => {
impl_numeric_into_const_common!{ $type }
impl AssociatedUnsigned for $type {
type Type = $unsigned;
}
impl<Param: IntoConst<$unsigned>> IntoConst<$type> for ConstApplyPosSign<$type, Param> {
const VALUE : $type = Param::VALUE as $type;
}
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
} else {
-(Param::VALUE as $type)
};
}
};
}
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>,
Bit: IntoConst<Ret>,
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: IntoConst<Ret>,
{
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>,
{
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>,
{
const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}
mod test {
use static_assertions::const_assert_eq;
use typenum::consts::*;
use typenum::op;
macro_rules! test_const_conversion {
// Type groups
(($($typenum:ty),*) >= u7 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as (u8, u16, u32, u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as (i8, i16, i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u8 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as (u8, u16, u32, u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i16, i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u15 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u16, u32, u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i16, i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u16 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u16, u32, u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u31 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u32, u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u32 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u32, u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u63 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u64 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u64, u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u127 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= u128 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( u128) = $const } )*
$( test_const_conversion! { ($typenum) as ( ) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= i8 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as (i8, i16, i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= i16 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( i16, i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= i32 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( i32, i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= i64 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( i64, i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) >= i128 = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { ($typenum) as ( i128) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
// Basic operation
() => {};
(($($typenum:ty),*) as () = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) as ($type:ty) = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { @impl ($typenum) as ($type) = $const } )*
$( test_const_conversion! { $($rest)* } )?
};
(($($typenum:ty),*) as ($type_head:ty, $($type_tail:ty),*) = $const:expr $(; $($rest:tt)*)?) => {
$( test_const_conversion! { @impl ($typenum) as ($type_head) = $const } )*
test_const_conversion! { ($($typenum),*) as ($($type_tail),*) = $const }
$( test_const_conversion! { $($rest)* } )?
};
(@impl ($typenum:ty) as ($type:ty) = $const:expr $(; $($rest:tt)*)?) => {
const_assert_eq!(typenum2const!($typenum as $type), $const);
$( test_const_conversion!($($rest)*); )?
};
}
test_const_conversion! {
(B0, False) as (bool, bool) = false;
(B0, U0, Z0) >= u7 = 0;
(B1, U1, P1) >= u7 = 1;
(U2, P2) >= u7 = 2;
(B1, True) as (bool) = true;
(U3, P3) >= u7 = 3;
(U8, P8) >= u7 = 8;
(U127, P127) >= u7 = 127;
(U220, P220) >= u8 = 220;
(U255, P255) >= u8 = 255;
(U1000, P1000) >= u15 = 1000;
(U10000, P10000) >= u15 = 10000;
(U16384, P16384) >= u15 = 16384;
(U32768, P32768) >= u16 = 32768;
(U65536, P65536) >= u31 = 65536;
(U100000, P100000) >= u31 = 100000;
(U1000000000, P1000000000) >= u31 = 1000000000;
(U2147483648, P2147483648) >= u32 = 2147483648;
(U1000000000000000000, P1000000000000000000) >= u63 = 1000000000000000000;
(U1000000000000000000, P1000000000000000000) >= u63 = 1000000000000000000;
(U9223372036854775808) >= u64 = 9223372036854775808;
(U10000000000000000000) >= u64 = 10000000000000000000;
(N10000) >= i16 = -10000;
(N1000000) >= i32 = -1000000;
(N1000000000) >= i32 = -1000000000;
(N1000000000000) >= i64 = -1000000000000;
}
const_assert_eq!(127, (!(1u8.rotate_right(1)) - 0) as _);
const_assert_eq!(126, (!(1u8.rotate_right(1)) - 1) as _);
const_assert_eq!(255, (!(0u8.rotate_right(1)) - 0) as _);
const_assert_eq!(254, (!(0u8.rotate_right(1)) - 1) as _);
test_const_conversion! {
(op!(pow(U2, U7) - U1)) >= u7 = (!(1u8.rotate_right(1)) - 0) as _;
(op!(pow(U2, U7) - U2)) >= u7 = (!(1u8.rotate_right(1)) - 1) as _;
(op!(pow(U2, U15) - U1)) >= u15 = (!(1u16.rotate_right(1)) - 0) as _;
(op!(pow(U2, U15) - U2)) >= u15 = (!(1u16.rotate_right(1)) - 1) as _;
(op!(pow(U2, U31) - U1)) >= u31 = (!(1u32.rotate_right(1)) - 0) as _;
(op!(pow(U2, U31) - U2)) >= u31 = (!(1u32.rotate_right(1)) - 1) as _;
(op!(pow(U2, U63) - U1)) >= u63 = (!(1u64.rotate_right(1)) - 0) as _;
(op!(pow(U2, U63) - U2)) >= u63 = (!(1u64.rotate_right(1)) - 1) as _;
(op!(pow(U2, U127) - U1)) >= u127 = (!(1u128.rotate_right(1)) - 0) as _;
(op!(pow(U2, U127) - U2)) >= u127 = (!(1u128.rotate_right(1)) - 1) as _;
(op!(pow(U2, U8) - U1)) >= u8 = (u8::MAX - 0) as _;
(op!(pow(U2, U8) - U2)) >= u8 = (u8::MAX - 1) as _;
(op!(pow(U2, U16) - U1)) >= u16 = (u16::MAX - 0) as _;
(op!(pow(U2, U16) - U2)) >= u16 = (u16::MAX - 1) as _;
(op!(pow(U2, U32) - U1)) >= u32 = (u32::MAX - 0) as _;
(op!(pow(U2, U32) - U2)) >= u32 = (u32::MAX - 1) as _;
(op!(pow(U2, U64) - U1)) >= u64 = (u64::MAX - 0) as _;
(op!(pow(U2, U64) - U2)) >= u64 = (u64::MAX - 1) as _;
(op!(pow(U2, U128) - U1)) >= u128 = (u128::MAX - 0) as _;
(op!(pow(U2, U128) - U2)) >= u128 = (u128::MAX - 1) as _;
(op!(Z0 - pow(P2, P7) + Z0)) >= i8 = (i8::MIN + 0) as _;
(op!(Z0 - pow(P2, P7) + P1)) >= i8 = (i8::MIN + 1) as _;
(op!(Z0 - pow(P2, P15) + Z0)) >= i16 = (i16::MIN + 0) as _;
(op!(Z0 - pow(P2, P15) + P1)) >= i16 = (i16::MIN + 1) as _;
(op!(Z0 - pow(P2, P31) + Z0)) >= i32 = (i32::MIN + 0) as _;
(op!(Z0 - pow(P2, P31) + P1)) >= i32 = (i32::MIN + 1) as _;
(op!(Z0 - pow(P2, P63) + Z0)) >= i64 = (i64::MIN + 0) as _;
(op!(Z0 - pow(P2, P63) + P1)) >= i64 = (i64::MIN + 1) as _;
(op!(Z0 - pow(P2, P127) + Z0)) >= i128 = (i128::MIN + 0) as _;
(op!(Z0 - pow(P2, P127) + P1)) >= i128 = (i128::MIN + 1) as _;
}
}

View File

@@ -0,0 +1,41 @@
[package]
name = "rosenpass-wireguard-broker"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal broker that runs as root and supplies exchanged keys to the kernel."
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
thiserror = { workspace = true }
rosenpass-lenses = { workspace = true }
paste = { workspace = true } # TODO: Using lenses should not necessitate importing paste
# Privileged only
wireguard-uapi = { workspace = true }
# Socket handler only
rosenpass-to = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }
# Mio broker client
mio = { workspace = true }
rosenpass-util = { workspace = true }
[[bin]]
name = "rosenpass-wireguard-broker-privileged"
path = "src/bin/priviledged.rs"
test = false
doc = false
[[bin]]
name = "rosenpass-wireguard-broker-socket-handler"
test = false
path = "src/bin/socket_handler.rs"
doc = false

View File

@@ -0,0 +1,5 @@
# Rosenpass internal broker supplying WireGuard with keys.
This crate contains a small application purpose-built to supply WireGuard in the linux kernel with pre-shared keys.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -0,0 +1,152 @@
use std::{borrow::BorrowMut, marker::PhantomData};
use rosenpass_lenses::LenseView;
use crate::{
api::msgs::{self, EnvelopeExt, SetPskRequestExt, SetPskResponseExt},
WireGuardBroker,
};
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientPollResponseError<RecvError> {
#[error(transparent)]
IoError(RecvError),
#[error("Invalid message.")]
InvalidMessage,
}
impl<RecvError> From<msgs::InvalidMessageTypeError> for BrokerClientPollResponseError<RecvError> {
fn from(value: msgs::InvalidMessageTypeError) -> Self {
let msgs::InvalidMessageTypeError = value; // Assert that this is a unit type
BrokerClientPollResponseError::<RecvError>::InvalidMessage
}
}
fn io_pollerr<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::IoError(e)
}
fn invalid_msg_pollerr<RecvError>() -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::InvalidMessage
}
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientSetPskError<SendError> {
#[error(transparent)]
IoError(SendError),
#[error("Interface name out of bounds")]
IfaceOutOfBounds,
}
pub trait BrokerClientIo {
type SendError;
type RecvError;
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>;
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>;
}
#[derive(Debug)]
pub struct BrokerClient<'a, Io, IoRef>
where
Io: BrokerClientIo,
IoRef: 'a + BorrowMut<Io>,
{
io: IoRef,
_phantom_io: PhantomData<&'a mut Io>,
}
impl<'a, Io, IoRef> BrokerClient<'a, Io, IoRef>
where
Io: BrokerClientIo,
IoRef: 'a + BorrowMut<Io>,
{
pub fn new(io: IoRef) -> Self {
Self {
io,
_phantom_io: PhantomData,
}
}
pub fn io(&self) -> &IoRef {
&self.io
}
pub fn io_mut(&mut self) -> &mut IoRef {
&mut self.io
}
pub fn poll_response(
&mut self,
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
let res: &[u8] = match self.io.borrow_mut().recv_msg().map_err(io_pollerr)? {
Some(r) => r,
None => return Ok(None),
};
let typ = res.get(0).ok_or(invalid_msg_pollerr())?;
let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type
let res: msgs::Envelope<_, msgs::SetPskResponse<&[u8]>> = res
.envelope_truncating()
.map_err(|_| invalid_msg_pollerr())?;
let res: msgs::SetPskResponse<&[u8]> = res
.payload()
.set_psk_response()
.map_err(|_| invalid_msg_pollerr())?;
let res: msgs::SetPskResponseReturnCode = res.return_code()[0]
.try_into()
.map_err(|_| invalid_msg_pollerr())?;
let res: msgs::SetPskResult = res.into();
Ok(Some(res))
}
}
impl<'a, Io, IoRef> WireGuardBroker for BrokerClient<'a, Io, IoRef>
where
Io: BrokerClientIo,
IoRef: 'a + BorrowMut<Io>,
{
type Error = BrokerClientSetPskError<Io::SendError>;
fn set_psk(
&mut self,
iface: &str,
peer_id: [u8; 32],
psk: [u8; 32],
) -> Result<(), Self::Error> {
use BrokerClientSetPskError::*;
const BUF_SIZE: usize = <msgs::Envelope<(), msgs::SetPskRequest<()>> as LenseView>::LEN;
// Allocate message
let mut req = [0u8; BUF_SIZE];
// Construct message view
let mut req: msgs::Envelope<_, msgs::SetPskRequest<&mut [u8]>> =
(&mut req as &mut [u8]).envelope_truncating().unwrap();
// Populate envelope
req.msg_type_mut()
.copy_from_slice(&[msgs::MsgType::SetPsk as u8]);
{
// Derived payload
let mut req: msgs::SetPskRequest<&mut [u8]> =
req.payload_mut().set_psk_request().unwrap();
// Populate payload
req.peer_id_mut().copy_from_slice(&peer_id);
req.psk_mut().copy_from_slice(&psk);
req.set_iface(iface).ok_or(IfaceOutOfBounds)?;
}
// Send message
self.io
.borrow_mut()
.send_msg(req.all_bytes())
.map_err(IoError)?;
Ok(())
}
}

View File

@@ -0,0 +1,204 @@
use std::collections::VecDeque;
use std::io::{ErrorKind, Read, Write};
use anyhow::{bail, ensure};
use crate::WireGuardBroker;
use super::client::{
BrokerClient, BrokerClientIo, BrokerClientPollResponseError, BrokerClientSetPskError,
};
use super::msgs;
#[derive(Debug)]
pub struct MioBrokerClient {
inner: BrokerClient<'static, MioBrokerClientIo, MioBrokerClientIo>,
}
#[derive(Debug)]
struct MioBrokerClientIo {
socket: mio::net::TcpStream,
send_buf: VecDeque<u8>,
receiving_size: bool,
recv_buf: Vec<u8>,
recv_off: usize,
}
impl MioBrokerClient {
pub fn new(socket: mio::net::TcpStream) -> Self {
let io = MioBrokerClientIo {
socket,
send_buf: VecDeque::new(),
receiving_size: false,
recv_buf: Vec::new(),
recv_off: 0,
};
let inner = BrokerClient::new(io);
Self { inner }
}
pub fn poll(&mut self) -> anyhow::Result<Option<msgs::SetPskResult>> {
self.inner.io_mut().flush()?;
// This sucks
match self.inner.poll_response() {
Ok(res) => {
return Ok(res);
}
Err(BrokerClientPollResponseError::IoError(e)) => {
return Err(e);
}
Err(BrokerClientPollResponseError::InvalidMessage) => {
bail!("Invalid message");
}
};
}
}
impl WireGuardBroker for MioBrokerClient {
type Error = anyhow::Error;
fn set_psk(&mut self, iface: &str, peer_id: [u8; 32], psk: [u8; 32]) -> anyhow::Result<()> {
use BrokerClientSetPskError::*;
let e = self.inner.set_psk(iface, peer_id, psk);
match e {
Ok(()) => Ok(()),
Err(IoError(e)) => Err(e),
Err(IfaceOutOfBounds) => bail!("Interface name size is out of bounds."),
}
}
}
impl BrokerClientIo for MioBrokerClientIo {
type SendError = anyhow::Error;
type RecvError = anyhow::Error;
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
self.flush()?;
self.send_or_buffer(&(buf.len() as u64).to_le_bytes())?;
self.send_or_buffer(&buf)?;
self.flush()?;
Ok(())
}
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
// Stale message in receive buffer. Reset!
if self.recv_off == self.recv_buf.len() {
self.receiving_size = true;
self.recv_off = 0;
self.recv_buf.resize(8, 0);
}
// Try filling the receive buffer
self.recv_off += raw_recv(&self.socket, &mut self.recv_buf[self.recv_off..])?;
if self.recv_off < self.recv_buf.len() {
return Ok(None);
}
// Received size, now start receiving
if self.receiving_size {
// Received the size
// Parse the received length
let len: &[u8; 8] = self.recv_buf[..].try_into().unwrap();
let len: usize = u64::from_le_bytes(*len) as usize;
ensure!(
len <= msgs::RESPONSE_MSG_BUFFER_SIZE,
"Oversized buffer ({len}) in psk buffer response."
);
// Prepare the message buffer for receiving an actual message of the given size
self.receiving_size = false;
self.recv_off = 0;
self.recv_buf.resize(len, 0);
// Try to receive the message
return self.recv_msg();
}
// Received an actual message
return Ok(Some(&self.recv_buf[..]));
}
}
impl MioBrokerClientIo {
fn flush(&mut self) -> anyhow::Result<()> {
let (fst, snd) = self.send_buf.as_slices();
let (written, res) = match raw_send(&self.socket, fst) {
Ok(w1) if w1 >= fst.len() => match raw_send(&self.socket, snd) {
Ok(w2) => (w1 + w2, Ok(())),
Err(e) => (w1, Err(e)),
},
Ok(w1) => (w1, Ok(())),
Err(e) => (0, Err(e)),
};
self.send_buf.drain(..written);
(&self.socket).try_io(|| (&self.socket).flush())?;
res
}
fn send_or_buffer(&mut self, buf: &[u8]) -> anyhow::Result<()> {
let mut off = 0;
if self.send_buf.is_empty() {
off += raw_send(&self.socket, buf)?;
}
self.send_buf.extend((&buf[off..]).iter());
Ok(())
}
}
fn raw_send(mut socket: &mio::net::TcpStream, data: &[u8]) -> anyhow::Result<usize> {
let mut off = 0;
socket.try_io(|| {
loop {
if off == data.len() {
return Ok(());
}
match socket.write(&data[off..]) {
Ok(n) => {
off += n;
}
Err(e) if e.kind() == ErrorKind::Interrupted => {
// pass retry
}
Err(e) if off > 0 || e.kind() == ErrorKind::WouldBlock => return Ok(()),
Err(e) => return Err(e),
}
}
})?;
return Ok(off);
}
fn raw_recv(mut socket: &mio::net::TcpStream, out: &mut [u8]) -> anyhow::Result<usize> {
let mut off = 0;
socket.try_io(|| {
loop {
if off == out.len() {
return Ok(());
}
match socket.read(&mut out[off..]) {
Ok(n) => {
off += n;
}
Err(e) if e.kind() == ErrorKind::Interrupted => {
// pass retry
}
Err(e) if off > 0 || e.kind() == ErrorKind::WouldBlock => return Ok(()),
Err(e) => return Err(e),
}
}
})?;
return Ok(off);
}

View File

@@ -0,0 +1,4 @@
pub mod client;
pub mod mio_client;
pub mod msgs;
pub mod server;

View File

@@ -0,0 +1,140 @@
use std::result::Result;
use std::str::{from_utf8, Utf8Error};
use rosenpass_lenses::{lense, LenseView};
pub const REQUEST_MSG_BUFFER_SIZE: usize = <Envelope<(), SetPskRequest<()>> as LenseView>::LEN;
pub const RESPONSE_MSG_BUFFER_SIZE: usize = <Envelope<(), SetPskResponse<()>> as LenseView>::LEN;
lense! { Envelope<M> :=
/// [MsgType] of this message
msg_type: 1,
/// Reserved for future use
reserved: 3,
/// The actual Paylod
payload: M::LEN
}
lense! { SetPskRequest :=
peer_id: 32,
psk: 32,
iface_size: 1, // TODO: We should have variable length strings in lenses
iface_buf: 255
}
impl SetPskRequest<&[u8]> {
pub fn iface_bin(&self) -> &[u8] {
let len = self.iface_size()[0] as usize;
&self.iface_buf()[..len]
}
pub fn iface(&self) -> Result<&str, Utf8Error> {
from_utf8(self.iface_bin())
}
}
impl SetPskRequest<&mut [u8]> {
pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> {
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256
self.iface_size_mut()[0] = iface.len() as u8;
self.iface_buf_mut().fill(0);
(&mut self.iface_buf_mut()[..iface.len()]).copy_from_slice(iface);
Some(())
}
pub fn set_iface(&mut self, iface: &str) -> Option<()> {
self.set_iface_bin(iface.as_bytes())
}
}
lense! { SetPskResponse :=
return_code: 1
}
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum SetPskError {
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
InternalError,
#[error("The indicated wireguard interface does not exist")]
NoSuchInterface,
#[error("The indicated peer does not exist on the wireguard interface")]
NoSuchPeer,
}
pub type SetPskResult = Result<(), SetPskError>;
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum SetPskResponseReturnCode {
Success = 0x00,
InternalError = 0x01,
NoSuchInterface = 0x02,
NoSuchPeer = 0x03,
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct InvalidSetPskResponseError;
impl TryFrom<u8> for SetPskResponseReturnCode {
type Error = InvalidSetPskResponseError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use SetPskResponseReturnCode::*;
match value {
0x00 => Ok(Success),
0x01 => Ok(InternalError),
0x02 => Ok(NoSuchInterface),
0x03 => Ok(NoSuchPeer),
_ => Err(InvalidSetPskResponseError),
}
}
}
impl From<SetPskResponseReturnCode> for SetPskResult {
fn from(value: SetPskResponseReturnCode) -> Self {
use SetPskError as E;
use SetPskResponseReturnCode as C;
match value {
C::Success => Ok(()),
C::InternalError => Err(E::InternalError),
C::NoSuchInterface => Err(E::NoSuchInterface),
C::NoSuchPeer => Err(E::NoSuchPeer),
}
}
}
impl From<SetPskResult> for SetPskResponseReturnCode {
fn from(value: SetPskResult) -> Self {
use SetPskError as E;
use SetPskResponseReturnCode as C;
match value {
Ok(()) => C::Success,
Err(E::InternalError) => C::InternalError,
Err(E::NoSuchInterface) => C::NoSuchInterface,
Err(E::NoSuchPeer) => C::NoSuchPeer,
}
}
}
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum MsgType {
SetPsk = 0x01,
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct InvalidMessageTypeError;
impl TryFrom<u8> for MsgType {
type Error = InvalidMessageTypeError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(MsgType::SetPsk),
_ => Err(InvalidMessageTypeError),
}
}
}

View File

@@ -0,0 +1,99 @@
use std::borrow::BorrowMut;
use std::marker::PhantomData;
use std::result::Result;
use rosenpass_lenses::LenseError;
use crate::api::msgs::{self, EnvelopeExt, SetPskRequestExt, SetPskResponseExt};
use crate::WireGuardBroker;
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerServerError {
#[error("No such request type: {}", .0)]
NoSuchRequestType(u8),
#[error("Invalid message received.")]
InvalidMessage,
}
impl From<LenseError> for BrokerServerError {
fn from(value: LenseError) -> Self {
use BrokerServerError as Be;
use LenseError as Le;
match value {
Le::BufferSizeMismatch => Be::InvalidMessage,
}
}
}
impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
fn from(value: msgs::InvalidMessageTypeError) -> Self {
let msgs::InvalidMessageTypeError = value; // Assert that this is a unit type
BrokerServerError::InvalidMessage
}
}
pub struct BrokerServer<'a, Err, Inner, Ref>
where
msgs::SetPskError: From<Err>,
Inner: WireGuardBroker<Error = Err>,
Ref: BorrowMut<Inner> + 'a,
{
inner: Ref,
_phantom: PhantomData<&'a mut Inner>,
}
impl<'a, Err, Inner, Ref> BrokerServer<'a, Err, Inner, Ref>
where
msgs::SetPskError: From<Err>,
Inner: WireGuardBroker<Error = Err>,
Ref: 'a + BorrowMut<Inner>,
{
pub fn new(inner: Ref) -> Self {
Self {
inner,
_phantom: PhantomData,
}
}
pub fn handle_message(
&mut self,
req: &[u8],
res: &mut [u8; msgs::RESPONSE_MSG_BUFFER_SIZE],
) -> Result<usize, BrokerServerError> {
use BrokerServerError::*;
let typ = req.get(0).ok_or(InvalidMessage)?;
let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type
let req: msgs::Envelope<_, msgs::SetPskRequest<&[u8]>> = req.envelope_truncating()?;
let mut res: msgs::Envelope<_, msgs::SetPskResponse<&mut [u8]>> =
(res as &mut [u8]).envelope_truncating()?;
(&mut res).msg_type_mut()[0] = msgs::MsgType::SetPsk as u8;
self.handle_set_psk(
req.payload().set_psk_request()?,
res.payload_mut().set_psk_response()?,
)?;
Ok(res.all_bytes().len())
}
fn handle_set_psk(
&mut self,
req: msgs::SetPskRequest<&[u8]>,
mut res: msgs::SetPskResponse<&mut [u8]>,
) -> Result<(), BrokerServerError> {
// Using unwrap here since lenses can not return fixed-size arrays
// TODO: Slices should give access to fixed size arrays
let r: Result<(), Err> = self.inner.borrow_mut().set_psk(
req.iface()
.map_err(|_e| BrokerServerError::InvalidMessage)?,
req.peer_id().try_into().unwrap(),
req.psk().try_into().unwrap(),
);
let r: msgs::SetPskResult = r.map_err(|e| e.into());
let r: msgs::SetPskResponseReturnCode = r.into();
res.return_code_mut()[0] = r as u8;
Ok(())
}
}

View File

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

View File

@@ -0,0 +1,191 @@
use std::process::Stdio;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{UnixListener, UnixStream};
use tokio::process::Command;
use tokio::sync::{mpsc, oneshot};
use tokio::task;
use anyhow::{bail, ensure, Result};
use clap::{ArgGroup, Parser};
use rosenpass_util::fd::claim_fd;
use rosenpass_wireguard_broker::api::msgs;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(group(
ArgGroup::new("socket")
.required(true)
.args(&["listen_path", "listen_fd", "stream_fd"]),
))]
struct Args {
/// Where in the file-system to create the unix socket this broker will be listening for
/// connections on
#[arg(long)]
listen_path: Option<String>,
/// When this broker is called from another process, the other process can open and bind the
/// unix socket to use themselves, passing it to this process. In Rust this can be achieved
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
#[arg(long)]
listen_fd: Option<i32>,
/// When this broker is called from another process, the other process can connect the unix socket
/// themselves, for instance using the `socketpair(2)` system call.
#[arg(long)]
stream_fd: Option<i32>,
/// The underlying broker, accepting commands through stdin and sending results through stdout.
#[arg(
last = true,
allow_hyphen_values = true,
default_value = "rosenpass-wireguard-broker-privileged"
)]
command: Vec<String>,
}
struct BrokerRequest {
reply_to: oneshot::Sender<BrokerResponse>,
request: Vec<u8>,
}
struct BrokerResponse {
response: Vec<u8>,
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
let args = Args::parse();
let (proc_tx, proc_rx) = mpsc::channel(100);
// Start the inner broker handler
task::spawn(async move {
if let Err(e) = direct_broker_process(proc_rx, args.command).await {
log::error!("Error in broker command handler: {e}");
panic!("Can not proceed without underlying broker process");
}
});
// Listen for incoming requests
if let Some(path) = args.listen_path {
let sock = UnixListener::bind(path)?;
listen_for_clients(proc_tx, sock).await
} else if let Some(fd) = args.listen_fd {
let sock = std::os::unix::net::UnixListener::from(claim_fd(fd)?);
sock.set_nonblocking(true)?;
listen_for_clients(proc_tx, UnixListener::from_std(sock)?).await
} else if let Some(fd) = args.stream_fd {
let stream = std::os::unix::net::UnixStream::from(claim_fd(fd)?);
stream.set_nonblocking(true)?;
on_accept(proc_tx, UnixStream::from_std(stream)?).await
} else {
unreachable!();
}
}
async fn direct_broker_process(
mut queue: mpsc::Receiver<BrokerRequest>,
cmd: Vec<String>,
) -> Result<()> {
let proc = Command::new(&cmd[0])
.args(&cmd[1..])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let mut stdin = proc.stdin.unwrap();
let mut stdout = proc.stdout.unwrap();
loop {
let BrokerRequest { reply_to, request } = queue.recv().await.unwrap();
stdin
.write_all(&(request.len() as u64).to_le_bytes())
.await?;
stdin.write_all(&request[..]).await?;
// Read the response length
let mut len = [0u8; 8];
stdout.read_exact(&mut len).await?;
// Parse the response length
let len = u64::from_le_bytes(len) as usize;
ensure!(
len <= msgs::RESPONSE_MSG_BUFFER_SIZE,
"Oversized buffer ({len}) in broker stdout."
);
// Read the message itself
let mut res_buf = request; // Avoid allocating memory if we don't have to
res_buf.resize(len as usize, 0);
stdout.read_exact(&mut res_buf[..len]).await?;
// Return to the unix socket connection worker
reply_to
.send(BrokerResponse { response: res_buf })
.or_else(|_| bail!("Unable to send respnse to unix socket worker."))?;
}
}
async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListener) -> Result<()> {
loop {
let (stream, _addr) = sock.accept().await?;
let queue = queue.clone();
task::spawn(async move {
if let Err(e) = on_accept(queue, stream).await {
log::error!("Error during connection processing: {e}");
}
});
}
// NOTE: If loop can ever terminate we need to join the spawned tasks
}
async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> {
let mut req_buf = Vec::new();
loop {
stream.readable().await?;
// Read the message length
let mut len = [0u8; 8];
stream.read_exact(&mut len).await?;
// Parse the message length
let len = u64::from_le_bytes(len) as usize;
ensure!(
len <= msgs::REQUEST_MSG_BUFFER_SIZE,
"Oversized buffer ({len}) in unix socket input."
);
// Read the message itself
req_buf.resize(len as usize, 0);
stream.read_exact(&mut req_buf[..len]).await?;
// Handle the message
let (reply_tx, reply_rx) = oneshot::channel();
queue
.send(BrokerRequest {
reply_to: reply_tx,
request: req_buf,
})
.await?;
// Wait for the reply
let BrokerResponse { response } = reply_rx.await.unwrap();
// Write reply back to unix socket
stream
.write_all(&(response.len() as u64).to_le_bytes())
.await?;
stream.write_all(&response[..]).await?;
stream.flush().await?;
// Reuse the same memory for the next message
req_buf = response;
}
}

View File

@@ -0,0 +1,14 @@
use std::result::Result;
pub trait WireGuardBroker {
type Error;
fn set_psk(
&mut self,
interface: &str,
peer_id: [u8; 32],
psk: [u8; 32],
) -> Result<(), Self::Error>;
}
pub mod api;