mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
23 Commits
dev/broker
...
bench
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d58aa363cd | ||
|
|
1b233bc600 | ||
|
|
2e7f34f4b2 | ||
|
|
292b4bbae0 | ||
|
|
c75d222477 | ||
|
|
478fadb80d | ||
|
|
7c1ada4b10 | ||
|
|
4f4e8e1018 | ||
|
|
971e49b894 | ||
|
|
262e32fe35 | ||
|
|
4dab97d84e | ||
|
|
1a5ffdd495 | ||
|
|
fb91688672 | ||
|
|
27ba729c14 | ||
|
|
60235dc6ea | ||
|
|
36c99c020e | ||
|
|
8c469af6b1 | ||
|
|
e96968b8bc | ||
|
|
81487b103d | ||
|
|
8ea253f86b | ||
|
|
fd8f2e4424 | ||
|
|
a996b08279 | ||
|
|
e38a6b8ed4 |
66
.github/workflows/nix.yaml
vendored
66
.github/workflows/nix.yaml
vendored
@@ -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:
|
||||
|
||||
6
.github/workflows/qc.yaml
vendored
6
.github/workflows/qc.yaml
vendored
@@ -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
39
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)))
|
||||
|
||||
25
analysis/03_identity_hiding_initiator.entry.mpv
Normal file
25
analysis/03_identity_hiding_initiator.entry.mpv
Normal 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].
|
||||
96
analysis/03_identity_hiding_responder.entry.mpv
Normal file
96
analysis/03_identity_hiding_responder.entry.mpv
Normal 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].
|
||||
29
analysis/03_identity_hiding_test.entry.mpv
Normal file
29
analysis/03_identity_hiding_test.entry.mpv
Normal 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.
|
||||
136
analysis/04_dos_protection.entry.mpv
Normal file
136
analysis/04_dos_protection.entry.mpv
Normal 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)).
|
||||
|
||||
@@ -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. *)
|
||||
|
||||
@@ -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
|
||||
36
analysis/rosenpass/cookie.mpv
Normal file
36
analysis/rosenpass/cookie.mpv
Normal 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).)
|
||||
@@ -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].
|
||||
|
||||
@@ -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 */ \
|
||||
|
||||
@@ -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"
|
||||
|
||||
42
constant-time/src/compare.rs
Normal file
42
constant-time/src/compare.rs
Normal 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()) }
|
||||
}
|
||||
48
constant-time/src/increment.rs
Normal file
48
constant-time/src/increment.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
110
constant-time/src/memcmp.rs
Normal 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
34
constant-time/src/xor.rs
Normal 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);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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.
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
@@ -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 }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user