Compare commits

..

48 Commits

Author SHA1 Message Date
Karolin Varner
d58aa363cd fix: Add test for rosenpass_constant_time::compare being little endian 2024-03-10 19:25:00 +01:00
Paul Spooren
1b233bc600 ci: enable cargo bench again
It only takes a few seconds to run, enable it.

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-03-07 12:21:55 +01:00
Ilka Schulz
2e7f34f4b2 Merge pull request #253 from aparcar/welcome-home
config: drop deprecated std::env::home_dir()
2024-03-05 14:54:42 +01:00
Ilka Schulz
292b4bbae0 Merge pull request #255 from aparcar/aarch64-ci
ci: Enable aarch64-linux builds again
2024-03-05 14:51:34 +01:00
Ilka Schulz
c75d222477 Merge pull request #254 from aparcar/manual
build: add link to manual
2024-03-05 12:26:51 +01:00
Paul Spooren
478fadb80d ci: Enable aarch64-linux builds again
Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-03-05 10:39:46 +01:00
Paul Spooren
7c1ada4b10 build: add link to manual
Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-03-01 19:12:30 +01:00
Paul Spooren
4f4e8e1018 config: drop deprecated std::env::home_dir()
Instead use the `home` create.

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-03-01 19:00:00 +01:00
Ilka Schulz
971e49b894 debug-log change in log level filter via CLI parameter 2024-02-29 13:38:54 +01:00
Ilka Schulz
262e32fe35 resolve #92: add CLI argument to specify log level filter 2024-02-29 13:38:54 +01:00
Ilka Schulz
4dab97d84e use <> brackets around hyperlinks in comments because GitHub actions complained 2024-02-29 13:37:43 +01:00
Ilka Schulz
1a5ffdd495 resolve #237: resolve paths starting with "~/" in config file 2024-02-29 13:37:43 +01:00
Ilka Schulz
fb91688672 add few comments to config.rs 2024-02-29 13:37:43 +01:00
Ilka Schulz
27ba729c14 move each primitive into its own module; add rough documentation
This commit does not change anything about the implementations.
2024-02-29 13:36:54 +01:00
Ilka Schulz
60235dc6ea GihHub Workflow "Quality Control": add flag "--all-features" to cargo in order to run all available tests behind feature flags 2024-02-28 17:07:40 +01:00
Ilka Schulz
36c99c020e implement test to statistically check constant run time of memcmp (feature: constant_time_tests) 2024-02-28 17:07:40 +01:00
James Brownlee
8c469af6b1 adding identity hiding improvements:
seperate files for responder and initiator tests
test file that shows other participants leaking info has an effect
general code clean up
performance improvement: initiator and responder tests now run in ~10s
2024-02-26 17:20:33 +01:00
James Brownlee
e96968b8bc adding dos protection code 2024-02-26 17:20:33 +01:00
Aaron Kaiser
81487b103d refactor: Get rid of comment and unessary truncation of buffer 2024-02-21 14:04:39 +01:00
Aaron Kaiser
8ea253f86b refactor: use memoffset crate instead of unstable offset_of feature 2024-02-21 14:04:39 +01:00
Aaron Kaiser
fd8f2e4424 style: apply rustfmt 2024-02-21 14:04:39 +01:00
Aaron Kaiser
a996b08279 refactor: replace lenses library with the zerocopy crate 2024-02-21 14:04:39 +01:00
Emil Engler
e38a6b8ed4 Merge pull request #238 from beau2am/contribution-beau2am
Fixed grammatical typo in 'cli.rs'. To resolve issue #236.
2024-02-10 17:46:45 +01:00
Beau McDermott
639541ab4f fix: Grammatical typo in cli.rs
Fixes #236
2024-02-10 17:45:20 +01:00
Karolin Varner
9690085156 chore: Cargo fmt 2024-01-27 21:38:13 +01:00
Karolin Varner
ca972e8b70 feat: Remove libsodium 2024-01-27 21:38:13 +01:00
Karolin Varner
2fa0a2a72a feat: Use core::hint::black_box in rosenpass_constant_time::xor 2024-01-27 21:38:13 +01:00
Karolin Varner
b6203683fc feat: Migrate away from sodium blake2b towards the rust crypto implementation 2024-01-27 21:38:13 +01:00
Karolin Varner
e0f75ab97e feat: Use xchacha implementation from rust crypto instead of sodium 2024-01-27 21:38:13 +01:00
Karolin Varner
0789c60602 feat: Use chacha implementation from rust crypto instead of sodium 2024-01-27 21:38:13 +01:00
Karolin Varner
e42f90b048 chore: Add helper to turn typenums into const values 2024-01-27 21:38:13 +01:00
Emil Engler
29917fd7a6 doc: Fix keygen/gen-keys misspell
Fixes #166
2024-01-21 20:54:29 +01:00
wucke13
62aa9b4351 fix: second round of clippy lints
Clippy would not automatically apply these fixes, so they were applied
by hand.
2024-01-03 18:43:05 +01:00
wucke13
26cb4a587f fix: apply clippy lints 2024-01-03 18:43:05 +01:00
wucke13
1c14be38dd fix: make benches work again
Somehow in the past while splitting into many crates, we broke the bench
setup. This commit both fixes it, and adds a CI job that ensures it is
still working to avoid such silent failure in the future. The benchmarks
are not actually run, they would take forever on the slow GitHub Actions
runners, but they are at least compiled.
2024-01-03 18:43:05 +01:00
Karolin Varner
30cb0e9801 chore: Remove references to libsodium from secret-memory 2024-01-03 18:43:05 +01:00
Karolin Varner
9824db4f09 fix: Migrate away from lazy_static in favor of thread_local
The new secret memory pool was causing CI failures in the fuzzing code,
due to the fuzzer compiling its binaries with memory sanitizer support.

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

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

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

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

Finally, this was likely triggered with the migration to memsec, because libsodium circumvents the malloc/free calls,
relying on direct calls to MMAP.
2024-01-03 18:43:05 +01:00
Karolin Varner
e3b72487db fix: Make sure all tests are run during CI runs
Had to fix the tests in util/src/result.rs.
2024-01-03 18:43:05 +01:00
Karolin Varner
85c447052e feat: Migrate to memsec 2024-01-03 18:43:05 +01:00
James Brownlee
b2a64ed17a feat: add INITIATOR_TEST and RESPONDER_TEST macros
Added INITIATOR_TEST and RESPONDER_TEST macros to the identity hiding
mpv file that can be used to selectively test the anonymity of the
initiator or the responder.
2024-01-03 18:35:54 +01:00
James Brownlee
91da0dfd2d feat: identity hiding in two stage process
Changed identity hiding test to work as a two stage process where
participants with fresh secure secret keys communicate with each other
and other compromised participants. Then the attacker is asked to
identify the difference between two of the secure participants as on of
them acts as a responder.
2024-01-03 18:35:54 +01:00
James Brownlee
4a170b1983 feat: add inital identity hiding code to proverif 2024-01-03 18:35:54 +01:00
wucke13
7c83e244f9 fix: fix Rust code in markdown files
This applies the novel format_rustcode.sh script to the markdown files in the
repo, to maintain a consistent style across code examples.
2023-12-22 17:57:32 +01:00
alankritdabral_2
eb76179dc4 feat: add format_rustcode.sh script
This script makes it possible to check formatting of rust code found in the various markdown files in the repo. It is also added as a job to the QC CI workflow.
2023-12-22 17:57:32 +01:00
wucke13
d84efa7422 Merge pull request #197 from guhitb/main
Add backwards compatibility for keygen command
2023-12-21 11:28:25 +01:00
user
61ef5b92bb fix: add deprecated keygen command
This allows users to use the old keygen command, while being informed
about its deprecation.
2023-12-20 16:03:47 +01:00
wucke13
184cff0e5e Merge pull request #196 from rosenpass/dev/fix-65
fix: remove OSFONTDIR var from whitepaper build
2023-12-03 14:01:25 +01:00
wucke13
9819148b6f fix: remove OSFONTDIR var from whitepaper build
Fixes #65. I checked with `pdffonts` that the whitepaper still has all fonts embedded.
2023-12-03 13:27:47 +01:00
76 changed files with 2838 additions and 1469 deletions

View File

@@ -223,6 +223,29 @@ jobs:
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
aarch64-linux---release-package:
name: Build aarch64-linux.release-package
runs-on:
- ubuntu-latest
needs:
- aarch64-linux---rosenpass-oci-image
- aarch64-linux---rosenpass
steps:
- run: |
DEBIAN_FRONTEND=noninteractive
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22
with:
nix_path: nixpkgs=channel:nixos-unstable
extra_nix_config: |
system = aarch64-linux
- uses: cachix/cachix-action@v12
with:
name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build
run: nix build .#packages.aarch64-linux.release-package --print-build-logs
x86_64-linux---rosenpass:
name: Build x86_64-linux.rosenpass
runs-on:
@@ -239,6 +262,27 @@ jobs:
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build
run: nix build .#packages.x86_64-linux.rosenpass --print-build-logs
aarch64-linux---rosenpass:
name: Build aarch64-linux.rosenpass
runs-on:
- ubuntu-latest
needs: []
steps:
- run: |
DEBIAN_FRONTEND=noninteractive
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22
with:
nix_path: nixpkgs=channel:nixos-unstable
extra_nix_config: |
system = aarch64-linux
- uses: cachix/cachix-action@v12
with:
name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build
run: nix build .#packages.aarch64-linux.rosenpass --print-build-logs
x86_64-linux---rosenpass-oci-image:
name: Build x86_64-linux.rosenpass-oci-image
runs-on:
@@ -256,6 +300,28 @@ jobs:
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build
run: nix build .#packages.x86_64-linux.rosenpass-oci-image --print-build-logs
aarch64-linux---rosenpass-oci-image:
name: Build aarch64-linux.rosenpass-oci-image
runs-on:
- ubuntu-latest
needs:
- aarch64-linux---rosenpass
steps:
- run: |
DEBIAN_FRONTEND=noninteractive
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22
with:
nix_path: nixpkgs=channel:nixos-unstable
extra_nix_config: |
system = aarch64-linux
- uses: cachix/cachix-action@v12
with:
name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build
run: nix build .#packages.aarch64-linux.rosenpass-oci-image --print-build-logs
x86_64-linux---rosenpass-static:
name: Build x86_64-linux.rosenpass-static
runs-on:

View File

@@ -25,6 +25,34 @@ jobs:
- name: Run ShellCheck
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 --workspace
cargo-audit:
runs-on: ubuntu-latest
steps:
@@ -93,7 +121,7 @@ jobs:
# 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 test
- run: RUST_MIN_STACK=8388608 cargo test --workspace --all-features
cargo-test-nix-devshell-x86_64-linux:
runs-on:
@@ -116,7 +144,7 @@ jobs:
with:
name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix develop --command cargo test
- run: nix develop --command cargo test --workspace --all-features
cargo-fuzz:
runs-on: ubuntu-latest
@@ -146,5 +174,5 @@ jobs:
cargo fuzz run fuzz_handle_msg -- -max_total_time=5
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=5
cargo fuzz run fuzz_box_sodium_alloc -- -max_total_time=5
cargo fuzz run fuzz_vec_sodium_alloc -- -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

629
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,10 @@ members = [
"ciphers",
"util",
"constant-time",
"sodium",
"oqs",
"to",
"fuzz",
"secret-memory",
"lenses",
]
default-members = [
@@ -27,13 +25,11 @@ tag-prefix = ""
rosenpass = { path = "rosenpass" }
rosenpass-util = { path = "util" }
rosenpass-constant-time = { path = "constant-time" }
rosenpass-sodium = { path = "sodium" }
rosenpass-cipher-traits = { path = "cipher-traits" }
rosenpass-ciphers = { path = "ciphers" }
rosenpass-to = { path = "to" }
rosenpass-secret-memory = { path = "secret-memory" }
rosenpass-oqs = { path = "oqs" }
rosenpass-lenses = { path = "lenses" }
criterion = "0.4.0"
test_bin = "0.4.0"
libfuzzer-sys = "0.4"
@@ -42,19 +38,24 @@ doc-comment = "0.3.3"
base64 = "0.21.5"
zeroize = "1.7.0"
memoffset = "0.9.0"
lazy_static = "1.4.0"
thiserror = "1.0.50"
paste = "1.0.14"
env_logger = "0.10.1"
toml = "0.7.8"
static_assertions = "1.1.0"
allocator-api2 = "0.2.16"
allocator-api2 = "0.2.14"
allocator-api2-tests = "0.2.14"
memsec = "0.6.3"
rand = "0.8.5"
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"] }
anyhow = { version = "1.0.75", features = ["backtrace", "std"] }
mio = { version = "0.8.9", features = ["net", "os-poll"] }
libsodium-sys-stable= { version = "1.20.4", features = ["use-pkg-config"] }
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" ] }
zerocopy = { version = "0.7.32", features = ["derive"] }
home = "0.5.9"

View File

@@ -3,12 +3,33 @@
#define SESSION_START_EVENTS 0
#define RANDOMIZED_CALL_IDS 0
#include "config.mpv"
#include "prelude/basic.mpv"
#include "crypto/key.mpv"
#include "crypto/kem.mpv"
#include "rosenpass/oracles.mpv"
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
nounif Spk:kem_sk_tmpl;
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
nounif rh:RespHello_t;
attacker(Cresp_hello( *rh ))/6107[conclusion].
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].
let main = rosenpass_main.
@lemma "state coherence, initiator: Initiator accepting a RespHello message implies they also generated the associated InitHello message"

View File

@@ -10,6 +10,26 @@
let main = rosenpass_main.
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
nounif Spk:kem_sk_tmpl;
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
nounif rh:RespHello_t;
attacker(Cresp_hello( *rh ))/6107[conclusion].
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].
@lemma "non-interruptability: Adv cannot prevent a genuine InitHello message from being accepted"
lemma ih:InitHello_t, psk:key, sski:kem_sk, sskr:kem_sk;
event(IHRjct(ih, psk, sskr, kem_pub(sski)))

View File

@@ -0,0 +1,25 @@
#define INITIATOR_TEST 1
#include "rosenpass/03_identity_hiding.mpv"
// nounif a:Atom, s:seed, a2:Atom;
// ConsumeSeed(a, s, a2) / 6300[conclusion].
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
nounif Spk:kem_sk_tmpl;
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
nounif rh:RespHello_t;
attacker(Cresp_hello( *rh ))/6107[conclusion].
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].

View File

@@ -0,0 +1,96 @@
#define RESPONDER_TEST 1
#include "rosenpass/03_identity_hiding.mpv"
// select k:kem_pk,ih: InitHello_t; attacker(prf(prf(prf(prf(key0, PROTOCOL), MAC), kem_pk2b(k) ), IH2b(ih))) phase 1/6300[hypothesis].
// select epki:kem_pk, sctr:bits, pidiC:bits, auth:bits, epki2:kem_pk, sctr2:bits, pidiC2:bits, auth2:bits;
// mess(D, prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pk2b(kem_pub(trusted_kem_sk(responder1)))),
// IH2b(InitHello(secure_sidi, *epki, *sctr, *pidiC, *auth)))
// ) [hypothesis, conclusion].
// select epki:kem_pk, sctr:bits, pidiC:bits, auth:bits, epki2:kem_pk, sctr2:bits, pidiC2:bits, auth2:bits;
// attacker(choice[prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pk2b(kem_pub(trusted_kem_sk(responder1)))),
// IH2b(InitHello(secure_sidi, *epki, *sctr, *pidiC, *auth))),
// prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pk2b(kem_pub(trusted_kem_sk(responder2)))),
// IH2b(InitHello(secure_sidi, *epki2, *sctr2, *pidiC2, *auth2)))]
// ) [hypothesis, conclusion].
// select
// attacker(prf(prf(key0,PROTOCOL),MAC)) [hypothesis, conclusion].
// select
// attacker(prf(key0,PROTOCOL)) [conclusion].
// select
// attacker(key0) [conclusion].
// select
// attacker(PROTOCOL) [conclusion].
// select
// attacker(kem_pub(trusted_kem_sk(responder1))) /9999 [hypothesis, conclusion].
// select
// attacker(kem_pub(trusted_kem_sk(responder2))) /9999 [hypothesis, conclusion].
// nounif ih:InitHello_t;
// attacker(ih) / 9999 [hypothesis].
// nounif rh:RespHello_t;
// attacker(rh) / 9999 [hypothesis].
// nounif ic:InitConf_t;
// attacker(ic) / 9999 [hypothesis].
// nounif k:key;
// attacker(ck_hs_enc( *k )) [hypothesis, conclusion].
// nounif k:key;
// attacker(ck_hs_enc( *k )) phase 1 [hypothesis, conclusion].
// nounif k:key, b:bits;
// attacker(ck_mix( *k , *b )) [hypothesis, conclusion].
// nounif k:key, b:bits;
// attacker(ck_mix( *k , *b ))phase 1 [hypothesis, conclusion].
// // select k:kem_pk, epki2:kem_pk, sctr2:bits, pidiC2:bits, auth2:bits, epki:kem_pk, sctr:bits, pidiC:bits, auth:bits;
// // attacker(choice[Envelope(prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pub(trusted_kem_sk(responder1))),
// // InitHello(secure_sidi, *epki2, *sctr2, *pidiC2, *auth2)
// // ), InitHello(secure_sidi, *epki2, *sctr2, *pidiC2, *auth2))
// // Envelope(prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pub(trusted_kem_sk(responder2))),
// // InitHello(secure_sidi, *epki, *sctr, *pidiC, *auth)),
// // InitHello(secure_sidi, *epki, *sctr, *pidiC, *auth))
// // ]) / 9999[hypothesis, conclusion].
// nounif k:key, b1:bits, b2:bits;
// attacker(xaead_enc( *k, *b1, *b2)) / 9999[hypothesis,conclusion].
// nounif pk:kem_pk, k:key;
// attacker(kem_enc( *pk , *k )) / 9999[hypothesis,conclusion].
// nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
// attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/9999[hypothesis, conclusion].
// nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
// attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/9999[hypothesis, conclusion].
// nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
// attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr )) /9999 [hypothesis, conclusion].
// nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
// mess(C, Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/9999[hypothesis, conclusion].
// nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
// mess(C, Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/9999[hypothesis, conclusion].
// nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
// mess(C, Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr )) /9999 [hypothesis, conclusion].
// nounif rh:RespHello_t;
// attacker(Cresp_hello( *rh ))[conclusion].
// nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
// nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
// nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
// nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
// nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
// nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
// nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
// nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].

View File

@@ -0,0 +1,29 @@
#define INITIATOR_TEST 1
#define CUSTOM_MAIN 1
#include "rosenpass/03_identity_hiding.mpv"
let Oinitiator_bad_actor_inner(sk_tmp:kem_sk_prec) =
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
#endif
in(C, last_cookie:key);
tmpl <- make_trusted_kem_sk(sk_tmp);
out(C, setup_kem_sk(tmpl));
Oinitiator_inner(sidi, Ssskm, Spsk, tmpl, Seski, Ssptr, last_cookie, C, call).
let Oinitiator_bad_actor() =
Oinitiator_bad_actor_inner(responder1) | Oinitiator_bad_actor_inner(responder2) | Oinitiator_bad_actor_inner(initiator1) | Oinitiator_bad_actor_inner(initiator2).
let identity_hiding_main2() =
0 | Oinitiator_bad_actor() | rosenpass_main2() | participants_communication() | phase 1; secretCommunication().
let main = identity_hiding_main2.

View File

@@ -0,0 +1,136 @@
#define CHAINING_KEY_EVENTS 1
#define MESSAGE_TRANSMISSION_EVENTS 0
#define SESSION_START_EVENTS 0
#define RANDOMIZED_CALL_IDS 0
#define COOKIE_EVENTS 1
#define KEM_EVENTS 1
#include "config.mpv"
#include "prelude/basic.mpv"
#include "crypto/key.mpv"
#include "crypto/kem.mpv"
#include "rosenpass/handshake_state.mpv"
/* The cookie data structure is implemented based on the WireGuard protocol.
* The ip and port is based purely on the public key and the implementation of the private cookie key is intended to mirror the biscuit key.
* The code tests the response to a possible DOS attack by setting up alternative branches for the protocol
* processes: Oinit_conf, Oinit_hello and resp_hello to simulate what happens when the responder or initiator is overloaded.
* When under heavy load a valid cookie is required. When such a cookie is not present a cookie message is sent as a response.
* Queries then test to make sure that expensive KEM operations are only conducted after a cookie has been successfully validated.
*/
type CookieMsg_t.
fun CookieMsg(
SessionId, // sender
bits, // nonce
bits // cookie
) : CookieMsg_t [data].
#define COOKIE_EVENTS(eventLbl) \
COOKIE_EV(event MCAT(eventLbl, _UnderLoadEV) (SessionId, SessionId, Atom).) \
COOKIE_EV(event MCAT(eventLbl, _CookieValidated) (SessionId, SessionId, Atom).) \
COOKIE_EV(event MCAT(eventLbl, _CookieSent) (SessionId, SessionId, Atom, CookieMsg_t).)
fun cookie_key(kem_sk) : key [private].
fun ip_and_port(kem_pk):bits.
letfun create_mac2_key(sskm:kem_sk, spkt:kem_pk) = prf(cookie_key(sskm), ip_and_port(spkt)).
letfun create_cookie(sskm:kem_sk, spkm:kem_pk, spkt:kem_pk, nonce:bits, msg:bits) = xaead_enc(lprf2(COOKIE, kem_pk2b(spkm), nonce),
k2b(create_mac2_key(sskm, spkm)), msg).
#define COOKIE_PROCESS(eventLbl, innerFunc) \
new nonce:bits; \
in(C, Ccookie(mac1, mac2)); \
COOKIE_EV(event MCAT(eventLbl, _UnderLoadEV) (sidi, sidr, call);) \
msgB <- Envelope(mac1, msg); \
mac2_key <- create_mac2_key(sskm, spkt); \
if k2b(create_mac2(mac2_key, msgB)) = mac2 then \
COOKIE_EV(event MCAT(eventLbl, _CookieValidated) (sidi, sidr, call);) \
innerFunc \
else \
cookie <- create_cookie(sskm, spkm, spkt, nonce, msg); \
cookie_msg <- CookieMsg(sidi, nonce, cookie); \
COOKIE_EV(event MCAT(eventLbl, _CookieSent) (sidi, sidr, call, cookie_msg);) \
out(C, cookie_msg). \
#include "rosenpass/oracles.mpv"
#include "rosenpass/responder.macro"
COOKIE_EVENTS(Oinit_conf)
let Oinit_conf_underLoad() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
in(C, last_cookie:bits);
msg <- IC2b(ic);
let InitConf(sidi, sidr, biscuit, auth) = ic in
new call:Atom;
SETUP_HANDSHAKE_STATE()
COOKIE_PROCESS(Oinit_conf, Oinit_conf_inner(Ssskm, Spsk, Sspkt, ic, call))
#include "rosenpass/responder.macro"
COOKIE_EVENTS(Oinit_hello)
let Oinit_hello_underLoad() =
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
in(C, Oinit_hello_last_cookie:key);
new call:Atom;
msg <- IH2b(ih);
let InitHello(sidi, epki, sctr, pidic, auth) = ih in
SETUP_HANDSHAKE_STATE()
COOKIE_PROCESS(Oinit_hello, Oinit_hello_inner(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih, Oinit_hello_last_cookie, C, call))
let rosenpass_dos_main() = 0
| !Oreveal_kem_pk
| REP(INITIATOR_BOUND, Oinitiator)
| REP(RESPONDER_BOUND, Oinit_hello)
| REP(RESPONDER_BOUND, Oinit_conf)
| REP(RESPONDER_BOUND, Oinit_hello_underLoad)
| REP(RESPONDER_BOUND, Oinit_conf_underLoad).
let main = rosenpass_dos_main.
select cookie:CookieMsg_t; attacker(cookie)/6220[hypothesis].
nounif v:key; attacker(prepare_key( v ))/6217[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
// nounif Spk:kem_sk_tmpl;
// attacker(Creveal_kem_pk(Spk))/6110[conclusion].
// nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
// attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
// nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
// attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
nounif rh:RespHello_t;
attacker(Cresp_hello( *rh ))/6107[conclusion].
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].
@reachable "DOS protection: cookie sent"
query sidi:SessionId, sidr:SessionId, call:Atom, cookieMsg:CookieMsg_t;
event (Oinit_hello_CookieSent(sidi, sidr, call, cookieMsg)).
@lemma "DOS protection: Oinit_hello kem use when under load implies validated cookie"
lemma sidi:SessionId, sidr:SessionId, call:Atom;
event(Oinit_hello_UnderLoadEV(sidi, sidr, call))
&& event(Oinit_hello_KemUse(sidi, sidr, call))
==> event(Oinit_hello_CookieValidated(sidi, sidr, call)).
@lemma "DOS protection: Oinit_conf kem use when under load implies validated cookie"
lemma sidi:SessionId, sidr:SessionId, call:Atom;
event(Oinit_conf_UnderLoadEV(sidi, sidr, call))
&& event(Oinit_conf_KemUse(sidi, sidr, call))
==> event(Oinit_conf_CookieValidated(sidi, sidr, call)).
@lemma "DOS protection: Oresp_hello kem use when under load implies validated cookie"
lemma sidi:SessionId, sidr:SessionId, call:Atom;
event(Oresp_hello_UnderLoadEV(sidi, sidr, call))
&& event(Oresp_hello_KemUse(sidi, sidr, call))
==> event(Oresp_hello_CookieValidated(sidi, sidr, call)).

View File

@@ -88,6 +88,18 @@ set verboseCompleted=VERBOSE.
#define SES_EV(...)
#endif
#if COOKIE_EVENTS
#define COOKIE_EV(...) __VA_ARGS__
#else
#define COOKIE_EV(...)
#endif
#if KEM_EVENTS
#define KEM_EV(...) __VA_ARGS__
#else
#define KEM_EV(...)
#endif
(* TODO: Authentication timing properties *)
(* TODO: Proof that every adversary submitted package is equivalent to one generated by the proper algorithm using different coins. This probably requires introducing an oracle that extracts the coins used and explicitly adding the notion of coins used for Packet->Packet steps and an inductive RNG notion. *)

View File

@@ -0,0 +1,155 @@
/*
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 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 epkit:kem_pk; // epki
new sctrt:bits; // sctr
new pidiCt:bits; // pidiC
new autht:bits; // auth
NEW_TRUSTED_SEED(seski_trusted_seed)
NEW_TRUSTED_SEED(ssptr_trusted_seed)
new last_cookie:key;
new call:Atom;
Oinitiator_inner(sidi, initiator, psk, responder, seski_trusted_seed, ssptr_trusted_seed, last_cookie, D, call).
let secure_resp_hello(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, sidi:SessionId, sidr:SessionId, biscuit_no:Atom, psk:key_tmpl) =
in(D, InitHello(=secure_sidi, epki, sctr, pidiC, auth));
ih <- InitHello(sidi, epki, sctr, pidiC, auth);
NEW_TRUSTED_SEED(septi_trusted_seed)
NEW_TRUSTED_SEED(sspti_trusted_seed)
new last_cookie:key;
new call:Atom;
Oinit_hello_inner(sidr, biscuit_no, responder, psk, initiator, septi_trusted_seed, sspti_trusted_seed, ih, last_cookie, D, call).
let secure_init_conf(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, psk:key_tmpl, sidi:SessionId, sidr:SessionId) =
in(D, InitConf(=sidi, =sidr, biscuit, auth3));
ic <- InitConf(sidi,sidr,biscuit, auth3);
NEW_TRUSTED_SEED(seski_trusted_seed)
NEW_TRUSTED_SEED(ssptr_trusted_seed)
new last_cookie:key;
call <- Cinit_conf(initiator, psk, responder, ic);
Oinit_conf_inner(initiator, psk, responder, ic, call).
let secure_communication(initiator: kem_sk_tmpl, responder:kem_sk_tmpl, key:key) =
key_tmpl <- prepare_key(key);
(!secure_init_hello(initiator, secure_sidi, key_tmpl, responder))
| !secure_resp_hello(initiator, responder, secure_sidi, secure_sidr, secure_biscuit_no, key_tmpl)
| !(secure_init_conf(initiator, responder, key_tmpl, secure_sidi, secure_sidr)).
let participant_communication_initiator(participant:kem_sk_tmpl) =
in(C, responder:kem_sk_tmpl);
in(C, k:key);
secure_communication(participant, responder, k).
let participant_communication_responder(participant:kem_sk_tmpl) =
in(C, initiator:kem_sk_tmpl);
in(C, k:key);
secure_communication(initiator, participant, k).
let participants_communication() =
initiator1_tmpl <- make_trusted_kem_sk(initiator1);
initiator2_tmpl <- make_trusted_kem_sk(initiator2);
responder1_tmpl <- make_trusted_kem_sk(responder1);
responder2_tmpl <- make_trusted_kem_sk(responder2);
!participant_communication_initiator(initiator1_tmpl) | !participant_communication_responder(initiator1_tmpl)
| !participant_communication_initiator(initiator2_tmpl) | !participant_communication_responder(initiator2_tmpl)
| !participant_communication_initiator(responder1_tmpl) | !participant_communication_responder(responder1_tmpl)
| !participant_communication_initiator(responder2_tmpl) | !participant_communication_responder(responder2_tmpl).
let pipeChannel(D:channel, C:channel) =
in(D, b:bits);
out(C, b).
let secretCommunication() =
#ifdef INITIATOR_TEST
initiator_seed <- choice[make_trusted_kem_sk(initiator1), make_trusted_kem_sk(initiator2)];
#else
initiator_seed <- make_trusted_kem_sk(initiator1);
#endif
#ifdef RESPONDER_TEST
responder_seed <- choice[make_trusted_kem_sk(responder1), make_trusted_kem_sk(responder2)];
#else
responder_seed <- make_trusted_kem_sk(responder1);
#endif
secure_communication(initiator_seed, responder_seed, secure_psk) | !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() | participants_communication() | phase 1; secretCommunication().
#ifndef CUSTOM_MAIN
let main = identity_hiding_main.
#endif

View File

@@ -0,0 +1,36 @@
fun cookie_key(kem_sk) : key [private].
fun ip_and_port(kem_pk):bits.
letfun create_mac2_key(sskm:kem_sk, spkt:kem_pk) = prf(cookie_key(sskm), ip_and_port(spkt)).
letfun create_cookie(sskm:kem_sk, spkm:kem_pk, spkt:kem_pk, nonce:bits, msg:bits) = xaead_enc(lprf2(COOKIE, kem_pk2b(spkm), nonce),
k2b(create_mac2_key(sskm, spkm)), msg).
type CookieMsg_t.
fun CookieMsg(
SessionId, // sender
bits, // nonce
bits // cookie
) : CookieMsg_t [data].
#define COOKIE_PROCESS(eventLbl, innerFunc) \
in(C, Ccookie(mac1, mac2)); \
COOKIE_EV(event MCAT(eventLbl, _UnderLoadEV) (spkm, spkt, last_cookie);) \
msgB <- Envelope(mac1, RH2b(rh)); \
mac2_key <- create_mac2_key(sskm, spkt) \
let RespHello(sidi, sidr, ecti, scti, biscuit, auth) = rh in \
if Envelope(mac2_key, msgB) = mac2 then \
COOKIE_EV(event MCAT(eventLbl, _CookieValidated) (spkm, last_cookie);) \
innerFunc \
else \
new nonce:bits; \
cookie <- create_cookie(sskm, spkm, spkt, nonce, msg) \
cookie_msg <- CookieMsg(sidi, nonce, cookie); \
COOKIE_EV(event MCAT(eventLbl, _CookieSent) (spkm, cookie, cookie_k, cookie_msg);) \
out(C, cookie_msg).
#define COOKIE_EVENTS(eventLbl) \
COOKIE_EV(event MCAT(eventLbl, _UnderLoadEV) (kem_pk, kem_pk, bits).) \
COOKIE_EV(event MCAT(eventLbl, _CookieValidated) (kem_pk, bits, key, CookieMsg_t).) \
COOKIE_EV(event MCAT(eventLbl, _CookieSent) (kem_pk, bits).)

View File

@@ -41,23 +41,32 @@ restriction s:seed, p1:Atom, p2:Atom, ad1:Atom, ad2:Atom;
event(ConsumeSeed(p1, s, ad1)) && event(ConsumeSeed(p2, s, ad2))
==> p1 = p2 && ad1 = ad2.
letfun create_mac2(k:key, msg:bits) = prf(k,msg).
#include "rosenpass/responder.macro"
fun Cinit_conf(kem_sk_tmpl, key_tmpl, kem_pk_tmpl, InitConf_t) : Atom [data].
CK_EV( event OskOinit_conf(key, key). )
MTX_EV( event ICRjct(InitConf_t, key, kem_sk, kem_pk). )
SES_EV( event ResponderSession(InitConf_t, key). )
event ConsumeBiscuit(Atom, kem_sk, kem_pk, Atom).
let Oinit_conf() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
KEM_EV(event Oinit_conf_KemUse(SessionId, SessionId, Atom).)
#ifdef KEM_EVENTS
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
event(Oinit_conf_KemUse(sidi, sidr, ad1)) && event(Oinit_conf_KemUse(sidi, sidr, ad2))
==> ad1 = ad2.
#endif
event ConsumeBiscuit(Atom, kem_sk, kem_pk, Atom).
fun Ccookie(key, bits) : Atom[data].
let Oinit_conf_inner(Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t, call:Atom) =
SETUP_HANDSHAKE_STATE()
eski <- kem_sk0;
epki <- kem_pk0;
let try_ = (
let InitConf(sidi, sidr, biscuit, auth) = ic in
KEM_EV(event Oinit_conf_KemUse(sidi, sidr, call);)
INITCONF_CONSUME()
event ConsumeBiscuit(biscuit_no, sskm, spkt, call);
CK_EV( event OskOinit_conf(ck_rh, osk); )
@@ -72,11 +81,21 @@ let Oinit_conf() =
0
#endif
).
let Oinit_conf() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
#endif
Oinit_conf_inner(Ssskm, Spsk, Sspkt, ic, call).
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))
==> ad1 = ad2.
// TODO: Restriction biscuit no invalidation
#include "rosenpass/initiator.macro"
@@ -85,27 +104,56 @@ CK_EV( event OskOresp_hello(key, key, key). )
MTX_EV( event RHRjct(RespHello_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). )
let Oresp_hello(HS_DECL_ARGS) =
in(C, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth)));
rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth);
/* try */ let ic = (
ck_ini <- ck;
RESPHELLO_CONSUME()
ck_ih <- ck;
INITCONF_PRODUCE()
CK_EV (event OskOresp_hello(ck_ini, ck_ih, osk); ) // TODO: Queries testing that there is no duplication
MTX_EV( event ICSent(rh, ic, psk, sski, spkr); )
SES_EV( event InitiatorSession(rh, osk); )
ic
/* success */ ) in (
out(C, ic)
/* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS
event RHRjct(rh, psk, sski, spkr)
#else
0
KEM_EV(event Oresp_hello_KemUse(SessionId, SessionId, Atom).)
#ifdef KEM_EVENTS
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
event(Oresp_hello_KemUse(sidi, sidr, ad1)) && event(Oresp_hello_KemUse(sidi, sidr, ad2))
==> ad1 = ad2.
#endif
#ifdef COOKIE_EVENTS
COOKIE_EVENTS(Oresp_hello)
#endif
let Oresp_hello(HS_DECL_ARGS, C_in:channel, call:Atom) =
in(C_in, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth)));
in(C_in, mac2_key:key);
rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth);
#ifdef COOKIE_EVENTS
msg <- RH2b(rh);
COOKIE_PROCESS(Oresp_hello,
#endif
/* try */ let ic = (
ck_ini <- ck;
KEM_EV(event Oresp_hello_KemUse(sidi, sidr, call);)
RESPHELLO_CONSUME()
ck_ih <- ck;
INITCONF_PRODUCE()
CK_EV (event OskOresp_hello(ck_ini, ck_ih, osk); ) // TODO: Queries testing that there is no duplication
MTX_EV( event ICSent(rh, ic, psk, sski, spkr); )
SES_EV( event InitiatorSession(rh, osk); )
ic
/* success */ ) in (
icbits <- IC2b(ic);
mac <- create_mac(spkt, icbits);
mac2 <- create_mac2(mac2_key, mac_envelope2b(mac));
out(C_in, ic);
out(C_in, mac);
out(C_in, mac2)
/* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS
event RHRjct(rh, psk, sski, spkr)
#else
0
#endif
)
#ifdef COOKIE_EVENTS
)
#else
.
#endif
).
// TODO: Restriction: Biscuit no invalidation
@@ -116,24 +164,33 @@ MTX_EV( event IHRjct(InitHello_t, key, kem_sk, kem_pk). )
MTX_EV( event RHSent(InitHello_t, RespHello_t, key, kem_sk, kem_pk). )
event ConsumeSidr(SessionId, 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));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih);
KEM_EV(event Oinit_hello_KemUse(SessionId, SessionId, Atom).)
#ifdef KEM_EVENTS
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
event(Oinit_hello_KemUse(sidi, sidr, ad1)) && event(Oinit_hello_KemUse(sidi, sidr, ad2))
==> ad1 = ad2.
#endif
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, mac2_key:key, C_out:channel, call:Atom) =
// TODO: This is ugly
let InitHello(sidi, epki, sctr, pidiC, auth) = ih in
SETUP_HANDSHAKE_STATE()
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 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(Spti, setup_seed(Sspti), call);
// out(C_out, spkt);
let rh = (
KEM_EV(event Oinit_hello_KemUse(sidi, sidr, call);)
INITHELLO_CONSUME()
ck_ini <- ck;
RESPHELLO_PRODUCE()
@@ -141,7 +198,14 @@ let Oinit_hello() =
MTX_EV( event RHSent(ih, rh, psk, sskr, spki); )
rh
/* success */ ) in (
out(C, rh)
rhbits <- RH2b(rh);
mac <- create_mac(spkt, rhbits);
out(C_out, rh);
out(C_out, mac);
mac2 <- create_mac2(mac2_key, mac_envelope2b(mac));
out(C_out, mac2)
/* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS
event IHRjct(ih, psk, sskr, spki)
@@ -150,6 +214,18 @@ let Oinit_hello() =
#endif
).
let Oinit_hello() =
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
in(C, mac2_key:key);
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih);
#endif
Oinit_hello_inner(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih, mac2_key, C, call).
restriction sid:SessionId, ad1:Atom, ad2:Atom;
event(ConsumeSidr(sid, ad1)) && event(ConsumeSidr(sid, ad2))
==> ad1 = ad2.
@@ -166,27 +242,55 @@ fun Cinitiator(SessionId, kem_sk_tmpl, key_tmpl, kem_pk_tmpl, seed_tmpl, seed_tm
CK_EV( event OskOinitiator_ck(key). )
CK_EV( event OskOinitiator(key, key, kem_sk, kem_pk, key). )
MTX_EV( event IHSent(InitHello_t, key, kem_sk, kem_pk). )
event ConsumeSidi(SessionId, Atom).
let Oinitiator() =
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
KEM_EV(event Oinitiator_inner_KemUse(SessionId, SessionId, Atom).)
#ifdef KEM_EVENTS
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
event(Oinitiator_inner_KemUse(sidi, sidr, ad1)) && event(Oinitiator_inner_KemUse(sidi, sidr, ad2))
==> ad1 = ad2.
#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).
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, last_cookie:key, C_out:channel, call:Atom) =
SETUP_HANDSHAKE_STATE()
sidr <- sid0;
KEM_EV(event Oinitiator_inner_KemUse(sidi, sidr, call);)
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, ih);
ihbits <- IH2b(ih);
mac <- create_mac(spkt, ihbits);
out(C_out, mac);
mac2 <- create_mac2(last_cookie, mac_envelope2b(mac));
out(C_out, mac2);
Oresp_hello(HS_PASS_ARGS, C_out, call).
let Oinitiator() =
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
#endif
in(C, last_cookie:key);
Oinitiator_inner(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr, last_cookie, C, call).
restriction sid:SessionId, ad1:Atom, ad2:Atom;
event(ConsumeSidi(sid, ad1)) && event(ConsumeSidi(sid, ad2))
@@ -207,21 +311,3 @@ let rosenpass_main() = 0
| REP(RESPONDER_BOUND, Oinit_hello)
| REP(RESPONDER_BOUND, Oinit_conf).
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
nounif Spk:kem_sk_tmpl;
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
nounif rh:RespHello_t;
attacker(Cresp_hello( *rh ))/6107[conclusion].
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].

View File

@@ -2,6 +2,26 @@
#include "crypto/kem.mpv"
#include "rosenpass/handshake_state.mpv"
fun Envelope(
key,
bits
): bits [data].
type mac_envelope_t.
fun mac_envelope(
key,
bits
) : mac_envelope_t.
fun mac_envelope2b(mac_envelope_t) : bits [typeConverter].
letfun create_mac(pk:kem_pk, payload:bits) = mac_envelope(lprf2(MAC, kem_pk2b(pk), payload), payload).
fun mac_envelope_pk_test(mac_envelope_t, kem_pk) : bool
reduc forall pk:kem_pk, b:bits;
mac_envelope_pk_test(mac_envelope(prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pk2b(pk)),
b), b), pk) = true.
type InitHello_t.
fun InitHello(
SessionId, // sidi
@@ -11,6 +31,8 @@ fun InitHello(
bits // auth
) : InitHello_t [data].
fun IH2b(InitHello_t) : bitstring [typeConverter].
#define INITHELLO_PRODUCE() \
ck <- lprf1(CK_INIT, kem_pk2b(spkr)); /* IHI1 */ \
/* not handled here */ /* IHI2 */ \
@@ -41,7 +63,9 @@ fun RespHello(
bits // auth
) : RespHello_t [data].
#define RESPHELLO_PRODUCE() \
fun RH2b(RespHello_t) : bitstring [typeConverter].
#define RESPHELLO_PRODUCE() \
/* not handled here */ /* RHR1 */ \
MIX2(sid2b(sidr), sid2b(sidi)) /* RHR3 */ \
ENCAPS_AND_MIX(ecti, epki, epti) /* RHR4 */ \
@@ -67,13 +91,14 @@ fun InitConf(
bits // auth
) : InitConf_t [data].
fun IC2b(InitConf_t) : bitstring [typeConverter].
#define INITCONF_PRODUCE() \
MIX2(sid2b(sidi), sid2b(sidr)) /* ICI3 */ \
ENCRYPT_AND_MIX(auth, empty) /* ICI4 */ \
ic <- InitConf(sidi, sidr, biscuit, auth);
#define INITCONF_CONSUME() \
let InitConf(sidi, sidr, biscuit, auth) = ic in \
LOAD_BISCUIT(biscuit_no, biscuit) /* ICR1 */ \
ENCRYPT_AND_MIX(rh_auth, empty) /* ICIR */ \
ck_rh <- ck; /* ---- */ /* TODO: Move into oracles.mpv */ \

View File

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

View File

@@ -9,14 +9,12 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN);
/// Authenticated encryption with associated data
pub mod aead {
pub use rosenpass_sodium::aead::chacha20poly1305_ietf::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
}
/// Authenticated encryption with associated data with a constant nonce
pub mod xaead {
pub use rosenpass_sodium::aead::xchacha20poly1305_ietf::{
pub use crate::subtle::xchacha20poly1305_ietf::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
}

View File

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

View File

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

View File

@@ -1,9 +1,11 @@
use anyhow::ensure;
use rosenpass_constant_time::xor;
use rosenpass_sodium::hash::blake2b;
use rosenpass_to::{ops::copy_slice, with_destination, To};
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_MIN: usize = KEY_LEN;
pub const KEY_MAX: usize = KEY_LEN;

View File

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

View File

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

View File

@@ -11,5 +11,12 @@ readme = "readme.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
constant_time_tests = []
[dependencies]
rosenpass-to = { workspace = true }
memsec = { workspace = true }
[dev-dependencies]
rand = "0.8.5"

View File

@@ -0,0 +1,42 @@
/// Compares two slices of memory containing arbitrary-length little endian unsigned integers
/// and returns an integer indicating the relationship between the slices.
///
/// ## Returns
///
/// - -1 if a < b
/// - 0 if a = b
/// - 1 if a > b
///
/// ## Leaks
/// If the two slices have differents lengths, the function will return immediately. This
/// effectively leaks the information whether the slices have equal length or not. This is widely
/// considered safe.
///
/// The execution time of the function grows approx. linear with the length of the input. This is
/// considered safe.
///
/// ## Tests
///
/// ```rust
/// use rosenpass_constant_time::compare;
/// assert_eq!(compare(&[], &[]), 0);
///
/// assert_eq!(compare(&[0], &[1]), -1);
/// assert_eq!(compare(&[0], &[0]), 0);
/// assert_eq!(compare(&[1], &[0]), 1);
///
/// assert_eq!(compare(&[0, 0], &[1, 0]), -1);
/// assert_eq!(compare(&[0, 0], &[0, 0]), 0);
/// assert_eq!(compare(&[1, 0], &[0, 0]), 1);
///
/// assert_eq!(compare(&[1, 0], &[0, 1]), -1);
/// assert_eq!(compare(&[0, 1], &[0, 0]), 1);
/// ```
///
/// For discussion on how to ensure the constant-time execution of this function, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
#[inline]
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
unsafe { memsec::memcmp(a.as_ptr(), b.as_ptr(), a.len()) }
}

View File

@@ -0,0 +1,48 @@
use core::hint::black_box;
/// Interpret the given slice as a little-endian unsigned integer
/// and increment that integer.
///
/// # Leaks
/// TODO: mention here if this function leaks any information, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
///
/// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
///
/// # Examples
///
/// ```
/// use rosenpass_constant_time::increment as inc;
/// use rosenpass_to::To;
///
/// fn testcase(v: &[u8], correct: &[u8]) {
/// let mut v = v.to_owned();
/// inc(&mut v);
/// assert_eq!(&v, correct);
/// }
///
/// testcase(b"", b"");
/// testcase(b"\x00", b"\x01");
/// testcase(b"\x01", b"\x02");
/// testcase(b"\xfe", b"\xff");
/// testcase(b"\xff", b"\x00");
/// testcase(b"\x00\x00", b"\x01\x00");
/// testcase(b"\x01\x00", b"\x02\x00");
/// testcase(b"\xfe\x00", b"\xff\x00");
/// testcase(b"\xff\x00", b"\x00\x01");
/// testcase(b"\x00\x00\x00\x00\x00\x00", b"\x01\x00\x00\x00\x00\x00");
/// testcase(b"\x00\xa3\x00\x77\x00\x00", b"\x01\xa3\x00\x77\x00\x00");
/// testcase(b"\xff\xa3\x00\x77\x00\x00", b"\x00\xa4\x00\x77\x00\x00");
/// testcase(b"\xff\xff\xff\x77\x00\x00", b"\x00\x00\x00\x78\x00\x00");
/// ```
#[inline]
pub fn increment(v: &mut [u8]) {
let mut carry = 1u8;
for val in v.iter_mut() {
let (v, c) = black_box(*val).overflowing_add(black_box(carry));
*black_box(val) = v;
*black_box(&mut carry) = black_box(black_box(c) as u8);
}
}

View File

@@ -1,26 +1,17 @@
use rosenpass_to::{with_destination, To};
//! constant-time implementations of some primitives
//!
//! Rosenpass internal library providing basic constant-time operations.
//!
//! ## TODO
//! Figure out methodology to ensure that code is actually constant time, see
//! <https://github.com/rosenpass/rosenpass/issues/232>
/// Xors the source into the destination
///
/// # Examples
///
/// ```
/// use rosenpass_constant_time::xor;
/// use rosenpass_to::To;
/// assert_eq!(
/// xor(b"world").to_this(|| b"hello".to_vec()),
/// b"\x1f\n\x1e\x00\x0b");
/// ```
///
/// # Panics
///
/// If source and destination are of different sizes.
#[inline]
pub fn xor<'a>(src: &'a [u8]) -> impl To<[u8], ()> + 'a {
with_destination(|dst: &mut [u8]| {
assert!(src.len() == dst.len());
for (dv, sv) in dst.iter_mut().zip(src.iter()) {
*dv ^= *sv;
}
})
}
mod compare;
mod increment;
mod memcmp;
mod xor;
pub use compare::compare;
pub use increment::increment;
pub use memcmp::memcmp;
pub use xor::xor;

110
constant-time/src/memcmp.rs Normal file
View File

@@ -0,0 +1,110 @@
/// compares two sclices of memory content and returns whether they are equal
///
/// ## Leaks
/// If the two slices have differents lengths, the function will return immediately. This
/// effectively leaks the information whether the slices have equal length or not. This is widely
/// considered safe.
///
/// The execution time of the function grows approx. linear with the length of the input. This is
/// considered safe.
///
/// ## Tests
/// [`tests::memcmp_runs_in_constant_time`] runs a stasticial test that the equality of the two
/// input parameters does not correlate with the run time.
///
/// For discussion on how to (further) ensure the constant-time execution of this function,
/// see <https://github.com/rosenpass/rosenpass/issues/232>
#[inline]
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
a.len() == b.len()
&& unsafe { memsec::memeq(a.as_ptr() as *const u8, b.as_ptr() as *const u8, a.len()) }
}
#[cfg(all(test, feature = "constant_time_tests"))]
mod tests {
use super::*;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::time::Instant;
#[test]
/// tests whether [memcmp] actually runs in constant time
///
/// This test function will run an equal amount of comparisons on two different sets of parameters:
/// - completely equal slices
/// - completely unequal slices.
/// All comparisons are executed in a randomized order. The test will fail if one of the
/// two sets is checked for equality significantly faster than the other set
/// (absolute correlation coefficient ≥ 0.01)
fn memcmp_runs_in_constant_time() {
// prepare data to compare
let n: usize = 1E6 as usize; // number of comparisons to run
let len = 1024; // length of each slice passed as parameters to the tested comparison function
let a1 = "a".repeat(len);
let a2 = a1.clone();
let b = "b".repeat(len);
let a1 = a1.as_bytes();
let a2 = a2.as_bytes();
let b = b.as_bytes();
// vector representing all timing tests
//
// Each element is a tuple of:
// 0: whether the test compared two equal slices
// 1: the duration needed for the comparison to run
let mut tests = (0..n)
.map(|i| (i < n / 2, std::time::Duration::ZERO))
.collect::<Vec<_>>();
tests.shuffle(&mut thread_rng());
// run comparisons / call function to test
for test in tests.iter_mut() {
let now = Instant::now();
if test.0 {
memcmp(a1, a2);
} else {
memcmp(a1, b);
}
test.1 = now.elapsed();
// println!("eq: {}, elapsed: {:.2?}", test.0, test.1);
}
// sort by execution time and calculate Pearson correlation coefficient
tests.sort_by_key(|v| v.1);
let tests = tests
.iter()
.map(|t| (if t.0 { 1_f64 } else { 0_f64 }, t.1.as_nanos() as f64))
.collect::<Vec<_>>();
// averages
let (avg_x, avg_y): (f64, f64) = (
tests.iter().map(|t| t.0).sum::<f64>() / n as f64,
tests.iter().map(|t| t.1).sum::<f64>() / n as f64,
);
assert!((avg_x - 0.5).abs() < 1E-12);
// standard deviations
let sd_x = 0.5;
let sd_y = (1_f64 / n as f64
* tests
.iter()
.map(|t| {
let difference = t.1 - avg_y;
difference * difference
})
.sum::<f64>())
.sqrt();
// covariance
let cv = 1_f64 / n as f64
* tests
.iter()
.map(|t| (t.0 - avg_x) * (t.1 - avg_y))
.sum::<f64>();
// Pearson correlation
let correlation = cv / (sd_x * sd_y);
println!("correlation: {:.6?}", correlation);
assert!(
correlation.abs() < 0.01,
"execution time correlates with result"
)
}
}

34
constant-time/src/xor.rs Normal file
View File

@@ -0,0 +1,34 @@
use core::hint::black_box;
use rosenpass_to::{with_destination, To};
/// Xors the source into the destination
///
/// # Panics
/// If source and destination are of different sizes.
///
/// # Leaks
/// TODO: mention here if this function leaks any information, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
///
/// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
///
/// # Examples
///
/// ```
/// use rosenpass_constant_time::xor;
/// use rosenpass_to::To;
/// assert_eq!(
/// xor(b"world").to_this(|| b"hello".to_vec()),
/// b"\x1f\n\x1e\x00\x0b");
/// ```
#[inline]
pub fn xor(src: &[u8]) -> impl To<[u8], ()> + '_ {
with_destination(|dst: &mut [u8]| {
assert!(black_box(src.len()) == black_box(dst.len()));
for (dv, sv) in dst.iter_mut().zip(src.iter()) {
*black_box(dv) ^= black_box(*sv);
}
})
}

View File

@@ -23,7 +23,7 @@ If you are not specifically tasked with developing post-quantum secure systems,
you probably do not need this tool.
.Ss COMMANDS
.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.
Send the public-key file to your communication partner and keep the private-key
file secret!

View File

@@ -264,7 +264,6 @@
inherit system;
};
packages = self.packages.${system};
devShells = self.devShells.${system};
in
{
#
@@ -292,7 +291,6 @@
];
buildPhase = ''
export HOME=$(mktemp -d)
export OSFONTDIR="$(kpsewhich --var-value TEXMF)/fonts/{opentype/public/nunito,truetype/google/noto}"
latexmk -r tex/CI.rc
'';
installPhase = ''
@@ -326,28 +324,6 @@
'';
};
#
### A DevContainer attempt
#
packages.dev-container = pkgs.dockerTools.buildImage rec {
name = "rosenpass-dev-container";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = with pkgs; [
bash
coreutils
curl
gnutar
gzip
openssh
stdenv.cc
]; #++ lib.lists.filter (p: builtins.hasAttr "version" p)
#devShells.default.nativeBuildInputs;
pathsToLink = [ "/bin" "/lib" ];
};
config.Cmd = [ "/bin/bash" ];
};
#
### Devshells ###

115
format_rust_code.sh Executable file
View File

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

View File

@@ -12,7 +12,6 @@ arbitrary = { workspace = true }
libfuzzer-sys = { workspace = true }
stacker = { workspace = true }
rosenpass-secret-memory = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-ciphers = { workspace = true }
rosenpass-cipher-traits = { workspace = true }
rosenpass-to = { workspace = true }
@@ -49,13 +48,13 @@ test = false
doc = false
[[bin]]
name = "fuzz_box_sodium_alloc"
path = "fuzz_targets/box_sodium_alloc.rs"
name = "fuzz_box_secret_alloc"
path = "fuzz_targets/box_secret_alloc.rs"
test = false
doc = false
[[bin]]
name = "fuzz_vec_sodium_alloc"
path = "fuzz_targets/vec_sodium_alloc.rs"
name = "fuzz_vec_secret_alloc"
path = "fuzz_targets/vec_secret_alloc.rs"
test = false
doc = false

View File

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

View File

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

View File

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

View File

@@ -1,12 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use rosenpass_sodium::{
alloc::{Alloc as SodiumAlloc, Box as SodiumBox},
init,
};
fuzz_target!(|data: &[u8]| {
let _ = init();
let _ = SodiumBox::new_in(data, SodiumAlloc::new());
});

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use rosenpass_sodium::{
alloc::{Alloc as SodiumAlloc, Vec as SodiumVec},
init,
};
fuzz_target!(|data: &[u8]| {
let _ = init();
let mut vec = SodiumVec::new_in(SodiumAlloc::new());
vec.extend_from_slice(data);
});

View File

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

View File

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

View File

@@ -1,206 +0,0 @@
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])
}
}
});
);

View File

@@ -16,16 +16,13 @@ harness = false
[dependencies]
rosenpass-util = { workspace = true }
rosenpass-constant-time = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-ciphers = { workspace = true }
rosenpass-cipher-traits = { workspace = true }
rosenpass-to = { workspace = true }
rosenpass-secret-memory = { workspace = true }
rosenpass-lenses = { workspace = true }
anyhow = { workspace = true }
static_assertions = { workspace = true }
memoffset = { workspace = true }
libsodium-sys-stable = { workspace = true }
thiserror = { workspace = true }
paste = { workspace = true }
log = { workspace = true }
@@ -35,6 +32,8 @@ toml = { workspace = true }
clap = { workspace = true }
mio = { workspace = true }
rand = { workspace = true }
zerocopy = { workspace = true }
home = { workspace = true }
[build-dependencies]
anyhow = { workspace = true }

View File

@@ -1,10 +1,8 @@
use anyhow::Result;
use rosenpass::pqkem::KEM;
use rosenpass::{
pqkem::StaticKEM,
protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey},
sodium::sodium_init,
};
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
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)> {
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))
}
@@ -58,7 +56,6 @@ fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
}
fn criterion_benchmark(c: &mut Criterion) {
sodium_init().unwrap();
let (mut a, mut b) = make_server_pair().unwrap();
c.bench_function("cca_secret_alloc", |bench| {
bench.iter(|| {

View File

@@ -30,8 +30,7 @@ fn generate_man() -> String {
return man;
}
// TODO: Link to online manual here
"Cannot render manual page\n".into()
"Cannot render manual page. Please visit https://rosenpass.eu/docs/manuals/\n".into()
}
fn man() {

View File

@@ -1,5 +1,5 @@
use anyhow::{bail, ensure};
use clap::Parser;
use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::file::StoreSecret;
@@ -12,9 +12,50 @@ use crate::protocol::{SPk, SSk, SymKey};
use super::config;
/// struct holding all CLI arguments for `clap` crate to parse
#[derive(Parser, Debug)]
#[command(author, version, about, long_about)]
pub enum Cli {
pub struct CliArgs {
/// lowest log level to show log messages at higher levels will be omitted
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
log_level: Option<log::LevelFilter>,
/// show verbose log output sets log level to "debug"
#[arg(short, long, group = "log-level")]
verbose: bool,
/// show no log output sets log level to "error"
#[arg(short, long, group = "log-level")]
quiet: bool,
#[command(subcommand)]
pub command: CliCommand,
}
impl CliArgs {
/// returns the log level filter set by CLI args
/// returns `None` if the user did not specify any log level filter via CLI
///
/// NOTE: the clap feature of ["argument groups"](https://docs.rs/clap/latest/clap/_derive/_tutorial/chapter_3/index.html#argument-relations)
/// ensures that the user can not specify more than one of the possible log level arguments.
/// Note the `#[arg("group")]` in the [`CliArgs`] struct.
pub fn get_log_level(&self) -> Option<log::LevelFilter> {
if self.verbose {
return Some(log::LevelFilter::Info);
}
if self.quiet {
return Some(log::LevelFilter::Error);
}
if let Some(level_filter) = self.log_level {
return Some(level_filter);
}
None
}
}
/// represents a command specified via CLI
#[derive(Subcommand, Debug)]
pub enum CliCommand {
/// Start Rosenpass in server mode and carry on with the key exchange
///
/// This will parse the configuration file and perform the key exchange
@@ -87,6 +128,15 @@ pub enum Cli {
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 { config_files: Vec<PathBuf> },
@@ -95,12 +145,14 @@ pub enum Cli {
Man,
}
impl Cli {
pub fn run() -> anyhow::Result<()> {
let cli = Self::parse();
use Cli::*;
match cli {
impl CliCommand {
/// runs the command specified via CLI
///
/// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
pub fn run(self) -> anyhow::Result<()> {
use CliCommand::*;
match self {
Man => {
let man_cmd = std::process::Command::new("man")
.args(["1", "rosenpass"])
@@ -119,6 +171,40 @@ impl Cli {
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 {
config_file,
public_key,
@@ -160,12 +246,7 @@ impl Cli {
}
// generate the keys and store them in files
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(skf)?;
spk.store_secret(pkf)?;
generate_and_save_keypair(skf, pkf)?;
}
ExchangeConfig { config_file } => {
@@ -202,7 +283,7 @@ impl Cli {
Ok(config) => {
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
match config.validate() {
Ok(_) => eprintln!("{file:?} is passed all logical checks"),
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
Err(_) => eprintln!("{file:?} contains logical errors"),
}
}
@@ -246,3 +327,12 @@ impl Cli {
srv.event_loop()
}
}
/// generate secret and public keys, store in files according to the paths passed as arguments
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(secret_key)?;
spk.store_secret(public_key)
}

View File

@@ -1,3 +1,12 @@
//! Configuration readable from a config file.
//!
//! Rosenpass supports reading its configuration from a TOML file. This module contains a struct
//! [`Rosenpass`] which holds such a configuration.
//!
//! ## TODO
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
use std::{
collections::HashSet,
fs,
@@ -12,57 +21,123 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass {
/// path to the public key file
pub public_key: PathBuf,
/// path to the secret key file
pub secret_key: PathBuf,
/// list of [`SocketAddr`] to listen on
///
/// Examples:
/// - `0.0.0.0:123`
pub listen: Vec<SocketAddr>,
/// log verbosity
///
/// This is subject to change. See [`Verbosity`] for details.
#[serde(default)]
pub verbosity: Verbosity,
/// list of peers
///
/// See the [`RosenpassPeer`] type for more information and examples.
pub peers: Vec<RosenpassPeer>,
/// path to the file which provided this configuration
///
/// This item is of course not read from the TOML but is added by the algorithm that parses
/// the config file.
#[serde(skip)]
pub config_file_path: PathBuf,
}
/// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Verbosity {
Quiet,
Verbose,
}
/// ## TODO
/// - examples
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer {
/// path to the public key of the peer
pub public_key: PathBuf,
/// ## TODO
/// - documentation
pub endpoint: Option<String>,
/// path to the pre-shared key with the peer
///
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
pub pre_shared_key: Option<PathBuf>,
/// ## TODO
/// - documentation
#[serde(default)]
pub key_out: Option<PathBuf>,
// TODO make this field only available on binary builds, not on library builds
/// ## TODO
/// - documentation
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
#[serde(flatten)]
pub wg: Option<WireGuard>,
}
/// ## TODO
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireGuard {
/// ## TODO
/// - documentation
pub device: String,
/// ## TODO
/// - documentation
pub peer: String,
/// ## TODO
/// - documentation
#[serde(default)]
pub extra_params: Vec<String>,
}
impl Rosenpass {
/// Load a config file from a file path
/// load configuration from a TOML file
///
/// no validation is conducted
/// NOTE: no validation is conducted, e.g. the paths specified in the configuration are not
/// checked whether they even exist.
///
/// ## TODO
/// - consider using a different algorithm to determine home directory the below one may
/// behave unexpectedly on Windows
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
// read file and deserialize
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
use util::resolve_path_with_tilde;
resolve_path_with_tilde(&mut config.public_key);
resolve_path_with_tilde(&mut config.secret_key);
for peer in config.peers.iter_mut() {
resolve_path_with_tilde(&mut peer.public_key);
if let Some(ref mut psk) = &mut peer.pre_shared_key {
resolve_path_with_tilde(psk);
}
if let Some(ref mut ko) = &mut peer.key_out {
resolve_path_with_tilde(ko);
}
}
// add path to "self"
config.config_file_path = p.as_ref().to_owned();
// return
Ok(config)
}
@@ -83,18 +158,22 @@ impl Rosenpass {
}
/// Validate a configuration
///
/// ## TODO
/// - check that files do not just exist but are also readable
/// - warn if neither out_key nor exchange_command of a peer is defined (v.i.)
pub fn validate(&self) -> anyhow::Result<()> {
// check the public-key file exists
// check the public key file exists
ensure!(
self.public_key.is_file(),
"public-key file {:?} does not exist",
"could not find public-key file {:?}: no such file",
self.public_key
);
// check the secret-key file exists
ensure!(
self.secret_key.is_file(),
"secret-key file {:?} does not exist",
"could not find secret-key file {:?}: no such file",
self.secret_key
);
@@ -374,7 +453,7 @@ mod test {
use super::*;
fn split_str(s: &str) -> Vec<String> {
s.split(" ").map(|s| s.to_string()).collect()
s.split(' ').map(|s| s.to_string()).collect()
}
#[test]
@@ -442,3 +521,67 @@ mod test {
)
}
}
pub mod util {
use std::path::PathBuf;
/// takes a path that can potentially start with a `~` and resolves that `~` to the user's home directory
///
/// ## Example
/// ```
/// use rosenpass::config::util::resolve_path_with_tilde;
/// std::env::set_var("HOME","/home/dummy");
/// let mut path = std::path::PathBuf::from("~/foo.toml");
/// resolve_path_with_tilde(&mut path);
/// assert!(path == std::path::PathBuf::from("/home/dummy/foo.toml"));
/// ```
pub fn resolve_path_with_tilde(path: &mut PathBuf) {
if let Some(first_segment) = path.iter().next() {
if !path.has_root() && first_segment == "~" {
let home_dir = home::home_dir().unwrap_or_else(|| {
log::error!("config file contains \"~\" but can not determine home diretory");
std::process::exit(1);
});
let orig_path = path.clone();
path.clear();
path.push(home_dir);
for segment in orig_path.iter().skip(1) {
path.push(segment);
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_resolve_path_with_tilde() {
let test = |path_str: &str, resolved: &str| {
let mut path = PathBuf::from(path_str);
resolve_path_with_tilde(&mut path);
assert!(
path == PathBuf::from(resolved),
"Path {:?} has been resolved to {:?} but should have been resolved to {:?}.",
path_str,
path,
resolved
);
};
// set environment because otherwise the test result would depend on the system running this
std::env::set_var("USER", "dummy");
std::env::set_var("HOME", "/home/dummy");
// should resolve
test("~/foo.toml", "/home/dummy/foo.toml");
test("~//foo", "/home/dummy/foo");
test("~/../other_user/foo", "/home/dummy/../other_user/foo");
// should _not_ resolve
test("~foo/bar", "~foo/bar");
test(".~/foo", ".~/foo");
test("/~/foo.toml", "/~/foo.toml");
test(r"~\foo", r"~\foo");
test(r"C:\~\foo.toml", r"C:\~\foo.toml");
}
}
}

View File

@@ -1,5 +1,3 @@
use rosenpass_lenses::LenseError;
pub mod app_server;
pub mod cli;
pub mod config;
@@ -14,11 +12,3 @@ pub enum RosenpassError {
#[error("invalid message type")]
InvalidMessageType(u8),
}
impl From<LenseError> for RosenpassError {
fn from(value: LenseError) -> Self {
match value {
LenseError::BufferSizeMismatch => RosenpassError::BufferSizeMismatch,
}
}
}

View File

@@ -1,18 +1,32 @@
use clap::Parser;
use log::error;
use rosenpass::cli::Cli;
use rosenpass_util::attempt;
use rosenpass::cli::CliArgs;
use std::process::exit;
/// Catches errors, prints them through the logger, then exits
pub fn main() {
env_logger::init();
// parse CLI arguments
let args = CliArgs::parse();
let res = attempt!({
rosenpass_sodium::init()?;
Cli::run()
});
// init logging
{
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
if let Some(level) = args.get_log_level() {
log::debug!("setting log level to {:?} (set via CLI parameter)", level);
log_builder.filter_level(level); // set log level filter from CLI args if available
}
log_builder.init();
match res {
// // check the effectiveness of the log level filter with the following lines:
// use log::{debug, error, info, trace, warn};
// trace!("trace dummy");
// debug!("debug dummy");
// info!("info dummy");
// warn!("warn dummy");
// error!("error dummy");
}
match args.command.run() {
Ok(_) => {}
Err(e) => {
error!("{e}");

View File

@@ -6,129 +6,108 @@
//! always serialized instance of the data in question. This is closely related
//! to the concept of lenses in function programming; more on that here:
//! [https://sinusoid.es/misc/lager/lenses.pdf](https://sinusoid.es/misc/lager/lenses.pdf)
//!
//! # Example
//!
//! The following example uses the [`lense` macro](rosenpass_lenses::lense) to create a lense that
//! might be useful when dealing with UDP headers.
//!
//! ```
//! use rosenpass_lenses::{lense, LenseView};
//! use rosenpass::RosenpassError;
//! # fn main() -> Result<(), RosenpassError> {
//!
//! lense! {UdpDatagramHeader :=
//! source_port: 2,
//! dest_port: 2,
//! length: 2,
//! checksum: 2
//! }
//!
//! let mut buf = [0u8; 8];
//!
//! // read-only lense, no check of size:
//! let lense = UdpDatagramHeader(&buf);
//! assert_eq!(lense.checksum(), &[0, 0]);
//!
//! // mutable lense, runtime check of size
//! let mut lense = buf.as_mut().udp_datagram_header()?;
//! lense.source_port_mut().copy_from_slice(&53u16.to_be_bytes()); // some DNS, anyone?
//!
//! // the original buffer is still available
//! assert_eq!(buf, [0, 53, 0, 0, 0, 0, 0, 0]);
//!
//! // read-only lense, runtime check of size
//! let lense = buf.as_ref().udp_datagram_header()?;
//! assert_eq!(lense.source_port(), &[0, 53]);
//! # Ok(())
//! # }
//! ```
//! To achieve this we utilize the zerocopy library.
use super::RosenpassError;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_lenses::{lense, LenseView};
use std::mem::size_of;
use zerocopy::{AsBytes, FromBytes, FromZeroes};
// Macro magic ////////////////////////////////////////////////////////////////
lense! { Envelope<M> :=
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Envelope<M: AsBytes + FromBytes> {
/// [MsgType] of this message
msg_type: 1,
pub msg_type: u8,
/// Reserved for future use
reserved: 3,
pub reserved: [u8; 3],
/// The actual Paylod
payload: M::LEN,
pub payload: M,
/// Message Authentication Code (mac) over all bytes until (exclusive)
/// `mac` itself
mac: 16,
pub mac: [u8; 16],
/// Currently unused, TODO: do something with this
cookie: 16
pub cookie: [u8; 16],
}
lense! { InitHello :=
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct InitHello {
/// Randomly generated connection id
sidi: 4,
pub sidi: [u8; 4],
/// Kyber 512 Ephemeral Public Key
epki: EphemeralKem::PK_LEN,
pub epki: [u8; EphemeralKem::PK_LEN],
/// Classic McEliece Ciphertext
sctr: StaticKem::CT_LEN,
pub sctr: [u8; StaticKem::CT_LEN],
/// Encryped: 16 byte hash of McEliece initiator static key
pidic: aead::TAG_LEN + 32,
pub pidic: [u8; aead::TAG_LEN + 32],
/// Encrypted TAI64N Time Stamp (against replay attacks)
auth: aead::TAG_LEN
pub auth: [u8; aead::TAG_LEN],
}
lense! { RespHello :=
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct RespHello {
/// Randomly generated connection id
sidr: 4,
pub sidr: [u8; 4],
/// Copied from InitHello
sidi: 4,
pub sidi: [u8; 4],
/// Kyber 512 Ephemeral Ciphertext
ecti: EphemeralKem::CT_LEN,
pub ecti: [u8; EphemeralKem::CT_LEN],
/// Classic McEliece Ciphertext
scti: StaticKem::CT_LEN,
pub scti: [u8; StaticKem::CT_LEN],
/// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN,
pub auth: [u8; aead::TAG_LEN],
/// Responders handshake state in encrypted form
biscuit: BISCUIT_CT_LEN
pub biscuit: [u8; BISCUIT_CT_LEN],
}
lense! { InitConf :=
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct InitConf {
/// Copied from InitHello
sidi: 4,
pub sidi: [u8; 4],
/// Copied from RespHello
sidr: 4,
pub sidr: [u8; 4],
/// Responders handshake state in encrypted form
biscuit: BISCUIT_CT_LEN,
pub biscuit: [u8; BISCUIT_CT_LEN],
/// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN
pub auth: [u8; aead::TAG_LEN],
}
lense! { EmptyData :=
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct EmptyData {
/// Copied from RespHello
sid: 4,
pub sid: [u8; 4],
/// Nonce
ctr: 8,
pub ctr: [u8; 8],
/// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN
pub auth: [u8; aead::TAG_LEN],
}
lense! { Biscuit :=
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Biscuit {
/// H(spki) Ident ifies the initiator
pidi: KEY_LEN,
pub pidi: [u8; KEY_LEN],
/// The biscuit number (replay protection)
biscuit_no: 12,
pub biscuit_no: [u8; 12],
/// Chaining key
ck: KEY_LEN
pub ck: [u8; KEY_LEN],
}
lense! { DataMsg :=
dummy: 4
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct DataMsg {
pub dummy: [u8; 4],
}
lense! { CookieReply :=
dummy: 4
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct CookieReply {
pub dummy: [u8; 4],
}
// Traits /////////////////////////////////////////////////////////////////////
@@ -178,7 +157,7 @@ impl TryFrom<u8> for MsgType {
}
/// length in bytes of an unencrypted Biscuit (plain text)
pub const BISCUIT_PT_LEN: usize = Biscuit::<()>::LEN;
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
/// Length in bytes of an encrypted Biscuit (cipher text)
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;

View File

@@ -26,9 +26,6 @@
//! };
//! # fn main() -> anyhow::Result<()> {
//!
//! // always initialize libsodium before anything
//! rosenpass_sodium::init()?;
//!
//! // initialize secret and public key for peer a ...
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
@@ -68,27 +65,33 @@
//! # }
//! ```
use crate::{hash_domains, msgs::*};
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_lenses::LenseView;
use rosenpass_secret_memory::{Public, Secret};
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
use std::collections::hash_map::{
Entry::{Occupied, Vacant},
HashMap,
};
use std::convert::Infallible;
use std::mem::size_of;
use anyhow::{bail, ensure, Context, Result};
use memoffset::span_of;
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_secret_memory::{Public, Secret};
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
use zerocopy::{AsBytes, FromBytes, Ref};
use crate::{hash_domains, msgs::*, RosenpassError};
// CONSTANTS & SETTINGS //////////////////////////
/// Size required to fit any message in binary form
pub const RTX_BUFFER_SIZE: usize = max_usize(
<Envelope<(), InitHello<()>> as LenseView>::LEN,
<Envelope<(), InitConf<()>> as LenseView>::LEN,
size_of::<Envelope<InitHello>>(),
size_of::<Envelope<InitConf>>(),
);
/// A type for time, e.g. for backoff before re-tries
@@ -738,11 +741,12 @@ impl CryptoServer {
// TODO remove unnecessary copying between global tx_buf and per-peer buf
// TODO move retransmission storage to io server
pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result<usize> {
let mut msg = tx_buf.envelope_truncating::<InitHello<()>>()?; // Envelope::<InitHello>::default(); // TODO
self.handle_initiation(peer, msg.payload_mut().init_hello()?)?;
let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?;
// Envelope::<InitHello>::default(); // TODO
let mut msg = truncating_cast_into::<Envelope<InitHello>>(tx_buf)?;
self.handle_initiation(peer, &mut msg.payload)?;
let len = self.seal_and_commit_msg(peer, MsgType::InitHello, &mut msg)?;
peer.hs()
.store_msg_for_retransmission(self, &tx_buf[..len])?;
.store_msg_for_retransmission(self, msg.as_bytes())?;
Ok(len)
}
}
@@ -792,50 +796,45 @@ impl CryptoServer {
let peer = match rx_buf[0].try_into() {
Ok(MsgType::InitHello) => {
let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?;
let msg_in: Ref<&[u8], Envelope<InitHello>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = tx_buf.envelope_truncating::<RespHello<&mut [u8]>>()?;
let peer = self.handle_init_hello(
msg_in.payload().init_hello()?,
msg_out.payload_mut().resp_hello()?,
)?;
len = self.seal_and_commit_msg(peer, MsgType::RespHello, msg_out)?;
let mut msg_out = truncating_cast_into::<Envelope<RespHello>>(tx_buf)?;
let peer = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload)?;
len = self.seal_and_commit_msg(peer, MsgType::RespHello, &mut msg_out)?;
peer
}
Ok(MsgType::RespHello) => {
let msg_in = rx_buf.envelope::<RespHello<&[u8]>>()?;
let msg_in: Ref<&[u8], Envelope<RespHello>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = tx_buf.envelope_truncating::<InitConf<&mut [u8]>>()?;
let peer = self.handle_resp_hello(
msg_in.payload().resp_hello()?,
msg_out.payload_mut().init_conf()?,
)?;
len = self.seal_and_commit_msg(peer, MsgType::InitConf, msg_out)?;
let mut msg_out = truncating_cast_into::<Envelope<InitConf>>(tx_buf)?;
let peer = self.handle_resp_hello(&msg_in.payload, &mut msg_out.payload)?;
len = self.seal_and_commit_msg(peer, MsgType::InitConf, &mut msg_out)?;
peer.hs()
.store_msg_for_retransmission(self, &tx_buf[..len])?;
.store_msg_for_retransmission(self, &msg_out.as_bytes()[..len])?;
exchanged = true;
peer
}
Ok(MsgType::InitConf) => {
let msg_in = rx_buf.envelope::<InitConf<&[u8]>>()?;
let msg_in: Ref<&[u8], Envelope<InitConf>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = tx_buf.envelope_truncating::<EmptyData<&mut [u8]>>()?;
let peer = self.handle_init_conf(
msg_in.payload().init_conf()?,
msg_out.payload_mut().empty_data()?,
)?;
len = self.seal_and_commit_msg(peer, MsgType::EmptyData, msg_out)?;
let mut msg_out = truncating_cast_into::<Envelope<EmptyData>>(tx_buf)?;
let peer = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload)?;
len = self.seal_and_commit_msg(peer, MsgType::EmptyData, &mut msg_out)?;
exchanged = true;
peer
}
Ok(MsgType::EmptyData) => {
let msg_in = rx_buf.envelope::<EmptyData<&[u8]>>()?;
let msg_in: Ref<&[u8], Envelope<EmptyData>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
self.handle_resp_conf(msg_in.payload().empty_data()?)?
self.handle_resp_conf(&msg_in.payload)?
}
Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"),
Ok(MsgType::CookieReply) => bail!("CookieReply handling not implemented!"),
@@ -855,15 +854,15 @@ impl CryptoServer {
///
/// The message type is explicitly required here because it is very easy to
/// forget setting that, which creates subtle but far ranging errors.
pub fn seal_and_commit_msg<M: LenseView>(
pub fn seal_and_commit_msg<M: AsBytes + FromBytes>(
&mut self,
peer: PeerPtr,
msg_type: MsgType,
mut msg: Envelope<&mut [u8], M>,
msg: &mut Ref<&mut [u8], Envelope<M>>,
) -> Result<usize> {
msg.msg_type_mut()[0] = msg_type as u8;
msg.msg_type = msg_type as u8;
msg.seal(peer, self)?;
Ok(<Envelope<(), M> as LenseView>::LEN)
Ok(size_of::<Envelope<M>>())
}
}
@@ -1169,32 +1168,31 @@ impl IniHsPtr {
// CRYPTO/HANDSHAKE HANDLING /////////////////////
impl<M> Envelope<&mut [u8], M>
impl<M> Envelope<M>
where
M: LenseView,
M: AsBytes + FromBytes,
{
/// Calculate the message authentication code (`mac`)
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = hash_domains::mac()?
.mix(peer.get(srv).spkt.secret())?
.mix(self.until_mac())?;
self.mac_mut()
.copy_from_slice(mac.into_value()[..16].as_ref());
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
self.mac.copy_from_slice(mac.into_value()[..16].as_ref());
Ok(())
}
}
impl<M> Envelope<&[u8], M>
impl<M> Envelope<M>
where
M: LenseView,
M: AsBytes + FromBytes,
{
/// Check the message authentication code
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
let expected = hash_domains::mac()?
.mix(srv.spkm.secret())?
.mix(self.until_mac())?;
Ok(rosenpass_sodium::helpers::memcmp(
self.mac(),
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
Ok(constant_time::memcmp(
&self.mac,
&expected.into_value()[..16],
))
}
@@ -1281,15 +1279,16 @@ impl HandshakeState {
biscuit_ct: &mut [u8],
) -> Result<&mut Self> {
let mut biscuit = Secret::<BISCUIT_PT_LEN>::zero(); // pt buffer
let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // lens view
let mut biscuit: Ref<&mut [u8], Biscuit> =
Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap();
// calculate pt contents
biscuit
.pidi_mut()
.pidi
.copy_from_slice(peer.get(srv).pidt()?.as_slice());
biscuit.biscuit_no_mut().copy_from_slice(&*srv.biscuit_ctr);
biscuit.biscuit_no.copy_from_slice(&*srv.biscuit_ctr);
biscuit
.ck_mut()
.ck
.copy_from_slice(self.ck.clone().danger_into_secret().secret());
// calculate ad contents
@@ -1300,7 +1299,7 @@ impl HandshakeState {
.into_value();
// 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
// TODO: This is premature optimization. Remove!
@@ -1310,7 +1309,7 @@ impl HandshakeState {
n[0] |= (bk.0 as u8 & 0x1) << 7;
let k = bk.get(srv).key.secret();
let pt = biscuit.all_bytes();
let pt = biscuit.as_bytes();
xaead::encrypt(biscuit_ct, k, &*n, &ad, pt)?;
self.mix(biscuit_ct)
@@ -1336,18 +1335,19 @@ impl HandshakeState {
// Allocate and decrypt the biscuit data
let mut biscuit = Secret::<BISCUIT_PT_LEN>::zero(); // pt buf
let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // slice
let mut biscuit: Ref<&mut [u8], Biscuit> =
Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap();
xaead::decrypt(
biscuit.all_bytes_mut(),
biscuit.as_bytes_mut(),
bk.get(srv).key.secret(),
&ad,
biscuit_ct,
)?;
// Reconstruct the biscuit fields
let no = BiscuitId::from_slice(biscuit.biscuit_no());
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
let pid = PeerId::from_slice(biscuit.pidi());
let no = BiscuitId::from_slice(&biscuit.biscuit_no);
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(&biscuit.ck)).dup();
let pid = PeerId::from_slice(&biscuit.pidi);
// Reconstruct the handshake state
let mut hs = Self { sidi, sidr, ck };
@@ -1363,8 +1363,7 @@ impl HandshakeState {
// indicates retransmission
// TODO: Handle retransmissions without involving the crypto code
ensure!(
rosenpass_sodium::helpers::compare(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used)
>= 0,
constant_time::compare(&biscuit.biscuit_no, &*peer.get(srv).biscuit_used) >= 0,
"Rejecting biscuit: Outdated biscuit number"
);
@@ -1412,11 +1411,7 @@ impl CryptoServer {
impl CryptoServer {
/// Implementation of the cryptographic protocol using the already
/// established primitives
pub fn handle_initiation(
&mut self,
peer: PeerPtr,
mut ih: InitHello<&mut [u8]>,
) -> Result<PeerPtr> {
pub fn handle_initiation(&mut self, peer: PeerPtr, ih: &mut InitHello) -> Result<PeerPtr> {
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
// IHI1
@@ -1424,25 +1419,25 @@ impl CryptoServer {
// IHI2
hs.core.sidi.randomize();
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
ih.sidi.copy_from_slice(&hs.core.sidi.value);
// IHI3
EphemeralKem::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
ih.epki_mut().copy_from_slice(&hs.epki.value);
ih.epki.copy_from_slice(&hs.epki.value);
// IHI4
hs.core.mix(ih.sidi())?.mix(ih.epki())?;
hs.core.mix(ih.sidi.as_slice())?.mix(ih.epki.as_slice())?;
// IHI5
hs.core
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
ih.sctr_mut(),
ih.sctr.as_mut_slice(),
peer.get(self).spkt.secret(),
)?;
// IHI6
hs.core
.encrypt_and_mix(ih.pidic_mut(), self.pidm()?.as_ref())?;
.encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm()?.as_ref())?;
// IHI7
hs.core
@@ -1450,7 +1445,7 @@ impl CryptoServer {
.mix(peer.get(self).psk.secret())?;
// IHI8
hs.core.encrypt_and_mix(ih.auth_mut(), &[])?;
hs.core.encrypt_and_mix(ih.auth.as_mut_slice(), &[])?;
// Update the handshake hash last (not changing any state on prior error
peer.hs().insert(self, hs)?;
@@ -1458,32 +1453,28 @@ impl CryptoServer {
Ok(peer)
}
pub fn handle_init_hello(
&mut self,
ih: InitHello<&[u8]>,
mut rh: RespHello<&mut [u8]>,
) -> Result<PeerPtr> {
pub fn handle_init_hello(&mut self, ih: &InitHello, rh: &mut RespHello) -> Result<PeerPtr> {
let mut core = HandshakeState::zero();
core.sidi = SessionId::from_slice(ih.sidi());
core.sidi = SessionId::from_slice(&ih.sidi);
// IHR1
core.init(self.spkm.secret())?;
// IHR4
core.mix(ih.sidi())?.mix(ih.epki())?;
core.mix(&ih.sidi)?.mix(&ih.epki)?;
// IHR5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(),
self.spkm.secret(),
ih.sctr(),
&ih.sctr,
)?;
// IHR6
let peer = {
let mut peerid = PeerId::zero();
core.decrypt_and_mix(&mut *peerid, ih.pidic())?;
core.decrypt_and_mix(&mut *peerid, &ih.pidic)?;
self.find_peer(peerid)
.with_context(|| format!("No such peer {peerid:?}."))?
};
@@ -1493,46 +1484,42 @@ impl CryptoServer {
.mix(peer.get(self).psk.secret())?;
// IHR8
core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?;
core.decrypt_and_mix(&mut [0u8; 0], &ih.auth)?;
// RHR1
core.sidr.randomize();
rh.sidi_mut().copy_from_slice(core.sidi.as_ref());
rh.sidr_mut().copy_from_slice(core.sidr.as_ref());
rh.sidi.copy_from_slice(core.sidi.as_ref());
rh.sidr.copy_from_slice(core.sidr.as_ref());
// RHR3
core.mix(rh.sidr())?.mix(rh.sidi())?;
core.mix(&rh.sidr)?.mix(&rh.sidi)?;
// RHR4
core.encaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(rh.ecti_mut(), ih.epki())?;
core.encaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(&mut rh.ecti, &ih.epki)?;
// RHR5
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
rh.scti_mut(),
&mut rh.scti,
peer.get(self).spkt.secret(),
)?;
// RHR6
core.store_biscuit(self, peer, rh.biscuit_mut())?;
core.store_biscuit(self, peer, &mut rh.biscuit)?;
// RHR7
core.encrypt_and_mix(rh.auth_mut(), &[])?;
core.encrypt_and_mix(&mut rh.auth, &[])?;
Ok(peer)
}
pub fn handle_resp_hello(
&mut self,
rh: RespHello<&[u8]>,
mut ic: InitConf<&mut [u8]>,
) -> Result<PeerPtr> {
pub fn handle_resp_hello(&mut self, rh: &RespHello, ic: &mut InitConf) -> Result<PeerPtr> {
// RHI2
let peer = self
.lookup_handshake(SessionId::from_slice(rh.sidi()))
.lookup_handshake(SessionId::from_slice(&rh.sidi))
.with_context(|| {
format!(
"Got RespHello packet for non-existent session {:?}",
rh.sidi()
rh.sidi
)
})?
.peer();
@@ -1557,52 +1544,52 @@ impl CryptoServer {
ensure!(
exp == got,
"Unexpected package in session {:?}. Expected {:?}, got {:?}.",
SessionId::from_slice(rh.sidi()),
SessionId::from_slice(&rh.sidi),
exp,
got
);
let mut core = hs!().core.clone();
core.sidr.copy_from_slice(rh.sidr());
core.sidr.copy_from_slice(&rh.sidr);
// TODO: decaps_and_mix should take Secret<> directly
// to save us from the repetitive secret unwrapping
// RHI3
core.mix(rh.sidr())?.mix(rh.sidi())?;
core.mix(&rh.sidr)?.mix(&rh.sidi)?;
// RHI4
core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
hs!().eski.secret(),
&*hs!().epki,
rh.ecti(),
&rh.ecti,
)?;
// RHI5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(),
self.spkm.secret(),
rh.scti(),
&rh.scti,
)?;
// RHI6
core.mix(rh.biscuit())?;
core.mix(&rh.biscuit)?;
// RHI7
core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?;
core.decrypt_and_mix(&mut [0u8; 0], &rh.auth)?;
// TODO: We should just authenticate the entire network package up to the auth
// tag as a pattern instead of mixing in fields separately
ic.sidi_mut().copy_from_slice(rh.sidi());
ic.sidr_mut().copy_from_slice(rh.sidr());
ic.sidi.copy_from_slice(&rh.sidi);
ic.sidr.copy_from_slice(&rh.sidr);
// ICI3
core.mix(ic.sidi())?.mix(ic.sidr())?;
ic.biscuit_mut().copy_from_slice(rh.biscuit());
core.mix(&ic.sidi)?.mix(&ic.sidr)?;
ic.biscuit.copy_from_slice(&rh.biscuit);
// ICI4
core.encrypt_and_mix(ic.auth_mut(), &[])?;
core.encrypt_and_mix(&mut ic.auth, &[])?;
// Split() We move the secrets into the session; we do not
// delete the InitiatorHandshake, just clear it's secrets because
@@ -1617,31 +1604,27 @@ impl CryptoServer {
Ok(peer)
}
pub fn handle_init_conf(
&mut self,
ic: InitConf<&[u8]>,
mut rc: EmptyData<&mut [u8]>,
) -> Result<PeerPtr> {
pub fn handle_init_conf(&mut self, ic: &InitConf, rc: &mut EmptyData) -> Result<PeerPtr> {
// (peer, bn) ← LoadBiscuit(InitConf.biscuit)
// ICR1
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit(
self,
ic.biscuit(),
SessionId::from_slice(ic.sidi()),
SessionId::from_slice(ic.sidr()),
&ic.biscuit,
SessionId::from_slice(&ic.sidi),
SessionId::from_slice(&ic.sidr),
)?;
// ICR2
core.encrypt_and_mix(&mut [0u8; aead::TAG_LEN], &[])?;
// ICR3
core.mix(ic.sidi())?.mix(ic.sidr())?;
core.mix(&ic.sidi)?.mix(&ic.sidr)?;
// ICR4
core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?;
core.decrypt_and_mix(&mut [0u8; 0], &ic.auth)?;
// 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
peer.get_mut(self).biscuit_used = biscuit_no;
@@ -1682,19 +1665,19 @@ impl CryptoServer {
.get_mut(self)
.as_mut()
.context("Cannot send acknowledgement. No session.")?;
rc.sid_mut().copy_from_slice(&ses.sidt.value);
rc.ctr_mut().copy_from_slice(&ses.txnm.to_le_bytes());
rc.sid.copy_from_slice(&ses.sidt.value);
rc.ctr.copy_from_slice(&ses.txnm.to_le_bytes());
ses.txnm += 1; // Increment nonce before encryption, just in case an error is raised
let n = cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]);
let n = cat!(aead::NONCE_LEN; &rc.ctr, &[0u8; 4]);
let k = ses.txkm.secret();
aead::encrypt(rc.auth_mut(), k, &n, &[], &[])?; // ct, k, n, ad, pt
aead::encrypt(&mut rc.auth, k, &n, &[], &[])?; // ct, k, n, ad, pt
Ok(peer)
}
pub fn handle_resp_conf(&mut self, rc: EmptyData<&[u8]>) -> Result<PeerPtr> {
let sid = SessionId::from_slice(rc.sid());
pub fn handle_resp_conf(&mut self, rc: &EmptyData) -> Result<PeerPtr> {
let sid = SessionId::from_slice(&rc.sid);
let hs = self
.lookup_handshake(sid)
.with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?;
@@ -1717,16 +1700,16 @@ impl CryptoServer {
})?;
// the unwrap can not fail, because the slice returned by ctr() is
// guaranteed to have the correct size
let n = u64::from_le_bytes(rc.ctr().try_into().unwrap());
let n = u64::from_le_bytes(rc.ctr.try_into().unwrap());
ensure!(n >= s.txnt, "Stale nonce");
s.txnt = n;
aead::decrypt(
// pt, k, n, ad, ct
&mut [0u8; 0],
s.txkt.secret(),
&cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]),
&cat!(aead::NONCE_LEN; &rc.ctr, &[0u8; 4]),
&[],
rc.auth(),
&rc.auth,
)?;
}
@@ -1737,6 +1720,10 @@ impl CryptoServer {
}
}
fn truncating_cast_into<T: FromBytes>(buf: &mut [u8]) -> Result<Ref<&mut [u8], T>, RosenpassError> {
Ok(Ref::new(&mut buf[..size_of::<T>()]).ok_or(RosenpassError::BufferSizeMismatch)?)
}
#[cfg(test)]
mod test {
use super::*;
@@ -1757,8 +1744,6 @@ mod test {
/// Through all this, the handshake should still successfully terminate;
/// i.e. an exchanged key must be produced in both servers.
fn handles_incorrect_size_messages() {
rosenpass_sodium::init().unwrap();
stacker::grow(8 * 1024 * 1024, || {
const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize;
type MsgBufPlus = Public<OVERSIZED_MESSAGE>;
@@ -1770,14 +1755,10 @@ mod test {
// Process the entire handshake
let mut msglen = Some(me.initiate_handshake(PEER0, &mut *resbuf).unwrap());
loop {
if let Some(l) = msglen {
std::mem::swap(&mut me, &mut they);
std::mem::swap(&mut msgbuf, &mut resbuf);
msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf);
} else {
break;
}
while let Some(l) = msglen {
std::mem::swap(&mut me, &mut they);
std::mem::swap(&mut msgbuf, &mut resbuf);
msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf);
}
assert_eq!(
@@ -1804,8 +1785,8 @@ mod test {
}
let res = srv.handle_msg(&msgbuf[..l], resbuf);
assert!(matches!(res, Err(_))); // handle_msg should raise an error
assert!(!resbuf.iter().find(|x| **x != 0).is_some()); // resbuf should not have been changed
assert!(res.is_err()); // handle_msg should raise an error
assert!(!resbuf.iter().any(|x| *x != 0)); // resbuf should not have been changed
}
// Apply the proper handle_msg operation

View File

@@ -30,11 +30,8 @@ fn generate_keys() {
fn find_udp_socket() -> u16 {
for port in 1025..=u16::MAX {
match UdpSocket::bind(("127.0.0.1", port)) {
Ok(_) => {
return port;
}
_ => {}
if UdpSocket::bind(("127.0.0.1", port)).is_ok() {
return port;
}
}
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()) {
let output = test_bin::get_test_bin(BIN)
.args(["gen-keys", "--secret-key"])
.arg(&secret_key_path)
.arg(secret_key_path)
.arg("--public-key")
.arg(&pub_key_path)
.arg(pub_key_path)
.output()
.expect("Failed to start {BIN}");

View File

@@ -12,9 +12,12 @@ readme = "readme.md"
[dependencies]
anyhow = { workspace = true }
rosenpass-to = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-util = { workspace = true }
libsodium-sys-stable = { workspace = true }
lazy_static = { workspace = true }
zeroize = { workspace = true }
rand = { workspace = true }
memsec = { workspace = true }
allocator-api2 = { workspace = true }
log = { workspace = true }
[dev-dependencies]
allocator-api2-tests = { workspace = true }

View File

@@ -0,0 +1,108 @@
use std::fmt;
use std::ptr::NonNull;
use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MemsecAllocatorContents;
/// Memory allocation using using the memsec crate
#[derive(Copy, Clone, Default)]
pub struct MemsecAllocator {
_dummy_private_data: MemsecAllocatorContents,
}
/// 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 {
_dummy_private_data: MemsecAllocatorContents,
}
}
}
unsafe impl Allocator for MemsecAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
// Call memsec allocator
let mem: Option<NonNull<[u8]>> = unsafe { memsec::malloc_sized(layout.size()) };
// Unwrap the option
let Some(mem) = mem else {
log::error!("Allocation {layout:?} was requested but memsec returned a null pointer");
return Err(AllocError);
};
// Ensure the right alignment is used
let off = (mem.as_ptr() as *const u8).align_offset(layout.align());
if off != 0 {
log::error!("Allocation {layout:?} was requested but memsec returned allocation \
with offset {off} from the requested alignment. Memsec always allocates values \
at the end of a memory page for security reasons, custom alignments are not supported. \
You could try allocating an oversized value.");
unsafe { memsec::free(mem) };
return Err(AllocError);
};
Ok(mem)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
unsafe {
memsec::free(ptr);
}
}
}
impl fmt::Debug for MemsecAllocator {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<memsec based Rust allocator>")
}
}
#[cfg(test)]
mod test {
use allocator_api2_tests::make_test;
use super::*;
make_test! { test_sizes(MemsecAllocator::new()) }
make_test! { test_vec(MemsecAllocator::new()) }
make_test! { test_many_boxes(MemsecAllocator::new()) }
#[test]
fn memsec_allocation() {
let alloc = MemsecAllocator::new();
memsec_allocation_impl::<0>(&alloc);
memsec_allocation_impl::<7>(&alloc);
memsec_allocation_impl::<8>(&alloc);
memsec_allocation_impl::<64>(&alloc);
memsec_allocation_impl::<999>(&alloc);
}
fn memsec_allocation_impl<const N: usize>(alloc: &MemsecAllocator) {
let layout = Layout::new::<[u8; N]>();
let mem = alloc.allocate(layout).unwrap();
// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations
// promises us that allocated memory is initialized with the magic byte 0xDB
// and memsec promises to provide a reimplementation of the libsodium mechanism;
// it uses the magic value 0xD0 though
assert_eq!(unsafe { mem.as_ref() }, &[0xD0u8; N]);
let mem = NonNull::new(mem.as_ptr() as *mut u8).unwrap();
unsafe { alloc.deallocate(mem, layout) };
}
}

View File

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

View File

@@ -2,6 +2,8 @@ pub mod debug;
pub mod file;
pub mod rand;
pub mod alloc;
mod public;
pub use crate::public::Public;

View File

@@ -20,7 +20,7 @@ pub struct Public<const N: usize> {
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())
copy_slice(value).to_this(Self::zero)
}
/// Create a new [Public] from a byte array

View File

@@ -1,21 +1,96 @@
use crate::file::StoreSecret;
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 lazy_static::lazy_static;
use rand::{Fill as Randomize, Rng};
use rosenpass_sodium::alloc::{Alloc as SodiumAlloc, Box as SodiumBox, Vec as SodiumVec};
use rosenpass_util::{
b64::b64_reader,
file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd},
functional::mutating,
};
use std::{collections::HashMap, convert::TryInto, fmt, path::Path, sync::Mutex};
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…
lazy_static! {
static ref SECRET_CACHE: Mutex<SecretMemoryPool> = Mutex::new(SecretMemoryPool::new());
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
@@ -23,12 +98,9 @@ lazy_static! {
/// 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
struct SecretMemoryPool {
pool: HashMap<usize, Vec<SodiumBox<[u8]>>>,
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
}
impl SecretMemoryPool {
@@ -41,33 +113,37 @@ impl SecretMemoryPool {
}
/// Return secret back to the pool for future re-use
pub fn release<const N: usize>(&mut self, mut sec: SodiumBox<[u8; N]>) {
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: SodiumVec<u8> = sec.into();
let sec: SodiumBox<[u8]> = sec.into();
let sec: SecretVec<u8> = sec.take().into();
let sec: SecretBox<[u8]> = sec.into();
self.pool.entry(N).or_default().push(sec);
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) -> SodiumBox<[u8; N]> {
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
let entry = self.pool.entry(N).or_default();
match entry.pop() {
None => SodiumBox::new_in([0u8; N], SodiumAlloc::default()),
Some(sec) => sec.try_into().unwrap(),
}
let inner = match entry.pop() {
None => secret_box([0u8; N]),
Some(sec) => sec.take().try_into().unwrap(),
};
ZeroizingSecretBox::from_secret_box(inner)
}
}
/// Storeage for a secret backed by [rosenpass_sodium::alloc::Alloc]
/// Storage for secret data
pub struct Secret<const N: usize> {
storage: Option<SodiumBox<[u8; N]>>,
storage: Option<ZeroizingSecretBox<[u8; N]>>,
}
impl<const N: usize> Secret<N> {
@@ -81,9 +157,12 @@ impl<const N: usize> Secret<N> {
pub fn zero() -> Self {
// Using [SecretMemoryPool] here because this operation is expensive,
// yet it is used in hot loops
Self {
storage: Some(SECRET_CACHE.lock().unwrap().take()),
}
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
@@ -107,13 +186,6 @@ impl<const N: usize> Secret<N> {
}
}
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
impl<const N: usize> Zeroize for Secret<N> {
fn zeroize(&mut self) {
self.secret_mut().zeroize();
}
}
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
@@ -124,11 +196,26 @@ impl<const N: usize> Randomize for Secret<N> {
}
}
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) {
self.storage
.take()
.map(|sec| SECRET_CACHE.lock().unwrap().release(sec));
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()
}
}
@@ -197,20 +284,18 @@ mod test {
/// check that we can alloc using the magic pool
#[test]
fn secret_memory_pool_take() {
rosenpass_sodium::init().unwrap();
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: SodiumBox<[u8; N]> = pool.take();
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() {
rosenpass_sodium::init().unwrap();
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: SodiumBox<[u8; N]> = pool.take();
let secret: ZeroizingSecretBox<[u8; N]> = pool.take();
std::mem::drop(pool);
assert_eq!(secret.as_ref(), &[0; N]);
}
@@ -218,17 +303,16 @@ mod test {
/// check that a secrete can be reborn, freshly initialized with zero
#[test]
fn secret_memory_pool_release() {
rosenpass_sodium::init().unwrap();
const N: usize = 1;
let mut pool = SecretMemoryPool::new();
let mut secret: SodiumBox<[u8; N]> = pool.take();
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: SodiumBox<[u8; N]> = pool.take();
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

View File

@@ -1,18 +0,0 @@
[package]
name = "rosenpass-sodium"
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 libsodium"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
rosenpass-util = { workspace = true }
rosenpass-to = { workspace = true }
anyhow = { workspace = true }
libsodium-sys-stable = { workspace = true }
log = { workspace = true }
allocator-api2 = { workspace = true }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,95 +0,0 @@
use allocator_api2::alloc::{AllocError, Allocator, Layout};
use libsodium_sys as libsodium;
use std::fmt;
use std::os::raw::c_void;
use std::ptr::NonNull;
#[derive(Clone, Default)]
struct AllocatorContents;
/// Memory allocation using sodium_malloc/sodium_free
#[derive(Clone, Default)]
pub struct Alloc {
_dummy_private_data: AllocatorContents,
}
impl Alloc {
pub fn new() -> Self {
Alloc {
_dummy_private_data: AllocatorContents,
}
}
}
unsafe impl Allocator for Alloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
// Call sodium allocator
let ptr = unsafe { libsodium::sodium_malloc(layout.size()) };
// Ensure the right allocation is used
let off = ptr.align_offset(layout.align());
if off != 0 {
log::error!("Allocation {layout:?} was requested but libsodium returned allocation \
with offset {off} from the requested alignment. Libsodium always allocates values \
at the end of a memory page for security reasons, custom alignments are not supported. \
You could try allocating an oversized value.");
return Err(AllocError);
}
// Convert to a pointer size
let ptr = core::ptr::slice_from_raw_parts_mut(ptr as *mut u8, layout.size());
// Conversion to a *const u8, then to a &[u8]
match NonNull::new(ptr) {
None => {
log::error!(
"Allocation {layout:?} was requested but libsodium returned a null pointer"
);
Err(AllocError)
}
Some(ret) => Ok(ret),
}
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
unsafe {
libsodium::sodium_free(ptr.as_ptr() as *mut c_void);
}
}
}
impl fmt::Debug for Alloc {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<libsodium based Rust allocator>")
}
}
#[cfg(test)]
mod test {
use super::*;
/// checks that the can malloc with libsodium
#[test]
fn sodium_allocation() {
crate::init().unwrap();
let alloc = Alloc::new();
sodium_allocation_impl::<0>(&alloc);
sodium_allocation_impl::<7>(&alloc);
sodium_allocation_impl::<8>(&alloc);
sodium_allocation_impl::<64>(&alloc);
sodium_allocation_impl::<999>(&alloc);
}
fn sodium_allocation_impl<const N: usize>(alloc: &Alloc) {
crate::init().unwrap();
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
assert_eq!(unsafe { mem.as_ref() }, &[0xDBu8; N]);
let mem = NonNull::new(mem.as_ptr() as *mut u8).unwrap();
unsafe { alloc.deallocate(mem, layout) };
}
}

View File

@@ -1,10 +0,0 @@
//! Access to sodium_malloc/sodium_free
mod allocator;
pub use allocator::Alloc;
/// A box backed by sodium_malloc
pub type Box<T> = allocator_api2::boxed::Box<T, Alloc>;
/// A vector backed by sodium_malloc
pub type Vec<T> = allocator_api2::vec::Vec<T, Alloc>;

View File

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

View File

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

View File

@@ -1,28 +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());
}
}

View File

@@ -1,21 +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 alloc;
pub mod hash;
pub mod helpers;

View File

@@ -12,15 +12,17 @@ The crate provides chained functions to simplify allocating the destination para
For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may.
```rust
use std::ops::BitXorAssign;
use rosenpass_to::{To, to, with_destination};
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.
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
// be without destination parameters
fn xor_slice<'a, T>(src: &'a[T]) -> impl To<[T], ()> + 'a
where T: BitXorAssign + Clone {
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where
T: BitXorAssign + Clone,
{
// Custom implementations of the to trait can be created, but the easiest
with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len());
@@ -65,7 +67,7 @@ assert_eq!(&dst[..], &flip01[..]);
// The builtin function copy_array supports to_value() since its
// destination parameter is a fixed size array, which can be allocated
// using default()
let dst : [u8; 4] = copy_array(flip01).to_value();
let dst: [u8; 4] = copy_array(flip01).to_value();
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;
fn xor_slice<T>(dst: &mut [T], src: &[T])
where T: BitXorAssign + Clone {
where
T: BitXorAssign + Clone,
{
assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d ^= s.clone();
@@ -114,8 +118,8 @@ assert_eq!(&dst[..], &flip01[..]);
There are a couple of ways to use a function with destination:
```rust
use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice_least};
use rosenpass_to::{to, To};
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");
// 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");
// 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.
```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::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();
@@ -161,18 +169,33 @@ to(&mut dst[4..], copy_slice_least_src(b"!!!"));
assert_eq!(&dst[..], b"Hell!!!orld");
// 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");
// 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!(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");
// 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!(None, to(&mut dst[4..], try_copy_slice_least_src(b"-------------------------")));
assert_eq!(
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");
// 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.
```rust
use rosenpass_to::{To, to, with_destination};
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
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>| {
dst.clear();
dst.extend_from_slice(src);
@@ -217,7 +242,9 @@ The same pattern can be implemented without `to`, at the cost of being slightly
```rust
/// 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])
where T: Clone {
where
T: Clone,
{
dst.clear();
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.
```rust
use std::cmp::{min, max};
use rosenpass_to::{To, to, with_destination, Beside};
use rosenpass_to::{to, with_destination, Beside, To};
use std::cmp::{max, min};
/// 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]| {
assert!(src.len() == dst.len());
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.
```rust
use rosenpass_to::Beside;
use std::result::Result;
use rosenpass_to::{Beside};
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:
```rust
use rosenpass_to::ops::try_copy_slice;
use rosenpass_to::To;
use rosenpass_to::ops::try_copy_slice;;
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>();
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:
```rust
use rosenpass_to::{to, with_destination, To};
use std::result::Result;
use rosenpass_to::{to, To, with_destination};
#[derive(PartialEq, Eq, Debug, Default)]
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.
```rust
use rosenpass_to::{To, with_destination, Beside, CondenseBeside};
use rosenpass_to::ops::copy_slice;
use rosenpass_to::{with_destination, Beside, CondenseBeside, To};
#[derive(PartialEq, Eq, Debug, Default)]
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
where T: Copy, U: 'a {
where
T: Copy,
U: 'a,
{
with_destination(move |dst: &mut [T]| {
copy_slice(src).to(dst);
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.
```rust
use rosenpass_to::{to, To, with_destination};
use rosenpass_to::{to, with_destination, To};
struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T],
@@ -425,17 +455,20 @@ struct TryCopySliceSource<'a, T: Copy> {
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len())
.then(|| dst.copy_from_slice(self.src))
(self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src))
}
}
fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T>
where T: Copy {
where
T: Copy,
{
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!(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.
```rust
use rosenpass_to::{to, with_destination, To};
use std::borrow::Borrow;
use rosenpass_to::{to, To, with_destination};
struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T],
@@ -454,24 +487,24 @@ struct TryCopySliceSource<'a, T: Copy> {
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len())
.then(|| dst.copy_from_slice(self.src))
(self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src))
}
}
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 {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> {
TryCopySliceSource {
src: self.borrow()
}
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> {
TryCopySliceSource { 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!(None, to(&mut dst[..], b"---".try_copy_slice()));
```

View File

@@ -8,7 +8,7 @@ use crate::{with_destination, To};
/// # Panics
///
/// 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
T: Copy,
{
@@ -23,7 +23,7 @@ where
/// # Panics
///
/// 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
T: Copy,
{
@@ -34,7 +34,7 @@ where
/// destination.
///
/// 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
T: Copy,
{
@@ -47,7 +47,7 @@ where
/// Function with destination that attempts to copy data from origin into the destination.
///
/// 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
T: Copy,
{
@@ -62,7 +62,7 @@ where
/// Destination may be longer 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
T: Copy,
{
@@ -72,7 +72,7 @@ where
}
/// 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
T: Copy,
{

View File

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

View File

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

View File

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

View File

@@ -35,25 +35,30 @@ pub trait GuaranteedValue {
/// ```
/// use std::num::Wrapping;
/// use std::result::Result;
/// use std::convert::Infallible
/// use std::convert::Infallible;
/// use std::ops::Add;
///
/// trait FailableAddition {
/// 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> {
/// 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> {
/// self + other
/// Ok(self + other)
/// }
/// }
///
/// impl<T> FailableAddition for u32 {
/// type Error = Infallible;
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
/// 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),
@@ -64,10 +69,11 @@ pub trait GuaranteedValue {
/// fn failable_multiply<T>(a: &T, b: u32)
/// -> Result<T, T::Error>
/// where
/// T: FailableAddition<Error> {
/// T: FailableAddition {
/// assert!(b >= 2); // Acceptable only because this is for demonstration purposes
/// let mut accu = a.failable_addition(a)?;
/// for _ in ..(b-1) {
/// accu.failable_addition(a)?;
/// for _ in 2..b {
/// accu = accu.failable_addition(a)?;
/// }
/// Ok(accu)
/// }
@@ -75,12 +81,12 @@ pub trait GuaranteedValue {
/// // 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::new(42u32), 3).guaranteed(), 126);
/// assert_eq!(failable_multiply(&Wrapping::new(42u32), 3).unwrap(), 126);
/// 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).guaranteed(), 126); // COMPILER ERROR
/// assert_eq!(failable_multiply(&42u32, 3).unwrap(), 126);
/// ```
pub type Guaranteed<T> = Result<T, Infallible>;

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

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