mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
7 Commits
bench
...
dev/broker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c539af8696 | ||
|
|
c4e56b890f | ||
|
|
f07fedabc5 | ||
|
|
058069e41f | ||
|
|
3a4df6d41b | ||
|
|
a7a2ddb982 | ||
|
|
83d3e39dc3 |
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
examples/
|
||||
target/
|
||||
flake.*
|
||||
.ci
|
||||
.direnv
|
||||
.git
|
||||
.github
|
||||
66
.github/workflows/nix.yaml
vendored
66
.github/workflows/nix.yaml
vendored
@@ -223,29 +223,6 @@ 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:
|
||||
@@ -262,27 +239,6 @@ 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:
|
||||
@@ -300,28 +256,6 @@ 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 --workspace
|
||||
- run: RUST_MIN_STACK=8388608 cargo bench --no-run --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 --all-features
|
||||
- run: RUST_MIN_STACK=8388608 cargo test --workspace
|
||||
|
||||
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 --all-features
|
||||
- run: nix develop --command cargo test --workspace
|
||||
|
||||
cargo-fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
327
Cargo.lock
generated
327
Cargo.lock
generated
@@ -190,7 +190,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
"which",
|
||||
]
|
||||
|
||||
@@ -245,6 +245,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
@@ -392,7 +398,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -425,6 +431,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "command-fds"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f190f3c954f7bca3c6296d0ec561c739bdbe6c7e990294ed168d415f6e1b5b01"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
@@ -519,6 +535,41 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
@@ -527,7 +578,38 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -582,6 +664,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -678,11 +766,11 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -691,6 +779,12 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -878,6 +972,42 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1805440578ced23f85145d00825c0a831e43c587132a90e100552172543ae30"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libc",
|
||||
"log",
|
||||
"neli-proc-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli-proc-macros"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4"
|
||||
dependencies = [
|
||||
"either",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -897,6 +1027,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.1"
|
||||
@@ -942,6 +1082,29 @@ version = "6.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
@@ -954,6 +1117,12 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.5"
|
||||
@@ -1006,7 +1175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1086,6 +1255,15 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
@@ -1121,9 +1299,9 @@ version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.10",
|
||||
"command-fds",
|
||||
"criterion",
|
||||
"env_logger",
|
||||
"home",
|
||||
"log",
|
||||
"memoffset",
|
||||
"mio",
|
||||
@@ -1132,16 +1310,18 @@ dependencies = [
|
||||
"rosenpass-cipher-traits",
|
||||
"rosenpass-ciphers",
|
||||
"rosenpass-constant-time",
|
||||
"rosenpass-lenses",
|
||||
"rosenpass-secret-memory",
|
||||
"rosenpass-to",
|
||||
"rosenpass-util",
|
||||
"rosenpass-wireguard-broker",
|
||||
"rustix",
|
||||
"serde",
|
||||
"stacker",
|
||||
"static_assertions",
|
||||
"test_bin",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1169,7 +1349,6 @@ name = "rosenpass-constant-time"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"memsec",
|
||||
"rand",
|
||||
"rosenpass-to",
|
||||
]
|
||||
|
||||
@@ -1187,6 +1366,14 @@ dependencies = [
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-lenses"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-oqs"
|
||||
version = "0.1.0"
|
||||
@@ -1225,10 +1412,29 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"rustix",
|
||||
"static_assertions",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-wireguard-broker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.10",
|
||||
"env_logger",
|
||||
"log",
|
||||
"mio",
|
||||
"paste",
|
||||
"rosenpass-lenses",
|
||||
"rosenpass-to",
|
||||
"rosenpass-util",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"wireguard-uapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
@@ -1252,9 +1458,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.26"
|
||||
version = "0.38.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
|
||||
checksum = "bfeae074e687625746172d639330f1de242a178bf3189b51e35a7a21573513ac"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
@@ -1307,7 +1513,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1336,6 +1542,31 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
@@ -1382,6 +1613,17 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
@@ -1431,7 +1673,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1444,6 +1686,36 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.8"
|
||||
@@ -1549,7 +1821,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -1571,7 +1843,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -1843,24 +2115,15 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
name = "wireguard-uapi"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
checksum = "89ba4e9811befc20af3b6efb15924a7238ee5e8e8706a196576462a00b9f1af1"
|
||||
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",
|
||||
"derive_builder",
|
||||
"libc",
|
||||
"neli",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -11,10 +11,13 @@ members = [
|
||||
"to",
|
||||
"fuzz",
|
||||
"secret-memory",
|
||||
"lenses",
|
||||
"wireguard-broker",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
"rosenpass"
|
||||
"rosenpass",
|
||||
"wireguard-broker"
|
||||
]
|
||||
|
||||
[workspace.metadata.release]
|
||||
@@ -30,6 +33,8 @@ rosenpass-ciphers = { path = "ciphers" }
|
||||
rosenpass-to = { path = "to" }
|
||||
rosenpass-secret-memory = { path = "secret-memory" }
|
||||
rosenpass-oqs = { path = "oqs" }
|
||||
rosenpass-lenses = { path = "lenses" }
|
||||
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
||||
criterion = "0.4.0"
|
||||
test_bin = "0.4.0"
|
||||
libfuzzer-sys = "0.4"
|
||||
@@ -47,6 +52,10 @@ allocator-api2 = "0.2.14"
|
||||
allocator-api2-tests = "0.2.14"
|
||||
memsec = "0.6.3"
|
||||
rand = "0.8.5"
|
||||
wireguard-uapi = "3.0.0"
|
||||
command-fds = "0.2.3"
|
||||
rustix = { version = "0.38.27", features = ["net"] }
|
||||
tokio = { version = "1.34.0", features = ["sync", "full", "mio"] }
|
||||
typenum = "1.17.0"
|
||||
log = { version = "0.4.20" }
|
||||
clap = { version = "4.4.10", features = ["derive"] }
|
||||
@@ -57,5 +66,3 @@ 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,33 +3,12 @@
|
||||
#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,26 +10,6 @@
|
||||
|
||||
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)))
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
In this case the test uses secure rng and a fresh secure biscuit key.
|
||||
*/
|
||||
|
||||
|
||||
#include "config.mpv"
|
||||
|
||||
#define CHAINING_KEY_EVENTS 1
|
||||
@@ -43,6 +44,7 @@
|
||||
#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)); \
|
||||
@@ -55,86 +57,52 @@ free initiator1, initiator2:kem_sk_prec.
|
||||
free responder1, responder2:kem_sk_prec.
|
||||
|
||||
let secure_init_hello(initiator: kem_sk_tmpl, sidi : SessionId, psk: key_tmpl, responder: kem_sk_tmpl) =
|
||||
|
||||
new epkit:kem_pk; // epki
|
||||
new sctrt:bits; // sctr
|
||||
new pidiCt:bits; // pidiC
|
||||
new autht:bits; // auth
|
||||
|
||||
NEW_TRUSTED_SEED(seski_trusted_seed)
|
||||
NEW_TRUSTED_SEED(ssptr_trusted_seed)
|
||||
new last_cookie:key;
|
||||
new call:Atom;
|
||||
|
||||
Oinitiator_inner(sidi, initiator, psk, responder, seski_trusted_seed, ssptr_trusted_seed, last_cookie, D, call).
|
||||
|
||||
let secure_resp_hello(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, sidi:SessionId, sidr:SessionId, biscuit_no:Atom, psk:key_tmpl) =
|
||||
|
||||
in(D, InitHello(=secure_sidi, epki, sctr, pidiC, auth));
|
||||
Oinitiator_inner(sidi, initiator, psk, responder, seski_trusted_seed, ssptr_trusted_seed, D).
|
||||
|
||||
let secure_resp_hello(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, sidr:SessionId, sidi:SessionId, biscuit_no:Atom, psk:key_tmpl) =
|
||||
in(D, Envelope(k, IH2b(InitHello(=sidi, epki, sctr, pidiC, auth))));
|
||||
ih <- InitHello(sidi, epki, sctr, pidiC, auth);
|
||||
NEW_TRUSTED_SEED(septi_trusted_seed)
|
||||
NEW_TRUSTED_SEED(sspti_trusted_seed)
|
||||
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).
|
||||
Oinit_hello_inner(sidr, biscuit_no, responder, psk, initiator, septi_trusted_seed, sspti_trusted_seed, ih, D).
|
||||
|
||||
let secure_init_conf(initiator: kem_sk_tmpl, responder: kem_sk_tmpl, psk:key_tmpl, sidi:SessionId, sidr:SessionId) =
|
||||
in(D, InitConf(=sidi, =sidr, biscuit, auth3));
|
||||
in(D, Envelope(k3, IC2b(InitConf(=sidi, =sidr, biscuit, auth3))));
|
||||
ic <- InitConf(sidi,sidr,biscuit, auth3);
|
||||
NEW_TRUSTED_SEED(seski_trusted_seed)
|
||||
NEW_TRUSTED_SEED(ssptr_trusted_seed)
|
||||
new last_cookie:key;
|
||||
call <- Cinit_conf(initiator, psk, responder, ic);
|
||||
Oinit_conf_inner(initiator, psk, responder, ic).
|
||||
|
||||
Oinit_conf_inner(initiator, psk, responder, ic, call).
|
||||
|
||||
let secure_communication(initiator: kem_sk_tmpl, responder:kem_sk_tmpl, key:key) =
|
||||
key_tmpl <- prepare_key(key);
|
||||
(!secure_init_hello(initiator, secure_sidi, key_tmpl, responder))
|
||||
| !secure_resp_hello(initiator, responder, secure_sidi, secure_sidr, secure_biscuit_no, key_tmpl)
|
||||
| !(secure_init_conf(initiator, responder, key_tmpl, secure_sidi, secure_sidr)).
|
||||
|
||||
let participant_communication_initiator(participant:kem_sk_tmpl) =
|
||||
in(C, responder:kem_sk_tmpl);
|
||||
in(C, k:key);
|
||||
secure_communication(participant, responder, k).
|
||||
|
||||
let participant_communication_responder(participant:kem_sk_tmpl) =
|
||||
in(C, initiator:kem_sk_tmpl);
|
||||
in(C, k:key);
|
||||
secure_communication(initiator, participant, k).
|
||||
|
||||
let participants_communication() =
|
||||
initiator1_tmpl <- make_trusted_kem_sk(initiator1);
|
||||
initiator2_tmpl <- make_trusted_kem_sk(initiator2);
|
||||
responder1_tmpl <- make_trusted_kem_sk(responder1);
|
||||
responder2_tmpl <- make_trusted_kem_sk(responder2);
|
||||
|
||||
!participant_communication_initiator(initiator1_tmpl) | !participant_communication_responder(initiator1_tmpl)
|
||||
| !participant_communication_initiator(initiator2_tmpl) | !participant_communication_responder(initiator2_tmpl)
|
||||
| !participant_communication_initiator(responder1_tmpl) | !participant_communication_responder(responder1_tmpl)
|
||||
| !participant_communication_initiator(responder2_tmpl) | !participant_communication_responder(responder2_tmpl).
|
||||
let secure_communication(initiator: kem_sk_tmpl, responder:kem_sk_tmpl) =
|
||||
secure_key <- prepare_key(secure_psk);
|
||||
(!secure_init_hello(initiator, secure_sidi, secure_key, responder))
|
||||
| !secure_resp_hello(initiator, responder, secure_sidr, secure_sidi, secure_biscuit_no, secure_key)
|
||||
| !(secure_init_conf(initiator, responder, secure_key, secure_sidi, secure_sidr)).
|
||||
|
||||
let pipeChannel(D:channel, C:channel) =
|
||||
in(D, b:bits);
|
||||
out(C, b).
|
||||
|
||||
let secretCommunication() =
|
||||
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_seed <- choice[make_trusted_kem_sk(initiator1), make_trusted_kem_sk(initiator2)];
|
||||
initiator_pk <- choice[setup_kem_pk(make_trusted_kem_sk(initiator1)), setup_kem_pk(make_trusted_kem_sk(initiator2))];
|
||||
initiator_seed <- prepare_kem_sk(kem_private(initiator_pk));
|
||||
#else
|
||||
initiator_seed <- make_trusted_kem_sk(initiator1);
|
||||
initiator_seed <- prepare_kem_sk(trusted_kem_sk(initiator1));
|
||||
#endif
|
||||
#ifdef RESPONDER_TEST
|
||||
responder_seed <- choice[make_trusted_kem_sk(responder1), make_trusted_kem_sk(responder2)];
|
||||
responder_pk <- choice[setup_kem_pk(make_trusted_kem_sk(responder1)), setup_kem_pk(make_trusted_kem_sk(responder2))];
|
||||
responder_seed <- prepare_kem_sk(kem_private(responder_pk));
|
||||
#else
|
||||
responder_seed <- make_trusted_kem_sk(responder1);
|
||||
responder_seed <- prepare_kem_sk(trusted_kem_sk(responder1));
|
||||
#endif
|
||||
|
||||
secure_communication(initiator_seed, responder_seed, secure_psk) | !pipeChannel(D, C).
|
||||
secure_communication(initiator_seed, responder_seed) | !pipeChannel(D, C).
|
||||
|
||||
let reveal_pks() =
|
||||
out(C, setup_kem_pk(make_trusted_kem_sk(responder1)));
|
||||
@@ -148,8 +116,6 @@ let rosenpass_main2() =
|
||||
| REP(RESPONDER_BOUND, Oinit_conf).
|
||||
|
||||
let identity_hiding_main() =
|
||||
0 | reveal_pks() | rosenpass_main2() | participants_communication() | phase 1; secretCommunication().
|
||||
0 | reveal_pks() | rosenpass_main2() | phase 1; secretCommunication().
|
||||
|
||||
#ifndef CUSTOM_MAIN
|
||||
let main = identity_hiding_main.
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#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].
|
||||
@@ -1,96 +0,0 @@
|
||||
#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].
|
||||
@@ -1,29 +0,0 @@
|
||||
#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.
|
||||
@@ -1,136 +0,0 @@
|
||||
#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,18 +88,6 @@ 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. *)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
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,32 +41,25 @@ 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).
|
||||
|
||||
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) =
|
||||
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
|
||||
|
||||
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); )
|
||||
@@ -83,19 +76,13 @@ 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));
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
|
||||
#endif
|
||||
|
||||
Oinit_conf_inner(Ssskm, Spsk, Sspkt, ic, call).
|
||||
Oinit_conf_inner(Ssskm, Spsk, Sspkt, ic).
|
||||
|
||||
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"
|
||||
@@ -104,56 +91,27 @@ 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). )
|
||||
|
||||
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) =
|
||||
let Oresp_hello(HS_DECL_ARGS, C_in:channel) =
|
||||
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
|
||||
)
|
||||
/* 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
|
||||
#endif
|
||||
).
|
||||
|
||||
// TODO: Restriction: Biscuit no invalidation
|
||||
|
||||
@@ -164,15 +122,13 @@ 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).)
|
||||
|
||||
#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.
|
||||
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);
|
||||
#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
|
||||
|
||||
@@ -187,10 +143,8 @@ 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()
|
||||
@@ -198,13 +152,7 @@ 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 (
|
||||
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)
|
||||
out(C_out, Envelope(create_mac(spkt, RH2b(rh)), RH2b(rh)))
|
||||
|
||||
/* fail */ ) else (
|
||||
#if MESSAGE_TRANSMISSION_EVENTS
|
||||
@@ -216,15 +164,7 @@ 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));
|
||||
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).
|
||||
Oinit_hello_inner(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih, C).
|
||||
|
||||
restriction sid:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(ConsumeSidr(sid, ad1)) && event(ConsumeSidr(sid, ad2))
|
||||
@@ -242,21 +182,18 @@ 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, last_cookie:key, C_out:channel, call:Atom) =
|
||||
let Oinitiator_inner(sidi: SessionId, Ssskm: kem_sk_tmpl, Spsk: key_tmpl, Sspkt: kem_sk_tmpl, Seski: seed_tmpl, Ssptr: seed_tmpl, C_out:channel) =
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
|
||||
#endif
|
||||
|
||||
SETUP_HANDSHAKE_STATE()
|
||||
sidr <- sid0;
|
||||
|
||||
KEM_EV(event Oinitiator_inner_KemUse(sidi, sidr, call);)
|
||||
sidr <- sid0;
|
||||
|
||||
RNG_KEM_PAIR(eski, epki, Seski) // IHI3
|
||||
sptr <- rng_key(setup_seed(Ssptr)); // IHI5
|
||||
@@ -268,29 +205,12 @@ 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, 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).
|
||||
out(C_out, Envelope(create_mac(spkt, IH2b(ih)), IH2b(ih)));
|
||||
Oresp_hello(HS_PASS_ARGS, C_out).
|
||||
|
||||
let Oinitiator() =
|
||||
|
||||
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
|
||||
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
|
||||
#endif
|
||||
|
||||
in(C, last_cookie:key);
|
||||
Oinitiator_inner(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr, last_cookie, C, call).
|
||||
|
||||
Oinitiator_inner(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr, C).
|
||||
|
||||
restriction sid:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(ConsumeSidi(sid, ad1)) && event(ConsumeSidi(sid, ad2))
|
||||
@@ -311,3 +231,21 @@ 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,21 +6,7 @@ fun Envelope(
|
||||
key,
|
||||
bits
|
||||
): bits [data].
|
||||
|
||||
type mac_envelope_t.
|
||||
fun mac_envelope(
|
||||
key,
|
||||
bits
|
||||
) : mac_envelope_t.
|
||||
|
||||
fun mac_envelope2b(mac_envelope_t) : bits [typeConverter].
|
||||
|
||||
letfun create_mac(pk:kem_pk, payload:bits) = mac_envelope(lprf2(MAC, kem_pk2b(pk), payload), payload).
|
||||
|
||||
fun mac_envelope_pk_test(mac_envelope_t, kem_pk) : bool
|
||||
reduc forall pk:kem_pk, b:bits;
|
||||
mac_envelope_pk_test(mac_envelope(prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pk2b(pk)),
|
||||
b), b), pk) = true.
|
||||
letfun create_mac(pk:kem_pk, payload:bits) = lprf2(MAC, kem_pk2b(pk), payload).
|
||||
|
||||
type InitHello_t.
|
||||
fun InitHello(
|
||||
@@ -99,6 +85,7 @@ 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,12 +11,6 @@ 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"
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/// 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()) }
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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,17 +1,79 @@
|
||||
//! 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 core::hint::black_box;
|
||||
|
||||
mod compare;
|
||||
mod increment;
|
||||
mod memcmp;
|
||||
mod xor;
|
||||
use rosenpass_to::{with_destination, To};
|
||||
|
||||
pub use compare::compare;
|
||||
pub use increment::increment;
|
||||
pub use memcmp::memcmp;
|
||||
pub use xor::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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/// 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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);
|
||||
}
|
||||
})
|
||||
}
|
||||
109
doc/setup/multi_device_isolation.md
Normal file
109
doc/setup/multi_device_isolation.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Multi Device Isolation
|
||||
|
||||
On supported systems (just Linux, state Feb. 2024), Rosenpass uses a so-called broker architecture where multiple operating system processes work together to negotiate a key using post quantum cryptography and then send it to WireGuard to run an actual VPN. This is similar to the sandboxes used by web browsers to prevent websites accessing the rest of the computer.
|
||||
|
||||
These processes communicate using what is called a "unix socket"; a special file that can be used by to processes to send data back and forth. There are tools to forward data from a unix socket one one host to a unix socket on another host. By using one of these tools, you can run most of Rosenpass' processes on one host while running just the process that forwards keys from Rosenpass to WireGuard on the device that establishes the WireGuard tunnel.
|
||||
|
||||
This type of setup can provide a very high degree of isolation. When set up correctly, a critical bug in the Rosenpass code can not affect the host running WireGuard and vice versa. Keep in mind though that for this goal to be reached, the method to connect both hosts must be sufficiently secured: If the WireGuard host is allowed to perform arbitrary commands on the Rosenpass host, then an attacker with access to the WireGuard device can also take over the Rosenpass device.
|
||||
|
||||
You can use the instructions from [Unix Domain Socket Forwarding with OpenSSH](https://www.skreutz.com/posts/unix-domain-socket-forwarding-with-openssh/) to harden the connection between the two devices after following the instructions from this tutorial.
|
||||
|
||||
## Instructions
|
||||
|
||||
In this manual, we are dealing with three hosts:
|
||||
|
||||
- The **local peer**: The local host running WireGuard
|
||||
- The **remote peer**: The remote host we are connecting to.
|
||||
- The **the rosenpass device**: The dedicated host running rosenpass. It connects with *local peer* to supply WireGuard with keys and it connects with remote peer to perform key exchanges.
|
||||
|
||||
Lets assume, that you are starting from a working Rosenpass setup on *local peer* and *remote peer*, running rosenpass and WireGuard on each host. Both setups use configuration files. We will move the rosenpass instance running on *local peer* to the *rosenpass device* in this tutorial.
|
||||
|
||||
### Step 0: Setup Rosenpass
|
||||
|
||||
If you do not have a functioning rosenpass deployment on the local and remote peer at this point, you can create one by using the configuration files from the configuration-examples directory.
|
||||
|
||||
You will need to set up the WireGuard device manually using instructions for your linux distribution. You can use the tutorial on the [arch wiki](https://wiki.archlinux.org/title/WireGuard) for reference.
|
||||
|
||||
Make sure to set a random pre-shared key during creation of the WireGuard setup on both hosts at startup. Random pre-shared keys can be generated by using `wg genpks` on each host.
|
||||
|
||||
For the broker based setup to work, you might have to assign the broker process the CAP_NET_ADMIN linux capability:
|
||||
|
||||
```bash
|
||||
sudo setcap CAP_NET_ADMIN=+eip ./target/debug/rosenpass-wireguard-broker-privileged
|
||||
```
|
||||
|
||||
Make sure the broker binaries are in your system path when starting rosenpass:
|
||||
|
||||
```bash
|
||||
PATH="$PWD/target/debug:$PATH" ./target/debug/rosenpass exchange-config ./path/to/config/file.toml
|
||||
```
|
||||
|
||||
You will also need to setup rosenpass on the rosenpass device.
|
||||
|
||||
### Step 1: Verify that your rosenpass setup is working
|
||||
|
||||
Start rosenpass on both peers using the following command.
|
||||
|
||||
```bash
|
||||
PATH="$PWD/target/debug:$PATH" rosenpass exchange-config ./path/to/config/file.toml
|
||||
```
|
||||
|
||||
Now you can verify that rosenpass inserted a pre-shared key on both hosts:
|
||||
|
||||
```bash
|
||||
wg show wgRpTest preshared-keys
|
||||
```
|
||||
|
||||
The shell output will look similar to this:
|
||||
|
||||
```
|
||||
tdnV/wa/0Uf8Nrm3cZkKXOm4atrOEPzv1+dvaG7p7y0= 5235LJ/ONgrO8XuxECtLPzGOyWSvuzHcexzcgoHubfs=
|
||||
```
|
||||
|
||||
The first value is the peer's public key, the second in the pre-shared key. The pre-shared keys should match.
|
||||
|
||||
### Step 2: Manually start the broker
|
||||
|
||||
Rosenpass starts the psk-broker internally by default. We are looking to manually start it instead.
|
||||
|
||||
On the *local peer*, first start the broker manually:
|
||||
|
||||
```bash
|
||||
rm -fv broker.sock; PATH="target/debug" ./target/debug/rosenpass-wireguard-broker-socket-handler --listen-path broker.sock
|
||||
```
|
||||
|
||||
Now you should call rosenpass while make use of the created socket. Use the `psk_broker` configuration key. Your configuration will now look something like this:
|
||||
|
||||
```bash
|
||||
PATH="$PWD/target/debug:$PATH" rosenpass --psk-broker broker.sock exchange-config ./path/to/config/file.toml
|
||||
```
|
||||
|
||||
### Step 2: Forward the unix socket to the rosenpass device
|
||||
|
||||
OpenSSH socket forwarding can be used; on the local peer you can execute something like the following command:
|
||||
|
||||
```bash
|
||||
ssh -vgMR path/to/rosenpass/broker.sock:./broker.sock -L user@rosenpass_device
|
||||
```
|
||||
|
||||
### Step 3: Start rosenpass on the rosenpass device
|
||||
|
||||
You may need to copy your configuration files to the rosenpass device:
|
||||
|
||||
```bash
|
||||
scp ./path/to/config/file.toml ./path/to/peer/pk ./path/to/pk ./path/to/sk user@rosenpass_device:path/to/rosenpass/
|
||||
```
|
||||
|
||||
Now you can start rosenpass on the rosenpass device:
|
||||
|
||||
```bash
|
||||
PATH="$PWD/target/debug:$PATH" ./target/debug/rosenpass exchange-config ./path/to/config.toml
|
||||
```
|
||||
|
||||
### Step 4: Harden the setup
|
||||
|
||||
This tutorial is in a very rough state; it currently provides enough hints to advanced users to convey how the setup is supposed work. For a real production setup it needs to be adapted.
|
||||
|
||||
In particular, you can use the guide from from [Unix Domain Socket Forwarding with OpenSSH](https://www.skreutz.com/posts/unix-domain-socket-forwarding-with-openssh/) to make sure neither the *local peer* nor the *rosenpass device* can execute arbitrary commands on each other. The socat tutorial used in this setup can be used to achieve a diversity of setups, such as forwarding the unix socket via a plain TCP socket without encryption to the rosenpass device, if a trusted network setup is used to connect the two. Other setups such as securing the connection using TLS or forwarding the connection via a serial connection can be achieved.
|
||||
|
||||
You should also make sure that the rosenpass secret key is at no point in time stored in the *local peer*, so if you followed this tutorial you might want to regenerate the keypair on the *rosenpass device* itself.
|
||||
9
examples/broker-in-podman-container/config/config.toml
Normal file
9
examples/broker-in-podman-container/config/config.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
public_key = "./pk1"
|
||||
secret_key = "./sk1"
|
||||
listen = ["[::]:9999"]
|
||||
verbosity = "Verbose"
|
||||
|
||||
[[peers]]
|
||||
public_key = "./pk2"
|
||||
device = "YOUR WIREGUARD DEVICE"
|
||||
peer = "YOUR PEER PK"
|
||||
@@ -0,0 +1,26 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
rosenpass:
|
||||
build: ../rosenpass/
|
||||
command: rosenpass
|
||||
ports:
|
||||
- name: rosenpass-ipv4
|
||||
target: 9999
|
||||
host_ip: 127.0.0.1
|
||||
published: 9999
|
||||
protocol: udp
|
||||
mode: host
|
||||
- name: rosenpass-ipv4
|
||||
target: 9999
|
||||
host_ip: '[::1]'
|
||||
published: 9999
|
||||
protocol: udp
|
||||
mode: host
|
||||
environment:
|
||||
RUST_LOG: info
|
||||
#DEBUG_ENTRYPOINT: 1
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
volumes:
|
||||
- ../socket:/socket:ro
|
||||
- ../config:/config:ro
|
||||
@@ -0,0 +1,14 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
rosenpass:
|
||||
build: ../rosenpass
|
||||
command: psk_broker
|
||||
environment:
|
||||
RUST_LOG: info
|
||||
USER_GAINS_CAP_NET_ADMIN: 1
|
||||
#DEBUG_ENTRYPOINT: 1
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
volumes:
|
||||
- ../socket:/socket:rw
|
||||
network_mode: host
|
||||
35
examples/broker-in-podman-container/rosenpass/Dockerfile
Normal file
35
examples/broker-in-podman-container/rosenpass/Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM rust:slim as build
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
libclang-dev \
|
||||
libcap2-bin \
|
||||
git \
|
||||
inotify-tools
|
||||
|
||||
RUN adduser --system --uid 768 --group rosenpass --home /var/lib/rosenpass
|
||||
|
||||
USER rosenpass:rosenpass
|
||||
RUN cd ~rosenpass \
|
||||
&& git clone --depth 1 "https://github.com/rosenpass/rosenpass" -b dev/broker-architecture rosenpass-source \
|
||||
&& cd rosenpass-source \
|
||||
&& mkdir -p ~rosenpass/usr \
|
||||
&& cargo install --path rosenpass --root ~rosenpass/usr --bins \
|
||||
&& cargo install --path wireguard-broker --root ~rosenpass/usr --bins \
|
||||
&& cd ~rosenpass \
|
||||
&& rm -R rosenpass-source
|
||||
USER root:root
|
||||
|
||||
# TODO: Is this proper handling of ambient capabilities?
|
||||
RUN setcap CAP_NET_ADMIN=+ep ~rosenpass/usr/bin/rosenpass-wireguard-broker-privileged
|
||||
|
||||
VOLUME /config
|
||||
COPY ./entrypoint.sh /usr/local/sbin/docker_entrypoint
|
||||
COPY ./fd_passing.pl /usr/local/sbin/fd_passing
|
||||
RUN chmod a+x /usr/local/sbin/docker_entrypoint /usr/local/sbin/fd_passing
|
||||
ENTRYPOINT ["/usr/local/sbin/docker_entrypoint"]
|
||||
|
||||
VOLUME /socket
|
||||
|
||||
CMD rosenpass config.toml
|
||||
187
examples/broker-in-podman-container/rosenpass/entrypoint.sh
Normal file
187
examples/broker-in-podman-container/rosenpass/entrypoint.sh
Normal file
@@ -0,0 +1,187 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -e # Needed by bail()
|
||||
|
||||
log() {
|
||||
local lvl; lvl="${1}"; shift || bail "log()" "USAGE: log LEVEL CONTEXT MSG..."
|
||||
local ctx; ctx="${1}"; shift || bail "log()" "USAGE: log LEVEL CONTEXT MSG..."
|
||||
echo >&2 "[entrypoint.sh/${ctx} ${lvl}]:" "$@"
|
||||
}
|
||||
|
||||
log_debug() {
|
||||
if [[ -n "${DEBUG_ENTRYPOINT}" ]]; then
|
||||
log "DEBUG" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info() {
|
||||
log "INFO" "$@"
|
||||
}
|
||||
|
||||
log_err() {
|
||||
log "ERROR" "$@"
|
||||
}
|
||||
|
||||
exc() {
|
||||
local ctx; ctx="${1}"; shift || bail "exc()" "USAGE: exc CONTEXT CMD..."
|
||||
log_debug '$' "$@"
|
||||
"$@"
|
||||
}
|
||||
|
||||
bail() {
|
||||
local ctx; ctx="${1}"; shift || bail "bail()" "USAGE: bail CONTEXT MSG..."
|
||||
(( "$#" != 0 )) || bail "${ctx}" $'USAGE: bail CONTEXT MSG... # Bail called without parameters! Please use error messages dear developer.'
|
||||
log_err "${ctx}" "$@"
|
||||
return 1
|
||||
}
|
||||
|
||||
join() {
|
||||
local delim; delim="$1"; shift || bail "join()" "USAGE: join DELIM ELMS..."
|
||||
local tmp fst
|
||||
fst="true"
|
||||
for tmp in "$@"; do
|
||||
if [[ "${fst}" = "true" ]]; then
|
||||
printf "%s" "${tmp}"
|
||||
fst=""
|
||||
else
|
||||
printf "%s%s" "${delim}" "${tmp}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Shred files after they where red (recursively)
|
||||
# USAGE: $ burn_after_reading DIR
|
||||
burn_after_reading() {
|
||||
local dir; dir="$1"; shift || bail "join()" "USAGE: burn_after_reading DIR"
|
||||
|
||||
log_info burn_after_reading "Started for ${dir}"
|
||||
|
||||
# Load the list of configuration files
|
||||
local -a files_arr # Array
|
||||
readarray -td $'\0' files_arr < <(find "${dir}" -type f -print0)
|
||||
|
||||
# Convert configuration file list to associative array
|
||||
local file
|
||||
local -A files_todo # Associative array
|
||||
for file in "${files_arr[@]}"; do
|
||||
files_todo["${file}"]="1"
|
||||
done
|
||||
|
||||
# Watch for closed files
|
||||
local file
|
||||
# The --exclude '/$' excludes directories
|
||||
inotifywait --quiet --monitor --event close_nowrite --exclude '/$' --recursive . --no-newline --format "%w%f%0" \
|
||||
| while read -d $'\0' -r file; do
|
||||
|
||||
# Check if the file is in the todo list, if yes, erase it
|
||||
if [[ "${files_todo["${file}"]+1}" = "1" ]]; then
|
||||
log_info burn_after_reading "File loaded from configuration; removing now: ${file}";
|
||||
shred "${file}"
|
||||
# Clear from the todo list; What in the devils name is this quoting style bash
|
||||
unset 'files_todo["${file}"]'
|
||||
fi
|
||||
|
||||
# We're done if the todo list is empty
|
||||
if (( "${#files_todo[@]}" == 0 )); then
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
as_user() {
|
||||
local -a cmd_prefix
|
||||
if [[ "$1" = "--exec" ]]; then
|
||||
cmd_prefix=("exec")
|
||||
shift
|
||||
fi
|
||||
|
||||
local user; user="$1"; shift || bail "as_user()" "USAGE: as_user USER CMD..."
|
||||
(( "$#" > 0 )) || bail "as_user()" "USAGE: as_user USER CMD..."
|
||||
|
||||
if [[ -n "${USER_GAINS_CAP_NET_ADMIN}" ]]; then # TODO: Dirty to do this here; use --cap-net-admin or something?
|
||||
exc "as_user()" "${cmd_prefix[@]}" \
|
||||
capsh --caps="cap_net_admin+eip cap_setuid,cap_setgid+ep" --keep=1 \
|
||||
--user="${user}" --addamb=cap_net_admin -- -c 'exec "$@"' -- "$@"
|
||||
elif [[ "${user}" = "$(whoami)" ]]; then
|
||||
exc "as_user()" "${cmd_prefix[@]}" "$@"
|
||||
else
|
||||
exc "as_user()" "${cmd_prefix[@]}" runuser -u "${user}" -- "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
bail "USAGE: ${SCRIPT} rosenpass|psk_broker"
|
||||
}
|
||||
|
||||
cmd_internal() {
|
||||
"$@"
|
||||
}
|
||||
|
||||
cmd_run_command() {
|
||||
exc "run_command()" as_user --exec "${SWITCH_USER}" "$@"
|
||||
}
|
||||
|
||||
cmd_psk_broker() {
|
||||
exc "psk_broker()" exec \
|
||||
fd_passing --listen /socket/psk_broker.sock \
|
||||
"$SCRIPT" internal as_user --exec "${SWITCH_USER}" \
|
||||
rosenpass-wireguard-broker-socket-handler --listen-fd
|
||||
}
|
||||
|
||||
rosenpass_start_with_socket_fd() {
|
||||
local fd; fd="$1"; shift || bail "rosenpass_start_with_socket_fd()" "USAGE: rosenpass_start_with_socket_fd PSK_BROKER_FD"
|
||||
exc "rosenpass_start_with_socket_fd()" exec \
|
||||
rosenpass --psk-broker-fd "$fd" exchange-config /config/config.toml
|
||||
}
|
||||
|
||||
cmd_rosenpass() {
|
||||
test -z "${USER_GAINS_CAP_NET_ADMIN}" || bail "rosenpass()" "USER_GAINS_CAP_NET_ADMIN should be unset. The rosenpass instance doing key exchanges should not have network admin privileges!"
|
||||
exc "psk_broker()" exec \
|
||||
fd_passing --connect /socket/psk_broker.sock \
|
||||
"$SCRIPT" internal as_user --exec "${SWITCH_USER}" \
|
||||
"$SCRIPT" internal rosenpass_start_with_socket_fd
|
||||
}
|
||||
|
||||
main() {
|
||||
local command; command="$1"; shift || usage
|
||||
case "${command}" in
|
||||
internal) cmd_internal "$@" ;;
|
||||
run_command) ;;
|
||||
psk_broker) ;;
|
||||
rosenpass) ;;
|
||||
*) usage;;
|
||||
esac
|
||||
|
||||
exc "main()" umask u=rw,og=
|
||||
exc "main()" cp -R "${CONFIG_MOUNT}" "${CONFIG_TMP}"
|
||||
exc "main()" chmod -R u+X "${CONFIG_TMP}"
|
||||
exc "main()" chown -R rosenpass:rosenpass "${CONFIG_TMP}"
|
||||
# TODO: How can we do this? We should probably use a dedicated config broker.
|
||||
#exc "main()" umount "${CONFIG_MOUNT}"
|
||||
exc "main()" cd "${CONFIG_TMP}"
|
||||
|
||||
if [[ -n "${BURN_AFTER_READING}" ]]; then
|
||||
( burn_after_reading /dev/shm/rosenpass-config )&
|
||||
fi
|
||||
|
||||
local -a path_cpy extra_path_cpy
|
||||
mapfile -td ':' path_cpy < <(echo -n "$PATH")
|
||||
mapfile -td ':' extra_path_cpy < <(echo -n "$EXTRA_PATH")
|
||||
PATH="$(join ":" "${extra_path_cpy[@]}" "${path_cpy[@]}")"
|
||||
export PATH
|
||||
|
||||
exc "main()" "cmd_${command}" "$@"
|
||||
}
|
||||
|
||||
SCRIPT="$0"
|
||||
|
||||
# Config
|
||||
CONFIG_MOUNT="${CONFIG_MOUNT:-/config}"
|
||||
CONFIG_TMP="${CONFIG_TMP:-/dev/shm/rosenpass-config}"
|
||||
BURN_AFTER_READING="${BURN_AFTER_READING:-true}"
|
||||
SWITCH_USER="${SWITCH_USER:-rosenpass}"
|
||||
#USER_GAINS_CAP_NET_ADMIN="${USER_GAINS_CAP_NET_ADMIN}"
|
||||
EXTRA_PATH="${EXTRA_PATH:-"$(eval echo ~rosenpass)/usr/bin"}"
|
||||
|
||||
main "$@"
|
||||
38
examples/broker-in-podman-container/rosenpass/fd_passing.pl
Executable file
38
examples/broker-in-podman-container/rosenpass/fd_passing.pl
Executable file
@@ -0,0 +1,38 @@
|
||||
#! /usr/bin/perl
|
||||
|
||||
use Fcntl;
|
||||
use IO::Socket::UNIX;
|
||||
|
||||
my $usage = "[$0] Usage: $0 SOCKETPATH [--connect|--listen] CMD...";
|
||||
|
||||
my $mode = shift or die($usage);
|
||||
my $sopath = shift or die($usage);
|
||||
|
||||
|
||||
my $listen;
|
||||
if ($mode eq "--listen") {
|
||||
$listen = 1;
|
||||
} elsif ($mode eq "--connect") {
|
||||
$listen = 0;
|
||||
} else {
|
||||
die($usage);
|
||||
}
|
||||
|
||||
my $socket;
|
||||
if ($listen == 1) {
|
||||
$socket = IO::Socket::UNIX->new(
|
||||
Type => SOCK_STREAM(),
|
||||
Local => $sopath,
|
||||
Listen => 1,
|
||||
) or die "[$0] Error listening on socket socket: $!";
|
||||
} else {
|
||||
$socket = IO::Socket::UNIX->new(
|
||||
Type => SOCK_STREAM(),
|
||||
Peer => $sopath,
|
||||
) or die "[$0] Error listening on socket socket: $!";
|
||||
}
|
||||
|
||||
my $fd_flags = $socket->fcntl(F_GETFD, 0) or die "[$0] fcntl F_GETFD: $!";
|
||||
$socket->fcntl(F_SETFD, $fd_flags & ~FD_CLOEXEC) or die "[$0] fcntl F_SETFD: $!";
|
||||
|
||||
exec(@ARGV, $socket->fileno); # pass it on the command line
|
||||
0
examples/broker-in-podman-container/socket/.gitignore
vendored
Normal file
0
examples/broker-in-podman-container/socket/.gitignore
vendored
Normal file
16
lenses/Cargo.toml
Normal file
16
lenses/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "rosenpass-lenses"
|
||||
version = "0.1.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal library for parsing binary data securely"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
paste = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
3
lenses/readme.md
Normal file
3
lenses/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Rosenpass internal binary parsing library
|
||||
|
||||
This is an internal library; no guarantee is made about its API at this point in time.
|
||||
206
lenses/src/lib.rs
Normal file
206
lenses/src/lib.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use std::result::Result;
|
||||
|
||||
/// Common trait shared by all Lenses
|
||||
pub trait LenseView {
|
||||
const LEN: usize;
|
||||
}
|
||||
|
||||
/// Error during lense creation
|
||||
#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
|
||||
pub enum LenseError {
|
||||
#[error("buffer size mismatch")]
|
||||
BufferSizeMismatch,
|
||||
}
|
||||
|
||||
pub type LenseResult<T> = Result<T, LenseError>;
|
||||
|
||||
impl LenseError {
|
||||
pub fn ensure_exact_buffer_size(len: usize, required: usize) -> LenseResult<()> {
|
||||
(len == required)
|
||||
.then_some(())
|
||||
.ok_or(LenseError::BufferSizeMismatch)
|
||||
}
|
||||
|
||||
pub fn ensure_sufficient_buffer_size(len: usize, required: usize) -> LenseResult<()> {
|
||||
(len >= required)
|
||||
.then_some(())
|
||||
.ok_or(LenseError::BufferSizeMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro to create data lenses.
|
||||
#[macro_export]
|
||||
macro_rules! lense(
|
||||
// prefix @ offset ; optional meta ; field name : field length, ...
|
||||
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
|
||||
::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
$( #[ $attr ] )*
|
||||
///
|
||||
#[doc = lense!(maybe_docstring_link $len)]
|
||||
/// bytes long
|
||||
pub fn $field(&self) -> &__ContainerType::Output {
|
||||
&self.0[$offset .. $offset + $len]
|
||||
}
|
||||
|
||||
/// The bytes until the
|
||||
#[doc = lense!(maybe_docstring_link Self::$field)]
|
||||
/// field
|
||||
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
|
||||
&self.0[0 .. $offset]
|
||||
}
|
||||
|
||||
// if the tail exits, consume it as well
|
||||
$(
|
||||
lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// prefix @ offset ; optional meta ; field name : field length, ...
|
||||
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
|
||||
::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
$( #[ $attr ] )*
|
||||
///
|
||||
#[doc = lense!(maybe_docstring_link $len)]
|
||||
/// bytes long
|
||||
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
|
||||
&mut self.0[$offset .. $offset + $len]
|
||||
}
|
||||
|
||||
// if the tail exits, consume it as well
|
||||
$(
|
||||
lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// switch that yields literals unchanged, but creates docstring links to
|
||||
// constants
|
||||
// TODO the doc string link doesn't work if $x is taken from a generic,
|
||||
(maybe_docstring_link $x:literal) => (stringify!($x));
|
||||
(maybe_docstring_link $x:expr) => (stringify!([$x]));
|
||||
|
||||
// struct name < optional generics > := optional doc string field name : field length, ...
|
||||
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
/// A data lense to manipulate byte slices.
|
||||
///
|
||||
//// # Fields
|
||||
///
|
||||
$(
|
||||
/// - `
|
||||
#[doc = stringify!($field)]
|
||||
/// `:
|
||||
#[doc = lense!(maybe_docstring_link $len)]
|
||||
/// bytes
|
||||
)+
|
||||
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
|
||||
__ContainerType,
|
||||
// The phantom data is required, since all generics declared on a
|
||||
// type need to be used on the type.
|
||||
// https://doc.rust-lang.org/stable/error_codes/E0392.html
|
||||
$( $( ::core::marker::PhantomData<$generic> ),+ )?
|
||||
);
|
||||
|
||||
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
|
||||
$(
|
||||
/// Size in bytes of the field `
|
||||
#[doc = !($field)]
|
||||
/// `
|
||||
pub const fn [< $field _len >]() -> usize{
|
||||
$len
|
||||
}
|
||||
)+
|
||||
|
||||
/// Verify that `len` exactly holds [Self]
|
||||
pub fn check_size(len: usize) -> ::rosenpass_lenses::LenseResult<()> {
|
||||
::rosenpass_lenses::LenseError::ensure_exact_buffer_size(len, $( $len + )+ 0)
|
||||
}
|
||||
}
|
||||
|
||||
// read-only accessor functions
|
||||
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
|
||||
where
|
||||
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
|
||||
{
|
||||
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes(&self) -> &__ContainerType::Output {
|
||||
&self.0[0..Self::LEN]
|
||||
}
|
||||
}
|
||||
|
||||
// mutable accessor functions
|
||||
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
|
||||
where
|
||||
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
|
||||
{
|
||||
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes(&self) -> &__ContainerType::Output {
|
||||
&self.0[0..Self::LEN]
|
||||
}
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
|
||||
&mut self.0[0..Self::LEN]
|
||||
}
|
||||
}
|
||||
|
||||
// lense trait, allowing us to know the implementing lenses size
|
||||
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
|
||||
/// Number of bytes required to store this type in binary format
|
||||
const LEN: usize = $( $len + )+ 0;
|
||||
}
|
||||
|
||||
/// Extension trait to allow checked creation of a lense over
|
||||
/// some byte slice that contains a
|
||||
#[doc = lense!(maybe_docstring_link $type)]
|
||||
pub trait [< $type Ext >] {
|
||||
type __ContainerType;
|
||||
|
||||
/// Create a lense to the byte slice
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
|
||||
|
||||
/// Create a lense to the byte slice, automatically truncating oversized buffers
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
|
||||
}
|
||||
|
||||
impl<'a> [< $type Ext >] for &'a [u8] {
|
||||
type __ContainerType = &'a [u8];
|
||||
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
|
||||
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
|
||||
}
|
||||
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
let required_size = $( $len + )+ 0;
|
||||
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
|
||||
[< $type Ext >]::[< $type:snake >](&self[..required_size])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> [< $type Ext >] for &'a mut [u8] {
|
||||
type __ContainerType = &'a mut [u8];
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
|
||||
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
|
||||
}
|
||||
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
let required_size = $( $len + )+ 0;
|
||||
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
|
||||
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "rosenpass"
|
||||
description = "Build post-quantum-secure VPNs with WireGuard!"
|
||||
version = "0.2.1"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Build post-quantum-secure VPNs with WireGuard!"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
@@ -20,6 +20,8 @@ rosenpass-ciphers = { workspace = true }
|
||||
rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-lenses = { workspace = true }
|
||||
rosenpass-wireguard-broker = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
memoffset = { workspace = true }
|
||||
@@ -32,8 +34,8 @@ toml = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
mio = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
zerocopy = { workspace = true }
|
||||
home = { workspace = true }
|
||||
command-fds = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -30,7 +30,8 @@ fn generate_man() -> String {
|
||||
return man;
|
||||
}
|
||||
|
||||
"Cannot render manual page. Please visit https://rosenpass.eu/docs/manuals/\n".into()
|
||||
// TODO: Link to online manual here
|
||||
"Cannot render manual page\n".into()
|
||||
}
|
||||
|
||||
fn man() {
|
||||
|
||||
@@ -1,38 +1,26 @@
|
||||
use anyhow::bail;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, error, info, warn};
|
||||
use mio::Interest;
|
||||
use mio::Token;
|
||||
use rosenpass_util::file::fopen_w;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io::Write;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::net::SocketAddrV6;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::io::{ErrorKind, Write};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
use std::slice;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
config::Verbosity,
|
||||
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
||||
};
|
||||
use rosenpass_util::attempt;
|
||||
use anyhow::{bail, Result};
|
||||
use log::{error, info, warn};
|
||||
use mio::{Interest, Token};
|
||||
|
||||
use rosenpass_secret_memory::Public;
|
||||
use rosenpass_util::b64::{b64_writer, fmt_b64};
|
||||
use rosenpass_util::{attempt, file::fopen_w};
|
||||
use rosenpass_wireguard_broker::api::mio_client::MioBrokerClient as PskBroker;
|
||||
use rosenpass_wireguard_broker::WireGuardBroker;
|
||||
|
||||
use crate::config::Verbosity;
|
||||
use crate::protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing};
|
||||
|
||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
fn ipv4_any_binding() -> SocketAddr {
|
||||
// addr, port
|
||||
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
||||
@@ -43,6 +31,19 @@ fn ipv6_any_binding() -> SocketAddr {
|
||||
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MioTokenDispenser {
|
||||
counter: usize,
|
||||
}
|
||||
|
||||
impl MioTokenDispenser {
|
||||
fn get_token(&mut self) -> Token {
|
||||
let r = self.counter;
|
||||
self.counter += 1;
|
||||
Token(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AppPeer {
|
||||
pub outfile: Option<PathBuf>,
|
||||
@@ -59,14 +60,24 @@ impl AppPeer {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct WireguardOut {
|
||||
// impl KeyOutput
|
||||
pub dev: String,
|
||||
pub pk: String,
|
||||
pub pk: Public<32>,
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for WireguardOut {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dev: Default::default(),
|
||||
pk: Public::zero(),
|
||||
extra_params: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds the state of the application, namely the external IO
|
||||
///
|
||||
/// Responsible for file IO, network IO
|
||||
@@ -77,6 +88,7 @@ pub struct AppServer {
|
||||
pub sockets: Vec<mio::net::UdpSocket>,
|
||||
pub events: mio::Events,
|
||||
pub mio_poll: mio::Poll,
|
||||
pub psk_broker: RefCell<PskBroker>,
|
||||
pub peers: Vec<AppPeer>,
|
||||
pub verbosity: Verbosity,
|
||||
pub all_sockets_drained: bool,
|
||||
@@ -341,11 +353,24 @@ impl AppServer {
|
||||
sk: SSk,
|
||||
pk: SPk,
|
||||
addrs: Vec<SocketAddr>,
|
||||
psk_broker_socket: UnixStream,
|
||||
verbosity: Verbosity,
|
||||
) -> anyhow::Result<Self> {
|
||||
// setup mio
|
||||
let mio_poll = mio::Poll::new()?;
|
||||
let events = mio::Events::with_capacity(8);
|
||||
let mut dispenser = MioTokenDispenser::default();
|
||||
|
||||
// Create the Wireguard broker connection
|
||||
let psk_broker = {
|
||||
let mut sock = mio::net::UnixStream::from_std(psk_broker_socket);
|
||||
mio_poll.registry().register(
|
||||
&mut sock,
|
||||
dispenser.get_token(),
|
||||
Interest::READABLE | Interest::WRITABLE,
|
||||
)?;
|
||||
PskBroker::new(sock)
|
||||
};
|
||||
|
||||
// bind each SocketAddr to a socket
|
||||
let maybe_sockets: Result<Vec<_>, _> =
|
||||
@@ -430,6 +455,7 @@ impl AppServer {
|
||||
Ok(Self {
|
||||
crypt: CryptoServer::new(sk, pk),
|
||||
peers: Vec::new(),
|
||||
psk_broker: RefCell::new(psk_broker),
|
||||
verbosity,
|
||||
sockets,
|
||||
events,
|
||||
@@ -624,31 +650,9 @@ impl AppServer {
|
||||
}
|
||||
|
||||
if let Some(owg) = ap.outwg.as_ref() {
|
||||
let mut child = Command::new("wg")
|
||||
.arg("set")
|
||||
.arg(&owg.dev)
|
||||
.arg("peer")
|
||||
.arg(&owg.pk)
|
||||
.arg("preshared-key")
|
||||
.arg("/dev/stdin")
|
||||
.stdin(Stdio::piped())
|
||||
.args(&owg.extra_params)
|
||||
.spawn()?;
|
||||
b64_writer(child.stdin.take().unwrap()).write_all(key.secret())?;
|
||||
|
||||
thread::spawn(move || {
|
||||
let status = child.wait();
|
||||
|
||||
if let Ok(status) = status {
|
||||
if status.success() {
|
||||
debug!("successfully passed psk to wg")
|
||||
} else {
|
||||
error!("could not pass psk to wg {:?}", status)
|
||||
}
|
||||
} else {
|
||||
error!("wait failed: {:?}", status)
|
||||
}
|
||||
});
|
||||
self.psk_broker
|
||||
.borrow_mut()
|
||||
.set_psk(&owg.dev, owg.pk.value, *key.secret())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -706,9 +710,16 @@ impl AppServer {
|
||||
|
||||
// only poll if we drained all sockets before
|
||||
if self.all_sockets_drained {
|
||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||
self.mio_poll
|
||||
.poll(&mut self.events, Some(timeout))
|
||||
.or_else(|e| match e.kind() {
|
||||
ErrorKind::Interrupted | ErrorKind::WouldBlock => Ok(()),
|
||||
_ => Err(e),
|
||||
})?;
|
||||
}
|
||||
|
||||
self.psk_broker.get_mut().poll()?;
|
||||
|
||||
let mut would_block_count = 0;
|
||||
for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
|
||||
match socket.recv_from(buf) {
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
use anyhow::{bail, ensure};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::io::{BufReader, Read};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::thread;
|
||||
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use clap::{ArgGroup, Parser, Subcommand};
|
||||
use command_fds::{CommandFdExt, FdMapping};
|
||||
use log::{error, info};
|
||||
use rustix::fd::AsRawFd;
|
||||
use rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType};
|
||||
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::file::StoreSecret;
|
||||
use rosenpass_secret_memory::Public;
|
||||
use rosenpass_util::b64::b64_reader;
|
||||
use rosenpass_util::fd::claim_fd;
|
||||
use rosenpass_util::file::{LoadValue, LoadValueB64};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::app_server;
|
||||
use crate::app_server::AppServer;
|
||||
@@ -12,49 +25,29 @@ 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 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>,
|
||||
#[command(author, version, about)]
|
||||
#[clap(group(
|
||||
ArgGroup::new("psk_broker_specs")
|
||||
.args(&["psk_broker", "psk_broker_fd"]),
|
||||
))]
|
||||
pub struct Cli {
|
||||
// Path of the wireguard_psk broker socket to connect to
|
||||
#[arg(long)]
|
||||
psk_broker: Option<PathBuf>,
|
||||
|
||||
/// 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,
|
||||
/// When this command is called from another process, the other process can open and bind the
|
||||
/// unix socket for the psk broker connectionto use themselves, passing it to this process. In Rust this can be achieved
|
||||
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
|
||||
#[arg(long)]
|
||||
psk_broker_fd: Option<i32>,
|
||||
|
||||
#[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)]
|
||||
#[command(author, version, about, long_about)]
|
||||
pub enum CliCommand {
|
||||
/// Start Rosenpass in server mode and carry on with the key exchange
|
||||
///
|
||||
@@ -103,6 +96,7 @@ pub enum CliCommand {
|
||||
config_file: PathBuf,
|
||||
|
||||
/// Forcefully overwrite existing config file
|
||||
/// - [ ] Janepie
|
||||
#[clap(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
@@ -145,15 +139,13 @@ pub enum CliCommand {
|
||||
Man,
|
||||
}
|
||||
|
||||
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<()> {
|
||||
impl Cli {
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
let cli = Self::parse();
|
||||
|
||||
use CliCommand::*;
|
||||
match self {
|
||||
Man => {
|
||||
match cli {
|
||||
Cli { command: Man, .. } => {
|
||||
let man_cmd = std::process::Command::new("man")
|
||||
.args(["1", "rosenpass"])
|
||||
.status();
|
||||
@@ -162,7 +154,10 @@ impl CliCommand {
|
||||
println!(include_str!(env!("ROSENPASS_MAN")));
|
||||
}
|
||||
}
|
||||
GenConfig { config_file, force } => {
|
||||
Cli {
|
||||
command: GenConfig { config_file, force },
|
||||
..
|
||||
} => {
|
||||
ensure!(
|
||||
force || !config_file.exists(),
|
||||
"config file {config_file:?} already exists"
|
||||
@@ -172,7 +167,10 @@ impl CliCommand {
|
||||
}
|
||||
|
||||
// Deprecated - use gen-keys instead
|
||||
Keygen { args } => {
|
||||
Cli {
|
||||
command: Keygen { args },
|
||||
..
|
||||
} => {
|
||||
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
||||
|
||||
let mut public_key: Option<PathBuf> = None;
|
||||
@@ -205,11 +203,15 @@ impl CliCommand {
|
||||
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
||||
}
|
||||
|
||||
GenKeys {
|
||||
config_file,
|
||||
public_key,
|
||||
secret_key,
|
||||
force,
|
||||
Cli {
|
||||
command:
|
||||
GenKeys {
|
||||
config_file,
|
||||
public_key,
|
||||
secret_key,
|
||||
force,
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// figure out where the key file is specified, in the config file or directly as flag?
|
||||
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
||||
@@ -249,7 +251,10 @@ impl CliCommand {
|
||||
generate_and_save_keypair(skf, pkf)?;
|
||||
}
|
||||
|
||||
ExchangeConfig { config_file } => {
|
||||
ref cli @ Cli {
|
||||
command: ExchangeConfig { ref config_file },
|
||||
..
|
||||
} => {
|
||||
ensure!(
|
||||
config_file.exists(),
|
||||
"config file '{config_file:?}' does not exist"
|
||||
@@ -257,27 +262,35 @@ impl CliCommand {
|
||||
|
||||
let config = config::Rosenpass::load(config_file)?;
|
||||
config.validate()?;
|
||||
Self::event_loop(config)?;
|
||||
Self::event_loop(&cli, &config)?;
|
||||
}
|
||||
|
||||
Exchange {
|
||||
first_arg,
|
||||
mut rest_of_args,
|
||||
config_file,
|
||||
ref cli @ Cli {
|
||||
command:
|
||||
Exchange {
|
||||
ref first_arg,
|
||||
ref rest_of_args,
|
||||
ref config_file,
|
||||
},
|
||||
..
|
||||
} => {
|
||||
rest_of_args.insert(0, first_arg);
|
||||
let args = rest_of_args;
|
||||
let mut args = Vec::new();
|
||||
args.push(first_arg.clone());
|
||||
args.extend_from_slice(&rest_of_args[..]);
|
||||
let mut config = config::Rosenpass::parse_args(args)?;
|
||||
|
||||
if let Some(p) = config_file {
|
||||
if let Some(p) = &config_file {
|
||||
config.store(&p)?;
|
||||
config.config_file_path = p;
|
||||
config.config_file_path = p.clone();
|
||||
}
|
||||
config.validate()?;
|
||||
Self::event_loop(config)?;
|
||||
Self::event_loop(&cli, &config)?;
|
||||
}
|
||||
|
||||
Validate { config_files } => {
|
||||
Cli {
|
||||
command: Validate { config_files },
|
||||
..
|
||||
} => {
|
||||
for file in config_files {
|
||||
match config::Rosenpass::load(&file) {
|
||||
Ok(config) => {
|
||||
@@ -296,30 +309,99 @@ impl CliCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn event_loop(config: config::Rosenpass) -> anyhow::Result<()> {
|
||||
fn event_loop(cli: &Cli, config: &config::Rosenpass) -> anyhow::Result<()> {
|
||||
// load own keys
|
||||
let sk = SSk::load(&config.secret_key)?;
|
||||
let pk = SPk::load(&config.public_key)?;
|
||||
|
||||
// Connect to the psk broker unix socket if one was specified
|
||||
// OR OTHERWISE pawn the psk broker and use socketpair(2) to connect with them
|
||||
let psk_broker_socket = if let Some(ref broker_path) = cli.psk_broker {
|
||||
let sock = UnixStream::connect(broker_path)?;
|
||||
sock.set_nonblocking(true)?;
|
||||
sock
|
||||
} else if let Some(broker_fd) = cli.psk_broker_fd {
|
||||
let sock = UnixStream::from(claim_fd(broker_fd)?);
|
||||
sock.set_nonblocking(true)?;
|
||||
sock
|
||||
} else {
|
||||
let (ours, theirs) = socketpair(
|
||||
AddressFamily::UNIX,
|
||||
SocketType::STREAM,
|
||||
SocketFlags::empty(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Setup our end of the socketpair
|
||||
let ours = UnixStream::from(ours);
|
||||
ours.set_nonblocking(true)?;
|
||||
|
||||
// Start the PSK broker
|
||||
let mut child = Command::new("rosenpass-wireguard-broker-socket-handler")
|
||||
.args(&["--stream-fd", "3"])
|
||||
.fd_mappings(vec![FdMapping {
|
||||
parent_fd: theirs.as_raw_fd(),
|
||||
child_fd: 3,
|
||||
}])?
|
||||
.spawn()?;
|
||||
|
||||
// Handle the PSK broker crashing
|
||||
thread::spawn(move || {
|
||||
let status = child.wait();
|
||||
|
||||
if let Ok(status) = status {
|
||||
if status.success() {
|
||||
// Maybe they are doing double forking?
|
||||
info!("PSK broker exited.");
|
||||
} else {
|
||||
error!("PSK broker exited with an error ({status:?})");
|
||||
}
|
||||
} else {
|
||||
error!("Wait on PSK broker process failed ({status:?})");
|
||||
}
|
||||
});
|
||||
|
||||
ours
|
||||
};
|
||||
|
||||
// start an application server
|
||||
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
||||
sk,
|
||||
pk,
|
||||
config.listen,
|
||||
config.verbosity,
|
||||
config.listen.clone(),
|
||||
psk_broker_socket,
|
||||
config.verbosity.clone(),
|
||||
)?);
|
||||
|
||||
for cfg_peer in config.peers {
|
||||
for cfg_peer in config.peers.iter().by_ref() {
|
||||
srv.add_peer(
|
||||
// psk, pk, outfile, outwg, tx_addr
|
||||
cfg_peer.pre_shared_key.map(SymKey::load_b64).transpose()?,
|
||||
cfg_peer
|
||||
.pre_shared_key
|
||||
.as_ref()
|
||||
.map(SymKey::load_b64)
|
||||
.transpose()?,
|
||||
SPk::load(&cfg_peer.public_key)?,
|
||||
cfg_peer.key_out,
|
||||
cfg_peer.wg.map(|cfg| app_server::WireguardOut {
|
||||
dev: cfg.device,
|
||||
pk: cfg.peer,
|
||||
extra_params: cfg.extra_params,
|
||||
}),
|
||||
cfg_peer.key_out.clone(),
|
||||
cfg_peer
|
||||
.wg
|
||||
.as_ref()
|
||||
.map(|cfg| -> anyhow::Result<_> {
|
||||
let b64pk = &cfg.peer;
|
||||
let mut pk = Public::zero();
|
||||
b64_reader(BufReader::new(b64pk.as_bytes()))
|
||||
.read_exact(&mut pk.value)
|
||||
.with_context(|| {
|
||||
format!("Could not decode base64 public key: '{b64pk}'")
|
||||
})?;
|
||||
|
||||
Ok(app_server::WireguardOut {
|
||||
pk,
|
||||
dev: cfg.device.clone(),
|
||||
extra_params: cfg.extra_params.clone(),
|
||||
})
|
||||
})
|
||||
.transpose()?,
|
||||
cfg_peer.endpoint.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
//! 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,
|
||||
@@ -21,123 +12,57 @@ 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)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||
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
|
||||
/// - documentation
|
||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
||||
// TODO make this field only available on binary builds, not on library builds
|
||||
#[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 configuration from a TOML file
|
||||
/// Load a config file from a file path
|
||||
///
|
||||
/// 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
|
||||
/// no validation is conducted
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -158,22 +83,18 @@ 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(),
|
||||
"could not find public-key file {:?}: no such file",
|
||||
"public-key file {:?} does not exist",
|
||||
self.public_key
|
||||
);
|
||||
|
||||
// check the secret-key file exists
|
||||
ensure!(
|
||||
self.secret_key.is_file(),
|
||||
"could not find secret-key file {:?}: no such file",
|
||||
"secret-key file {:?} does not exist",
|
||||
self.secret_key
|
||||
);
|
||||
|
||||
@@ -521,67 +442,3 @@ 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,3 +1,5 @@
|
||||
use rosenpass_lenses::LenseError;
|
||||
|
||||
pub mod app_server;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
@@ -12,3 +14,11 @@ 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,35 +1,16 @@
|
||||
use clap::Parser;
|
||||
use log::error;
|
||||
use rosenpass::cli::CliArgs;
|
||||
use rosenpass::cli::Cli;
|
||||
use std::process::exit;
|
||||
|
||||
/// Catches errors, prints them through the logger, then exits
|
||||
pub fn main() {
|
||||
// parse CLI arguments
|
||||
let args = CliArgs::parse();
|
||||
// default to displaying warning and error log messages only
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
// 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() {
|
||||
match Cli::run() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
error!("{e:?}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,108 +6,129 @@
|
||||
//! 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)
|
||||
//! To achieve this we utilize the zerocopy library.
|
||||
//!
|
||||
//! # 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(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use super::RosenpassError;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||
use std::mem::size_of;
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
use rosenpass_lenses::{lense, LenseView};
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
// Macro magic ////////////////////////////////////////////////////////////////
|
||||
|
||||
lense! { Envelope<M> :=
|
||||
/// [MsgType] of this message
|
||||
pub msg_type: u8,
|
||||
msg_type: 1,
|
||||
/// Reserved for future use
|
||||
pub reserved: [u8; 3],
|
||||
reserved: 3,
|
||||
/// The actual Paylod
|
||||
pub payload: M,
|
||||
payload: M::LEN,
|
||||
/// Message Authentication Code (mac) over all bytes until (exclusive)
|
||||
/// `mac` itself
|
||||
pub mac: [u8; 16],
|
||||
mac: 16,
|
||||
/// Currently unused, TODO: do something with this
|
||||
pub cookie: [u8; 16],
|
||||
cookie: 16
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct InitHello {
|
||||
lense! { InitHello :=
|
||||
/// Randomly generated connection id
|
||||
pub sidi: [u8; 4],
|
||||
sidi: 4,
|
||||
/// Kyber 512 Ephemeral Public Key
|
||||
pub epki: [u8; EphemeralKem::PK_LEN],
|
||||
epki: EphemeralKem::PK_LEN,
|
||||
/// Classic McEliece Ciphertext
|
||||
pub sctr: [u8; StaticKem::CT_LEN],
|
||||
sctr: StaticKem::CT_LEN,
|
||||
/// Encryped: 16 byte hash of McEliece initiator static key
|
||||
pub pidic: [u8; aead::TAG_LEN + 32],
|
||||
pidic: aead::TAG_LEN + 32,
|
||||
/// Encrypted TAI64N Time Stamp (against replay attacks)
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
auth: aead::TAG_LEN
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct RespHello {
|
||||
lense! { RespHello :=
|
||||
/// Randomly generated connection id
|
||||
pub sidr: [u8; 4],
|
||||
sidr: 4,
|
||||
/// Copied from InitHello
|
||||
pub sidi: [u8; 4],
|
||||
sidi: 4,
|
||||
/// Kyber 512 Ephemeral Ciphertext
|
||||
pub ecti: [u8; EphemeralKem::CT_LEN],
|
||||
ecti: EphemeralKem::CT_LEN,
|
||||
/// Classic McEliece Ciphertext
|
||||
pub scti: [u8; StaticKem::CT_LEN],
|
||||
scti: StaticKem::CT_LEN,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
auth: aead::TAG_LEN,
|
||||
/// Responders handshake state in encrypted form
|
||||
pub biscuit: [u8; BISCUIT_CT_LEN],
|
||||
biscuit: BISCUIT_CT_LEN
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct InitConf {
|
||||
lense! { InitConf :=
|
||||
/// Copied from InitHello
|
||||
pub sidi: [u8; 4],
|
||||
sidi: 4,
|
||||
/// Copied from RespHello
|
||||
pub sidr: [u8; 4],
|
||||
sidr: 4,
|
||||
/// Responders handshake state in encrypted form
|
||||
pub biscuit: [u8; BISCUIT_CT_LEN],
|
||||
biscuit: BISCUIT_CT_LEN,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
auth: aead::TAG_LEN
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct EmptyData {
|
||||
lense! { EmptyData :=
|
||||
/// Copied from RespHello
|
||||
pub sid: [u8; 4],
|
||||
sid: 4,
|
||||
/// Nonce
|
||||
pub ctr: [u8; 8],
|
||||
ctr: 8,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
auth: aead::TAG_LEN
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct Biscuit {
|
||||
lense! { Biscuit :=
|
||||
/// H(spki) – Ident ifies the initiator
|
||||
pub pidi: [u8; KEY_LEN],
|
||||
pidi: KEY_LEN,
|
||||
/// The biscuit number (replay protection)
|
||||
pub biscuit_no: [u8; 12],
|
||||
biscuit_no: 12,
|
||||
/// Chaining key
|
||||
pub ck: [u8; KEY_LEN],
|
||||
ck: KEY_LEN
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct DataMsg {
|
||||
pub dummy: [u8; 4],
|
||||
lense! { DataMsg :=
|
||||
dummy: 4
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct CookieReply {
|
||||
pub dummy: [u8; 4],
|
||||
lense! { CookieReply :=
|
||||
dummy: 4
|
||||
}
|
||||
|
||||
// Traits /////////////////////////////////////////////////////////////////////
|
||||
@@ -157,7 +178,7 @@ impl TryFrom<u8> for MsgType {
|
||||
}
|
||||
|
||||
/// length in bytes of an unencrypted Biscuit (plain text)
|
||||
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
||||
pub const BISCUIT_PT_LEN: usize = Biscuit::<()>::LEN;
|
||||
|
||||
/// 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,28 +70,26 @@ 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::*, RosenpassError};
|
||||
use crate::{hash_domains, msgs::*};
|
||||
|
||||
// CONSTANTS & SETTINGS //////////////////////////
|
||||
|
||||
/// Size required to fit any message in binary form
|
||||
pub const RTX_BUFFER_SIZE: usize = max_usize(
|
||||
size_of::<Envelope<InitHello>>(),
|
||||
size_of::<Envelope<InitConf>>(),
|
||||
<Envelope<(), InitHello<()>> as LenseView>::LEN,
|
||||
<Envelope<(), InitConf<()>> as LenseView>::LEN,
|
||||
);
|
||||
|
||||
/// A type for time, e.g. for backoff before re-tries
|
||||
@@ -741,12 +739,11 @@ 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> {
|
||||
// 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)?;
|
||||
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)?;
|
||||
peer.hs()
|
||||
.store_msg_for_retransmission(self, msg.as_bytes())?;
|
||||
.store_msg_for_retransmission(self, &tx_buf[..len])?;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
@@ -796,45 +793,50 @@ impl CryptoServer {
|
||||
|
||||
let peer = match rx_buf[0].try_into() {
|
||||
Ok(MsgType::InitHello) => {
|
||||
let msg_in: Ref<&[u8], Envelope<InitHello>> =
|
||||
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
|
||||
let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?;
|
||||
ensure!(msg_in.check_seal(self)?, seal_broken);
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
peer
|
||||
}
|
||||
Ok(MsgType::RespHello) => {
|
||||
let msg_in: Ref<&[u8], Envelope<RespHello>> =
|
||||
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
|
||||
let msg_in = rx_buf.envelope::<RespHello<&[u8]>>()?;
|
||||
ensure!(msg_in.check_seal(self)?, seal_broken);
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
peer.hs()
|
||||
.store_msg_for_retransmission(self, &msg_out.as_bytes()[..len])?;
|
||||
.store_msg_for_retransmission(self, &tx_buf[..len])?;
|
||||
exchanged = true;
|
||||
peer
|
||||
}
|
||||
Ok(MsgType::InitConf) => {
|
||||
let msg_in: Ref<&[u8], Envelope<InitConf>> =
|
||||
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
|
||||
let msg_in = rx_buf.envelope::<InitConf<&[u8]>>()?;
|
||||
ensure!(msg_in.check_seal(self)?, seal_broken);
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
exchanged = true;
|
||||
peer
|
||||
}
|
||||
Ok(MsgType::EmptyData) => {
|
||||
let msg_in: Ref<&[u8], Envelope<EmptyData>> =
|
||||
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
|
||||
let msg_in = rx_buf.envelope::<EmptyData<&[u8]>>()?;
|
||||
ensure!(msg_in.check_seal(self)?, seal_broken);
|
||||
|
||||
self.handle_resp_conf(&msg_in.payload)?
|
||||
self.handle_resp_conf(msg_in.payload().empty_data()?)?
|
||||
}
|
||||
Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"),
|
||||
Ok(MsgType::CookieReply) => bail!("CookieReply handling not implemented!"),
|
||||
@@ -854,15 +856,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: AsBytes + FromBytes>(
|
||||
pub fn seal_and_commit_msg<M: LenseView>(
|
||||
&mut self,
|
||||
peer: PeerPtr,
|
||||
msg_type: MsgType,
|
||||
msg: &mut Ref<&mut [u8], Envelope<M>>,
|
||||
mut msg: Envelope<&mut [u8], M>,
|
||||
) -> Result<usize> {
|
||||
msg.msg_type = msg_type as u8;
|
||||
msg.msg_type_mut()[0] = msg_type as u8;
|
||||
msg.seal(peer, self)?;
|
||||
Ok(size_of::<Envelope<M>>())
|
||||
Ok(<Envelope<(), M> as LenseView>::LEN)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,31 +1170,32 @@ impl IniHsPtr {
|
||||
|
||||
// CRYPTO/HANDSHAKE HANDLING /////////////////////
|
||||
|
||||
impl<M> Envelope<M>
|
||||
impl<M> Envelope<&mut [u8], M>
|
||||
where
|
||||
M: AsBytes + FromBytes,
|
||||
M: LenseView,
|
||||
{
|
||||
/// 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.as_bytes()[span_of!(Self, msg_type..mac)])?;
|
||||
self.mac.copy_from_slice(mac.into_value()[..16].as_ref());
|
||||
.mix(self.until_mac())?;
|
||||
self.mac_mut()
|
||||
.copy_from_slice(mac.into_value()[..16].as_ref());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Envelope<M>
|
||||
impl<M> Envelope<&[u8], M>
|
||||
where
|
||||
M: AsBytes + FromBytes,
|
||||
M: LenseView,
|
||||
{
|
||||
/// 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.as_bytes()[span_of!(Self, msg_type..mac)])?;
|
||||
.mix(self.until_mac())?;
|
||||
Ok(constant_time::memcmp(
|
||||
&self.mac,
|
||||
self.mac(),
|
||||
&expected.into_value()[..16],
|
||||
))
|
||||
}
|
||||
@@ -1279,16 +1282,15 @@ impl HandshakeState {
|
||||
biscuit_ct: &mut [u8],
|
||||
) -> Result<&mut Self> {
|
||||
let mut biscuit = Secret::<BISCUIT_PT_LEN>::zero(); // pt buffer
|
||||
let mut biscuit: Ref<&mut [u8], Biscuit> =
|
||||
Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap();
|
||||
let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // lens view
|
||||
|
||||
// calculate pt contents
|
||||
biscuit
|
||||
.pidi
|
||||
.pidi_mut()
|
||||
.copy_from_slice(peer.get(srv).pidt()?.as_slice());
|
||||
biscuit.biscuit_no.copy_from_slice(&*srv.biscuit_ctr);
|
||||
biscuit.biscuit_no_mut().copy_from_slice(&*srv.biscuit_ctr);
|
||||
biscuit
|
||||
.ck
|
||||
.ck_mut()
|
||||
.copy_from_slice(self.ck.clone().danger_into_secret().secret());
|
||||
|
||||
// calculate ad contents
|
||||
@@ -1309,7 +1311,7 @@ impl HandshakeState {
|
||||
n[0] |= (bk.0 as u8 & 0x1) << 7;
|
||||
|
||||
let k = bk.get(srv).key.secret();
|
||||
let pt = biscuit.as_bytes();
|
||||
let pt = biscuit.all_bytes();
|
||||
xaead::encrypt(biscuit_ct, k, &*n, &ad, pt)?;
|
||||
|
||||
self.mix(biscuit_ct)
|
||||
@@ -1335,19 +1337,18 @@ impl HandshakeState {
|
||||
|
||||
// Allocate and decrypt the biscuit data
|
||||
let mut biscuit = Secret::<BISCUIT_PT_LEN>::zero(); // pt buf
|
||||
let mut biscuit: Ref<&mut [u8], Biscuit> =
|
||||
Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap();
|
||||
let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // slice
|
||||
xaead::decrypt(
|
||||
biscuit.as_bytes_mut(),
|
||||
biscuit.all_bytes_mut(),
|
||||
bk.get(srv).key.secret(),
|
||||
&ad,
|
||||
biscuit_ct,
|
||||
)?;
|
||||
|
||||
// Reconstruct the biscuit fields
|
||||
let no = BiscuitId::from_slice(&biscuit.biscuit_no);
|
||||
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(&biscuit.ck)).dup();
|
||||
let pid = PeerId::from_slice(&biscuit.pidi);
|
||||
let no = BiscuitId::from_slice(biscuit.biscuit_no());
|
||||
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
|
||||
let pid = PeerId::from_slice(biscuit.pidi());
|
||||
|
||||
// Reconstruct the handshake state
|
||||
let mut hs = Self { sidi, sidr, ck };
|
||||
@@ -1363,7 +1364,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"
|
||||
);
|
||||
|
||||
@@ -1411,7 +1412,11 @@ impl CryptoServer {
|
||||
impl CryptoServer {
|
||||
/// Implementation of the cryptographic protocol using the already
|
||||
/// established primitives
|
||||
pub fn handle_initiation(&mut self, peer: PeerPtr, ih: &mut InitHello) -> Result<PeerPtr> {
|
||||
pub fn handle_initiation(
|
||||
&mut self,
|
||||
peer: PeerPtr,
|
||||
mut ih: InitHello<&mut [u8]>,
|
||||
) -> Result<PeerPtr> {
|
||||
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
|
||||
|
||||
// IHI1
|
||||
@@ -1419,25 +1424,25 @@ impl CryptoServer {
|
||||
|
||||
// IHI2
|
||||
hs.core.sidi.randomize();
|
||||
ih.sidi.copy_from_slice(&hs.core.sidi.value);
|
||||
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
|
||||
|
||||
// IHI3
|
||||
EphemeralKem::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
|
||||
ih.epki.copy_from_slice(&hs.epki.value);
|
||||
ih.epki_mut().copy_from_slice(&hs.epki.value);
|
||||
|
||||
// IHI4
|
||||
hs.core.mix(ih.sidi.as_slice())?.mix(ih.epki.as_slice())?;
|
||||
hs.core.mix(ih.sidi())?.mix(ih.epki())?;
|
||||
|
||||
// IHI5
|
||||
hs.core
|
||||
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||
ih.sctr.as_mut_slice(),
|
||||
ih.sctr_mut(),
|
||||
peer.get(self).spkt.secret(),
|
||||
)?;
|
||||
|
||||
// IHI6
|
||||
hs.core
|
||||
.encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm()?.as_ref())?;
|
||||
.encrypt_and_mix(ih.pidic_mut(), self.pidm()?.as_ref())?;
|
||||
|
||||
// IHI7
|
||||
hs.core
|
||||
@@ -1445,7 +1450,7 @@ impl CryptoServer {
|
||||
.mix(peer.get(self).psk.secret())?;
|
||||
|
||||
// IHI8
|
||||
hs.core.encrypt_and_mix(ih.auth.as_mut_slice(), &[])?;
|
||||
hs.core.encrypt_and_mix(ih.auth_mut(), &[])?;
|
||||
|
||||
// Update the handshake hash last (not changing any state on prior error
|
||||
peer.hs().insert(self, hs)?;
|
||||
@@ -1453,28 +1458,32 @@ impl CryptoServer {
|
||||
Ok(peer)
|
||||
}
|
||||
|
||||
pub fn handle_init_hello(&mut self, ih: &InitHello, rh: &mut RespHello) -> Result<PeerPtr> {
|
||||
pub fn handle_init_hello(
|
||||
&mut self,
|
||||
ih: InitHello<&[u8]>,
|
||||
mut rh: RespHello<&mut [u8]>,
|
||||
) -> 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:?}."))?
|
||||
};
|
||||
@@ -1484,42 +1493,46 @@ 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.copy_from_slice(core.sidi.as_ref());
|
||||
rh.sidr.copy_from_slice(core.sidr.as_ref());
|
||||
rh.sidi_mut().copy_from_slice(core.sidi.as_ref());
|
||||
rh.sidr_mut().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 }>(&mut rh.ecti, &ih.epki)?;
|
||||
core.encaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(rh.ecti_mut(), ih.epki())?;
|
||||
|
||||
// RHR5
|
||||
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||
&mut rh.scti,
|
||||
rh.scti_mut(),
|
||||
peer.get(self).spkt.secret(),
|
||||
)?;
|
||||
|
||||
// RHR6
|
||||
core.store_biscuit(self, peer, &mut rh.biscuit)?;
|
||||
core.store_biscuit(self, peer, rh.biscuit_mut())?;
|
||||
|
||||
// RHR7
|
||||
core.encrypt_and_mix(&mut rh.auth, &[])?;
|
||||
core.encrypt_and_mix(rh.auth_mut(), &[])?;
|
||||
|
||||
Ok(peer)
|
||||
}
|
||||
|
||||
pub fn handle_resp_hello(&mut self, rh: &RespHello, ic: &mut InitConf) -> Result<PeerPtr> {
|
||||
pub fn handle_resp_hello(
|
||||
&mut self,
|
||||
rh: RespHello<&[u8]>,
|
||||
mut ic: InitConf<&mut [u8]>,
|
||||
) -> 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();
|
||||
@@ -1544,52 +1557,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.copy_from_slice(&rh.sidi);
|
||||
ic.sidr.copy_from_slice(&rh.sidr);
|
||||
ic.sidi_mut().copy_from_slice(rh.sidi());
|
||||
ic.sidr_mut().copy_from_slice(rh.sidr());
|
||||
|
||||
// ICI3
|
||||
core.mix(&ic.sidi)?.mix(&ic.sidr)?;
|
||||
ic.biscuit.copy_from_slice(&rh.biscuit);
|
||||
core.mix(ic.sidi())?.mix(ic.sidr())?;
|
||||
ic.biscuit_mut().copy_from_slice(rh.biscuit());
|
||||
|
||||
// ICI4
|
||||
core.encrypt_and_mix(&mut ic.auth, &[])?;
|
||||
core.encrypt_and_mix(ic.auth_mut(), &[])?;
|
||||
|
||||
// Split() – We move the secrets into the session; we do not
|
||||
// delete the InitiatorHandshake, just clear it's secrets because
|
||||
@@ -1604,24 +1617,28 @@ impl CryptoServer {
|
||||
Ok(peer)
|
||||
}
|
||||
|
||||
pub fn handle_init_conf(&mut self, ic: &InitConf, rc: &mut EmptyData) -> Result<PeerPtr> {
|
||||
pub fn handle_init_conf(
|
||||
&mut self,
|
||||
ic: InitConf<&[u8]>,
|
||||
mut rc: EmptyData<&mut [u8]>,
|
||||
) -> 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 {
|
||||
@@ -1665,19 +1682,19 @@ impl CryptoServer {
|
||||
.get_mut(self)
|
||||
.as_mut()
|
||||
.context("Cannot send acknowledgement. No session.")?;
|
||||
rc.sid.copy_from_slice(&ses.sidt.value);
|
||||
rc.ctr.copy_from_slice(&ses.txnm.to_le_bytes());
|
||||
rc.sid_mut().copy_from_slice(&ses.sidt.value);
|
||||
rc.ctr_mut().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(&mut rc.auth, k, &n, &[], &[])?; // ct, k, n, ad, pt
|
||||
aead::encrypt(rc.auth_mut(), k, &n, &[], &[])?; // ct, k, n, ad, pt
|
||||
|
||||
Ok(peer)
|
||||
}
|
||||
|
||||
pub fn handle_resp_conf(&mut self, rc: &EmptyData) -> Result<PeerPtr> {
|
||||
let sid = SessionId::from_slice(&rc.sid);
|
||||
pub fn handle_resp_conf(&mut self, rc: EmptyData<&[u8]>) -> 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:?}"))?;
|
||||
@@ -1700,16 +1717,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(),
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -1720,10 +1737,6 @@ 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::*;
|
||||
|
||||
@@ -14,5 +14,6 @@ readme = "readme.md"
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
typenum = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
|
||||
12
util/src/fd.rs
Normal file
12
util/src/fd.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use std::os::fd::{OwnedFd, RawFd};
|
||||
|
||||
/// Clone some file descriptor
|
||||
///
|
||||
/// If the file descriptor is invalid, an error will be raised.
|
||||
pub fn claim_fd(fd: RawFd) -> anyhow::Result<OwnedFd> {
|
||||
use rustix::{fd::BorrowedFd, io::dup};
|
||||
|
||||
// This is safe since [dup] will simply raise
|
||||
let fd = unsafe { dup(BorrowedFd::borrow_raw(fd))? };
|
||||
Ok(fd)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
pub mod b64;
|
||||
pub mod fd;
|
||||
pub mod file;
|
||||
pub mod functional;
|
||||
pub mod mem;
|
||||
|
||||
42
wireguard-broker/Cargo.toml
Normal file
42
wireguard-broker/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "rosenpass-wireguard-broker"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal broker that runs as root and supplies exchanged keys to the kernel."
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
rosenpass-lenses = { workspace = true }
|
||||
paste = { workspace = true } # TODO: Using lenses should not necessitate importing paste
|
||||
|
||||
# Privileged only
|
||||
wireguard-uapi = { workspace = true }
|
||||
|
||||
# Socket handler only
|
||||
rosenpass-to = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
# Mio broker client
|
||||
mio = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
name = "rosenpass-wireguard-broker-privileged"
|
||||
path = "src/bin/priviledged.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "rosenpass-wireguard-broker-socket-handler"
|
||||
test = false
|
||||
path = "src/bin/socket_handler.rs"
|
||||
doc = false
|
||||
5
wireguard-broker/readme.md
Normal file
5
wireguard-broker/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Rosenpass internal broker supplying WireGuard with keys.
|
||||
|
||||
This crate contains a small application purpose-built to supply WireGuard in the linux kernel with pre-shared keys.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
152
wireguard-broker/src/api/client.rs
Normal file
152
wireguard-broker/src/api/client.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use std::{borrow::BorrowMut, marker::PhantomData};
|
||||
|
||||
use rosenpass_lenses::LenseView;
|
||||
|
||||
use crate::{
|
||||
api::msgs::{self, EnvelopeExt, SetPskRequestExt, SetPskResponseExt},
|
||||
WireGuardBroker,
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerClientPollResponseError<RecvError> {
|
||||
#[error(transparent)]
|
||||
IoError(RecvError),
|
||||
#[error("Invalid message.")]
|
||||
InvalidMessage,
|
||||
}
|
||||
|
||||
impl<RecvError> From<msgs::InvalidMessageTypeError> for BrokerClientPollResponseError<RecvError> {
|
||||
fn from(value: msgs::InvalidMessageTypeError) -> Self {
|
||||
let msgs::InvalidMessageTypeError = value; // Assert that this is a unit type
|
||||
BrokerClientPollResponseError::<RecvError>::InvalidMessage
|
||||
}
|
||||
}
|
||||
|
||||
fn io_pollerr<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> {
|
||||
BrokerClientPollResponseError::<RecvError>::IoError(e)
|
||||
}
|
||||
|
||||
fn invalid_msg_pollerr<RecvError>() -> BrokerClientPollResponseError<RecvError> {
|
||||
BrokerClientPollResponseError::<RecvError>::InvalidMessage
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerClientSetPskError<SendError> {
|
||||
#[error(transparent)]
|
||||
IoError(SendError),
|
||||
#[error("Interface name out of bounds")]
|
||||
IfaceOutOfBounds,
|
||||
}
|
||||
|
||||
pub trait BrokerClientIo {
|
||||
type SendError;
|
||||
type RecvError;
|
||||
|
||||
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>;
|
||||
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BrokerClient<'a, Io, IoRef>
|
||||
where
|
||||
Io: BrokerClientIo,
|
||||
IoRef: 'a + BorrowMut<Io>,
|
||||
{
|
||||
io: IoRef,
|
||||
_phantom_io: PhantomData<&'a mut Io>,
|
||||
}
|
||||
|
||||
impl<'a, Io, IoRef> BrokerClient<'a, Io, IoRef>
|
||||
where
|
||||
Io: BrokerClientIo,
|
||||
IoRef: 'a + BorrowMut<Io>,
|
||||
{
|
||||
pub fn new(io: IoRef) -> Self {
|
||||
Self {
|
||||
io,
|
||||
_phantom_io: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn io(&self) -> &IoRef {
|
||||
&self.io
|
||||
}
|
||||
|
||||
pub fn io_mut(&mut self) -> &mut IoRef {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
pub fn poll_response(
|
||||
&mut self,
|
||||
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
|
||||
let res: &[u8] = match self.io.borrow_mut().recv_msg().map_err(io_pollerr)? {
|
||||
Some(r) => r,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let typ = res.get(0).ok_or(invalid_msg_pollerr())?;
|
||||
let typ = msgs::MsgType::try_from(*typ)?;
|
||||
let msgs::MsgType::SetPsk = typ; // Assert type
|
||||
|
||||
let res: msgs::Envelope<_, msgs::SetPskResponse<&[u8]>> = res
|
||||
.envelope_truncating()
|
||||
.map_err(|_| invalid_msg_pollerr())?;
|
||||
let res: msgs::SetPskResponse<&[u8]> = res
|
||||
.payload()
|
||||
.set_psk_response()
|
||||
.map_err(|_| invalid_msg_pollerr())?;
|
||||
let res: msgs::SetPskResponseReturnCode = res.return_code()[0]
|
||||
.try_into()
|
||||
.map_err(|_| invalid_msg_pollerr())?;
|
||||
let res: msgs::SetPskResult = res.into();
|
||||
|
||||
Ok(Some(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Io, IoRef> WireGuardBroker for BrokerClient<'a, Io, IoRef>
|
||||
where
|
||||
Io: BrokerClientIo,
|
||||
IoRef: 'a + BorrowMut<Io>,
|
||||
{
|
||||
type Error = BrokerClientSetPskError<Io::SendError>;
|
||||
|
||||
fn set_psk(
|
||||
&mut self,
|
||||
iface: &str,
|
||||
peer_id: [u8; 32],
|
||||
psk: [u8; 32],
|
||||
) -> Result<(), Self::Error> {
|
||||
use BrokerClientSetPskError::*;
|
||||
const BUF_SIZE: usize = <msgs::Envelope<(), msgs::SetPskRequest<()>> as LenseView>::LEN;
|
||||
|
||||
// Allocate message
|
||||
let mut req = [0u8; BUF_SIZE];
|
||||
|
||||
// Construct message view
|
||||
let mut req: msgs::Envelope<_, msgs::SetPskRequest<&mut [u8]>> =
|
||||
(&mut req as &mut [u8]).envelope_truncating().unwrap();
|
||||
|
||||
// Populate envelope
|
||||
req.msg_type_mut()
|
||||
.copy_from_slice(&[msgs::MsgType::SetPsk as u8]);
|
||||
{
|
||||
// Derived payload
|
||||
let mut req: msgs::SetPskRequest<&mut [u8]> =
|
||||
req.payload_mut().set_psk_request().unwrap();
|
||||
|
||||
// Populate payload
|
||||
req.peer_id_mut().copy_from_slice(&peer_id);
|
||||
req.psk_mut().copy_from_slice(&psk);
|
||||
req.set_iface(iface).ok_or(IfaceOutOfBounds)?;
|
||||
}
|
||||
|
||||
// Send message
|
||||
self.io
|
||||
.borrow_mut()
|
||||
.send_msg(req.all_bytes())
|
||||
.map_err(IoError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
204
wireguard-broker/src/api/mio_client.rs
Normal file
204
wireguard-broker/src/api/mio_client.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
|
||||
use anyhow::{bail, ensure};
|
||||
|
||||
use crate::WireGuardBroker;
|
||||
|
||||
use super::client::{
|
||||
BrokerClient, BrokerClientIo, BrokerClientPollResponseError, BrokerClientSetPskError,
|
||||
};
|
||||
use super::msgs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MioBrokerClient {
|
||||
inner: BrokerClient<'static, MioBrokerClientIo, MioBrokerClientIo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MioBrokerClientIo {
|
||||
socket: mio::net::UnixStream,
|
||||
send_buf: VecDeque<u8>,
|
||||
receiving_size: bool,
|
||||
recv_buf: Vec<u8>,
|
||||
recv_off: usize,
|
||||
}
|
||||
|
||||
impl MioBrokerClient {
|
||||
pub fn new(socket: mio::net::UnixStream) -> Self {
|
||||
let io = MioBrokerClientIo {
|
||||
socket,
|
||||
send_buf: VecDeque::new(),
|
||||
receiving_size: false,
|
||||
recv_buf: Vec::new(),
|
||||
recv_off: 0,
|
||||
};
|
||||
let inner = BrokerClient::new(io);
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> anyhow::Result<Option<msgs::SetPskResult>> {
|
||||
self.inner.io_mut().flush()?;
|
||||
|
||||
// This sucks
|
||||
match self.inner.poll_response() {
|
||||
Ok(res) => {
|
||||
return Ok(res);
|
||||
}
|
||||
Err(BrokerClientPollResponseError::IoError(e)) => {
|
||||
return Err(e);
|
||||
}
|
||||
Err(BrokerClientPollResponseError::InvalidMessage) => {
|
||||
bail!("Invalid message");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl WireGuardBroker for MioBrokerClient {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn set_psk(&mut self, iface: &str, peer_id: [u8; 32], psk: [u8; 32]) -> anyhow::Result<()> {
|
||||
use BrokerClientSetPskError::*;
|
||||
let e = self.inner.set_psk(iface, peer_id, psk);
|
||||
match e {
|
||||
Ok(()) => Ok(()),
|
||||
Err(IoError(e)) => Err(e),
|
||||
Err(IfaceOutOfBounds) => bail!("Interface name size is out of bounds."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BrokerClientIo for MioBrokerClientIo {
|
||||
type SendError = anyhow::Error;
|
||||
type RecvError = anyhow::Error;
|
||||
|
||||
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
|
||||
self.flush()?;
|
||||
self.send_or_buffer(&(buf.len() as u64).to_le_bytes())?;
|
||||
self.send_or_buffer(&buf)?;
|
||||
self.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
|
||||
// Stale message in receive buffer. Reset!
|
||||
if self.recv_off == self.recv_buf.len() {
|
||||
self.receiving_size = true;
|
||||
self.recv_off = 0;
|
||||
self.recv_buf.resize(8, 0);
|
||||
}
|
||||
|
||||
// Try filling the receive buffer
|
||||
self.recv_off += raw_recv(&self.socket, &mut self.recv_buf[self.recv_off..])?;
|
||||
if self.recv_off < self.recv_buf.len() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Received size, now start receiving
|
||||
if self.receiving_size {
|
||||
// Received the size
|
||||
// Parse the received length
|
||||
let len: &[u8; 8] = self.recv_buf[..].try_into().unwrap();
|
||||
let len: usize = u64::from_le_bytes(*len) as usize;
|
||||
|
||||
ensure!(
|
||||
len <= msgs::RESPONSE_MSG_BUFFER_SIZE,
|
||||
"Oversized buffer ({len}) in psk buffer response."
|
||||
);
|
||||
|
||||
// Prepare the message buffer for receiving an actual message of the given size
|
||||
self.receiving_size = false;
|
||||
self.recv_off = 0;
|
||||
self.recv_buf.resize(len, 0);
|
||||
|
||||
// Try to receive the message
|
||||
return self.recv_msg();
|
||||
}
|
||||
|
||||
// Received an actual message
|
||||
return Ok(Some(&self.recv_buf[..]));
|
||||
}
|
||||
}
|
||||
|
||||
impl MioBrokerClientIo {
|
||||
fn flush(&mut self) -> anyhow::Result<()> {
|
||||
let (fst, snd) = self.send_buf.as_slices();
|
||||
|
||||
let (written, res) = match raw_send(&self.socket, fst) {
|
||||
Ok(w1) if w1 >= fst.len() => match raw_send(&self.socket, snd) {
|
||||
Ok(w2) => (w1 + w2, Ok(())),
|
||||
Err(e) => (w1, Err(e)),
|
||||
},
|
||||
Ok(w1) => (w1, Ok(())),
|
||||
Err(e) => (0, Err(e)),
|
||||
};
|
||||
|
||||
self.send_buf.drain(..written);
|
||||
|
||||
(&self.socket).try_io(|| (&self.socket).flush())?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn send_or_buffer(&mut self, buf: &[u8]) -> anyhow::Result<()> {
|
||||
let mut off = 0;
|
||||
|
||||
if self.send_buf.is_empty() {
|
||||
off += raw_send(&self.socket, buf)?;
|
||||
}
|
||||
|
||||
self.send_buf.extend((&buf[off..]).iter());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_send(mut socket: &mio::net::UnixStream, data: &[u8]) -> anyhow::Result<usize> {
|
||||
let mut off = 0;
|
||||
|
||||
socket.try_io(|| {
|
||||
loop {
|
||||
if off == data.len() {
|
||||
return Ok(());
|
||||
}
|
||||
match socket.write(&data[off..]) {
|
||||
Ok(n) => {
|
||||
off += n;
|
||||
}
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => {
|
||||
// pass – retry
|
||||
}
|
||||
Err(e) if off > 0 || e.kind() == ErrorKind::WouldBlock => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
return Ok(off);
|
||||
}
|
||||
|
||||
fn raw_recv(mut socket: &mio::net::UnixStream, out: &mut [u8]) -> anyhow::Result<usize> {
|
||||
let mut off = 0;
|
||||
|
||||
socket.try_io(|| {
|
||||
loop {
|
||||
if off == out.len() {
|
||||
return Ok(());
|
||||
}
|
||||
match socket.read(&mut out[off..]) {
|
||||
Ok(n) => {
|
||||
off += n;
|
||||
}
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => {
|
||||
// pass – retry
|
||||
}
|
||||
Err(e) if off > 0 || e.kind() == ErrorKind::WouldBlock => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
return Ok(off);
|
||||
}
|
||||
4
wireguard-broker/src/api/mod.rs
Normal file
4
wireguard-broker/src/api/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod client;
|
||||
pub mod mio_client;
|
||||
pub mod msgs;
|
||||
pub mod server;
|
||||
140
wireguard-broker/src/api/msgs.rs
Normal file
140
wireguard-broker/src/api/msgs.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use std::result::Result;
|
||||
use std::str::{from_utf8, Utf8Error};
|
||||
|
||||
use rosenpass_lenses::{lense, LenseView};
|
||||
|
||||
pub const REQUEST_MSG_BUFFER_SIZE: usize = <Envelope<(), SetPskRequest<()>> as LenseView>::LEN;
|
||||
pub const RESPONSE_MSG_BUFFER_SIZE: usize = <Envelope<(), SetPskResponse<()>> as LenseView>::LEN;
|
||||
|
||||
lense! { Envelope<M> :=
|
||||
/// [MsgType] of this message
|
||||
msg_type: 1,
|
||||
/// Reserved for future use
|
||||
reserved: 3,
|
||||
/// The actual Paylod
|
||||
payload: M::LEN
|
||||
}
|
||||
|
||||
lense! { SetPskRequest :=
|
||||
peer_id: 32,
|
||||
psk: 32,
|
||||
iface_size: 1, // TODO: We should have variable length strings in lenses
|
||||
iface_buf: 255
|
||||
}
|
||||
|
||||
impl SetPskRequest<&[u8]> {
|
||||
pub fn iface_bin(&self) -> &[u8] {
|
||||
let len = self.iface_size()[0] as usize;
|
||||
&self.iface_buf()[..len]
|
||||
}
|
||||
|
||||
pub fn iface(&self) -> Result<&str, Utf8Error> {
|
||||
from_utf8(self.iface_bin())
|
||||
}
|
||||
}
|
||||
|
||||
impl SetPskRequest<&mut [u8]> {
|
||||
pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> {
|
||||
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256
|
||||
|
||||
self.iface_size_mut()[0] = iface.len() as u8;
|
||||
|
||||
self.iface_buf_mut().fill(0);
|
||||
(&mut self.iface_buf_mut()[..iface.len()]).copy_from_slice(iface);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn set_iface(&mut self, iface: &str) -> Option<()> {
|
||||
self.set_iface_bin(iface.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
lense! { SetPskResponse :=
|
||||
return_code: 1
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum SetPskError {
|
||||
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
|
||||
InternalError,
|
||||
#[error("The indicated wireguard interface does not exist")]
|
||||
NoSuchInterface,
|
||||
#[error("The indicated peer does not exist on the wireguard interface")]
|
||||
NoSuchPeer,
|
||||
}
|
||||
|
||||
pub type SetPskResult = Result<(), SetPskError>;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum SetPskResponseReturnCode {
|
||||
Success = 0x00,
|
||||
InternalError = 0x01,
|
||||
NoSuchInterface = 0x02,
|
||||
NoSuchPeer = 0x03,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct InvalidSetPskResponseError;
|
||||
|
||||
impl TryFrom<u8> for SetPskResponseReturnCode {
|
||||
type Error = InvalidSetPskResponseError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
use SetPskResponseReturnCode::*;
|
||||
match value {
|
||||
0x00 => Ok(Success),
|
||||
0x01 => Ok(InternalError),
|
||||
0x02 => Ok(NoSuchInterface),
|
||||
0x03 => Ok(NoSuchPeer),
|
||||
_ => Err(InvalidSetPskResponseError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetPskResponseReturnCode> for SetPskResult {
|
||||
fn from(value: SetPskResponseReturnCode) -> Self {
|
||||
use SetPskError as E;
|
||||
use SetPskResponseReturnCode as C;
|
||||
match value {
|
||||
C::Success => Ok(()),
|
||||
C::InternalError => Err(E::InternalError),
|
||||
C::NoSuchInterface => Err(E::NoSuchInterface),
|
||||
C::NoSuchPeer => Err(E::NoSuchPeer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetPskResult> for SetPskResponseReturnCode {
|
||||
fn from(value: SetPskResult) -> Self {
|
||||
use SetPskError as E;
|
||||
use SetPskResponseReturnCode as C;
|
||||
match value {
|
||||
Ok(()) => C::Success,
|
||||
Err(E::InternalError) => C::InternalError,
|
||||
Err(E::NoSuchInterface) => C::NoSuchInterface,
|
||||
Err(E::NoSuchPeer) => C::NoSuchPeer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum MsgType {
|
||||
SetPsk = 0x01,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct InvalidMessageTypeError;
|
||||
|
||||
impl TryFrom<u8> for MsgType {
|
||||
type Error = InvalidMessageTypeError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x01 => Ok(MsgType::SetPsk),
|
||||
_ => Err(InvalidMessageTypeError),
|
||||
}
|
||||
}
|
||||
}
|
||||
99
wireguard-broker/src/api/server.rs
Normal file
99
wireguard-broker/src/api/server.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::borrow::BorrowMut;
|
||||
use std::marker::PhantomData;
|
||||
use std::result::Result;
|
||||
|
||||
use rosenpass_lenses::LenseError;
|
||||
|
||||
use crate::api::msgs::{self, EnvelopeExt, SetPskRequestExt, SetPskResponseExt};
|
||||
use crate::WireGuardBroker;
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerServerError {
|
||||
#[error("No such request type: {}", .0)]
|
||||
NoSuchRequestType(u8),
|
||||
#[error("Invalid message received.")]
|
||||
InvalidMessage,
|
||||
}
|
||||
|
||||
impl From<LenseError> for BrokerServerError {
|
||||
fn from(value: LenseError) -> Self {
|
||||
use BrokerServerError as Be;
|
||||
use LenseError as Le;
|
||||
match value {
|
||||
Le::BufferSizeMismatch => Be::InvalidMessage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
|
||||
fn from(value: msgs::InvalidMessageTypeError) -> Self {
|
||||
let msgs::InvalidMessageTypeError = value; // Assert that this is a unit type
|
||||
BrokerServerError::InvalidMessage
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BrokerServer<'a, Err, Inner, Ref>
|
||||
where
|
||||
msgs::SetPskError: From<Err>,
|
||||
Inner: WireGuardBroker<Error = Err>,
|
||||
Ref: BorrowMut<Inner> + 'a,
|
||||
{
|
||||
inner: Ref,
|
||||
_phantom: PhantomData<&'a mut Inner>,
|
||||
}
|
||||
|
||||
impl<'a, Err, Inner, Ref> BrokerServer<'a, Err, Inner, Ref>
|
||||
where
|
||||
msgs::SetPskError: From<Err>,
|
||||
Inner: WireGuardBroker<Error = Err>,
|
||||
Ref: 'a + BorrowMut<Inner>,
|
||||
{
|
||||
pub fn new(inner: Ref) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_message(
|
||||
&mut self,
|
||||
req: &[u8],
|
||||
res: &mut [u8; msgs::RESPONSE_MSG_BUFFER_SIZE],
|
||||
) -> Result<usize, BrokerServerError> {
|
||||
use BrokerServerError::*;
|
||||
|
||||
let typ = req.get(0).ok_or(InvalidMessage)?;
|
||||
let typ = msgs::MsgType::try_from(*typ)?;
|
||||
let msgs::MsgType::SetPsk = typ; // Assert type
|
||||
|
||||
let req: msgs::Envelope<_, msgs::SetPskRequest<&[u8]>> = req.envelope_truncating()?;
|
||||
let mut res: msgs::Envelope<_, msgs::SetPskResponse<&mut [u8]>> =
|
||||
(res as &mut [u8]).envelope_truncating()?;
|
||||
(&mut res).msg_type_mut()[0] = msgs::MsgType::SetPsk as u8;
|
||||
self.handle_set_psk(
|
||||
req.payload().set_psk_request()?,
|
||||
res.payload_mut().set_psk_response()?,
|
||||
)?;
|
||||
Ok(res.all_bytes().len())
|
||||
}
|
||||
|
||||
fn handle_set_psk(
|
||||
&mut self,
|
||||
req: msgs::SetPskRequest<&[u8]>,
|
||||
mut res: msgs::SetPskResponse<&mut [u8]>,
|
||||
) -> Result<(), BrokerServerError> {
|
||||
// Using unwrap here since lenses can not return fixed-size arrays
|
||||
// TODO: Slices should give access to fixed size arrays
|
||||
let r: Result<(), Err> = self.inner.borrow_mut().set_psk(
|
||||
req.iface()
|
||||
.map_err(|_e| BrokerServerError::InvalidMessage)?,
|
||||
req.peer_id().try_into().unwrap(),
|
||||
req.psk().try_into().unwrap(),
|
||||
);
|
||||
let r: msgs::SetPskResult = r.map_err(|e| e.into());
|
||||
let r: msgs::SetPskResponseReturnCode = r.into();
|
||||
res.return_code_mut()[0] = r as u8;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
56
wireguard-broker/src/bin/priviledged.rs
Normal file
56
wireguard-broker/src/bin/priviledged.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
use std::result::Result;
|
||||
|
||||
use rosenpass_wireguard_broker::api::msgs;
|
||||
use rosenpass_wireguard_broker::api::server::BrokerServer;
|
||||
use rosenpass_wireguard_broker::netlink as wg;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum BrokerAppError {
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
WgConnectError(#[from] wg::ConnectError),
|
||||
#[error(transparent)]
|
||||
WgSetPskError(#[from] wg::SetPskError),
|
||||
#[error("Oversized message {}; something about the request is fatally wrong", .0)]
|
||||
OversizedMessage(u64),
|
||||
}
|
||||
|
||||
fn main() -> Result<(), BrokerAppError> {
|
||||
let mut broker = BrokerServer::new(wg::NetlinkWireGuardBroker::new()?);
|
||||
|
||||
let mut stdin = stdin().lock();
|
||||
let mut stdout = stdout().lock();
|
||||
loop {
|
||||
// Read the message length
|
||||
let mut len = [0u8; 8];
|
||||
stdin.read_exact(&mut len)?;
|
||||
|
||||
// Parse the message length
|
||||
let len = u64::from_le_bytes(len);
|
||||
if (len as usize) > msgs::REQUEST_MSG_BUFFER_SIZE {
|
||||
return Err(BrokerAppError::OversizedMessage(len));
|
||||
}
|
||||
|
||||
// Read the message itself
|
||||
let mut req_buf = [0u8; msgs::REQUEST_MSG_BUFFER_SIZE];
|
||||
let req_buf = &mut req_buf[..(len as usize)];
|
||||
stdin.read_exact(req_buf)?;
|
||||
|
||||
// Process the message
|
||||
let mut res_buf = [0u8; msgs::RESPONSE_MSG_BUFFER_SIZE];
|
||||
let res = match broker.handle_message(req_buf, &mut res_buf) {
|
||||
Ok(len) => &res_buf[..len],
|
||||
Err(e) => {
|
||||
eprintln!("Error processing message for wireguard PSK broker: {e:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the response
|
||||
stdout.write_all(&(res.len() as u64).to_le_bytes())?;
|
||||
stdout.write_all(&res)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
191
wireguard-broker/src/bin/socket_handler.rs
Normal file
191
wireguard-broker/src/bin/socket_handler.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use std::process::Stdio;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::task;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use clap::{ArgGroup, Parser};
|
||||
|
||||
use rosenpass_util::fd::claim_fd;
|
||||
use rosenpass_wireguard_broker::api::msgs;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[clap(group(
|
||||
ArgGroup::new("socket")
|
||||
.required(true)
|
||||
.args(&["listen_path", "listen_fd", "stream_fd"]),
|
||||
))]
|
||||
struct Args {
|
||||
/// Where in the file-system to create the unix socket this broker will be listening for
|
||||
/// connections on
|
||||
#[arg(long)]
|
||||
listen_path: Option<String>,
|
||||
|
||||
/// When this broker is called from another process, the other process can open and bind the
|
||||
/// unix socket to use themselves, passing it to this process. In Rust this can be achieved
|
||||
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
|
||||
#[arg(long)]
|
||||
listen_fd: Option<i32>,
|
||||
|
||||
/// When this broker is called from another process, the other process can connect the unix socket
|
||||
/// themselves, for instance using the `socketpair(2)` system call.
|
||||
#[arg(long)]
|
||||
stream_fd: Option<i32>,
|
||||
|
||||
/// The underlying broker, accepting commands through stdin and sending results through stdout.
|
||||
#[arg(
|
||||
last = true,
|
||||
allow_hyphen_values = true,
|
||||
default_value = "rosenpass-wireguard-broker-privileged"
|
||||
)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
struct BrokerRequest {
|
||||
reply_to: oneshot::Sender<BrokerResponse>,
|
||||
request: Vec<u8>,
|
||||
}
|
||||
|
||||
struct BrokerResponse {
|
||||
response: Vec<u8>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let (proc_tx, proc_rx) = mpsc::channel(100);
|
||||
|
||||
// Start the inner broker handler
|
||||
task::spawn(async move {
|
||||
if let Err(e) = direct_broker_process(proc_rx, args.command).await {
|
||||
log::error!("Error in broker command handler: {e}");
|
||||
panic!("Can not proceed without underlying broker process");
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for incoming requests
|
||||
if let Some(path) = args.listen_path {
|
||||
let sock = UnixListener::bind(path)?;
|
||||
listen_for_clients(proc_tx, sock).await
|
||||
} else if let Some(fd) = args.listen_fd {
|
||||
let sock = std::os::unix::net::UnixListener::from(claim_fd(fd)?);
|
||||
sock.set_nonblocking(true)?;
|
||||
listen_for_clients(proc_tx, UnixListener::from_std(sock)?).await
|
||||
} else if let Some(fd) = args.stream_fd {
|
||||
let stream = std::os::unix::net::UnixStream::from(claim_fd(fd)?);
|
||||
stream.set_nonblocking(true)?;
|
||||
on_accept(proc_tx, UnixStream::from_std(stream)?).await
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
async fn direct_broker_process(
|
||||
mut queue: mpsc::Receiver<BrokerRequest>,
|
||||
cmd: Vec<String>,
|
||||
) -> Result<()> {
|
||||
let proc = Command::new(&cmd[0])
|
||||
.args(&cmd[1..])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let mut stdin = proc.stdin.unwrap();
|
||||
let mut stdout = proc.stdout.unwrap();
|
||||
|
||||
loop {
|
||||
let BrokerRequest { reply_to, request } = queue.recv().await.unwrap();
|
||||
|
||||
stdin
|
||||
.write_all(&(request.len() as u64).to_le_bytes())
|
||||
.await?;
|
||||
stdin.write_all(&request[..]).await?;
|
||||
|
||||
// Read the response length
|
||||
let mut len = [0u8; 8];
|
||||
stdout.read_exact(&mut len).await?;
|
||||
|
||||
// Parse the response length
|
||||
let len = u64::from_le_bytes(len) as usize;
|
||||
ensure!(
|
||||
len <= msgs::RESPONSE_MSG_BUFFER_SIZE,
|
||||
"Oversized buffer ({len}) in broker stdout."
|
||||
);
|
||||
|
||||
// Read the message itself
|
||||
let mut res_buf = request; // Avoid allocating memory if we don't have to
|
||||
res_buf.resize(len as usize, 0);
|
||||
stdout.read_exact(&mut res_buf[..len]).await?;
|
||||
|
||||
// Return to the unix socket connection worker
|
||||
reply_to
|
||||
.send(BrokerResponse { response: res_buf })
|
||||
.or_else(|_| bail!("Unable to send respnse to unix socket worker."))?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListener) -> Result<()> {
|
||||
loop {
|
||||
let (stream, _addr) = sock.accept().await?;
|
||||
let queue = queue.clone();
|
||||
task::spawn(async move {
|
||||
if let Err(e) = on_accept(queue, stream).await {
|
||||
log::error!("Error during connection processing: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: If loop can ever terminate we need to join the spawned tasks
|
||||
}
|
||||
|
||||
async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> {
|
||||
let mut req_buf = Vec::new();
|
||||
|
||||
loop {
|
||||
stream.readable().await?;
|
||||
|
||||
// Read the message length
|
||||
let mut len = [0u8; 8];
|
||||
stream.read_exact(&mut len).await?;
|
||||
|
||||
// Parse the message length
|
||||
let len = u64::from_le_bytes(len) as usize;
|
||||
ensure!(
|
||||
len <= msgs::REQUEST_MSG_BUFFER_SIZE,
|
||||
"Oversized buffer ({len}) in unix socket input."
|
||||
);
|
||||
|
||||
// Read the message itself
|
||||
req_buf.resize(len as usize, 0);
|
||||
stream.read_exact(&mut req_buf[..len]).await?;
|
||||
|
||||
// Handle the message
|
||||
let (reply_tx, reply_rx) = oneshot::channel();
|
||||
queue
|
||||
.send(BrokerRequest {
|
||||
reply_to: reply_tx,
|
||||
request: req_buf,
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Wait for the reply
|
||||
let BrokerResponse { response } = reply_rx.await.unwrap();
|
||||
|
||||
// Write reply back to unix socket
|
||||
stream
|
||||
.write_all(&(response.len() as u64).to_le_bytes())
|
||||
.await?;
|
||||
stream.write_all(&response[..]).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
// Reuse the same memory for the next message
|
||||
req_buf = response;
|
||||
}
|
||||
}
|
||||
15
wireguard-broker/src/lib.rs
Normal file
15
wireguard-broker/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::result::Result;
|
||||
|
||||
pub trait WireGuardBroker {
|
||||
type Error;
|
||||
|
||||
fn set_psk(
|
||||
&mut self,
|
||||
interface: &str,
|
||||
peer_id: [u8; 32],
|
||||
psk: [u8; 32],
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
pub mod api;
|
||||
pub mod netlink;
|
||||
103
wireguard-broker/src/netlink.rs
Normal file
103
wireguard-broker/src/netlink.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use wireguard_uapi::linux as wg;
|
||||
|
||||
use crate::api::msgs;
|
||||
use crate::WireGuardBroker;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ConnectError {
|
||||
#[error(transparent)]
|
||||
ConnectError(#[from] wg::err::ConnectError),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum NetlinkError {
|
||||
#[error(transparent)]
|
||||
SetDevice(#[from] wg::err::SetDeviceError),
|
||||
#[error(transparent)]
|
||||
GetDevice(#[from] wg::err::GetDeviceError),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SetPskError {
|
||||
#[error("The indicated wireguard interface does not exist")]
|
||||
NoSuchInterface,
|
||||
#[error("The indicated peer does not exist on the wireguard interface")]
|
||||
NoSuchPeer,
|
||||
#[error(transparent)]
|
||||
NetlinkError(#[from] NetlinkError),
|
||||
}
|
||||
|
||||
impl From<wg::err::SetDeviceError> for SetPskError {
|
||||
fn from(err: wg::err::SetDeviceError) -> Self {
|
||||
NetlinkError::from(err).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wg::err::GetDeviceError> for SetPskError {
|
||||
fn from(err: wg::err::GetDeviceError) -> Self {
|
||||
NetlinkError::from(err).into()
|
||||
}
|
||||
}
|
||||
|
||||
use msgs::SetPskError as SetPskMsgsError;
|
||||
use SetPskError as SetPskNetlinkError;
|
||||
impl From<SetPskNetlinkError> for SetPskMsgsError {
|
||||
fn from(err: SetPskError) -> Self {
|
||||
match err {
|
||||
SetPskNetlinkError::NoSuchPeer => SetPskMsgsError::NoSuchPeer,
|
||||
_ => SetPskMsgsError::InternalError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetlinkWireGuardBroker {
|
||||
sock: wg::WgSocket,
|
||||
}
|
||||
|
||||
impl NetlinkWireGuardBroker {
|
||||
pub fn new() -> Result<Self, ConnectError> {
|
||||
let sock = wg::WgSocket::connect()?;
|
||||
Ok(Self { sock })
|
||||
}
|
||||
}
|
||||
|
||||
impl WireGuardBroker for NetlinkWireGuardBroker {
|
||||
type Error = SetPskError;
|
||||
|
||||
fn set_psk(
|
||||
&mut self,
|
||||
interface: &str,
|
||||
peer_id: [u8; 32],
|
||||
psk: [u8; 32],
|
||||
) -> Result<(), Self::Error> {
|
||||
// Ensure that the peer exists by querying the device configuration
|
||||
// TODO: Use InvalidInterfaceError
|
||||
let state = self
|
||||
.sock
|
||||
.get_device(wg::DeviceInterface::from_name(interface.to_owned()))?;
|
||||
|
||||
if state
|
||||
.peers
|
||||
.iter()
|
||||
.find(|p| &p.public_key == &peer_id)
|
||||
.is_none()
|
||||
{
|
||||
return Err(SetPskError::NoSuchPeer);
|
||||
}
|
||||
|
||||
// Peer update description
|
||||
let mut set_peer = wireguard_uapi::set::Peer::from_public_key(&peer_id);
|
||||
set_peer
|
||||
.flags
|
||||
.push(wireguard_uapi::linux::set::WgPeerF::UpdateOnly);
|
||||
set_peer.preshared_key = Some(&psk);
|
||||
|
||||
// Device update description
|
||||
let mut set_dev = wireguard_uapi::set::Device::from_ifname(interface.to_owned());
|
||||
set_dev.peers.push(set_peer);
|
||||
|
||||
self.sock.set_device(set_dev)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user