Compare commits

...

23 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
32 changed files with 1318 additions and 639 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

@@ -51,7 +51,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 bench --no-run --workspace
- run: RUST_MIN_STACK=8388608 cargo bench --workspace
cargo-audit:
runs-on: ubuntu-latest
@@ -121,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 --workspace
- run: RUST_MIN_STACK=8388608 cargo test --workspace --all-features
cargo-test-nix-devshell-x86_64-linux:
runs-on:
@@ -144,7 +144,7 @@ jobs:
with:
name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix develop --command cargo test --workspace
- run: nix develop --command cargo test --workspace --all-features
cargo-fuzz:
runs-on: ubuntu-latest

39
Cargo.lock generated
View File

@@ -678,11 +678,11 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "home"
version = "0.5.5"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1123,6 +1123,7 @@ dependencies = [
"clap 4.4.10",
"criterion",
"env_logger",
"home",
"log",
"memoffset",
"mio",
@@ -1131,7 +1132,6 @@ dependencies = [
"rosenpass-cipher-traits",
"rosenpass-ciphers",
"rosenpass-constant-time",
"rosenpass-lenses",
"rosenpass-secret-memory",
"rosenpass-to",
"rosenpass-util",
@@ -1141,6 +1141,7 @@ dependencies = [
"test_bin",
"thiserror",
"toml",
"zerocopy",
]
[[package]]
@@ -1168,6 +1169,7 @@ name = "rosenpass-constant-time"
version = "0.1.0"
dependencies = [
"memsec",
"rand",
"rosenpass-to",
]
@@ -1185,14 +1187,6 @@ dependencies = [
"stacker",
]
[[package]]
name = "rosenpass-lenses"
version = "0.1.0"
dependencies = [
"paste",
"thiserror",
]
[[package]]
name = "rosenpass-oqs"
version = "0.1.0"
@@ -1848,6 +1842,27 @@ dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.7.0"

View File

@@ -11,7 +11,6 @@ members = [
"to",
"fuzz",
"secret-memory",
"lenses",
]
default-members = [
@@ -31,7 +30,6 @@ 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"
@@ -59,3 +57,5 @@ mio = { version = "0.8.9", features = ["net", "os-poll"] }
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

@@ -28,7 +28,6 @@
In this case the test uses secure rng and a fresh secure biscuit key.
*/
#include "config.mpv"
#define CHAINING_KEY_EVENTS 1
@@ -44,7 +43,6 @@
#include "rosenpass/oracles.mpv"
#include "crypto/kem.mpv"
#define INITIATOR_TEST
#define NEW_TRUSTED_SEED(name) \
new MCAT(name, _secret_seed):seed_prec; \
name <- make_trusted_seed(MCAT(name, _secret_seed)); \
@@ -57,52 +55,86 @@ 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)
Oinitiator_inner(sidi, initiator, psk, responder, seski_trusted_seed, ssptr_trusted_seed, D).
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));
let secure_resp_hello(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, sidr:SessionId, sidi:SessionId, biscuit_no:Atom, psk:key_tmpl) =
in(D, Envelope(k, IH2b(InitHello(=sidi, epki, sctr, pidiC, auth))));
ih <- InitHello(sidi, epki, sctr, pidiC, auth);
NEW_TRUSTED_SEED(septi_trusted_seed)
NEW_TRUSTED_SEED(sspti_trusted_seed)
Oinit_hello_inner(sidr, biscuit_no, responder, psk, initiator, septi_trusted_seed, sspti_trusted_seed, ih, D).
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, Envelope(k3, IC2b(InitConf(=sidi, =sidr, biscuit, auth3))));
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)
Oinit_conf_inner(initiator, psk, responder, ic).
new last_cookie:key;
call <- Cinit_conf(initiator, psk, responder, ic);
let secure_communication(initiator: kem_sk_tmpl, responder:kem_sk_tmpl) =
secure_key <- prepare_key(secure_psk);
(!secure_init_hello(initiator, secure_sidi, secure_key, responder))
| !secure_resp_hello(initiator, responder, secure_sidr, secure_sidi, secure_biscuit_no, secure_key)
| !(secure_init_conf(initiator, responder, secure_key, secure_sidi, secure_sidr)).
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).
fun kem_private(kem_pk): kem_sk
reduc forall sk_tmpl:kem_sk;
kem_private(kem_pub(sk_tmpl)) = sk_tmpl[private].
let secretCommunication() =
#ifdef INITIATOR_TEST
initiator_pk <- choice[setup_kem_pk(make_trusted_kem_sk(initiator1)), setup_kem_pk(make_trusted_kem_sk(initiator2))];
initiator_seed <- prepare_kem_sk(kem_private(initiator_pk));
initiator_seed <- choice[make_trusted_kem_sk(initiator1), make_trusted_kem_sk(initiator2)];
#else
initiator_seed <- prepare_kem_sk(trusted_kem_sk(initiator1));
initiator_seed <- make_trusted_kem_sk(initiator1);
#endif
#ifdef RESPONDER_TEST
responder_pk <- choice[setup_kem_pk(make_trusted_kem_sk(responder1)), setup_kem_pk(make_trusted_kem_sk(responder2))];
responder_seed <- prepare_kem_sk(kem_private(responder_pk));
responder_seed <- choice[make_trusted_kem_sk(responder1), make_trusted_kem_sk(responder2)];
#else
responder_seed <- prepare_kem_sk(trusted_kem_sk(responder1));
responder_seed <- make_trusted_kem_sk(responder1);
#endif
secure_communication(initiator_seed, responder_seed) | !pipeChannel(D, C).
secure_communication(initiator_seed, responder_seed, secure_psk) | !pipeChannel(D, C).
let reveal_pks() =
out(C, setup_kem_pk(make_trusted_kem_sk(responder1)));
@@ -116,6 +148,8 @@ let rosenpass_main2() =
| REP(RESPONDER_BOUND, Oinit_conf).
let identity_hiding_main() =
0 | reveal_pks() | rosenpass_main2() | phase 1; secretCommunication().
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,25 +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). )
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).
let Oinit_conf_inner(Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t) =
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
#endif
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); )
@@ -76,13 +83,19 @@ let Oinit_conf_inner(Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:Ini
).
let Oinit_conf() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
Oinit_conf_inner(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"
@@ -91,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, C_in:channel) =
in(C_in, 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_in, Envelope(create_mac(spkt, IC2b(ic)), IC2b(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
@@ -122,13 +164,15 @@ 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).
KEM_EV(event Oinit_hello_KemUse(SessionId, SessionId, Atom).)
let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt: kem_sk_tmpl, Septi: seed_tmpl, Sspti: seed_tmpl, ih: InitHello_t, C_out:channel) =
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih);
#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
@@ -143,8 +187,10 @@ let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:k
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()
@@ -152,7 +198,13 @@ let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:k
MTX_EV( event RHSent(ih, rh, psk, sskr, spki); )
rh
/* success */ ) in (
out(C_out, Envelope(create_mac(spkt, RH2b(rh)), RH2b(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
@@ -164,7 +216,15 @@ let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:k
let Oinit_hello() =
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
Oinit_hello_inner(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih, C).
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))
@@ -182,19 +242,22 @@ 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). )
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
event ConsumeSidi(SessionId, Atom).
let Oinitiator_inner(sidi: SessionId, Ssskm: kem_sk_tmpl, Spsk: key_tmpl, Sspkt: kem_sk_tmpl, Seski: seed_tmpl, Ssptr: seed_tmpl, C_out:channel) =
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
#endif
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);
@@ -205,12 +268,29 @@ let Oinitiator_inner(sidi: SessionId, Ssskm: kem_sk_tmpl, Spsk: key_tmpl, Sspkt:
CK_EV( event OskOinitiator_ck(ck); )
CK_EV( event OskOinitiator(ck, psk, sski, spkr, sptr); )
MTX_EV( event IHSent(ih, psk, sski, spkr); )
out(C_out, Envelope(create_mac(spkt, IH2b(ih)), IH2b(ih)));
Oresp_hello(HS_PASS_ARGS, C_out).
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));
Oinitiator_inner(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr, C).
#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))
@@ -231,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

@@ -6,7 +6,21 @@ fun Envelope(
key,
bits
): bits [data].
letfun create_mac(pk:kem_pk, payload:bits) = lprf2(MAC, kem_pk2b(pk), payload).
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(
@@ -85,7 +99,6 @@ fun IC2b(InitConf_t) : bitstring [typeConverter].
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,6 +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,79 +1,17 @@
use core::hint::black_box;
//! 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>
use rosenpass_to::{with_destination, To};
mod compare;
mod increment;
mod memcmp;
mod xor;
/// 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(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);
}
})
}
#[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()) }
}
#[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()) }
}
/// Interpret the given slice as a little-endian unsigned integer
/// and increment that integer.
///
/// # Examples
///
/// ```
/// use rosenpass_constant_time::increment as inc;
/// use rosenpass_to::To;
///
/// fn testcase(v: &[u8], correct: &[u8]) {
/// let mut v = v.to_owned();
/// inc(&mut v);
/// assert_eq!(&v, correct);
/// }
///
/// testcase(b"", b"");
/// testcase(b"\x00", b"\x01");
/// testcase(b"\x01", b"\x02");
/// testcase(b"\xfe", b"\xff");
/// testcase(b"\xff", b"\x00");
/// testcase(b"\x00\x00", b"\x01\x00");
/// testcase(b"\x01\x00", b"\x02\x00");
/// testcase(b"\xfe\x00", b"\xff\x00");
/// testcase(b"\xff\x00", b"\x00\x01");
/// testcase(b"\x00\x00\x00\x00\x00\x00", b"\x01\x00\x00\x00\x00\x00");
/// testcase(b"\x00\xa3\x00\x77\x00\x00", b"\x01\xa3\x00\x77\x00\x00");
/// testcase(b"\xff\xa3\x00\x77\x00\x00", b"\x00\xa4\x00\x77\x00\x00");
/// testcase(b"\xff\xff\xff\x77\x00\x00", b"\x00\x00\x00\x78\x00\x00");
/// ```
#[inline]
pub fn increment(v: &mut [u8]) {
let mut carry = 1u8;
for val in v.iter_mut() {
let (v, c) = black_box(*val).overflowing_add(black_box(carry));
*black_box(val) = v;
*black_box(&mut carry) = black_box(black_box(c) as u8);
}
}
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

@@ -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

@@ -20,7 +20,6 @@ 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 }
@@ -33,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

@@ -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
@@ -104,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"])

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
);
@@ -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,13 +1,32 @@
use clap::Parser;
use log::error;
use rosenpass::cli::Cli;
use rosenpass::cli::CliArgs;
use std::process::exit;
/// Catches errors, prints them through the logger, then exits
pub fn main() {
// default to displaying warning and error log messages only
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
// parse CLI arguments
let args = CliArgs::parse();
match 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();
// // 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

@@ -70,26 +70,28 @@ use std::collections::hash_map::{
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_lenses::LenseView;
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::*};
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
@@ -739,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)
}
}
@@ -793,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!"),
@@ -856,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>>())
}
}
@@ -1170,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())?;
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
Ok(constant_time::memcmp(
self.mac(),
&self.mac,
&expected.into_value()[..16],
))
}
@@ -1282,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
@@ -1311,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)
@@ -1337,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 };
@@ -1364,7 +1363,7 @@ impl HandshakeState {
// indicates retransmission
// TODO: Handle retransmissions without involving the crypto code
ensure!(
constant_time::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,28 +1604,24 @@ 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 constant_time::compare(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 {
@@ -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::*;