mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 21:34:37 +03:00
Compare commits
45 Commits
dev/karo/m
...
dev/broker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed5d7b4fa4 | ||
|
|
97a2a30678 | ||
|
|
9690085156 | ||
|
|
ca972e8b70 | ||
|
|
2fa0a2a72a | ||
|
|
b6203683fc | ||
|
|
e0f75ab97e | ||
|
|
0789c60602 | ||
|
|
e42f90b048 | ||
|
|
29917fd7a6 | ||
|
|
62aa9b4351 | ||
|
|
26cb4a587f | ||
|
|
1c14be38dd | ||
|
|
30cb0e9801 | ||
|
|
9824db4f09 | ||
|
|
e3b72487db | ||
|
|
85c447052e | ||
|
|
b2a64ed17a | ||
|
|
91da0dfd2d | ||
|
|
4a170b1983 | ||
|
|
7c83e244f9 | ||
|
|
eb76179dc4 | ||
|
|
d84efa7422 | ||
|
|
61ef5b92bb | ||
|
|
184cff0e5e | ||
|
|
9819148b6f | ||
|
|
3a0ebd2cbc | ||
|
|
1eefb5f263 | ||
|
|
d45e24e9b6 | ||
|
|
972e82b35f | ||
|
|
101c9bf4b3 | ||
|
|
955d57ea49 | ||
|
|
838f700a74 | ||
|
|
5448cdc565 | ||
|
|
77cd8a9fd1 | ||
|
|
0f89ab7976 | ||
|
|
70fa9bd6d7 | ||
|
|
85a61808de | ||
|
|
cf132bca11 | ||
|
|
7bda010a9b | ||
|
|
36089fd37f | ||
|
|
31d43accd5 | ||
|
|
205c301012 | ||
|
|
d014095469 | ||
|
|
7cece82119 |
44
.github/workflows/qc.yaml
vendored
44
.github/workflows/qc.yaml
vendored
@@ -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
800
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
46
Cargo.toml
46
Cargo.toml
@@ -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" ] }
|
||||||
|
|||||||
121
analysis/03_identity_hiding.entry.mpv
Normal file
121
analysis/03_identity_hiding.entry.mpv
Normal 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.
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
12
cipher-traits/Cargo.toml
Normal 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]
|
||||||
@@ -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
47
cipher-traits/src/kem.rs
Normal 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
2
cipher-traits/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod kem;
|
||||||
|
pub use kem::Kem;
|
||||||
@@ -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
109
ciphers/src/hash_domain.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
42
ciphers/src/subtle/blake2b.rs
Normal file
42
ciphers/src/subtle/blake2b.rs
Normal 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(())
|
||||||
|
})
|
||||||
|
}
|
||||||
43
ciphers/src/subtle/chacha20poly1305_ietf.rs
Normal file
43
ciphers/src/subtle/chacha20poly1305_ietf.rs
Normal 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(())
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
|
pub mod blake2b;
|
||||||
|
pub mod chacha20poly1305_ietf;
|
||||||
pub mod incorrect_hmac_blake2b;
|
pub mod incorrect_hmac_blake2b;
|
||||||
|
pub mod xchacha20poly1305_ietf;
|
||||||
|
|||||||
45
ciphers/src/subtle/xchacha20poly1305_ietf.rs
Normal file
45
ciphers/src/subtle/xchacha20poly1305_ietf.rs
Normal 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(())
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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
115
format_rust_code.sh
Executable 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
8
fuzz/fuzz_targets/box_secret_alloc.rs
Normal file
8
fuzz/fuzz_targets/box_secret_alloc.rs
Normal 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);
|
||||||
|
});
|
||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
9
fuzz/fuzz_targets/vec_secret_alloc.rs
Normal file
9
fuzz/fuzz_targets/vec_secret_alloc.rs
Normal 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
16
lenses/Cargo.toml
Normal 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
3
lenses/readme.md
Normal 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
206
lenses/src/lib.rs
Normal 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
16
oqs/Cargo.toml
Normal 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
5
oqs/readme.md
Normal 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
80
oqs/src/kem_macro.rs
Normal 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
21
oqs/src/lib.rs
Normal 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);
|
||||||
@@ -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}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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(|| {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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]
|
||||||
|
|||||||
46
rosenpass/src/hash_domains.rs
Normal file
46
rosenpass/src/hash_domains.rs
Normal 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");
|
||||||
@@ -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");
|
|
||||||
@@ -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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "1.74.1"
|
||||||
@@ -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
5
secret-memory/readme.md
Normal 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.
|
||||||
86
secret-memory/src/alloc/memsec.rs
Normal file
86
secret-memory/src/alloc/memsec.rs
Normal 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) };
|
||||||
|
}
|
||||||
|
}
|
||||||
6
secret-memory/src/alloc/mod.rs
Normal file
6
secret-memory/src/alloc/mod.rs
Normal 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,
|
||||||
|
};
|
||||||
20
secret-memory/src/debug.rs
Normal file
20
secret-memory/src/debug.rs
Normal 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(())
|
||||||
|
}
|
||||||
7
secret-memory/src/file.rs
Normal file
7
secret-memory/src/file.rs
Normal 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
11
secret-memory/src/lib.rs
Normal 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
112
secret-memory/src/public.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
secret-memory/src/rand.rs
Normal file
5
secret-memory/src/rand.rs
Normal 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
321
secret-memory/src/secret.rs
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod chacha20poly1305_ietf;
|
|
||||||
pub mod xchacha20poly1305_ietf;
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub mod blake2b;
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
109
to/README.md
109
to/README.md
@@ -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()));
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
0
util/src/fd.rs
Normal 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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
341
util/src/typenum.rs
Normal 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 _;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
wireguard-broker/Cargo.toml
Normal file
41
wireguard-broker/Cargo.toml
Normal 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
|
||||||
5
wireguard-broker/readme.md
Normal file
5
wireguard-broker/readme.md
Normal 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.
|
||||||
152
wireguard-broker/src/api/client.rs
Normal file
152
wireguard-broker/src/api/client.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
204
wireguard-broker/src/api/mio_client.rs
Normal file
204
wireguard-broker/src/api/mio_client.rs
Normal 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);
|
||||||
|
}
|
||||||
4
wireguard-broker/src/api/mod.rs
Normal file
4
wireguard-broker/src/api/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod client;
|
||||||
|
pub mod mio_client;
|
||||||
|
pub mod msgs;
|
||||||
|
pub mod server;
|
||||||
140
wireguard-broker/src/api/msgs.rs
Normal file
140
wireguard-broker/src/api/msgs.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
wireguard-broker/src/api/server.rs
Normal file
99
wireguard-broker/src/api/server.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
56
wireguard-broker/src/bin/priviledged.rs
Normal file
56
wireguard-broker/src/bin/priviledged.rs
Normal 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()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
191
wireguard-broker/src/bin/socket_handler.rs
Normal file
191
wireguard-broker/src/bin/socket_handler.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
wireguard-broker/src/lib.rs
Normal file
14
wireguard-broker/src/lib.rs
Normal 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;
|
||||||
Reference in New Issue
Block a user