mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
270 Commits
regression
...
systemd-un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3bebb9b9 | ||
|
|
9948169127 | ||
|
|
eced56bd70 | ||
|
|
df1e195b5d | ||
|
|
e1e280c4c5 | ||
|
|
06cd178977 | ||
|
|
e2c46f1ff0 | ||
|
|
c8b804b39d | ||
|
|
e56798b04c | ||
|
|
b76d18e3c8 | ||
|
|
a9792c3143 | ||
|
|
cb2c1c12ee | ||
|
|
08514d69e5 | ||
|
|
baf2d68070 | ||
|
|
cc7f7a4b4d | ||
|
|
5b701631b5 | ||
|
|
402158b706 | ||
|
|
e95636bf70 | ||
|
|
744e2bcf3e | ||
|
|
8c82ca18fb | ||
|
|
208e79c3a7 | ||
|
|
6ee023c9e9 | ||
|
|
6f75d34934 | ||
|
|
6b364a35dc | ||
|
|
2b6d10f0aa | ||
|
|
cb380b89d1 | ||
|
|
f703933e7f | ||
|
|
d02a5d2eb7 | ||
|
|
c7273e6f88 | ||
|
|
85eca49a5b | ||
|
|
9943f1336b | ||
|
|
bb2a0732cc | ||
|
|
1275b992a0 | ||
|
|
196767964f | ||
|
|
d4e9770fe6 | ||
|
|
8e2f6991d1 | ||
|
|
af0db88939 | ||
|
|
6601742903 | ||
|
|
9436281350 | ||
|
|
f3399907b9 | ||
|
|
0cea8c5eff | ||
|
|
5b3f4da23e | ||
|
|
c13badb697 | ||
|
|
cc7757a0db | ||
|
|
d267916445 | ||
|
|
03bc89a582 | ||
|
|
19b31bcdf0 | ||
|
|
939d216027 | ||
|
|
05fbaff2dc | ||
|
|
1d1c0e9da7 | ||
|
|
e19b724673 | ||
|
|
f879ad5020 | ||
|
|
29e7087cb5 | ||
|
|
637a08d222 | ||
|
|
6416c247f4 | ||
|
|
4b3b7e41e4 | ||
|
|
325fb915f0 | ||
|
|
43cb0c09c5 | ||
|
|
0836a2eb28 | ||
|
|
ca7df013d5 | ||
|
|
1209d68718 | ||
|
|
8806494899 | ||
|
|
582d27351a | ||
|
|
61136d79eb | ||
|
|
71bd406201 | ||
|
|
ce63cf534a | ||
|
|
d3ff19bdb9 | ||
|
|
3b6d0822d6 | ||
|
|
533afea129 | ||
|
|
da5b281b96 | ||
|
|
b9e873e534 | ||
|
|
a3b339b180 | ||
|
|
b4347c1382 | ||
|
|
0745019e10 | ||
|
|
2369006342 | ||
|
|
0fa6176d06 | ||
|
|
22bdeaf8f1 | ||
|
|
5731272844 | ||
|
|
bc7cef9de0 | ||
|
|
4cdcc35c3e | ||
|
|
a8f1292cbf | ||
|
|
ae5c5ed2b4 | ||
|
|
c483452a6a | ||
|
|
4ce331d299 | ||
|
|
d81eb7e2ed | ||
|
|
61043500ba | ||
|
|
9c4752559d | ||
|
|
6aec7acdb8 | ||
|
|
337cc1b4b4 | ||
|
|
387a266a49 | ||
|
|
179970b905 | ||
|
|
8b769e04c1 | ||
|
|
810bdf5519 | ||
|
|
d3a666bea0 | ||
|
|
2b8f780584 | ||
|
|
6aea3c0c1f | ||
|
|
e4fdfcae08 | ||
|
|
48e629fff7 | ||
|
|
6321bb36fc | ||
|
|
2f9ff487ba | ||
|
|
c0c06cd1dc | ||
|
|
e9772effa6 | ||
|
|
cf68f15674 | ||
|
|
dd5d45cdc9 | ||
|
|
17a6aed8a6 | ||
|
|
3f9926e353 | ||
|
|
f4ab2ac891 | ||
|
|
de51c1005f | ||
|
|
1e2cd589b1 | ||
|
|
02bc485d97 | ||
|
|
3ae52b9824 | ||
|
|
cbf361206b | ||
|
|
398da99df2 | ||
|
|
acfbb67abe | ||
|
|
c407b8b006 | ||
|
|
bc7213d8c0 | ||
|
|
69e68aad2c | ||
|
|
9b07f5803b | ||
|
|
5ce572b739 | ||
|
|
d9f8fa0092 | ||
|
|
a5208795f6 | ||
|
|
0959148305 | ||
|
|
f2bc3a8b64 | ||
|
|
06529df2c0 | ||
|
|
128c77f77a | ||
|
|
501cc9bb05 | ||
|
|
9ad5277a90 | ||
|
|
0cbcaeaf98 | ||
|
|
687ef3f6f8 | ||
|
|
b0706354d3 | ||
|
|
c1e86daec8 | ||
|
|
18a286e688 | ||
|
|
cb92313391 | ||
|
|
5cd30b4c13 | ||
|
|
76d8d38744 | ||
|
|
f63f0bbc2e | ||
|
|
4a449e6502 | ||
|
|
1e6d2df004 | ||
|
|
3fa9aadda2 | ||
|
|
0c79a4ce95 | ||
|
|
036960b5b1 | ||
|
|
e7258849cb | ||
|
|
8c88f68990 | ||
|
|
cf20536576 | ||
|
|
72e18e3ec2 | ||
|
|
6040156a0e | ||
|
|
d3b318b413 | ||
|
|
3a49345138 | ||
|
|
4ec7813259 | ||
|
|
db31da14d3 | ||
|
|
4c20efc8a8 | ||
|
|
c81d484294 | ||
|
|
cc578169d6 | ||
|
|
91527702f1 | ||
|
|
0179f1c673 | ||
|
|
2238919657 | ||
|
|
d913e19883 | ||
|
|
1555d0897b | ||
|
|
abdbf8f3da | ||
|
|
9f78531979 | ||
|
|
624d8d2f44 | ||
|
|
9bbf9433e6 | ||
|
|
77760d71df | ||
|
|
53e560191f | ||
|
|
93cd266c68 | ||
|
|
594f894206 | ||
|
|
a831e01a5c | ||
|
|
0884641d64 | ||
|
|
ae85d0ed2b | ||
|
|
163f66f20e | ||
|
|
3caff91515 | ||
|
|
24eebe29a1 | ||
|
|
1d2fa7d038 | ||
|
|
edf1e774c1 | ||
|
|
7a31b57227 | ||
|
|
d5a8c85abe | ||
|
|
48f7ff93e3 | ||
|
|
5f6c36e773 | ||
|
|
7b3b7612cf | ||
|
|
c1704b1464 | ||
|
|
2785aaf783 | ||
|
|
15002a74cc | ||
|
|
0fe2d9825b | ||
|
|
ab805dae75 | ||
|
|
08653c3338 | ||
|
|
520c8c6eaa | ||
|
|
258efe408c | ||
|
|
fd0f35b279 | ||
|
|
8808ed5dbc | ||
|
|
6fc45cab53 | ||
|
|
1f7196e473 | ||
|
|
c359b87d0c | ||
|
|
355b48169b | ||
|
|
274d245bed | ||
|
|
065b0fcc8a | ||
|
|
191fb10663 | ||
|
|
3faa84117f | ||
|
|
fda75a0184 | ||
|
|
96b1f6c0d3 | ||
|
|
fb73c68626 | ||
|
|
42b0e23695 | ||
|
|
c58f832727 | ||
|
|
7b6a9eebc1 | ||
|
|
4554dc4bb3 | ||
|
|
465c6beaab | ||
|
|
1853e0a3c0 | ||
|
|
245d4d1a0f | ||
|
|
d5d15cd9bc | ||
|
|
9fd3df67ed | ||
|
|
6d47169a5c | ||
|
|
4bcd38a4ea | ||
|
|
730a03957a | ||
|
|
ea071f5363 | ||
|
|
3063d3e4c2 | ||
|
|
1bf0eed90a | ||
|
|
138e6b6553 | ||
|
|
2dde0a2b47 | ||
|
|
3cc3b6009f | ||
|
|
1ab457ed37 | ||
|
|
c9c266fe7c | ||
|
|
8d3c8790fe | ||
|
|
648a94ead8 | ||
|
|
54ac5eecdb | ||
|
|
40c5bbd167 | ||
|
|
a4b8fc2226 | ||
|
|
37f7b3e4e9 | ||
|
|
deafc1c1af | ||
|
|
6bbe85a57b | ||
|
|
e70c5b33a8 | ||
|
|
25fdfef4d0 | ||
|
|
6ab8fafe59 | ||
|
|
c1aacf76b8 | ||
|
|
1bcaf5781f | ||
|
|
de60e5f8f0 | ||
|
|
b50ddda151 | ||
|
|
7282fba3b3 | ||
|
|
0cca389f10 | ||
|
|
8a08d49215 | ||
|
|
8637bc7884 | ||
|
|
4412c2bdd1 | ||
|
|
ecc815dd8e | ||
|
|
b7d7c03e35 | ||
|
|
f6320c3c35 | ||
|
|
19f7905bc9 | ||
|
|
9b5b7ee620 | ||
|
|
4fdd271de7 | ||
|
|
860e65965a | ||
|
|
87144233da | ||
|
|
d0a6e99a1f | ||
|
|
79b634fadf | ||
|
|
99ac3c0902 | ||
|
|
010c14dadf | ||
|
|
45b6132312 | ||
|
|
77f9fd38f3 | ||
|
|
775ed86adc | ||
|
|
40377dce1f | ||
|
|
19293471e8 | ||
|
|
cc5877dd83 | ||
|
|
ebb591aa6f | ||
|
|
07146d9914 | ||
|
|
cd04dbc4eb | ||
|
|
cc22165dc4 | ||
|
|
8496571765 | ||
|
|
ee3a1f580e | ||
|
|
89584645c3 | ||
|
|
3286e49370 | ||
|
|
100d7b6e1c | ||
|
|
921b2bfc39 | ||
|
|
a18658847c | ||
|
|
bdad414c90 |
14
.ci/boot_race/a.toml
Normal file
14
.ci/boot_race/a.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
public_key = "rp-a-public-key"
|
||||||
|
secret_key = "rp-a-secret-key"
|
||||||
|
listen = ["127.0.0.1:9999"]
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "rp-b-public-key"
|
||||||
|
endpoint = "127.0.0.1:9998"
|
||||||
|
key_out = "rp-b-key-out.txt"
|
||||||
14
.ci/boot_race/b.toml
Normal file
14
.ci/boot_race/b.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
public_key = "rp-b-public-key"
|
||||||
|
secret_key = "rp-b-secret-key"
|
||||||
|
listen = ["127.0.0.1:9998"]
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "rp-a-public-key"
|
||||||
|
endpoint = "127.0.0.1:9999"
|
||||||
|
key_out = "rp-a-key-out.txt"
|
||||||
48
.ci/boot_race/run.sh
Normal file
48
.ci/boot_race/run.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
iterations="$1"
|
||||||
|
sleep_time="$2"
|
||||||
|
config_a="$3"
|
||||||
|
config_b="$4"
|
||||||
|
|
||||||
|
PWD="$(pwd)"
|
||||||
|
EXEC="$PWD/target/release/rosenpass"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while [ "$i" -ne "$iterations" ]; do
|
||||||
|
echo "=> Iteration $i"
|
||||||
|
|
||||||
|
# flush the PSK files
|
||||||
|
echo "A" >rp-a-key-out.txt
|
||||||
|
echo "B" >rp-b-key-out.txt
|
||||||
|
|
||||||
|
# start the two instances
|
||||||
|
echo "Starting instance A"
|
||||||
|
"$EXEC" exchange-config "$config_a" &
|
||||||
|
PID_A=$!
|
||||||
|
sleep "$sleep_time"
|
||||||
|
echo "Starting instance B"
|
||||||
|
"$EXEC" exchange-config "$config_b" &
|
||||||
|
PID_B=$!
|
||||||
|
|
||||||
|
# give the key exchange some time to complete
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# kill the instances
|
||||||
|
kill $PID_A
|
||||||
|
kill $PID_B
|
||||||
|
|
||||||
|
# compare the keys
|
||||||
|
if cmp -s rp-a-key-out.txt rp-b-key-out.txt; then
|
||||||
|
echo "Keys match"
|
||||||
|
else
|
||||||
|
echo "::warning title=Key Exchange Race Condition::The key exchange resulted in different keys. Delay was ${sleep_time}s."
|
||||||
|
# TODO: set this to 1 when the race condition is fixed
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# give the instances some time to shut down
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
33
.ci/run-regression.sh
Executable file
33
.ci/run-regression.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
iterations="$1"
|
||||||
|
sleep_time="$2"
|
||||||
|
|
||||||
|
PWD="$(pwd)"
|
||||||
|
EXEC="$PWD/target/release/rosenpass"
|
||||||
|
LOGS="$PWD/output/logs"
|
||||||
|
|
||||||
|
mkdir -p "$LOGS"
|
||||||
|
|
||||||
|
run_command() {
|
||||||
|
local file=$1
|
||||||
|
local log_file="$2"
|
||||||
|
("$EXEC" exchange-config "$file" 2>&1 | tee -a "$log_file") &
|
||||||
|
echo $!
|
||||||
|
}
|
||||||
|
|
||||||
|
pids=()
|
||||||
|
|
||||||
|
(cd output/dut && run_command "configs/dut-$iterations.toml" "$LOGS/dut.log")
|
||||||
|
for (( x=0; x<iterations; x++ )); do
|
||||||
|
(cd output/ate && run_command "configs/ate-$x.toml" "$LOGS/ate-$x.log") & pids+=($!)
|
||||||
|
done
|
||||||
|
|
||||||
|
sleep "$sleep_time"
|
||||||
|
|
||||||
|
lsof -i :9999 | awk 'NR!=1 {print $2}' | xargs kill
|
||||||
|
|
||||||
|
for (( x=0; x<iterations; x++ )); do
|
||||||
|
port=$((x + 50000))
|
||||||
|
lsof -i :$port | awk 'NR!=1 {print $2}' | xargs kill
|
||||||
|
done
|
||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|||||||
4
.github/workflows/doc-upload.yml
vendored
4
.github/workflows/doc-upload.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Clone rosenpass-website repository
|
- name: Clone rosenpass-website repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: rosenpass/rosenpass-website
|
repository: rosenpass/rosenpass-website
|
||||||
ref: main
|
ref: main
|
||||||
|
|||||||
205
.github/workflows/nix.yaml
vendored
205
.github/workflows/nix.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
i686-linux---default:
|
i686-linux---default:
|
||||||
name: Build i686-linux.default
|
name: Build i686-linux.default
|
||||||
@@ -14,11 +19,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -30,11 +35,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -47,11 +52,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -62,11 +67,11 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -79,11 +84,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-darwin---rosenpass
|
- x86_64-darwin---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -98,11 +103,11 @@ jobs:
|
|||||||
- x86_64-darwin---rp
|
- x86_64-darwin---rp
|
||||||
- x86_64-darwin---rosenpass-oci-image
|
- x86_64-darwin---rosenpass-oci-image
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -114,11 +119,11 @@ jobs:
|
|||||||
- macos-13
|
- macos-13
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -130,11 +135,11 @@ jobs:
|
|||||||
- macos-13
|
- macos-13
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -147,11 +152,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-darwin---rosenpass
|
- x86_64-darwin---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -162,11 +167,11 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- macos-13
|
- macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -179,11 +184,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -196,11 +201,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---proverif-patched
|
- x86_64-linux---proverif-patched
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -212,11 +217,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -231,51 +236,51 @@ jobs:
|
|||||||
- x86_64-linux---rosenpass-static-oci-image
|
- x86_64-linux---rosenpass-static-oci-image
|
||||||
- x86_64-linux---rp-static
|
- x86_64-linux---rp-static
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
|
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
|
||||||
aarch64-linux---release-package:
|
# aarch64-linux---release-package:
|
||||||
name: Build aarch64-linux.release-package
|
# name: Build aarch64-linux.release-package
|
||||||
runs-on:
|
# runs-on:
|
||||||
- ubuntu-latest
|
# - ubuntu-latest
|
||||||
needs:
|
# needs:
|
||||||
- aarch64-linux---rosenpass-oci-image
|
# - aarch64-linux---rosenpass-oci-image
|
||||||
- aarch64-linux---rosenpass
|
# - aarch64-linux---rosenpass
|
||||||
- aarch64-linux---rp
|
# - aarch64-linux---rp
|
||||||
steps:
|
# steps:
|
||||||
- run: |
|
# - run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
# 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
|
# 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: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
# - uses: cachix/install-nix-action@v30
|
||||||
with:
|
# with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
# nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
# extra_nix_config: |
|
||||||
system = aarch64-linux
|
# system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
# - uses: cachix/cachix-action@v15
|
||||||
with:
|
# with:
|
||||||
name: rosenpass
|
# name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build
|
# - name: Build
|
||||||
run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
# run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
||||||
x86_64-linux---rosenpass:
|
x86_64-linux---rosenpass:
|
||||||
name: Build x86_64-linux.rosenpass
|
name: Build x86_64-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -290,13 +295,13 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
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
|
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: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -311,13 +316,13 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
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
|
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: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -330,11 +335,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -350,13 +355,13 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
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
|
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: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -368,11 +373,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -384,11 +389,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -401,11 +406,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass-static
|
- x86_64-linux---rosenpass-static
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -417,11 +422,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -432,11 +437,11 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -447,11 +452,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.ref == 'refs/heads/main' }}
|
if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -460,7 +465,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
||||||
- name: Deploy PDF artifacts
|
- name: Deploy PDF artifacts
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: result/
|
publish_dir: result/
|
||||||
|
|||||||
64
.github/workflows/qc.yaml
vendored
64
.github/workflows/qc.yaml
vendored
@@ -4,6 +4,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -12,8 +16,8 @@ jobs:
|
|||||||
prettier:
|
prettier:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actionsx/prettier@v2
|
- uses: actionsx/prettier@v3
|
||||||
with:
|
with:
|
||||||
args: --check .
|
args: --check .
|
||||||
|
|
||||||
@@ -21,7 +25,7 @@ jobs:
|
|||||||
name: Shellcheck
|
name: Shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: ludeeus/action-shellcheck@master
|
||||||
|
|
||||||
@@ -29,15 +33,15 @@ jobs:
|
|||||||
name: Rust Format
|
name: Rust Format
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run Rust Formatting Script
|
- name: Run Rust Formatting Script
|
||||||
run: bash format_rust_code.sh --mode check
|
run: bash format_rust_code.sh --mode check
|
||||||
|
|
||||||
cargo-bench:
|
cargo-bench:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -57,16 +61,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install mandoc
|
- name: Install mandoc
|
||||||
run: sudo apt-get install -y mandoc
|
run: sudo apt-get install -y mandoc
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Check rosenpass.1
|
|
||||||
run: doc/check.sh doc/rosenpass.1
|
|
||||||
- name: Check rp.1
|
- name: Check rp.1
|
||||||
run: doc/check.sh doc/rp.1
|
run: doc/check.sh doc/rp.1
|
||||||
|
|
||||||
cargo-audit:
|
cargo-audit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rs/audit-check@v1
|
- uses: actions-rs/audit-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -74,8 +76,8 @@ jobs:
|
|||||||
cargo-clippy:
|
cargo-clippy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -93,8 +95,8 @@ jobs:
|
|||||||
cargo-doc:
|
cargo-doc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -110,10 +112,15 @@ jobs:
|
|||||||
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
|
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
|
||||||
|
|
||||||
cargo-test:
|
cargo-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-13]
|
||||||
|
# - ubuntu is x86-64
|
||||||
|
# - macos-13 is also x86-64 architecture
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -131,8 +138,8 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -141,10 +148,10 @@ jobs:
|
|||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
- uses: cachix/install-nix-action@v21
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -153,8 +160,8 @@ jobs:
|
|||||||
cargo-fuzz:
|
cargo-fuzz:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -186,17 +193,20 @@ jobs:
|
|||||||
codecov:
|
codecov:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: rustup component add llvm-tools-preview
|
- run: rustup component add llvm-tools-preview
|
||||||
- run: |
|
- run: |
|
||||||
cargo install cargo-llvm-cov || true
|
cargo install cargo-llvm-cov || true
|
||||||
cargo llvm-cov --lcov --output-path coverage.lcov
|
cargo llvm-cov \
|
||||||
|
--workspace\
|
||||||
|
--all-features \
|
||||||
|
--lcov \
|
||||||
|
--output-path coverage.lcov
|
||||||
# If using tarapulin
|
# If using tarapulin
|
||||||
#- run: cargo install cargo-tarpaulin
|
#- run: cargo install cargo-tarpaulin
|
||||||
#- run: cargo tarpaulin --out Xml
|
#- run: cargo tarpaulin --out Xml
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
uses: codecov/codecov-action@v4.0.1
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
files: ./coverage.lcov
|
files: ./coverage.lcov
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|||||||
37
.github/workflows/regressions.yml
vendored
Normal file
37
.github/workflows/regressions.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Regressions
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
multi-peer:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: cargo build --bin rosenpass --release
|
||||||
|
- run: python misc/generate_configs.py
|
||||||
|
- run: chmod +x .ci/run-regression.sh
|
||||||
|
- run: .ci/run-regression.sh 100 20
|
||||||
|
- run: |
|
||||||
|
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
||||||
|
|
||||||
|
boot-race:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: cargo build --bin rosenpass --release
|
||||||
|
- run: chmod +x .ci/boot_race/run.sh
|
||||||
|
- run: cargo run --release --bin rosenpass gen-keys .ci/boot_race/a.toml
|
||||||
|
- run: cargo run --release --bin rosenpass gen-keys .ci/boot_race/b.toml
|
||||||
|
- run: .ci/boot_race/run.sh 5 2 .ci/boot_race/a.toml .ci/boot_race/b.toml
|
||||||
|
- run: .ci/boot_race/run.sh 5 1 .ci/boot_race/a.toml .ci/boot_race/b.toml
|
||||||
|
- run: .ci/boot_race/run.sh 5 0 .ci/boot_race/a.toml .ci/boot_race/b.toml
|
||||||
24
.github/workflows/release.yaml
vendored
24
.github/workflows/release.yaml
vendored
@@ -11,18 +11,18 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
@@ -32,18 +32,18 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- macos-13
|
- macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
@@ -53,18 +53,18 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -20,3 +20,8 @@ _markdown_*
|
|||||||
**/result
|
**/result
|
||||||
**/result-*
|
**/result-*
|
||||||
.direnv
|
.direnv
|
||||||
|
|
||||||
|
# Visual studio code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
/output
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.direnv/
|
.direnv/
|
||||||
|
flake.lock
|
||||||
papers/whitepaper.md
|
papers/whitepaper.md
|
||||||
target/
|
|
||||||
src/usage.md
|
src/usage.md
|
||||||
|
target/
|
||||||
|
|||||||
38
CONTRIBUTING.md
Normal file
38
CONTRIBUTING.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
**Making a new Release of Rosenpass — Cooking Recipe**
|
||||||
|
|
||||||
|
If you have to change a file, do what it takes to get the change as commit on the main branch, then **start from step 0**.
|
||||||
|
If any other issue occurs
|
||||||
|
|
||||||
|
0. Make sure you are in the root directory of the project
|
||||||
|
- `cd "$(git rev-parse --show-toplevel)"`
|
||||||
|
1. Make sure you locally checked out the head of the main branch
|
||||||
|
- `git stash --include-untracked && git checkout main && git pull`
|
||||||
|
2. Make sure all tests pass
|
||||||
|
- `cargo test --workspace --all-features`
|
||||||
|
3. Make sure the current version in `rosenpass/Cargo.toml` matches that in the [last release on GitHub](https://github.com/rosenpass/rosenpass/releases)
|
||||||
|
- Only normal releases count, release candidates and draft releases can be ignored
|
||||||
|
4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)
|
||||||
|
- See `cargo release --help` for more information on the available release types
|
||||||
|
- Pick `rc` if in doubt
|
||||||
|
5. Try to release a new version
|
||||||
|
- `cargo release rc --package rosenpass`
|
||||||
|
- An issue was reported? Go fix it, start again with step 0!
|
||||||
|
6. Actually make the release
|
||||||
|
- `cargo release rc --package rosenpass --execute`
|
||||||
|
- Tentatively wait for any interactions, such as entering ssh keys etc.
|
||||||
|
- You may be asked for your ssh key multiple times!
|
||||||
|
|
||||||
|
**Frequently Asked Questions (FAQ)**
|
||||||
|
|
||||||
|
- You have untracked files, which `cargo release` complains about?
|
||||||
|
- `git stash --include-untracked`
|
||||||
|
- You cannot push to crates.io because you are not logged in?
|
||||||
|
- Follow the steps displayed in [`cargo login`](https://doc.rust-lang.org/cargo/commands/cargo-login.html)
|
||||||
|
- How is the release page added to [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) itself?
|
||||||
|
- Our CI Pipeline will create the release, once `cargo release` pushed the new version tag to the repo. The new release should pop up almost immediately in [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) after the [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml) pipeline started.
|
||||||
|
- No new release pops up in the `Release` sidebar element on the [main page](https://github.com/rosenpass/rosenpass)
|
||||||
|
- Did you push a `rc` release? This view only shows non-draft release, but `rc` releases are considered as draft. See [Releases](https://github.com/rosenpass/rosenpass/releases) page to see all (including draft!) releases.
|
||||||
|
- The release page was created on GitHub, but there are no assets/artifacts other than the source code tar ball/zip?
|
||||||
|
- The artifacts are generated and pushed automatically to the release, but this takes some time (a couple of minutes). You can check the respective CI pipeline: [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml), which should start immediately after `cargo release` pushed the new release tag to the repo. The release artifacts only are added later to the release, once all jobs in bespoke pipeline finished.
|
||||||
|
- How are the release artifacts generated, and what are they?
|
||||||
|
- The release artifacts are built using one Nix derivation per platform, `nix build .#release-package`. It contains both statically linked versions of `rosenpass` itself and OCI container images.
|
||||||
1072
Cargo.lock
generated
1072
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
46
Cargo.toml
46
Cargo.toml
@@ -32,24 +32,28 @@ rosenpass-secret-memory = { path = "secret-memory" }
|
|||||||
rosenpass-oqs = { path = "oqs" }
|
rosenpass-oqs = { path = "oqs" }
|
||||||
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
||||||
doc-comment = "0.3.3"
|
doc-comment = "0.3.3"
|
||||||
base64ct = {version = "1.6.0", default-features=false}
|
base64ct = { version = "1.6.0", default-features = false }
|
||||||
zeroize = "1.8.1"
|
zeroize = "1.8.1"
|
||||||
memoffset = "0.9.1"
|
memoffset = "0.9.1"
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.69"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
env_logger = "0.10.2"
|
env_logger = "0.10.2"
|
||||||
toml = "0.7.8"
|
toml = "0.7.8"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
allocator-api2 = "0.2.14"
|
allocator-api2 = "0.2.14"
|
||||||
memsec = { git="https://github.com/rosenpass/memsec.git" ,rev="aceb9baee8aec6844125bd6612f92e9a281373df", features = [ "alloc_ext", ] }
|
memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec6844125bd6612f92e9a281373df", features = [
|
||||||
|
"alloc_ext",
|
||||||
|
] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
typenum = "1.17.0"
|
typenum = "1.17.0"
|
||||||
log = { version = "0.4.21" }
|
log = { version = "0.4.22" }
|
||||||
clap = { version = "4.5.7", features = ["derive"] }
|
clap = { version = "4.5.22", features = ["derive"] }
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
clap_mangen = "0.2.24"
|
||||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
clap_complete = "4.5.38"
|
||||||
anyhow = { version = "1.0.86", features = ["backtrace", "std"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
mio = { version = "0.8.11", features = ["net", "os-poll"] }
|
arbitrary = { version = "1.4.1", features = ["derive"] }
|
||||||
|
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
|
||||||
|
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
||||||
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||||
'classic_mceliece',
|
'classic_mceliece',
|
||||||
'kyber',
|
'kyber',
|
||||||
@@ -59,24 +63,30 @@ chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
|||||||
"std",
|
"std",
|
||||||
"heapless",
|
"heapless",
|
||||||
] }
|
] }
|
||||||
zerocopy = { version = "0.7.34", features = ["derive"] }
|
zerocopy = { version = "0.7.35", features = ["derive"] }
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
derive_builder = "0.20.0"
|
derive_builder = "0.20.1"
|
||||||
tokio = { version = "1.38", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] }
|
||||||
postcard= {version = "1.0.8", features = ["alloc"]}
|
postcard = { version = "1.1.1", features = ["alloc"] }
|
||||||
|
libcrux = { version = "0.0.2-pre.2" }
|
||||||
|
hex-literal = { version = "0.4.1" }
|
||||||
|
hex = { version = "0.4.3" }
|
||||||
|
heck = { version = "0.5.0" }
|
||||||
|
libc = { version = "0.2" }
|
||||||
|
uds = { git = "https://github.com/rosenpass/uds" }
|
||||||
|
|
||||||
#Dev dependencies
|
#Dev dependencies
|
||||||
serial_test = "3.1.1"
|
serial_test = "3.2.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
stacker = "0.1.15"
|
stacker = "0.1.17"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
test_bin = "0.4.0"
|
test_bin = "0.4.0"
|
||||||
criterion = "0.4.0"
|
criterion = "0.4.0"
|
||||||
allocator-api2-tests = "0.2.15"
|
allocator-api2-tests = "0.2.15"
|
||||||
procspawn = {version = "1.0.0", features= ["test-support"]}
|
procspawn = { version = "1.0.1", features = ["test-support"] }
|
||||||
|
|
||||||
|
|
||||||
#Broker dependencies (might need cleanup or changes)
|
#Broker dependencies (might need cleanup or changes)
|
||||||
wireguard-uapi = "3.0.0"
|
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||||
command-fds = "0.2.3"
|
command-fds = "0.2.3"
|
||||||
rustix = { version = "0.38.27", features = ["net"] }
|
rustix = { version = "0.38.41", features = ["net", "fs"] }
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
|
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
|
||||||
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
|
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
|
||||||
|
|
||||||
use std::result::Result;
|
|
||||||
|
|
||||||
/// Key Encapsulation Mechanism
|
/// Key Encapsulation Mechanism
|
||||||
///
|
///
|
||||||
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ homepage = "https://rosenpass.eu/"
|
|||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
experiment_libcrux = ["dep:libcrux"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
rosenpass-to = { workspace = true }
|
rosenpass-to = { workspace = true }
|
||||||
@@ -20,3 +23,4 @@ static_assertions = { workspace = true }
|
|||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
chacha20poly1305 = { workspace = true }
|
chacha20poly1305 = { workspace = true }
|
||||||
blake2 = { workspace = true }
|
blake2 = { workspace = true }
|
||||||
|
libcrux = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
|||||||
|
|
||||||
/// Authenticated encryption with associated data
|
/// Authenticated encryption with associated data
|
||||||
pub mod aead {
|
pub mod aead {
|
||||||
|
#[cfg(not(feature = "experiment_libcrux"))]
|
||||||
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
|
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||||
|
#[cfg(feature = "experiment_libcrux")]
|
||||||
|
pub use crate::subtle::chacha20poly1305_ietf_libcrux::{
|
||||||
|
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticated encryption with associated data with a constant nonce
|
/// Authenticated encryption with associated data with a constant nonce
|
||||||
|
|||||||
60
ciphers/src/subtle/chacha20poly1305_ietf_libcrux.rs
Normal file
60
ciphers/src/subtle/chacha20poly1305_ietf_libcrux.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use rosenpass_to::ops::copy_slice;
|
||||||
|
use rosenpass_to::To;
|
||||||
|
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
|
||||||
|
pub const TAG_LEN: usize = 16;
|
||||||
|
pub const NONCE_LEN: usize = 12;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn encrypt(
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: &[u8],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
|
||||||
|
use libcrux::aead as C;
|
||||||
|
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||||
|
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||||
|
|
||||||
|
copy_slice(plaintext).to(ciphertext);
|
||||||
|
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
|
||||||
|
copy_slice(crux_tag.as_ref()).to(mac);
|
||||||
|
|
||||||
|
match crux_key {
|
||||||
|
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn decrypt(
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: &[u8],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
|
||||||
|
use libcrux::aead as C;
|
||||||
|
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||||
|
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||||
|
let crux_tag = C::Tag::from_slice(mac).unwrap();
|
||||||
|
|
||||||
|
copy_slice(ciphertext).to(plaintext);
|
||||||
|
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
|
||||||
|
|
||||||
|
match crux_key {
|
||||||
|
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
pub mod blake2b;
|
pub mod blake2b;
|
||||||
|
#[cfg(not(feature = "experiment_libcrux"))]
|
||||||
pub mod chacha20poly1305_ietf;
|
pub mod chacha20poly1305_ietf;
|
||||||
|
#[cfg(feature = "experiment_libcrux")]
|
||||||
|
pub mod chacha20poly1305_ietf_libcrux;
|
||||||
pub mod incorrect_hmac_blake2b;
|
pub mod incorrect_hmac_blake2b;
|
||||||
pub mod xchacha20poly1305_ietf;
|
pub mod xchacha20poly1305_ietf;
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
//! Constant-time comparison
|
||||||
|
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
|
||||||
/// Little endian memcmp version of quinier/memsec
|
/// Little endian memcmp version of quinier/memsec
|
||||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
||||||
|
///
|
||||||
|
/// # Panic & Safety
|
||||||
|
///
|
||||||
|
/// Both input arrays must be at least of the indicated length.
|
||||||
|
///
|
||||||
|
/// See [std::ptr::read_volatile] on safety.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||||
let mut res = 0;
|
let mut res = 0;
|
||||||
@@ -13,6 +21,16 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
|||||||
((res - 1) >> 8) + (res >> 8) + 1
|
((res - 1) >> 8) + (res >> 8) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn memcmp_le_test() {
|
||||||
|
// use rosenpass_constant_time::memcmp_le;
|
||||||
|
let a = [0, 1, 0, 0];
|
||||||
|
let b = [0, 0, 0, 1];
|
||||||
|
assert_eq!(-1, unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), 4) });
|
||||||
|
assert_eq!(0, unsafe { memcmp_le(a.as_ptr(), a.as_ptr(), 4) });
|
||||||
|
assert_eq!(1, unsafe { memcmp_le(b.as_ptr(), a.as_ptr(), 4) });
|
||||||
|
}
|
||||||
|
|
||||||
/// compares two slices of memory content and returns an integer indicating the relationship between
|
/// compares two slices of memory content and returns an integer indicating the relationship between
|
||||||
/// the slices
|
/// the slices
|
||||||
///
|
///
|
||||||
@@ -32,6 +50,28 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
|||||||
/// ## Tests
|
/// ## Tests
|
||||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rosenpass_constant_time::compare;
|
||||||
|
/// let a = [0, 1, 0, 0];
|
||||||
|
/// let b = [0, 0, 0, 1];
|
||||||
|
/// assert_eq!(-1, compare(&a, &b));
|
||||||
|
/// assert_eq!(0, compare(&a, &a));
|
||||||
|
/// assert_eq!(1, compare(&b, &a));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Panic
|
||||||
|
///
|
||||||
|
/// This function will panic if the input arrays are of different lengths.
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// use rosenpass_constant_time::compare;
|
||||||
|
/// let a = [0, 1, 0];
|
||||||
|
/// let b = [0, 0, 0, 1];
|
||||||
|
/// compare(&a, &b);
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||||
assert!(a.len() == b.len());
|
assert!(a.len() == b.len());
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Incrementing numbers
|
||||||
|
|
||||||
use core::hint::black_box;
|
use core::hint::black_box;
|
||||||
|
|
||||||
/// Interpret the given slice as a little-endian unsigned integer
|
/// Interpret the given slice as a little-endian unsigned integer
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(clippy::missing_docs_in_private_items)]
|
||||||
//! constant-time implementations of some primitives
|
//! constant-time implementations of some primitives
|
||||||
//!
|
//!
|
||||||
//! Rosenpass internal library providing basic constant-time operations.
|
//! Rosenpass internal library providing basic constant-time operations.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! memcmp
|
||||||
|
|
||||||
/// compares two sclices of memory content and returns whether they are equal
|
/// compares two sclices of memory content and returns whether they are equal
|
||||||
///
|
///
|
||||||
/// ## Leaks
|
/// ## Leaks
|
||||||
@@ -8,17 +10,27 @@
|
|||||||
/// The execution time of the function grows approx. linear with the length of the input. This is
|
/// The execution time of the function grows approx. linear with the length of the input. This is
|
||||||
/// considered safe.
|
/// considered safe.
|
||||||
///
|
///
|
||||||
/// ## Tests
|
/// ## Examples
|
||||||
/// [`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,
|
/// ```rust
|
||||||
/// see <https://github.com/rosenpass/rosenpass/issues/232>
|
/// use rosenpass_constant_time::memcmp;
|
||||||
|
/// let a = [0, 0, 0, 0];
|
||||||
|
/// let b = [0, 0, 0, 1];
|
||||||
|
/// let c = [0, 0, 0];
|
||||||
|
/// assert!(memcmp(&a, &a));
|
||||||
|
/// assert!(!memcmp(&a, &b));
|
||||||
|
/// assert!(!memcmp(&a, &c));
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||||
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
|
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [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>
|
||||||
#[cfg(all(test, feature = "constant_time_tests"))]
|
#[cfg(all(test, feature = "constant_time_tests"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! xor
|
||||||
|
|
||||||
use core::hint::black_box;
|
use core::hint::black_box;
|
||||||
use rosenpass_to::{with_destination, To};
|
use rosenpass_to::{with_destination, To};
|
||||||
|
|
||||||
|
|||||||
114
doc/rosenpass.1
114
doc/rosenpass.1
@@ -1,114 +0,0 @@
|
|||||||
.Dd $Mdocdate$
|
|
||||||
.Dt ROSENPASS 1
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm rosenpass
|
|
||||||
.Nd builds post-quantum-secure VPNs
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm
|
|
||||||
.Op COMMAND
|
|
||||||
.Op Ar OPTIONS ...
|
|
||||||
.Op Ar ARGS ...
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
.Nm
|
|
||||||
performs cryptographic key exchanges that are secure against quantum-computers
|
|
||||||
and then outputs the keys.
|
|
||||||
These keys can then be passed to various services, such as wireguard or other
|
|
||||||
vpn services, as pre-shared-keys to achieve security against attackers with
|
|
||||||
quantum computers.
|
|
||||||
.Pp
|
|
||||||
This is a research project and quantum computers are not thought to become
|
|
||||||
practical in fewer than ten years.
|
|
||||||
If you are not specifically tasked with developing post-quantum secure systems,
|
|
||||||
you probably do not need this tool.
|
|
||||||
.Ss COMMANDS
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Ar gen-keys --secret-key <file-path> --public-key <file-path>
|
|
||||||
Generate a keypair to use in the exchange command later.
|
|
||||||
Send the public-key file to your communication partner and keep the private-key
|
|
||||||
file secret!
|
|
||||||
.It Ar exchange private-key <file-path> public-key <file-path> [ OPTIONS ] PEERS
|
|
||||||
Start a process to exchange keys with the specified peers.
|
|
||||||
You should specify at least one peer.
|
|
||||||
.Pp
|
|
||||||
Its
|
|
||||||
.Ar OPTIONS
|
|
||||||
are as follows:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Ar listen <ip>[:<port>]
|
|
||||||
Instructs
|
|
||||||
.Nm
|
|
||||||
to listen on the specified interface and port.
|
|
||||||
By default,
|
|
||||||
.Nm
|
|
||||||
will listen on all interfaces and select a random port.
|
|
||||||
.It Ar verbose
|
|
||||||
Extra logging.
|
|
||||||
.El
|
|
||||||
.El
|
|
||||||
.Ss PEER
|
|
||||||
Each
|
|
||||||
.Ar PEER
|
|
||||||
is defined as follows:
|
|
||||||
.Qq peer public-key <file-path> [endpoint <ip>[:<port>]] [preshared-key <file-path>] [outfile <file-path>] [wireguard <dev> <peer> <extra_params>]
|
|
||||||
.Pp
|
|
||||||
Providing a
|
|
||||||
.Ar PEER
|
|
||||||
instructs
|
|
||||||
.Nm
|
|
||||||
to exchange keys with the given peer and write the resulting PSK into the given
|
|
||||||
output file.
|
|
||||||
You must either specify the outfile or wireguard output option.
|
|
||||||
.Pp
|
|
||||||
The parameters of
|
|
||||||
.Ar PEER
|
|
||||||
are as follows:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Ar endpoint <ip>[:<port>]
|
|
||||||
Specifies the address where the peer can be reached.
|
|
||||||
This will be automatically updated after the first successful key exchange with
|
|
||||||
the peer.
|
|
||||||
If this is unspecified, the peer must initiate the connection.
|
|
||||||
.It Ar preshared-key <file-path>
|
|
||||||
You may specify a pre-shared key which will be mixed into the final secret.
|
|
||||||
.It Ar outfile <file-path>
|
|
||||||
You may specify a file to write the exchanged keys to.
|
|
||||||
If this option is specified,
|
|
||||||
.Nm
|
|
||||||
will write a notification to standard out every time the key is updated.
|
|
||||||
.It Ar wireguard <dev> <peer> <extra_params>
|
|
||||||
This allows you to directly specify a wireguard peer to deploy the
|
|
||||||
pre-shared-key to.
|
|
||||||
You may specify extra parameters you would pass to
|
|
||||||
.Qq wg set
|
|
||||||
besides the preshared-key parameter which is used by
|
|
||||||
.Nm .
|
|
||||||
This makes it possible to add peers entirely from
|
|
||||||
.Nm .
|
|
||||||
.El
|
|
||||||
.Sh EXIT STATUS
|
|
||||||
.Ex -std
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr rp 1 ,
|
|
||||||
.Xr wg 1
|
|
||||||
.Rs
|
|
||||||
.%A Karolin Varner
|
|
||||||
.%A Benjamin Lipp
|
|
||||||
.%A Wanja Zaeske
|
|
||||||
.%A Lisa Schmidt
|
|
||||||
.%D 2023
|
|
||||||
.%T Rosenpass
|
|
||||||
.%U https://rosenpass.eu/whitepaper.pdf
|
|
||||||
.Re
|
|
||||||
.Sh STANDARDS
|
|
||||||
This tool is the reference implementation of the Rosenpass protocol, as
|
|
||||||
specified within the whitepaper referenced above.
|
|
||||||
.Sh AUTHORS
|
|
||||||
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
|
|
||||||
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
|
||||||
.Pp
|
|
||||||
This manual page was written by
|
|
||||||
.An Emil Engler
|
|
||||||
.Sh BUGS
|
|
||||||
The bugs are tracked at
|
|
||||||
.Lk https://github.com/rosenpass/rosenpass/issues .
|
|
||||||
2
doc/rp.1
2
doc/rp.1
@@ -113,7 +113,7 @@ Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
|
|||||||
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
||||||
.Pp
|
.Pp
|
||||||
This manual page was written by
|
This manual page was written by
|
||||||
.An Emil Engler
|
.An Clara Engler
|
||||||
.Sh BUGS
|
.Sh BUGS
|
||||||
The bugs are tracked at
|
The bugs are tracked at
|
||||||
.Lk https://github.com/rosenpass/rosenpass/issues .
|
.Lk https://github.com/rosenpass/rosenpass/issues .
|
||||||
|
|||||||
49
flake.lock
generated
49
flake.lock
generated
@@ -2,15 +2,17 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"fenix": {
|
"fenix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": ["nixpkgs"],
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712298178,
|
"lastModified": 1728282832,
|
||||||
"narHash": "sha256-590fpCPXYAkaAeBz/V91GX4/KGzPObdYtqsTWzT6AhI=",
|
"narHash": "sha256-I7AbcwGggf+CHqpyd/9PiAjpIBGTGx5woYHqtwxaV7I=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "569b5b5781395da08e7064e825953c548c26af76",
|
"rev": "1ec71be1f4b8f3105c5d38da339cb061fefc43f4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -24,11 +26,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1726560853,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -37,36 +39,18 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"naersk": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": ["nixpkgs"]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1698420672,
|
|
||||||
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712168706,
|
"lastModified": 1728193676,
|
||||||
"narHash": "sha256-XP24tOobf6GGElMd0ux90FEBalUtw6NkBSVh/RlA6ik=",
|
"narHash": "sha256-PbDWAIjKJdlVg+qQRhzdSor04bAPApDqIv2DofTyynk=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1487bdea619e4a7a53a4590c475deabb5a9d1bfb",
|
"rev": "ecbc1ca8ffd6aea8372ad16be9ebbb39889e55b6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-23.11",
|
"ref": "nixos-24.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -75,18 +59,17 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"naersk": "naersk",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712156296,
|
"lastModified": 1728249780,
|
||||||
"narHash": "sha256-St7ZQrkrr5lmQX9wC1ZJAFxL8W7alswnyZk9d1se3Us=",
|
"narHash": "sha256-J269DvCI5dzBmPrXhAAtj566qt0b22TJtF3TIK+tMsI=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "8e581ac348e223488622f4d3003cb2bd412bf27e",
|
"rev": "2b750da1a1a2c1d2c70896108d7096089842d877",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
413
flake.nix
413
flake.nix
@@ -1,12 +1,8 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
# for quicker rust builds
|
|
||||||
naersk.url = "github:nix-community/naersk";
|
|
||||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
|
|
||||||
# for rust nightly with llvm-tools-preview
|
# for rust nightly with llvm-tools-preview
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -15,6 +11,15 @@
|
|||||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
||||||
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
### Export the overlay.nix from this flake ###
|
||||||
|
#
|
||||||
|
{
|
||||||
|
overlays.default = import ./overlay.nix;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
### Actual Rosenpass Package and Docker Container Images ###
|
### Actual Rosenpass Package and Docker Container Images ###
|
||||||
#
|
#
|
||||||
@@ -30,310 +35,39 @@
|
|||||||
]
|
]
|
||||||
(system:
|
(system:
|
||||||
let
|
let
|
||||||
scoped = (scope: scope.result);
|
|
||||||
lib = nixpkgs.lib;
|
|
||||||
|
|
||||||
# normal nixpkgs
|
# normal nixpkgs
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
};
|
|
||||||
|
|
||||||
# parsed Cargo.toml
|
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||||
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
|
overlays = [ self.overlays.default ];
|
||||||
|
|
||||||
# source files relevant for rust
|
|
||||||
src = scoped rec {
|
|
||||||
# File suffices to include
|
|
||||||
extensions = [
|
|
||||||
"lock"
|
|
||||||
"rs"
|
|
||||||
"toml"
|
|
||||||
];
|
|
||||||
# Files to explicitly include
|
|
||||||
files = [
|
|
||||||
"to/README.md"
|
|
||||||
];
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
filter = (path: type: scoped rec {
|
|
||||||
inherit (lib) any id removePrefix hasSuffix;
|
|
||||||
anyof = (any id);
|
|
||||||
|
|
||||||
basename = baseNameOf (toString path);
|
|
||||||
relative = removePrefix (toString src + "/") (toString path);
|
|
||||||
|
|
||||||
result = anyof [
|
|
||||||
(type == "directory")
|
|
||||||
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
|
||||||
(any (file: file == relative) files)
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
result = pkgs.lib.sources.cleanSourceWith { inherit src filter; };
|
|
||||||
};
|
|
||||||
|
|
||||||
# a function to generate a nix derivation for rosenpass against any
|
|
||||||
# given set of nixpkgs
|
|
||||||
rosenpassDerivation = p:
|
|
||||||
let
|
|
||||||
# whether we want to build a statically linked binary
|
|
||||||
isStatic = p.targetPlatform.isStatic;
|
|
||||||
|
|
||||||
# the rust target of `p`
|
|
||||||
target = p.rust.toRustTargetSpec p.targetPlatform;
|
|
||||||
|
|
||||||
# convert a string to shout case
|
|
||||||
shout = string: builtins.replaceStrings [ "-" ] [ "_" ] (pkgs.lib.toUpper string);
|
|
||||||
|
|
||||||
# suitable Rust toolchain
|
|
||||||
toolchain = with inputs.fenix.packages.${system}; combine [
|
|
||||||
stable.cargo
|
|
||||||
stable.rustc
|
|
||||||
targets.${target}.stable.rust-std
|
|
||||||
];
|
|
||||||
|
|
||||||
# naersk with a custom toolchain
|
|
||||||
naersk = pkgs.callPackage inputs.naersk {
|
|
||||||
cargo = toolchain;
|
|
||||||
rustc = toolchain;
|
|
||||||
};
|
|
||||||
|
|
||||||
# used to trick the build.rs into believing that CMake was ran **again**
|
|
||||||
fakecmake = pkgs.writeScriptBin "cmake" ''
|
|
||||||
#! ${pkgs.stdenv.shell} -e
|
|
||||||
true
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
naersk.buildPackage
|
|
||||||
{
|
|
||||||
# metadata and source
|
|
||||||
name = cargoToml.package.name;
|
|
||||||
version = cargoToml.package.version;
|
|
||||||
inherit src;
|
|
||||||
|
|
||||||
cargoBuildOptions = x: x ++ [ "-p" "rosenpass" ];
|
|
||||||
cargoTestOptions = x: x ++ [ "-p" "rosenpass" ];
|
|
||||||
|
|
||||||
doCheck = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
|
||||||
p.stdenv.cc
|
|
||||||
cmake # for oqs build in the oqs-sys crate
|
|
||||||
mandoc # for the built-in manual
|
|
||||||
removeReferencesTo
|
|
||||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
|
||||||
];
|
|
||||||
buildInputs = with p; [ bash ];
|
|
||||||
|
|
||||||
override = x: {
|
|
||||||
preBuild =
|
|
||||||
# nix defaults to building for aarch64 _without_ the armv8-a crypto
|
|
||||||
# extensions, but liboqs depens on these
|
|
||||||
(lib.optionalString (system == "aarch64-linux") ''
|
|
||||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -march=armv8-a+crypto"
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
# fortify is only compatible with dynamic linking
|
|
||||||
hardeningDisable = lib.optional isStatic "fortify";
|
|
||||||
};
|
|
||||||
|
|
||||||
overrideMain = x: {
|
|
||||||
# CMake detects that it was served a _foreign_ target dir, and CMake
|
|
||||||
# would be executed again upon the second build step of naersk.
|
|
||||||
# By adding our specially optimized CMake version, we reduce the cost
|
|
||||||
# of recompilation by 99 % while, while avoiding any CMake errors.
|
|
||||||
nativeBuildInputs = [ (lib.hiPrio fakecmake) ] ++ x.nativeBuildInputs;
|
|
||||||
|
|
||||||
# make sure that libc is linked, under musl this is not the case per
|
|
||||||
# default
|
|
||||||
preBuild = (lib.optionalString isStatic ''
|
|
||||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -lc"
|
|
||||||
'');
|
|
||||||
};
|
|
||||||
|
|
||||||
# We want to build for a specific target...
|
|
||||||
CARGO_BUILD_TARGET = target;
|
|
||||||
|
|
||||||
# ... which might require a non-default linker:
|
|
||||||
"CARGO_TARGET_${shout target}_LINKER" =
|
|
||||||
let
|
|
||||||
inherit (p.stdenv) cc;
|
|
||||||
in
|
|
||||||
"${cc}/bin/${cc.targetPrefix}cc";
|
|
||||||
|
|
||||||
meta = with pkgs.lib;
|
|
||||||
{
|
|
||||||
inherit (cargoToml.package) description homepage;
|
|
||||||
license = with licenses; [ mit asl20 ];
|
|
||||||
maintainers = [ maintainers.wucke13 ];
|
|
||||||
platforms = platforms.all;
|
|
||||||
};
|
|
||||||
} // (lib.mkIf isStatic {
|
|
||||||
# otherwise pkg-config tries to link non-existent dynamic libs
|
|
||||||
# documented here: https://docs.rs/pkg-config/latest/pkg_config/
|
|
||||||
PKG_CONFIG_ALL_STATIC = true;
|
|
||||||
|
|
||||||
# tell rust to build everything statically linked
|
|
||||||
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
|
|
||||||
});
|
|
||||||
# a function to generate a nix derivation for the rp helper against any
|
|
||||||
# given set of nixpkgs
|
|
||||||
rpDerivation = p:
|
|
||||||
let
|
|
||||||
# whether we want to build a statically linked binary
|
|
||||||
isStatic = p.targetPlatform.isStatic;
|
|
||||||
|
|
||||||
# the rust target of `p`
|
|
||||||
target = p.rust.toRustTargetSpec p.targetPlatform;
|
|
||||||
|
|
||||||
# convert a string to shout case
|
|
||||||
shout = string: builtins.replaceStrings [ "-" ] [ "_" ] (pkgs.lib.toUpper string);
|
|
||||||
|
|
||||||
# suitable Rust toolchain
|
|
||||||
toolchain = with inputs.fenix.packages.${system}; combine [
|
|
||||||
stable.cargo
|
|
||||||
stable.rustc
|
|
||||||
targets.${target}.stable.rust-std
|
|
||||||
];
|
|
||||||
|
|
||||||
# naersk with a custom toolchain
|
|
||||||
naersk = pkgs.callPackage inputs.naersk {
|
|
||||||
cargo = toolchain;
|
|
||||||
rustc = toolchain;
|
|
||||||
};
|
|
||||||
|
|
||||||
# used to trick the build.rs into believing that CMake was ran **again**
|
|
||||||
fakecmake = pkgs.writeScriptBin "cmake" ''
|
|
||||||
#! ${pkgs.stdenv.shell} -e
|
|
||||||
true
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
naersk.buildPackage
|
|
||||||
{
|
|
||||||
# metadata and source
|
|
||||||
name = cargoToml.package.name;
|
|
||||||
version = cargoToml.package.version;
|
|
||||||
inherit src;
|
|
||||||
|
|
||||||
cargoBuildOptions = x: x ++ [ "-p" "rp" ];
|
|
||||||
cargoTestOptions = x: x ++ [ "-p" "rp" ];
|
|
||||||
|
|
||||||
doCheck = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
|
||||||
p.stdenv.cc
|
|
||||||
cmake # for oqs build in the oqs-sys crate
|
|
||||||
mandoc # for the built-in manual
|
|
||||||
removeReferencesTo
|
|
||||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
|
||||||
];
|
|
||||||
buildInputs = with p; [ bash ];
|
|
||||||
|
|
||||||
override = x: {
|
|
||||||
preBuild =
|
|
||||||
# nix defaults to building for aarch64 _without_ the armv8-a crypto
|
|
||||||
# extensions, but liboqs depens on these
|
|
||||||
(lib.optionalString (system == "aarch64-linux") ''
|
|
||||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -march=armv8-a+crypto"
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
# fortify is only compatible with dynamic linking
|
|
||||||
hardeningDisable = lib.optional isStatic "fortify";
|
|
||||||
};
|
|
||||||
|
|
||||||
overrideMain = x: {
|
|
||||||
# CMake detects that it was served a _foreign_ target dir, and CMake
|
|
||||||
# would be executed again upon the second build step of naersk.
|
|
||||||
# By adding our specially optimized CMake version, we reduce the cost
|
|
||||||
# of recompilation by 99 % while, while avoiding any CMake errors.
|
|
||||||
nativeBuildInputs = [ (lib.hiPrio fakecmake) ] ++ x.nativeBuildInputs;
|
|
||||||
|
|
||||||
# make sure that libc is linked, under musl this is not the case per
|
|
||||||
# default
|
|
||||||
preBuild = (lib.optionalString isStatic ''
|
|
||||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -lc"
|
|
||||||
'');
|
|
||||||
};
|
|
||||||
|
|
||||||
# We want to build for a specific target...
|
|
||||||
CARGO_BUILD_TARGET = target;
|
|
||||||
|
|
||||||
# ... which might require a non-default linker:
|
|
||||||
"CARGO_TARGET_${shout target}_LINKER" =
|
|
||||||
let
|
|
||||||
inherit (p.stdenv) cc;
|
|
||||||
in
|
|
||||||
"${cc}/bin/${cc.targetPrefix}cc";
|
|
||||||
|
|
||||||
meta = with pkgs.lib;
|
|
||||||
{
|
|
||||||
inherit (cargoToml.package) description homepage;
|
|
||||||
license = with licenses; [ mit asl20 ];
|
|
||||||
maintainers = [ maintainers.wucke13 ];
|
|
||||||
platforms = platforms.all;
|
|
||||||
};
|
|
||||||
} // (lib.mkIf isStatic {
|
|
||||||
# otherwise pkg-config tries to link non-existent dynamic libs
|
|
||||||
# documented here: https://docs.rs/pkg-config/latest/pkg_config/
|
|
||||||
PKG_CONFIG_ALL_STATIC = true;
|
|
||||||
|
|
||||||
# tell rust to build everything statically linked
|
|
||||||
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
|
|
||||||
});
|
|
||||||
# a function to generate a docker image based of rosenpass
|
|
||||||
rosenpassOCI = name: pkgs.dockerTools.buildImage rec {
|
|
||||||
inherit name;
|
|
||||||
copyToRoot = pkgs.buildEnv {
|
|
||||||
name = "image-root";
|
|
||||||
paths = [ self.packages.${system}.${name} ];
|
|
||||||
pathsToLink = [ "/bin" ];
|
|
||||||
};
|
|
||||||
config.Cmd = [ "/bin/rosenpass" ];
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
rec {
|
{
|
||||||
packages = rec {
|
packages = {
|
||||||
default = rosenpass;
|
default = pkgs.rosenpass;
|
||||||
rosenpass = rosenpassDerivation pkgs;
|
rosenpass = pkgs.rosenpass;
|
||||||
rp = rpDerivation pkgs;
|
rosenpass-oci-image = pkgs.rosenpass-oci-image;
|
||||||
rosenpass-oci-image = rosenpassOCI "rosenpass";
|
rp = pkgs.rp;
|
||||||
|
|
||||||
# derivation for the release
|
release-package = pkgs.release-package;
|
||||||
release-package =
|
|
||||||
let
|
# for good measure, we also offer to cross compile to Linux on Arm
|
||||||
version = cargoToml.package.version;
|
aarch64-linux-rosenpass-static =
|
||||||
package =
|
pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass;
|
||||||
if pkgs.hostPlatform.isLinux then
|
aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp;
|
||||||
packages.rosenpass-static
|
}
|
||||||
else packages.rosenpass;
|
//
|
||||||
rp =
|
# We only offer static builds for linux, as this is not supported on OS X
|
||||||
if pkgs.hostPlatform.isLinux then
|
(nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
packages.rp-static
|
rosenpass-static = pkgs.pkgsStatic.rosenpass;
|
||||||
else packages.rp;
|
rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image;
|
||||||
oci-image =
|
rp-static = pkgs.pkgsStatic.rp;
|
||||||
if pkgs.hostPlatform.isLinux then
|
});
|
||||||
packages.rosenpass-static-oci-image
|
|
||||||
else packages.rosenpass-oci-image;
|
|
||||||
in
|
|
||||||
pkgs.runCommandNoCC "lace-result" { }
|
|
||||||
''
|
|
||||||
mkdir {bin,$out}
|
|
||||||
tar -cvf $out/rosenpass-${system}-${version}.tar \
|
|
||||||
-C ${package} bin/rosenpass \
|
|
||||||
-C ${rp} bin/rp
|
|
||||||
cp ${oci-image} \
|
|
||||||
$out/rosenpass-oci-image-${system}-${version}.tar.gz
|
|
||||||
'';
|
|
||||||
} // (if pkgs.stdenv.isLinux then rec {
|
|
||||||
rosenpass-static = rosenpassDerivation pkgs.pkgsStatic;
|
|
||||||
rp-static = rpDerivation pkgs.pkgsStatic;
|
|
||||||
rosenpass-static-oci-image = rosenpassOCI "rosenpass-static";
|
|
||||||
} else { });
|
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
### Linux specifics ###
|
### Linux specifics ###
|
||||||
#
|
#
|
||||||
@@ -341,92 +75,53 @@
|
|||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
|
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
};
|
};
|
||||||
packages = self.packages.${system};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
#
|
|
||||||
### Whitepaper ###
|
|
||||||
#
|
|
||||||
packages.whitepaper =
|
|
||||||
let
|
|
||||||
tlsetup = (pkgs.texlive.combine {
|
|
||||||
inherit (pkgs.texlive) scheme-basic acmart amsfonts ccicons
|
|
||||||
csquotes csvsimple doclicense fancyvrb fontspec gobble
|
|
||||||
koma-script ifmtarg latexmk lm markdown mathtools minted noto
|
|
||||||
nunito pgf soul unicode-math lualatex-math paralist
|
|
||||||
gitinfo2 eso-pic biblatex biblatex-trad biblatex-software
|
|
||||||
xkeyval xurl xifthen biber;
|
|
||||||
});
|
|
||||||
in
|
|
||||||
pkgs.stdenvNoCC.mkDerivation {
|
|
||||||
name = "whitepaper";
|
|
||||||
src = ./papers;
|
|
||||||
nativeBuildInputs = with pkgs; [
|
|
||||||
ncurses # tput
|
|
||||||
python3Packages.pygments
|
|
||||||
tlsetup # custom tex live scheme
|
|
||||||
which
|
|
||||||
];
|
|
||||||
buildPhase = ''
|
|
||||||
export HOME=$(mktemp -d)
|
|
||||||
latexmk -r tex/CI.rc
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
mv *.pdf readme.md $out/
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
#
|
||||||
|
### Reading materials ###
|
||||||
|
#
|
||||||
|
packages.whitepaper = pkgs.whitepaper;
|
||||||
|
|
||||||
#
|
#
|
||||||
### Proof and Proof Tools ###
|
### Proof and Proof Tools ###
|
||||||
#
|
#
|
||||||
packages.proverif-patched = pkgs.proverif.overrideAttrs (old: {
|
packages.proverif-patched = pkgs.proverif-patched;
|
||||||
postInstall = ''
|
packages.proof-proverif = pkgs.proof-proverif;
|
||||||
install -D -t $out/lib cryptoverif.pvl
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
packages.proof-proverif = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "rosenpass-proverif-proof";
|
|
||||||
version = "unstable";
|
|
||||||
src = pkgs.lib.sources.sourceByRegex ./. [
|
|
||||||
"analyze.sh"
|
|
||||||
"marzipan(/marzipan.awk)?"
|
|
||||||
"analysis(/.*)?"
|
|
||||||
];
|
|
||||||
nativeBuildInputs = [ pkgs.proverif pkgs.graphviz ];
|
|
||||||
CRYPTOVERIF_LIB = packages.proverif-patched + "/lib/cryptoverif.pvl";
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
bash analyze.sh -color -html $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
### Devshells ###
|
### Devshells ###
|
||||||
#
|
#
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
|
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||||
inputsFrom = [ packages.default ];
|
inputsFrom = [ pkgs.rosenpass ];
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
cmake # override the fakecmake from the main step above
|
|
||||||
cargo-release
|
cargo-release
|
||||||
clippy
|
clippy
|
||||||
|
rustfmt
|
||||||
nodePackages.prettier
|
nodePackages.prettier
|
||||||
nushell # for the .ci/gen-workflow-files.nu script
|
nushell # for the .ci/gen-workflow-files.nu script
|
||||||
rustfmt
|
proverif-patched
|
||||||
packages.proverif-patched
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
devShells.coverage = pkgs.mkShell {
|
devShells.coverage = pkgs.mkShell {
|
||||||
inputsFrom = [ packages.default ];
|
inputsFrom = [ pkgs.rosenpass ];
|
||||||
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
|
nativeBuildInputs = [
|
||||||
|
inputs.fenix.packages.${system}.complete.toolchain
|
||||||
|
pkgs.cargo-llvm-cov
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
|
systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix;
|
||||||
|
systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix;
|
||||||
|
|
||||||
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
||||||
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
||||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ version = "0.0.1"
|
|||||||
publish = false
|
publish = false
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
@@ -81,4 +84,4 @@ doc = false
|
|||||||
name = "fuzz_vec_secret_alloc_memfdsec_mallocfb"
|
name = "fuzz_vec_secret_alloc_memfdsec_mallocfb"
|
||||||
path = "fuzz_targets/vec_secret_alloc_memfdsec_mallocfb.rs"
|
path = "fuzz_targets/vec_secret_alloc_memfdsec_mallocfb.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ pub struct Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fuzz_target!(|input: Input| {
|
fuzz_target!(|input: Input| {
|
||||||
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
|
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
|
||||||
ciphertext.resize(input.plaintext.len() + 16, 0);
|
|
||||||
|
|
||||||
aead::encrypt(
|
aead::encrypt(
|
||||||
ciphertext.as_mut_slice(),
|
ciphertext.as_mut_slice(),
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ use rosenpass::protocol::CryptoServer;
|
|||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::kem::StaticKem;
|
||||||
use rosenpass_secret_memory::policy::*;
|
use rosenpass_secret_memory::policy::*;
|
||||||
use rosenpass_secret_memory::Secret;
|
use rosenpass_secret_memory::{PublicBox, Secret};
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
static ONCE: Once = Once::new();
|
static ONCE: Once = Once::new();
|
||||||
fuzz_target!(|rx_buf: &[u8]| {
|
fuzz_target!(|rx_buf: &[u8]| {
|
||||||
ONCE.call_once(secret_policy_use_only_malloc_secrets);
|
ONCE.call_once(secret_policy_use_only_malloc_secrets);
|
||||||
let sk = Secret::from_slice(&[0; StaticKem::SK_LEN]);
|
let sk = Secret::from_slice(&[0; StaticKem::SK_LEN]);
|
||||||
let pk = Secret::from_slice(&[0; StaticKem::PK_LEN]);
|
let pk = PublicBox::from_slice(&[0; StaticKem::PK_LEN]);
|
||||||
|
|
||||||
let mut cs = CryptoServer::new(sk, pk);
|
let mut cs = CryptoServer::new(sk, pk);
|
||||||
let mut tx_buf = [0; 10240];
|
let mut tx_buf = [0; 10240];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub struct Input {
|
|||||||
|
|
||||||
fuzz_target!(|input: Input| {
|
fuzz_target!(|input: Input| {
|
||||||
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
||||||
let mut shared_secret = [0u8; EphemeralKem::SK_LEN];
|
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
|
||||||
|
|
||||||
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
||||||
});
|
});
|
||||||
|
|||||||
13
manual_tests/psk_broker/peer_a.rp.config
Normal file
13
manual_tests/psk_broker/peer_a.rp.config
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
secret_key = "peer_a.rp.sk"
|
||||||
|
public_key = "peer_a.rp.pk"
|
||||||
|
listen = ["[::1]:46127"]
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "peer_b.rp.pk"
|
||||||
|
device = "rpPskBrkTestA"
|
||||||
14
manual_tests/psk_broker/peer_b.rp.config
Normal file
14
manual_tests/psk_broker/peer_b.rp.config
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
secret_key = "peer_b.rp.sk"
|
||||||
|
public_key = "peer_b.rp.pk"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "peer_a.rp.pk"
|
||||||
|
endpoint = "[::1]:46127"
|
||||||
|
device = "rpPskBrkTestB"
|
||||||
215
manual_tests/psk_broker/run_test.sh
Executable file
215
manual_tests/psk_broker/run_test.sh
Executable file
@@ -0,0 +1,215 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
enquote() {
|
||||||
|
while (( "$#" > 1)); do
|
||||||
|
printf "%q " "$1"
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
if (("$#" > 0)); then
|
||||||
|
printf "%q" "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEANUP_HOOKS=()
|
||||||
|
hook_cleanup() {
|
||||||
|
local hook
|
||||||
|
set +e +o pipefail
|
||||||
|
for hook in "${CLEANUP_HOOKS[@]}"; do
|
||||||
|
eval "${hook}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
CLEANUP_HOOKS=("$(enquote exc_with_ctx cleanup "$@")" "${CLEANUP_HOOKS[@]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_eval() {
|
||||||
|
cleanup eval "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr() {
|
||||||
|
echo >&2 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local level; level="$1"; shift || fatal "USAGE: log LVL MESSAGE.."
|
||||||
|
stderr "[${level}]" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
log "INFO" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
debug() {
|
||||||
|
log "DEBUG" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
fatal() {
|
||||||
|
log "FATAL" "$@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
local msg; msg="$1"; shift || fatal "USAGE: assert_cmd MESSAGE COMMAND.."
|
||||||
|
"$@" || fatal "${msg}"
|
||||||
|
}
|
||||||
|
|
||||||
|
abs_dir() {
|
||||||
|
local dir; dir="$1"; shift || fatal "USAGE: abs_dir DIR"
|
||||||
|
(
|
||||||
|
cd "${dir}"
|
||||||
|
pwd -P
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
exc_with_ctx() {
|
||||||
|
local ctx; ctx="$1"; shift || fatal "USAGE: exc_with_ctx CONTEXT COMMAND.."
|
||||||
|
if [[ -z "${ctx}" ]]; then
|
||||||
|
info '$' "$@"
|
||||||
|
else
|
||||||
|
info "${ctx}\$" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
exc() {
|
||||||
|
exc_with_ctx "" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
exc_eval() {
|
||||||
|
exc eval "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
exc_eval_with_ctx() {
|
||||||
|
local ctx; ctx="$1"; shift || fatal "USAGE: exc_eval_with_ctx CONTEXT EVAL_COMMAND.."
|
||||||
|
exc_with_ctx "eval:${ctx}" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
exc_as_user() {
|
||||||
|
exc sudo -u "${SUDO_USER}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
exc_eval_as_user() {
|
||||||
|
exc_as_user bash -c "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fork_eval_as_user() {
|
||||||
|
exc sudo -u "${SUDO_USER}" bash -c "$*" &
|
||||||
|
local pid; pid="$!"
|
||||||
|
cleanup wait "${pid}"
|
||||||
|
cleanup pkill -2 -P "${pid}" # Reverse ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
info_success() {
|
||||||
|
stderr
|
||||||
|
stderr
|
||||||
|
if [[ "${SUCCESS}" = 1 ]]; then
|
||||||
|
stderr " Test was a success!"
|
||||||
|
else
|
||||||
|
stderr " !!! TEST WAS A FAILURE!!!"
|
||||||
|
fi
|
||||||
|
stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
assert "Use as root with sudo" [ "$(id -u)" -eq 0 ]
|
||||||
|
assert "Use as root with sudo" [ -n "${SUDO_UID}" ]
|
||||||
|
assert "SUDO_UID is 0; refusing to build as root" [ "${SUDO_UID}" -ne 0 ]
|
||||||
|
|
||||||
|
cleanup info_success
|
||||||
|
|
||||||
|
trap hook_cleanup EXIT
|
||||||
|
|
||||||
|
SCRIPT="$0"
|
||||||
|
CFG_TEMPLATE_DIR="$(abs_dir "$(dirname "${SCRIPT}")")"
|
||||||
|
REPO="$(abs_dir "${CFG_TEMPLATE_DIR}/../..")"
|
||||||
|
BINS="${REPO}/target/debug"
|
||||||
|
|
||||||
|
# Create temp dir
|
||||||
|
TMP_DIR="/tmp/rosenpass-psk-broker-test-$(date +%s)-$(uuidgen)"
|
||||||
|
cleanup rm -rf "${TMP_DIR}"
|
||||||
|
exc_as_user mkdir -p "${TMP_DIR}"
|
||||||
|
|
||||||
|
# Copy config
|
||||||
|
CFG_DIR="${TMP_DIR}/cfg"
|
||||||
|
exc_as_user cp -R "${CFG_TEMPLATE_DIR}" "${CFG_DIR}"
|
||||||
|
|
||||||
|
exc umask 077
|
||||||
|
|
||||||
|
exc cd "${REPO}"
|
||||||
|
local build_cmd; build_cmd=(cargo build --workspace --color=always --all-features --bins --profile dev)
|
||||||
|
if test -e "${BINS}/rosenpass-wireguard-broker-privileged" -a -e "${BINS}/rosenpass"; then
|
||||||
|
info "Found the binaries rosenpass-wireguard-broker-privileged and rosenpass." \
|
||||||
|
"Run following commands as a regular user to recompile the binaries with the right options" \
|
||||||
|
"in case of an error:" '$' "${build_cmd[@]}"
|
||||||
|
else
|
||||||
|
exc_as_user "${build_cmd[@]}"
|
||||||
|
fi
|
||||||
|
exc sudo setcap CAP_NET_ADMIN=+eip "${BINS}/rosenpass-wireguard-broker-privileged"
|
||||||
|
|
||||||
|
exc cd "${CFG_DIR}"
|
||||||
|
exc_eval_as_user "wg genkey > peer_a.wg.sk"
|
||||||
|
exc_eval_as_user "wg pubkey < peer_a.wg.sk > peer_a.wg.pk"
|
||||||
|
exc_eval_as_user "wg genkey > peer_b.wg.sk"
|
||||||
|
exc_eval_as_user "wg pubkey < peer_b.wg.sk > peer_b.wg.pk"
|
||||||
|
exc_eval_as_user "wg genpsk > peer_a_invalid.psk"
|
||||||
|
exc_eval_as_user "wg genpsk > peer_b_invalid.psk"
|
||||||
|
exc_eval_as_user "echo $(enquote "peer = \"$(cat peer_b.wg.pk)\"") >> peer_a.rp.config"
|
||||||
|
exc_eval_as_user "echo $(enquote "peer = \"$(cat peer_a.wg.pk)\"") >> peer_b.rp.config"
|
||||||
|
exc_as_user "${BINS}"/rosenpass gen-keys peer_a.rp.config
|
||||||
|
exc_as_user "${BINS}"/rosenpass gen-keys peer_b.rp.config
|
||||||
|
|
||||||
|
cleanup ip l del dev rpPskBrkTestA
|
||||||
|
cleanup ip l del dev rpPskBrkTestB
|
||||||
|
exc ip l add dev rpPskBrkTestA type wireguard
|
||||||
|
exc ip l add dev rpPskBrkTestB type wireguard
|
||||||
|
|
||||||
|
exc wg set rpPskBrkTestA \
|
||||||
|
listen-port 46125 \
|
||||||
|
private-key peer_a.wg.sk \
|
||||||
|
peer "$(cat peer_b.wg.pk)" \
|
||||||
|
endpoint 'localhost:46126' \
|
||||||
|
preshared-key peer_a_invalid.psk \
|
||||||
|
allowed-ips fe80::2/64
|
||||||
|
exc wg set rpPskBrkTestB \
|
||||||
|
listen-port 46126 \
|
||||||
|
private-key peer_b.wg.sk \
|
||||||
|
peer "$(cat peer_a.wg.pk)" \
|
||||||
|
endpoint 'localhost:46125' \
|
||||||
|
preshared-key peer_b_invalid.psk \
|
||||||
|
allowed-ips fe80::1/64
|
||||||
|
|
||||||
|
exc ip l set rpPskBrkTestA up
|
||||||
|
exc ip l set rpPskBrkTestB up
|
||||||
|
|
||||||
|
exc ip a add fe80::1/64 dev rpPskBrkTestA
|
||||||
|
exc ip a add fe80::2/64 dev rpPskBrkTestB
|
||||||
|
|
||||||
|
fork_eval_as_user "\
|
||||||
|
RUST_LOG='info' \
|
||||||
|
PATH=$(enquote "${REPO}/target/debug:${PATH}") \
|
||||||
|
$(enquote "${BINS}/rosenpass") --psk-broker-spawn \
|
||||||
|
exchange-config peer_a.rp.config"
|
||||||
|
fork_eval_as_user "\
|
||||||
|
RUST_LOG='info' \
|
||||||
|
PATH=$(enquote "${REPO}/target/debug:${PATH}") \
|
||||||
|
$(enquote "${BINS}/rosenpass-wireguard-broker-socket-handler") \
|
||||||
|
--listen-path broker.sock"
|
||||||
|
fork_eval_as_user "\
|
||||||
|
RUST_LOG='info' \
|
||||||
|
PATH=$(enquote "$PWD/target/debug:${PATH}") \
|
||||||
|
$(enquote "${BINS}/rosenpass") --psk-broker-path broker.sock \
|
||||||
|
exchange-config peer_b.rp.config"
|
||||||
|
|
||||||
|
exc_as_user ping -c 2 -w 10 fe80::1%rpPskBrkTestA
|
||||||
|
exc_as_user ping -c 2 -w 10 fe80::2%rpPskBrkTestB
|
||||||
|
exc_as_user ping -c 2 -w 10 fe80::2%rpPskBrkTestA
|
||||||
|
exc_as_user ping -c 2 -w 10 fe80::1%rpPskBrkTestB
|
||||||
|
|
||||||
|
SUCCESS=1
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import run
|
from subprocess import run
|
||||||
|
import os
|
||||||
|
|
||||||
config = dict(
|
config = dict(
|
||||||
peer_counts=[1, 5, 10, 50, 100, 500],
|
peer_counts=[1, 5, 10, 50, 100, 500],
|
||||||
peer_count_max=100,
|
peer_count_max=100,
|
||||||
ate_ip="192.168.2.1",
|
ate_ip="127.0.0.1",
|
||||||
dut_ip="192.168.2.4",
|
dut_ip="127.0.0.1",
|
||||||
dut_port=9999,
|
dut_port=9999,
|
||||||
path_to_rosenpass_bin="/Users/user/src/rosenppass/rosenpass/target/debug/rosenpass",
|
path_to_rosenpass_bin=os.getcwd() + "/target/release/rosenpass",
|
||||||
)
|
)
|
||||||
|
|
||||||
print(config)
|
print(config)
|
||||||
|
|||||||
@@ -14,3 +14,7 @@ rosenpass-cipher-traits = { workspace = true }
|
|||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
oqs-sys = { workspace = true }
|
oqs-sys = { workspace = true }
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rosenpass-secret-memory = { workspace = true }
|
||||||
|
rosenpass-constant-time = { workspace = true }
|
||||||
|
|||||||
@@ -1,9 +1,42 @@
|
|||||||
|
//! Generic helpers for declaring bindings to liboqs kems
|
||||||
|
|
||||||
|
/// Generate bindings to a liboqs-provided KEM
|
||||||
macro_rules! oqs_kem {
|
macro_rules! oqs_kem {
|
||||||
($name:ident) => { ::paste::paste!{
|
($name:ident) => { ::paste::paste!{
|
||||||
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
mod [< $name:snake >] {
|
mod [< $name:snake >] {
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_util::result::Guaranteed;
|
use rosenpass_util::result::Guaranteed;
|
||||||
|
|
||||||
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "# Examples"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "```rust"]
|
||||||
|
#[doc = "use std::borrow::{Borrow, BorrowMut};"]
|
||||||
|
#[doc = "use rosenpass_cipher_traits::Kem;"]
|
||||||
|
#[doc = "use rosenpass_oqs::" $name:camel " as MyKem;"]
|
||||||
|
#[doc = "use rosenpass_secret_memory::{Secret, Public};"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "// Recipient generates secret key, transfers pk to sender"]
|
||||||
|
#[doc = "let mut sk = Secret::<{ MyKem::SK_LEN }>::zero();"]
|
||||||
|
#[doc = "let mut pk = Public::<{ MyKem::PK_LEN }>::zero();"]
|
||||||
|
#[doc = "MyKem::keygen(sk.secret_mut(), pk.borrow_mut());"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "// Sender generates ciphertext and local shared key, sends ciphertext to recipient"]
|
||||||
|
#[doc = "let mut shk_enc = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||||
|
#[doc = "let mut ct = Public::<{ MyKem::CT_LEN }>::zero();"]
|
||||||
|
#[doc = "MyKem::encaps(shk_enc.secret_mut(), ct.borrow_mut(), pk.borrow());"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "// Recipient decapsulates ciphertext"]
|
||||||
|
#[doc = "let mut shk_dec = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||||
|
#[doc = "MyKem::decaps(shk_dec.secret_mut(), sk.secret(), ct.borrow());"]
|
||||||
|
#[doc = ""]
|
||||||
|
#[doc = "// Both parties end up with the same shared key"]
|
||||||
|
#[doc = "assert!(rosenpass_constant_time::compare(shk_enc.secret_mut(), shk_dec.secret_mut()) == 0);"]
|
||||||
|
#[doc = "```"]
|
||||||
pub enum [< $name:camel >] {}
|
pub enum [< $name:camel >] {}
|
||||||
|
|
||||||
/// # Panic & Safety
|
/// # Panic & Safety
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(clippy::missing_docs_in_private_items)]
|
||||||
|
//! Bindings for liboqs used in Rosenpass
|
||||||
|
|
||||||
|
/// Call into a libOQS function
|
||||||
macro_rules! oqs_call {
|
macro_rules! oqs_call {
|
||||||
($name:path, $($args:expr),*) => {{
|
($name:path, $($args:expr),*) => {{
|
||||||
use oqs_sys::common::OQS_STATUS::*;
|
use oqs_sys::common::OQS_STATUS::*;
|
||||||
|
|||||||
39
overlay.nix
Normal file
39
overlay.nix
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
final: prev: {
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
### Actual rosenpass software ###
|
||||||
|
#
|
||||||
|
rosenpass = final.callPackage ./pkgs/rosenpass.nix { };
|
||||||
|
rosenpass-oci-image = final.callPackage ./pkgs/rosenpass-oci-image.nix { };
|
||||||
|
rp = final.callPackage ./pkgs/rosenpass.nix { package = "rp"; };
|
||||||
|
|
||||||
|
release-package = final.callPackage ./pkgs/release-package.nix { };
|
||||||
|
|
||||||
|
#
|
||||||
|
### Appendix ###
|
||||||
|
#
|
||||||
|
proverif-patched = prev.proverif.overrideAttrs (old: {
|
||||||
|
postInstall = ''
|
||||||
|
install -D -t $out/lib cryptoverif.pvl
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
|
||||||
|
proof-proverif = final.stdenv.mkDerivation {
|
||||||
|
name = "rosenpass-proverif-proof";
|
||||||
|
version = "unstable";
|
||||||
|
src = final.lib.sources.sourceByRegex ./. [
|
||||||
|
"analyze.sh"
|
||||||
|
"marzipan(/marzipan.awk)?"
|
||||||
|
"analysis(/.*)?"
|
||||||
|
];
|
||||||
|
nativeBuildInputs = [ final.proverif final.graphviz ];
|
||||||
|
CRYPTOVERIF_LIB = final.proverif-patched + "/lib/cryptoverif.pvl";
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
bash analyze.sh -color -html $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
whitepaper = final.callPackage ./pkgs/whitepaper.nix { };
|
||||||
|
}
|
||||||
27
pkgs/release-package.nix
Normal file
27
pkgs/release-package.nix
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{ lib, stdenvNoCC, runCommandNoCC, pkgsStatic, rosenpass, rosenpass-oci-image, rp } @ args:
|
||||||
|
|
||||||
|
let
|
||||||
|
version = rosenpass.version;
|
||||||
|
|
||||||
|
# select static packages on Linux, default packages otherwise
|
||||||
|
package =
|
||||||
|
if stdenvNoCC.hostPlatform.isLinux then
|
||||||
|
pkgsStatic.rosenpass
|
||||||
|
else args.rosenpass;
|
||||||
|
rp =
|
||||||
|
if stdenvNoCC.hostPlatform.isLinux then
|
||||||
|
pkgsStatic.rp
|
||||||
|
else args.rp;
|
||||||
|
oci-image =
|
||||||
|
if stdenvNoCC.hostPlatform.isLinux then
|
||||||
|
pkgsStatic.rosenpass-oci-image
|
||||||
|
else args.rosenpass-oci-image;
|
||||||
|
in
|
||||||
|
runCommandNoCC "lace-result" { } ''
|
||||||
|
mkdir {bin,$out}
|
||||||
|
tar -cvf $out/rosenpass-${stdenvNoCC.hostPlatform.system}-${version}.tar \
|
||||||
|
-C ${package} bin/rosenpass lib/systemd \
|
||||||
|
-C ${rp} bin/rp
|
||||||
|
cp ${oci-image} \
|
||||||
|
$out/rosenpass-oci-image-${stdenvNoCC.hostPlatform.system}-${version}.tar.gz
|
||||||
|
''
|
||||||
11
pkgs/rosenpass-oci-image.nix
Normal file
11
pkgs/rosenpass-oci-image.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ dockerTools, buildEnv, rosenpass }:
|
||||||
|
|
||||||
|
dockerTools.buildImage {
|
||||||
|
name = rosenpass.name + "-oci";
|
||||||
|
copyToRoot = buildEnv {
|
||||||
|
name = "image-root";
|
||||||
|
paths = [ rosenpass ];
|
||||||
|
pathsToLink = [ "/bin" ];
|
||||||
|
};
|
||||||
|
config.Cmd = [ "/bin/rosenpass" ];
|
||||||
|
}
|
||||||
87
pkgs/rosenpass.nix
Normal file
87
pkgs/rosenpass.nix
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{ lib, stdenv, rustPlatform, cmake, mandoc, removeReferencesTo, bash, package ? "rosenpass" }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# whether we want to build a statically linked binary
|
||||||
|
isStatic = stdenv.targetPlatform.isStatic;
|
||||||
|
|
||||||
|
scoped = (scope: scope.result);
|
||||||
|
|
||||||
|
# source files relevant for rust
|
||||||
|
src = scoped rec {
|
||||||
|
# File suffices to include
|
||||||
|
extensions = [
|
||||||
|
"lock"
|
||||||
|
"rs"
|
||||||
|
"service"
|
||||||
|
"target"
|
||||||
|
"toml"
|
||||||
|
];
|
||||||
|
# Files to explicitly include
|
||||||
|
files = [
|
||||||
|
"to/README.md"
|
||||||
|
];
|
||||||
|
|
||||||
|
src = ../.;
|
||||||
|
filter = (path: type: scoped rec {
|
||||||
|
inherit (lib) any id removePrefix hasSuffix;
|
||||||
|
anyof = (any id);
|
||||||
|
|
||||||
|
basename = baseNameOf (toString path);
|
||||||
|
relative = removePrefix (toString src + "/") (toString path);
|
||||||
|
|
||||||
|
result = anyof [
|
||||||
|
(type == "directory")
|
||||||
|
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
||||||
|
(any (file: file == relative) files)
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
result = lib.sources.cleanSourceWith { inherit src filter; };
|
||||||
|
};
|
||||||
|
|
||||||
|
# parsed Cargo.toml
|
||||||
|
cargoToml = builtins.fromTOML (builtins.readFile (src + "/rosenpass/Cargo.toml"));
|
||||||
|
in
|
||||||
|
rustPlatform.buildRustPackage {
|
||||||
|
name = cargoToml.package.name;
|
||||||
|
version = cargoToml.package.version;
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
cargoBuildOptions = [ "--package" package ];
|
||||||
|
cargoTestOptions = [ "--package" package ];
|
||||||
|
|
||||||
|
doCheck = true;
|
||||||
|
|
||||||
|
cargoLock = {
|
||||||
|
lockFile = src + "/Cargo.lock";
|
||||||
|
outputHashes = {
|
||||||
|
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
||||||
|
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
stdenv.cc
|
||||||
|
cmake # for oqs build in the oqs-sys crate
|
||||||
|
mandoc # for the built-in manual
|
||||||
|
removeReferencesTo
|
||||||
|
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
||||||
|
];
|
||||||
|
buildInputs = [ bash ];
|
||||||
|
|
||||||
|
hardeningDisable = lib.optional isStatic "fortify";
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
mkdir -p $out/lib/systemd/system
|
||||||
|
install systemd/rosenpass@.service $out/lib/systemd/system
|
||||||
|
install systemd/rp@.service $out/lib/systemd/system
|
||||||
|
install systemd/rosenpass.target $out/lib/systemd/system
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
inherit (cargoToml.package) description homepage;
|
||||||
|
license = with lib.licenses; [ mit asl20 ];
|
||||||
|
maintainers = [ lib.maintainers.wucke13 ];
|
||||||
|
platforms = lib.platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
29
pkgs/whitepaper.nix
Normal file
29
pkgs/whitepaper.nix
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{ stdenvNoCC, texlive, ncurses, python3Packages, which }:
|
||||||
|
|
||||||
|
let
|
||||||
|
customTexLiveSetup = (texlive.combine {
|
||||||
|
inherit (texlive) acmart amsfonts biber biblatex biblatex-software
|
||||||
|
biblatex-trad ccicons csquotes csvsimple doclicense eso-pic fancyvrb
|
||||||
|
fontspec gitinfo2 gobble ifmtarg koma-script latexmk lm lualatex-math
|
||||||
|
markdown mathtools minted noto nunito paralist pgf scheme-basic soul
|
||||||
|
unicode-math upquote xifthen xkeyval xurl;
|
||||||
|
});
|
||||||
|
in
|
||||||
|
stdenvNoCC.mkDerivation {
|
||||||
|
name = "whitepaper";
|
||||||
|
src = ../papers;
|
||||||
|
nativeBuildInputs = [
|
||||||
|
ncurses # tput
|
||||||
|
python3Packages.pygments
|
||||||
|
customTexLiveSetup # custom tex live scheme
|
||||||
|
which
|
||||||
|
];
|
||||||
|
buildPhase = ''
|
||||||
|
export HOME=$(mktemp -d)
|
||||||
|
latexmk -r tex/CI.rc
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
mv *.pdf readme.md $out/
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -66,6 +66,8 @@ A wrapper script provides instant feedback about which queries execute as expect
|
|||||||
|
|
||||||
# Getting Rosenpass
|
# Getting Rosenpass
|
||||||
|
|
||||||
|
Documentation and installation guides can be found at the [Rosenpass website](https://rosenpass.eu/docs).
|
||||||
|
|
||||||
Rosenpass is packaged for more and more distributions, maybe also for the distribution of your choice?
|
Rosenpass is packaged for more and more distributions, maybe also for the distribution of your choice?
|
||||||
|
|
||||||
[](https://repology.org/project/rosenpass/versions)
|
[](https://repology.org/project/rosenpass/versions)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rosenpass"
|
name = "rosenpass"
|
||||||
version = "0.2.1"
|
version = "0.3.0-dev"
|
||||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@@ -13,6 +13,19 @@ readme = "readme.md"
|
|||||||
name = "rosenpass"
|
name = "rosenpass"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rosenpass-gen-ipc-msg-types"
|
||||||
|
path = "src/bin/gen-ipc-msg-types.rs"
|
||||||
|
required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "api-integration-tests"
|
||||||
|
required-features = ["experiment_api", "internal_testing"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "api-integration-tests-api-setup"
|
||||||
|
required-features = ["experiment_api", "internal_testing"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "handshake"
|
name = "handshake"
|
||||||
harness = false
|
harness = false
|
||||||
@@ -34,12 +47,21 @@ env_logger = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
clap_complete = { workspace = true }
|
||||||
|
clap_mangen = { workspace = true }
|
||||||
mio = { workspace = true }
|
mio = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
zerocopy = { workspace = true }
|
zerocopy = { workspace = true }
|
||||||
home = { workspace = true }
|
home = { workspace = true }
|
||||||
derive_builder = {workspace = true}
|
derive_builder = { workspace = true }
|
||||||
rosenpass-wireguard-broker = {workspace = true}
|
rosenpass-wireguard-broker = { workspace = true }
|
||||||
|
zeroize = { workspace = true }
|
||||||
|
hex-literal = { workspace = true, optional = true }
|
||||||
|
hex = { workspace = true, optional = true }
|
||||||
|
heck = { workspace = true, optional = true }
|
||||||
|
command-fds = { workspace = true, optional = true }
|
||||||
|
rustix = { workspace = true, optional = true }
|
||||||
|
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
@@ -48,9 +70,22 @@ anyhow = { workspace = true }
|
|||||||
criterion = { workspace = true }
|
criterion = { workspace = true }
|
||||||
test_bin = { workspace = true }
|
test_bin = { workspace = true }
|
||||||
stacker = { workspace = true }
|
stacker = { workspace = true }
|
||||||
serial_test = {workspace = true}
|
serial_test = { workspace = true }
|
||||||
procspawn = {workspace = true}
|
procspawn = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
rustix = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
enable_broker_api = ["rosenpass-wireguard-broker/enable_broker_api"]
|
default = []
|
||||||
enable_memfd_alloc = []
|
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
||||||
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||||
|
experiment_api = [
|
||||||
|
"hex-literal",
|
||||||
|
"uds",
|
||||||
|
"command-fds",
|
||||||
|
"rustix",
|
||||||
|
"rosenpass-util/experiment_file_descriptor_passing",
|
||||||
|
"rosenpass-wireguard-broker/experiment_api",
|
||||||
|
]
|
||||||
|
internal_testing = []
|
||||||
|
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
|
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::kem::StaticKem;
|
||||||
@@ -40,7 +41,7 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
|
|||||||
|
|
||||||
fn keygen() -> Result<(SSk, SPk)> {
|
fn keygen() -> Result<(SSk, SPk)> {
|
||||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
|
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||||
Ok((sk, pk))
|
Ok((sk, pk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
use anyhow::bail;
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::env;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
/// Invokes a troff compiler to compile a manual page
|
|
||||||
fn render_man(compiler: &str, man: &str) -> Result<String> {
|
|
||||||
let out = Command::new(compiler).args(["-Tascii", man]).output()?;
|
|
||||||
if !out.status.success() {
|
|
||||||
bail!("{} returned an error", compiler);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(String::from_utf8(out.stdout)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates the manual page
|
|
||||||
fn generate_man() -> String {
|
|
||||||
// This function is purposely stupid and redundant
|
|
||||||
|
|
||||||
let man = render_man("mandoc", "./doc/rosenpass.1");
|
|
||||||
if let Ok(man) = man {
|
|
||||||
return man;
|
|
||||||
}
|
|
||||||
|
|
||||||
let man = render_man("groff", "./doc/rosenpass.1");
|
|
||||||
if let Ok(man) = man {
|
|
||||||
return man;
|
|
||||||
}
|
|
||||||
|
|
||||||
"Cannot render manual page. Please visit https://rosenpass.eu/docs/manuals/\n".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn man() {
|
|
||||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
||||||
let man = generate_man();
|
|
||||||
let path = out_dir.join("rosenpass.1.ascii");
|
|
||||||
|
|
||||||
let mut file = File::create(&path).unwrap();
|
|
||||||
file.write_all(man.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
println!("cargo:rustc-env=ROSENPASS_MAN={}", path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// For now, rerun the build script on every time, as the build script
|
|
||||||
// is not very expensive right now.
|
|
||||||
println!("cargo:rerun-if-changed=./");
|
|
||||||
man();
|
|
||||||
}
|
|
||||||
341
rosenpass/src/api/api_handler.rs
Normal file
341
rosenpass/src/api/api_handler.rs
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
// Note: This is business logic; tested through the integration tests in
|
||||||
|
// rosenpass/tests/
|
||||||
|
|
||||||
|
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use rosenpass_to::{ops::copy_slice, To};
|
||||||
|
use rosenpass_util::{
|
||||||
|
fd::FdIo,
|
||||||
|
functional::{run, ApplyExt},
|
||||||
|
io::ReadExt,
|
||||||
|
mem::DiscardResultExt,
|
||||||
|
mio::UnixStreamExt,
|
||||||
|
result::OkExt,
|
||||||
|
};
|
||||||
|
use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::{add_listen_socket_response_status, add_psk_broker_response_status},
|
||||||
|
app_server::AppServer,
|
||||||
|
protocol::BuildCryptoServer,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{supply_keypair_response_status, Server as ApiServer};
|
||||||
|
|
||||||
|
/// Stores the state of the API handler.
|
||||||
|
///
|
||||||
|
/// This is used in the context [ApiHandlerContext]; [ApiHandlerContext] exposes both
|
||||||
|
/// the [AppServer] and the API handler state.
|
||||||
|
///
|
||||||
|
/// [ApiHandlerContext] is what actually contains the API handler functions.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ApiHandler {
|
||||||
|
_dummy: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiHandler {
|
||||||
|
/// Construct an [Self]
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { _dummy: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The implementation of the API requires both access to its own state [ApiHandler] and to the
|
||||||
|
/// [AppServer] the API is supposed to operate on.
|
||||||
|
///
|
||||||
|
/// This trait provides both; it implements a pattern to allow for multiple - **potentially
|
||||||
|
/// overlapping** mutable references to be passed to the API handler functions.
|
||||||
|
///
|
||||||
|
/// This relatively complex scheme is chosen to appease the borrow checker: We want flexibility
|
||||||
|
/// with regard to where the [ApiHandler] is stored and we need a mutable reference to
|
||||||
|
/// [ApiHandler]. We also need a mutable reference to [AppServer]. Achieving this by using the
|
||||||
|
/// direct method would be impossible because the [ApiHandler] is actually stored somewhere inside
|
||||||
|
/// [AppServer]. The borrow checker does not allow this.
|
||||||
|
///
|
||||||
|
/// What we have instead is – in practice – a reference to [AppServer] and a function (as part of
|
||||||
|
/// the trait) that extracts an [ApiHandler] reference from [AppServer], which is allowed by the
|
||||||
|
/// borrow checker. A benefit of the use of a trait here is that we could, if desired, also store
|
||||||
|
/// the [ApiHandler] outside [AppServer]. It really depends on the trait.
|
||||||
|
pub trait ApiHandlerContext {
|
||||||
|
/// Retrieve the [ApiHandler]
|
||||||
|
fn api_handler(&self) -> &ApiHandler;
|
||||||
|
/// Retrieve the [AppServer]
|
||||||
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Retrieve the [ApiHandler]
|
||||||
|
fn api_handler_mut(&mut self) -> &mut ApiHandler;
|
||||||
|
/// Retrieve the [AppServer]
|
||||||
|
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is the Error raised by [ApiServer::supply_keypair]; it contains both
|
||||||
|
/// the underlying error message as well as the status value
|
||||||
|
/// returned by the API.
|
||||||
|
///
|
||||||
|
/// [ApiServer::supply_keypair] generally constructs a [Self] by using one of the
|
||||||
|
/// utility functions [SupplyKeypairErrorExt].
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[error("Error in SupplyKeypair")]
|
||||||
|
struct SupplyKeypairError {
|
||||||
|
/// The status code communicated via the Rosenpass API
|
||||||
|
status: u128,
|
||||||
|
/// The underlying error that caused the Rosenpass API level Error
|
||||||
|
#[source]
|
||||||
|
cause: anyhow::Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait SupplyKeypairErrorExt<T> {
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// an arbitrary error code
|
||||||
|
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError>;
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// the [supply_keypair_response_status::INTERNAL_ERROR] error code
|
||||||
|
fn einternal(self) -> Result<T, SupplyKeypairError>;
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// the [supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED] error code
|
||||||
|
fn ealready_supplied(self) -> Result<T, SupplyKeypairError>;
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// the [supply_keypair_response_status::INVALID_REQUEST] error code
|
||||||
|
fn einvalid_req(self) -> Result<T, SupplyKeypairError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: Into<anyhow::Error>> SupplyKeypairErrorExt<T> for Result<T, E> {
|
||||||
|
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError> {
|
||||||
|
self.map_err(|e| SupplyKeypairError {
|
||||||
|
status,
|
||||||
|
cause: e.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn einternal(self) -> Result<T, SupplyKeypairError> {
|
||||||
|
self.e_custom(supply_keypair_response_status::INTERNAL_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ealready_supplied(self) -> Result<T, SupplyKeypairError> {
|
||||||
|
self.e_custom(supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn einvalid_req(self) -> Result<T, SupplyKeypairError> {
|
||||||
|
self.e_custom(supply_keypair_response_status::INVALID_REQUEST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ApiServer for T
|
||||||
|
where
|
||||||
|
T: ?Sized + ApiHandlerContext,
|
||||||
|
{
|
||||||
|
fn ping(
|
||||||
|
&mut self,
|
||||||
|
req: &super::PingRequest,
|
||||||
|
_req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::PingResponse,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (req, res) = (&req.payload, &mut res.payload);
|
||||||
|
copy_slice(&req.echo).to(&mut res.echo);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair(
|
||||||
|
&mut self,
|
||||||
|
req: &super::SupplyKeypairRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::SupplyKeypairResponse,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let outcome: Result<(), SupplyKeypairError> = run(|| {
|
||||||
|
// Acquire the file descriptors
|
||||||
|
let mut sk_io = FdIo(
|
||||||
|
req_fds
|
||||||
|
.front()
|
||||||
|
.context("First file descriptor, secret key, missing.")
|
||||||
|
.einvalid_req()?,
|
||||||
|
);
|
||||||
|
let mut pk_io = FdIo(
|
||||||
|
req_fds
|
||||||
|
.get(1)
|
||||||
|
.context("Second file descriptor, public key, missing.")
|
||||||
|
.einvalid_req()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Actually read the secrets
|
||||||
|
let mut sk = crate::protocol::SSk::zero();
|
||||||
|
sk_io.read_exact_til_end(sk.secret_mut()).einvalid_req()?;
|
||||||
|
|
||||||
|
let mut pk = crate::protocol::SPk::zero();
|
||||||
|
pk_io.read_exact_til_end(pk.borrow_mut()).einvalid_req()?;
|
||||||
|
|
||||||
|
// Retrieve the construction site
|
||||||
|
let construction_site = self.app_server_mut().crypto_site.borrow_mut();
|
||||||
|
|
||||||
|
// Retrieve the builder
|
||||||
|
use rosenpass_util::build::ConstructionSite as C;
|
||||||
|
let maybe_builder = match construction_site {
|
||||||
|
C::Builder(builder) => Some(builder),
|
||||||
|
C::Product(_) => None,
|
||||||
|
C::Void => {
|
||||||
|
return Err(anyhow::Error::msg("CryptoServer construction side is void"))
|
||||||
|
.einternal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Retrieve a reference to the keypair
|
||||||
|
let Some(BuildCryptoServer {
|
||||||
|
ref mut keypair, ..
|
||||||
|
}) = maybe_builder
|
||||||
|
else {
|
||||||
|
return Err(anyhow::Error::msg("CryptoServer already built")).ealready_supplied();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supply the keypair to the CryptoServer
|
||||||
|
keypair
|
||||||
|
.insert(crate::protocol::Keypair { sk, pk })
|
||||||
|
.discard_result();
|
||||||
|
|
||||||
|
// Actually construct the CryptoServer
|
||||||
|
construction_site
|
||||||
|
.erect()
|
||||||
|
.map_err(|e| anyhow::Error::msg(format!("Error erecting the CryptoServer {e:?}")))
|
||||||
|
.einternal()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
use supply_keypair_response_status as status;
|
||||||
|
let status = match outcome {
|
||||||
|
Ok(()) => status::OK,
|
||||||
|
Err(e) => {
|
||||||
|
let lvl = match e.status {
|
||||||
|
status::INTERNAL_ERROR => log::Level::Warn,
|
||||||
|
_ => log::Level::Debug,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::log!(
|
||||||
|
lvl,
|
||||||
|
"Error while processing API Request.\n Request: {:?}\n Error: {:?}",
|
||||||
|
req,
|
||||||
|
e.cause
|
||||||
|
);
|
||||||
|
|
||||||
|
if e.status == status::INTERNAL_ERROR {
|
||||||
|
return Err(e.cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.status
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
res.payload.status = status;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket(
|
||||||
|
&mut self,
|
||||||
|
_req: &super::boilerplate::AddListenSocketRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::boilerplate::AddListenSocketResponse,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Retrieve file descriptor
|
||||||
|
let sock_res = run(|| -> anyhow::Result<mio::net::UdpSocket> {
|
||||||
|
let sock = req_fds
|
||||||
|
.pop_front()
|
||||||
|
.context("Invalid request – socket missing.")?;
|
||||||
|
// TODO: We need to have this outside linux
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
rosenpass_util::fd::GetSocketProtocol::demand_udp_socket(&sock)?;
|
||||||
|
let sock = std::net::UdpSocket::from(sock);
|
||||||
|
sock.set_nonblocking(true)?;
|
||||||
|
mio::net::UdpSocket::from_std(sock).ok()
|
||||||
|
});
|
||||||
|
|
||||||
|
let sock = match sock_res {
|
||||||
|
Ok(sock) => sock,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("Error processing AddListenSocket API request: {e:?}");
|
||||||
|
res.payload.status = add_listen_socket_response_status::INVALID_REQUEST;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register socket
|
||||||
|
let reg_result = self.app_server_mut().register_listen_socket(sock);
|
||||||
|
|
||||||
|
if let Err(internal_error) = reg_result {
|
||||||
|
log::warn!("Internal error processing AddListenSocket API request: {internal_error:?}");
|
||||||
|
res.payload.status = add_listen_socket_response_status::INTERNAL_ERROR;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
res.payload.status = add_listen_socket_response_status::OK;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker(
|
||||||
|
&mut self,
|
||||||
|
_req: &super::boilerplate::AddPskBrokerRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::boilerplate::AddPskBrokerResponse,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Retrieve file descriptor
|
||||||
|
let sock_res = run(|| {
|
||||||
|
let sock = req_fds
|
||||||
|
.pop_front()
|
||||||
|
.context("Invalid request – socket missing.")?;
|
||||||
|
mio::net::UnixStream::from_fd(sock)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
let sock = match sock_res {
|
||||||
|
Ok(sock) => sock,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!(
|
||||||
|
"Request found to be invalid while processing AddPskBroker API request: {e:?}"
|
||||||
|
);
|
||||||
|
res.payload.status = add_psk_broker_response_status::INVALID_REQUEST;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register Socket
|
||||||
|
let client = Box::new(MioBrokerClient::new(sock));
|
||||||
|
|
||||||
|
// Workaround: The broker code is currently impressively overcomplicated. Brokers are
|
||||||
|
// stored in a hash map but the hash map key used is just a counter so a vector could
|
||||||
|
// have been used. Broker configuration is abstracted, different peers can have different
|
||||||
|
// brokers but there is no facility to add multiple brokers in practice. The broker index
|
||||||
|
// uses a `Public` wrapper without actually holding any cryptographic data. Even the broker
|
||||||
|
// configuration uses a trait abstraction for no discernible reason and a lot of the code
|
||||||
|
// introduces pointless, single-field wrapper structs.
|
||||||
|
// We should use an implement-what-is-actually-needed strategy next time.
|
||||||
|
// The Broker code needs to be slimmed down, the right direction to go is probably to
|
||||||
|
// just add event and capability support to the API and use the API to deliver OSK events.
|
||||||
|
//
|
||||||
|
// For now, we just replace the latest broker.
|
||||||
|
let erase_ptr = {
|
||||||
|
use crate::app_server::BrokerStorePtr;
|
||||||
|
//
|
||||||
|
use rosenpass_secret_memory::Public;
|
||||||
|
use zerocopy::AsBytes;
|
||||||
|
(self.app_server().brokers.store.len() - 1)
|
||||||
|
.apply(|x| x as u64)
|
||||||
|
.apply(|x| Public::from_slice(x.as_bytes()))
|
||||||
|
.apply(BrokerStorePtr)
|
||||||
|
};
|
||||||
|
|
||||||
|
let register_result = run(|| {
|
||||||
|
let srv = self.app_server_mut();
|
||||||
|
srv.unregister_broker(erase_ptr)?;
|
||||||
|
srv.register_broker(client)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(e) = register_result {
|
||||||
|
log::warn!("Internal error while processing AddPskBroker API request: {e:?}");
|
||||||
|
res.payload.status = add_psk_broker_response_status::INTERNAL_ERROR;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
res.payload.status = add_psk_broker_response_status::OK;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
222
rosenpass/src/api/boilerplate/byte_slice_ext.rs
Normal file
222
rosenpass/src/api/boilerplate/byte_slice_ext.rs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
use zerocopy::{ByteSlice, Ref};
|
||||||
|
|
||||||
|
use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef,
|
||||||
|
ResponseMsgType, ResponseRef, SupplyKeypairRequest, SupplyKeypairResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait ByteSliceRefExt: ByteSlice {
|
||||||
|
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
||||||
|
self.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||||
|
self.msg_type_maker().parse_request_msg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
||||||
|
self.msg_type_maker()
|
||||||
|
.from_prefix()?
|
||||||
|
.parse_request_msg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
||||||
|
self.msg_type_maker()
|
||||||
|
.from_suffix()?
|
||||||
|
.parse_request_msg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
|
self.msg_type_maker().parse_response_msg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
|
self.msg_type_maker()
|
||||||
|
.from_prefix()?
|
||||||
|
.parse_response_msg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
|
self.msg_type_maker()
|
||||||
|
.from_suffix()?
|
||||||
|
.parse_response_msg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
|
RequestRef::parse(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
|
RequestRef::parse_from_prefix(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
|
RequestRef::parse_from_suffix(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
|
ResponseRef::parse(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
|
ResponseRef::parse_from_prefix(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
|
ResponseRef::parse_from_suffix(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
||||||
|
self.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
||||||
|
self.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
||||||
|
self.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_response_from_prefix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supply_keypair_response_from_suffix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_request_from_prefix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_request_from_suffix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
||||||
|
self.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_response(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_response_from_prefix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_listen_socket_response_from_suffix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_request_from_prefix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_request_from_suffix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
||||||
|
self.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
|
self.zk_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_response_from_prefix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
|
self.zk_parse_prefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_psk_broker_response_from_suffix(
|
||||||
|
self,
|
||||||
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
|
self.zk_parse_suffix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ByteSlice> ByteSliceRefExt for B {}
|
||||||
29
rosenpass/src/api/boilerplate/message_trait.rs
Normal file
29
rosenpass/src/api/boilerplate/message_trait.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use zerocopy::{ByteSliceMut, Ref};
|
||||||
|
|
||||||
|
use rosenpass_util::zerocopy::RefMaker;
|
||||||
|
|
||||||
|
use super::RawMsgType;
|
||||||
|
|
||||||
|
pub trait Message {
|
||||||
|
type Payload;
|
||||||
|
type MessageClass: Into<RawMsgType>;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self;
|
||||||
|
fn init(&mut self);
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
||||||
|
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, T> ZerocopyResponseMakerSetupMessageExt<B, T> for RefMaker<B, T>
|
||||||
|
where
|
||||||
|
B: ByteSliceMut,
|
||||||
|
T: Message,
|
||||||
|
{
|
||||||
|
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
||||||
|
T::setup(self.into_buf())
|
||||||
|
}
|
||||||
|
}
|
||||||
162
rosenpass/src/api/boilerplate/message_type.rs
Normal file
162
rosenpass/src/api/boilerplate/message_type.rs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
use hex_literal::hex;
|
||||||
|
use rosenpass_util::zerocopy::RefMaker;
|
||||||
|
use zerocopy::ByteSlice;
|
||||||
|
|
||||||
|
use crate::RosenpassError::{self, InvalidApiMessageType};
|
||||||
|
|
||||||
|
pub type RawMsgType = u128;
|
||||||
|
|
||||||
|
// constants generated by gen-ipc-msg-types:
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request
|
||||||
|
pub const PING_REQUEST: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d 0b02 ea31 45d3 4999"));
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Response
|
||||||
|
pub const PING_RESPONSE: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260"));
|
||||||
|
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Request
|
||||||
|
const SUPPLY_KEYPAIR_REQUEST: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("ac91 a5a6 4f4b 21d0 ac7f 9b55 74f7 3529"));
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Response
|
||||||
|
const SUPPLY_KEYPAIR_RESPONSE: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9"));
|
||||||
|
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Listen Socket Request
|
||||||
|
const ADD_LISTEN_SOCKET_REQUEST: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("3f21 434f 87cc a08c 02c4 61e4 0816 c7da"));
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Listen Socket Response
|
||||||
|
const ADD_LISTEN_SOCKET_RESPONSE: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("45d5 0f0d 93f0 6105 98f2 9469 5dfd 5f36"));
|
||||||
|
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Psk Broker Request
|
||||||
|
const ADD_PSK_BROKER_REQUEST: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("d798 b8dc bd61 5cab 8df1 c63d e4eb a2d1"));
|
||||||
|
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Psk Broker Response
|
||||||
|
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
||||||
|
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
||||||
|
|
||||||
|
pub trait MessageAttributes {
|
||||||
|
fn message_size(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
|
pub enum RequestMsgType {
|
||||||
|
Ping,
|
||||||
|
SupplyKeypair,
|
||||||
|
AddListenSocket,
|
||||||
|
AddPskBroker,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
|
pub enum ResponseMsgType {
|
||||||
|
Ping,
|
||||||
|
SupplyKeypair,
|
||||||
|
AddListenSocket,
|
||||||
|
AddPskBroker,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageAttributes for RequestMsgType {
|
||||||
|
fn message_size(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Ping => std::mem::size_of::<super::PingRequest>(),
|
||||||
|
Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairRequest>(),
|
||||||
|
Self::AddListenSocket => std::mem::size_of::<super::AddListenSocketRequest>(),
|
||||||
|
Self::AddPskBroker => std::mem::size_of::<super::AddPskBrokerRequest>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageAttributes for ResponseMsgType {
|
||||||
|
fn message_size(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Ping => std::mem::size_of::<super::PingResponse>(),
|
||||||
|
Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairResponse>(),
|
||||||
|
Self::AddListenSocket => std::mem::size_of::<super::AddListenSocketResponse>(),
|
||||||
|
Self::AddPskBroker => std::mem::size_of::<super::AddPskBrokerResponse>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<RawMsgType> for RequestMsgType {
|
||||||
|
type Error = RosenpassError;
|
||||||
|
|
||||||
|
fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
|
||||||
|
use RequestMsgType as E;
|
||||||
|
Ok(match value {
|
||||||
|
self::PING_REQUEST => E::Ping,
|
||||||
|
self::SUPPLY_KEYPAIR_REQUEST => E::SupplyKeypair,
|
||||||
|
self::ADD_LISTEN_SOCKET_REQUEST => E::AddListenSocket,
|
||||||
|
self::ADD_PSK_BROKER_REQUEST => E::AddPskBroker,
|
||||||
|
_ => return Err(InvalidApiMessageType(value)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RequestMsgType> for RawMsgType {
|
||||||
|
fn from(val: RequestMsgType) -> Self {
|
||||||
|
use RequestMsgType as E;
|
||||||
|
match val {
|
||||||
|
E::Ping => self::PING_REQUEST,
|
||||||
|
E::SupplyKeypair => self::SUPPLY_KEYPAIR_REQUEST,
|
||||||
|
E::AddListenSocket => self::ADD_LISTEN_SOCKET_REQUEST,
|
||||||
|
E::AddPskBroker => self::ADD_PSK_BROKER_REQUEST,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<RawMsgType> for ResponseMsgType {
|
||||||
|
type Error = RosenpassError;
|
||||||
|
|
||||||
|
fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
|
||||||
|
use ResponseMsgType as E;
|
||||||
|
Ok(match value {
|
||||||
|
self::PING_RESPONSE => E::Ping,
|
||||||
|
self::SUPPLY_KEYPAIR_RESPONSE => E::SupplyKeypair,
|
||||||
|
self::ADD_LISTEN_SOCKET_RESPONSE => E::AddListenSocket,
|
||||||
|
self::ADD_PSK_BROKER_RESPONSE => E::AddPskBroker,
|
||||||
|
_ => return Err(InvalidApiMessageType(value)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResponseMsgType> for RawMsgType {
|
||||||
|
fn from(val: ResponseMsgType) -> Self {
|
||||||
|
use ResponseMsgType as E;
|
||||||
|
match val {
|
||||||
|
E::Ping => self::PING_RESPONSE,
|
||||||
|
E::SupplyKeypair => self::SUPPLY_KEYPAIR_RESPONSE,
|
||||||
|
E::AddListenSocket => self::ADD_LISTEN_SOCKET_RESPONSE,
|
||||||
|
E::AddPskBroker => self::ADD_PSK_BROKER_RESPONSE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RawMsgTypeExt {
|
||||||
|
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
|
||||||
|
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawMsgTypeExt for RawMsgType {
|
||||||
|
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError> {
|
||||||
|
self.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError> {
|
||||||
|
self.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RefMakerRawMsgTypeExt {
|
||||||
|
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
||||||
|
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ByteSlice> RefMakerRawMsgTypeExt for RefMaker<B, RawMsgType> {
|
||||||
|
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||||
|
Ok(self.parse()?.read().try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
|
Ok(self.parse()?.read().try_into()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
rosenpass/src/api/boilerplate/mod.rs
Normal file
17
rosenpass/src/api/boilerplate/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
mod byte_slice_ext;
|
||||||
|
mod message_trait;
|
||||||
|
mod message_type;
|
||||||
|
mod payload;
|
||||||
|
mod request_ref;
|
||||||
|
mod request_response;
|
||||||
|
mod response_ref;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
pub use byte_slice_ext::*;
|
||||||
|
pub use message_trait::*;
|
||||||
|
pub use message_type::*;
|
||||||
|
pub use payload::*;
|
||||||
|
pub use request_ref::*;
|
||||||
|
pub use request_response::*;
|
||||||
|
pub use response_ref::*;
|
||||||
|
pub use server::*;
|
||||||
353
rosenpass/src/api/boilerplate/payload.rs
Normal file
353
rosenpass/src/api/boilerplate/payload.rs
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
use rosenpass_util::zerocopy::ZerocopyMutSliceExt;
|
||||||
|
use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
|
||||||
|
|
||||||
|
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
||||||
|
|
||||||
|
/// Size required to fit any message in binary form
|
||||||
|
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
|
||||||
|
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
|
||||||
|
pub const MAX_REQUEST_FDS: usize = 2;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||||
|
/// Which message this is
|
||||||
|
pub msg_type: RawMsgType,
|
||||||
|
/// The actual Paylod
|
||||||
|
pub payload: M,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type RequestEnvelope<M> = Envelope<M>;
|
||||||
|
pub type ResponseEnvelope<M> = Envelope<M>;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct PingRequestPayload {
|
||||||
|
/// Randomly generated connection id
|
||||||
|
pub echo: [u8; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
||||||
|
|
||||||
|
impl PingRequest {
|
||||||
|
pub fn new(echo: [u8; 256]) -> Self {
|
||||||
|
Self::from_payload(PingRequestPayload { echo })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for PingRequest {
|
||||||
|
type Payload = PingRequestPayload;
|
||||||
|
type MessageClass = RequestMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::Ping;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct PingResponsePayload {
|
||||||
|
/// Randomly generated connection id
|
||||||
|
pub echo: [u8; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
||||||
|
|
||||||
|
impl PingResponse {
|
||||||
|
pub fn new(echo: [u8; 256]) -> Self {
|
||||||
|
Self::from_payload(PingResponsePayload { echo })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for PingResponse {
|
||||||
|
type Payload = PingResponsePayload;
|
||||||
|
type MessageClass = ResponseMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::Ping;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct SupplyKeypairRequestPayload {}
|
||||||
|
|
||||||
|
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
||||||
|
|
||||||
|
impl Default for SupplyKeypairRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupplyKeypairRequest {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::from_payload(SupplyKeypairRequestPayload {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for SupplyKeypairRequest {
|
||||||
|
type Payload = SupplyKeypairRequestPayload;
|
||||||
|
type MessageClass = RequestMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::SupplyKeypair;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod supply_keypair_response_status {
|
||||||
|
pub const OK: u128 = 0;
|
||||||
|
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
||||||
|
// TODO: This is not actually part of the API. Remove.
|
||||||
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
|
pub const INVALID_REQUEST: u128 = 3;
|
||||||
|
/// TODO: Deprectaed, remove
|
||||||
|
pub const IO_ERROR: u128 = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct SupplyKeypairResponsePayload {
|
||||||
|
pub status: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
||||||
|
|
||||||
|
impl SupplyKeypairResponse {
|
||||||
|
pub fn new(status: u128) -> Self {
|
||||||
|
Self::from_payload(SupplyKeypairResponsePayload { status })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for SupplyKeypairResponse {
|
||||||
|
type Payload = SupplyKeypairResponsePayload;
|
||||||
|
type MessageClass = ResponseMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::SupplyKeypair;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct AddListenSocketRequestPayload {}
|
||||||
|
|
||||||
|
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
||||||
|
|
||||||
|
impl Default for AddListenSocketRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddListenSocketRequest {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::from_payload(AddListenSocketRequestPayload {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for AddListenSocketRequest {
|
||||||
|
type Payload = AddListenSocketRequestPayload;
|
||||||
|
type MessageClass = RequestMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::AddListenSocket;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod add_listen_socket_response_status {
|
||||||
|
pub const OK: u128 = 0;
|
||||||
|
pub const INVALID_REQUEST: u128 = 1;
|
||||||
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct AddListenSocketResponsePayload {
|
||||||
|
pub status: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
||||||
|
|
||||||
|
impl AddListenSocketResponse {
|
||||||
|
pub fn new(status: u128) -> Self {
|
||||||
|
Self::from_payload(AddListenSocketResponsePayload { status })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for AddListenSocketResponse {
|
||||||
|
type Payload = AddListenSocketResponsePayload;
|
||||||
|
type MessageClass = ResponseMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::AddListenSocket;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct AddPskBrokerRequestPayload {}
|
||||||
|
|
||||||
|
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
||||||
|
|
||||||
|
impl Default for AddPskBrokerRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddPskBrokerRequest {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::from_payload(AddPskBrokerRequestPayload {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for AddPskBrokerRequest {
|
||||||
|
type Payload = AddPskBrokerRequestPayload;
|
||||||
|
type MessageClass = RequestMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::AddPskBroker;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod add_psk_broker_response_status {
|
||||||
|
pub const OK: u128 = 0;
|
||||||
|
pub const INVALID_REQUEST: u128 = 1;
|
||||||
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
|
pub struct AddPskBrokerResponsePayload {
|
||||||
|
pub status: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
||||||
|
|
||||||
|
impl AddPskBrokerResponse {
|
||||||
|
pub fn new(status: u128) -> Self {
|
||||||
|
Self::from_payload(AddPskBrokerResponsePayload { status })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for AddPskBrokerResponse {
|
||||||
|
type Payload = AddPskBrokerResponsePayload;
|
||||||
|
type MessageClass = ResponseMsgType;
|
||||||
|
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::AddPskBroker;
|
||||||
|
|
||||||
|
fn from_payload(payload: Self::Payload) -> Self {
|
||||||
|
Self {
|
||||||
|
msg_type: Self::MESSAGE_TYPE.into(),
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||||
|
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||||
|
r.init();
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
146
rosenpass/src/api/boilerplate/request_ref.rs
Normal file
146
rosenpass/src/api/boilerplate/request_ref.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use anyhow::ensure;
|
||||||
|
|
||||||
|
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||||
|
|
||||||
|
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
||||||
|
|
||||||
|
struct RequestRefMaker<B> {
|
||||||
|
buf: B,
|
||||||
|
msg_type: RequestMsgType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ByteSlice> RequestRef<B> {
|
||||||
|
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||||
|
RequestRefMaker::new(buf)?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||||
|
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||||
|
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_type(&self) -> RequestMsgType {
|
||||||
|
match self {
|
||||||
|
Self::Ping(_) => RequestMsgType::Ping,
|
||||||
|
Self::SupplyKeypair(_) => RequestMsgType::SupplyKeypair,
|
||||||
|
Self::AddListenSocket(_) => RequestMsgType::AddListenSocket,
|
||||||
|
Self::AddPskBroker(_) => RequestMsgType::AddPskBroker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, PingRequest>> for RequestRef<B> {
|
||||||
|
fn from(v: Ref<B, PingRequest>) -> Self {
|
||||||
|
Self::Ping(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, super::SupplyKeypairRequest>> for RequestRef<B> {
|
||||||
|
fn from(v: Ref<B, super::SupplyKeypairRequest>) -> Self {
|
||||||
|
Self::SupplyKeypair(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, super::AddListenSocketRequest>> for RequestRef<B> {
|
||||||
|
fn from(v: Ref<B, super::AddListenSocketRequest>) -> Self {
|
||||||
|
Self::AddListenSocket(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, super::AddPskBrokerRequest>> for RequestRef<B> {
|
||||||
|
fn from(v: Ref<B, super::AddPskBrokerRequest>) -> Self {
|
||||||
|
Self::AddPskBroker(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ByteSlice> RequestRefMaker<B> {
|
||||||
|
fn new(buf: B) -> anyhow::Result<Self> {
|
||||||
|
let msg_type = buf.deref().request_msg_type_from_prefix()?;
|
||||||
|
Ok(Self { buf, msg_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_size(&self) -> usize {
|
||||||
|
self.msg_type.message_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(self) -> anyhow::Result<RequestRef<B>> {
|
||||||
|
Ok(match self.msg_type {
|
||||||
|
RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?),
|
||||||
|
RequestMsgType::SupplyKeypair => {
|
||||||
|
RequestRef::SupplyKeypair(self.buf.supply_keypair_request()?)
|
||||||
|
}
|
||||||
|
RequestMsgType::AddListenSocket => {
|
||||||
|
RequestRef::AddListenSocket(self.buf.add_listen_socket_request()?)
|
||||||
|
}
|
||||||
|
RequestMsgType::AddPskBroker => {
|
||||||
|
RequestRef::AddPskBroker(self.buf.add_psk_broker_request()?)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
fn from_prefix(self) -> anyhow::Result<Self> {
|
||||||
|
self.ensure_fit()?;
|
||||||
|
let point = self.target_size();
|
||||||
|
let Self { buf, msg_type } = self;
|
||||||
|
let (buf, _) = buf.split_at(point);
|
||||||
|
Ok(Self { buf, msg_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
fn from_suffix(self) -> anyhow::Result<Self> {
|
||||||
|
self.ensure_fit()?;
|
||||||
|
let point = self.buf.len() - self.target_size();
|
||||||
|
let Self { buf, msg_type } = self;
|
||||||
|
let (buf, _) = buf.split_at(point);
|
||||||
|
Ok(Self { buf, msg_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
||||||
|
let have = self.buf.len();
|
||||||
|
let need = self.target_size();
|
||||||
|
ensure!(
|
||||||
|
need <= have,
|
||||||
|
"Buffer is undersized at {have} bytes (need {need} bytes)!"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RequestRef<B> {
|
||||||
|
Ping(Ref<B, PingRequest>),
|
||||||
|
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
||||||
|
AddListenSocket(Ref<B, super::AddListenSocketRequest>),
|
||||||
|
AddPskBroker(Ref<B, super::AddPskBrokerRequest>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> RequestRef<B>
|
||||||
|
where
|
||||||
|
B: ByteSlice,
|
||||||
|
{
|
||||||
|
pub fn bytes(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::Ping(r) => r.bytes(),
|
||||||
|
Self::SupplyKeypair(r) => r.bytes(),
|
||||||
|
Self::AddListenSocket(r) => r.bytes(),
|
||||||
|
Self::AddPskBroker(r) => r.bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> RequestRef<B>
|
||||||
|
where
|
||||||
|
B: ByteSliceMut,
|
||||||
|
{
|
||||||
|
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::Ping(r) => r.bytes_mut(),
|
||||||
|
Self::SupplyKeypair(r) => r.bytes_mut(),
|
||||||
|
Self::AddListenSocket(r) => r.bytes_mut(),
|
||||||
|
Self::AddPskBroker(r) => r.bytes_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
190
rosenpass/src/api/boilerplate/request_response.rs
Normal file
190
rosenpass/src/api/boilerplate/request_response.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use rosenpass_util::zerocopy::{
|
||||||
|
RefMaker, ZerocopyEmancipateExt, ZerocopyEmancipateMutExt, ZerocopySliceExt,
|
||||||
|
};
|
||||||
|
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||||
|
|
||||||
|
use super::{Message, PingRequest, PingResponse};
|
||||||
|
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
||||||
|
|
||||||
|
pub trait RequestMsg: Sized + Message {
|
||||||
|
type ResponseMsg: ResponseMsg;
|
||||||
|
|
||||||
|
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
||||||
|
buf.zk_ref_maker()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
|
Self::zk_response_maker(buf).setup_msg()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_response_from_prefix<B: ByteSliceMut>(
|
||||||
|
buf: B,
|
||||||
|
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
|
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_response_from_suffix<B: ByteSliceMut>(
|
||||||
|
buf: B,
|
||||||
|
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
|
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ResponseMsg: Message {
|
||||||
|
type RequestMsg: RequestMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestMsg for PingRequest {
|
||||||
|
type ResponseMsg = PingResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseMsg for PingResponse {
|
||||||
|
type RequestMsg = PingRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestMsg for super::SupplyKeypairRequest {
|
||||||
|
type ResponseMsg = super::SupplyKeypairResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseMsg for super::SupplyKeypairResponse {
|
||||||
|
type RequestMsg = super::SupplyKeypairRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestMsg for super::AddListenSocketRequest {
|
||||||
|
type ResponseMsg = super::AddListenSocketResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseMsg for super::AddListenSocketResponse {
|
||||||
|
type RequestMsg = super::AddListenSocketRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestMsg for super::AddPskBrokerRequest {
|
||||||
|
type ResponseMsg = super::AddPskBrokerResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseMsg for super::AddPskBrokerResponse {
|
||||||
|
type RequestMsg = super::AddPskBrokerRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
||||||
|
pub type SupplyKeypairPair<B1, B2> = (
|
||||||
|
Ref<B1, super::SupplyKeypairRequest>,
|
||||||
|
Ref<B2, super::SupplyKeypairResponse>,
|
||||||
|
);
|
||||||
|
pub type AddListenSocketPair<B1, B2> = (
|
||||||
|
Ref<B1, super::AddListenSocketRequest>,
|
||||||
|
Ref<B2, super::AddListenSocketResponse>,
|
||||||
|
);
|
||||||
|
pub type AddPskBrokerPair<B1, B2> = (
|
||||||
|
Ref<B1, super::AddPskBrokerRequest>,
|
||||||
|
Ref<B2, super::AddPskBrokerResponse>,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub enum RequestResponsePair<B1, B2> {
|
||||||
|
Ping(PingPair<B1, B2>),
|
||||||
|
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
||||||
|
AddListenSocket(AddListenSocketPair<B1, B2>),
|
||||||
|
AddPskBroker(AddPskBrokerPair<B1, B2>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||||
|
fn from(v: PingPair<B1, B2>) -> Self {
|
||||||
|
RequestResponsePair::Ping(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B1, B2> From<SupplyKeypairPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||||
|
fn from(v: SupplyKeypairPair<B1, B2>) -> Self {
|
||||||
|
RequestResponsePair::SupplyKeypair(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B1, B2> From<AddListenSocketPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||||
|
fn from(v: AddListenSocketPair<B1, B2>) -> Self {
|
||||||
|
RequestResponsePair::AddListenSocket(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B1, B2> From<AddPskBrokerPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||||
|
fn from(v: AddPskBrokerPair<B1, B2>) -> Self {
|
||||||
|
RequestResponsePair::AddPskBroker(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B1, B2> RequestResponsePair<B1, B2>
|
||||||
|
where
|
||||||
|
B1: ByteSlice,
|
||||||
|
B2: ByteSlice,
|
||||||
|
{
|
||||||
|
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
||||||
|
match self {
|
||||||
|
Self::Ping((req, res)) => {
|
||||||
|
let req = RequestRef::Ping(req.emancipate());
|
||||||
|
let res = ResponseRef::Ping(res.emancipate());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
Self::SupplyKeypair((req, res)) => {
|
||||||
|
let req = RequestRef::SupplyKeypair(req.emancipate());
|
||||||
|
let res = ResponseRef::SupplyKeypair(res.emancipate());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
Self::AddListenSocket((req, res)) => {
|
||||||
|
let req = RequestRef::AddListenSocket(req.emancipate());
|
||||||
|
let res = ResponseRef::AddListenSocket(res.emancipate());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
Self::AddPskBroker((req, res)) => {
|
||||||
|
let req = RequestRef::AddPskBroker(req.emancipate());
|
||||||
|
let res = ResponseRef::AddPskBroker(res.emancipate());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request(&self) -> RequestRef<&[u8]> {
|
||||||
|
self.both().0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn response(&self) -> ResponseRef<&[u8]> {
|
||||||
|
self.both().1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B1, B2> RequestResponsePair<B1, B2>
|
||||||
|
where
|
||||||
|
B1: ByteSliceMut,
|
||||||
|
B2: ByteSliceMut,
|
||||||
|
{
|
||||||
|
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
||||||
|
match self {
|
||||||
|
Self::Ping((req, res)) => {
|
||||||
|
let req = RequestRef::Ping(req.emancipate_mut());
|
||||||
|
let res = ResponseRef::Ping(res.emancipate_mut());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
Self::SupplyKeypair((req, res)) => {
|
||||||
|
let req = RequestRef::SupplyKeypair(req.emancipate_mut());
|
||||||
|
let res = ResponseRef::SupplyKeypair(res.emancipate_mut());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
Self::AddListenSocket((req, res)) => {
|
||||||
|
let req = RequestRef::AddListenSocket(req.emancipate_mut());
|
||||||
|
let res = ResponseRef::AddListenSocket(res.emancipate_mut());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
Self::AddPskBroker((req, res)) => {
|
||||||
|
let req = RequestRef::AddPskBroker(req.emancipate_mut());
|
||||||
|
let res = ResponseRef::AddPskBroker(res.emancipate_mut());
|
||||||
|
(req, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
||||||
|
self.both_mut().0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
||||||
|
self.both_mut().1
|
||||||
|
}
|
||||||
|
}
|
||||||
147
rosenpass/src/api/boilerplate/response_ref.rs
Normal file
147
rosenpass/src/api/boilerplate/response_ref.rs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// TODO: This is copied verbatim from ResponseRef…not pretty
|
||||||
|
use anyhow::ensure;
|
||||||
|
|
||||||
|
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||||
|
|
||||||
|
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
||||||
|
|
||||||
|
struct ResponseRefMaker<B> {
|
||||||
|
buf: B,
|
||||||
|
msg_type: ResponseMsgType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ByteSlice> ResponseRef<B> {
|
||||||
|
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||||
|
ResponseRefMaker::new(buf)?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||||
|
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||||
|
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_type(&self) -> ResponseMsgType {
|
||||||
|
match self {
|
||||||
|
Self::Ping(_) => ResponseMsgType::Ping,
|
||||||
|
Self::SupplyKeypair(_) => ResponseMsgType::SupplyKeypair,
|
||||||
|
Self::AddListenSocket(_) => ResponseMsgType::AddListenSocket,
|
||||||
|
Self::AddPskBroker(_) => ResponseMsgType::AddPskBroker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, PingResponse>> for ResponseRef<B> {
|
||||||
|
fn from(v: Ref<B, PingResponse>) -> Self {
|
||||||
|
Self::Ping(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, super::SupplyKeypairResponse>> for ResponseRef<B> {
|
||||||
|
fn from(v: Ref<B, super::SupplyKeypairResponse>) -> Self {
|
||||||
|
Self::SupplyKeypair(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, super::AddListenSocketResponse>> for ResponseRef<B> {
|
||||||
|
fn from(v: Ref<B, super::AddListenSocketResponse>) -> Self {
|
||||||
|
Self::AddListenSocket(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Ref<B, super::AddPskBrokerResponse>> for ResponseRef<B> {
|
||||||
|
fn from(v: Ref<B, super::AddPskBrokerResponse>) -> Self {
|
||||||
|
Self::AddPskBroker(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ByteSlice> ResponseRefMaker<B> {
|
||||||
|
fn new(buf: B) -> anyhow::Result<Self> {
|
||||||
|
let msg_type = buf.deref().response_msg_type_from_prefix()?;
|
||||||
|
Ok(Self { buf, msg_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_size(&self) -> usize {
|
||||||
|
self.msg_type.message_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(self) -> anyhow::Result<ResponseRef<B>> {
|
||||||
|
Ok(match self.msg_type {
|
||||||
|
ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?),
|
||||||
|
ResponseMsgType::SupplyKeypair => {
|
||||||
|
ResponseRef::SupplyKeypair(self.buf.supply_keypair_response()?)
|
||||||
|
}
|
||||||
|
ResponseMsgType::AddListenSocket => {
|
||||||
|
ResponseRef::AddListenSocket(self.buf.add_listen_socket_response()?)
|
||||||
|
}
|
||||||
|
ResponseMsgType::AddPskBroker => {
|
||||||
|
ResponseRef::AddPskBroker(self.buf.add_psk_broker_response()?)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
fn from_prefix(self) -> anyhow::Result<Self> {
|
||||||
|
self.ensure_fit()?;
|
||||||
|
let point = self.target_size();
|
||||||
|
let Self { buf, msg_type } = self;
|
||||||
|
let (buf, _) = buf.split_at(point);
|
||||||
|
Ok(Self { buf, msg_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
fn from_suffix(self) -> anyhow::Result<Self> {
|
||||||
|
self.ensure_fit()?;
|
||||||
|
let point = self.buf.len() - self.target_size();
|
||||||
|
let Self { buf, msg_type } = self;
|
||||||
|
let (buf, _) = buf.split_at(point);
|
||||||
|
Ok(Self { buf, msg_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
||||||
|
let have = self.buf.len();
|
||||||
|
let need = self.target_size();
|
||||||
|
ensure!(
|
||||||
|
need <= have,
|
||||||
|
"Buffer is undersized at {have} bytes (need {need} bytes)!"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ResponseRef<B> {
|
||||||
|
Ping(Ref<B, PingResponse>),
|
||||||
|
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
||||||
|
AddListenSocket(Ref<B, super::AddListenSocketResponse>),
|
||||||
|
AddPskBroker(Ref<B, super::AddPskBrokerResponse>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> ResponseRef<B>
|
||||||
|
where
|
||||||
|
B: ByteSlice,
|
||||||
|
{
|
||||||
|
pub fn bytes(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::Ping(r) => r.bytes(),
|
||||||
|
Self::SupplyKeypair(r) => r.bytes(),
|
||||||
|
Self::AddListenSocket(r) => r.bytes(),
|
||||||
|
Self::AddPskBroker(r) => r.bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> ResponseRef<B>
|
||||||
|
where
|
||||||
|
B: ByteSliceMut,
|
||||||
|
{
|
||||||
|
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::Ping(r) => r.bytes_mut(),
|
||||||
|
Self::SupplyKeypair(r) => r.bytes_mut(),
|
||||||
|
Self::AddListenSocket(r) => r.bytes_mut(),
|
||||||
|
Self::AddPskBroker(r) => r.bytes_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
159
rosenpass/src/api/boilerplate/server.rs
Normal file
159
rosenpass/src/api/boilerplate/server.rs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair};
|
||||||
|
use std::{collections::VecDeque, os::fd::OwnedFd};
|
||||||
|
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||||
|
|
||||||
|
pub trait Server {
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
||||||
|
///
|
||||||
|
/// It merely takes a buffer and returns that same buffer.
|
||||||
|
fn ping(
|
||||||
|
&mut self,
|
||||||
|
req: &PingRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut PingResponse,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// Supply the cryptographic server keypair through file descriptor passing in the API
|
||||||
|
///
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::SupplyKeypair] API message.
|
||||||
|
///
|
||||||
|
/// # File descriptors
|
||||||
|
///
|
||||||
|
/// 1. The secret key (size must match exactly); the file descriptor must be backed by either
|
||||||
|
/// of
|
||||||
|
/// - file-system file
|
||||||
|
/// - [memfd](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
/// - [memfd_secret](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
/// 2. The public key (size must match exactly); the file descriptor must be backed by either
|
||||||
|
/// of
|
||||||
|
/// - file-system file
|
||||||
|
/// - [memfd](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
/// - [memfd_secret](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
///
|
||||||
|
/// # API Return Status
|
||||||
|
///
|
||||||
|
/// 1. [crate::api::supply_keypair_response_status::OK] - Indicates success
|
||||||
|
/// 2. [crate::api::supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED] – The endpoint was used but
|
||||||
|
/// the server already has server keys
|
||||||
|
/// 3. [crate::api::supply_keypair_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||||
|
/// - Missing file descriptors for public key
|
||||||
|
/// - File descriptors contain data of invalid length
|
||||||
|
/// - Invalid file descriptor type
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// At startup, if no server keys are specified in the rosenpass configuration, and if the API
|
||||||
|
/// is enabled, the Rosenpass process waits for server keys to be supplied to the API. Before
|
||||||
|
/// then, any messages for the rosenpass cryptographic protocol are ignored and dropped – all
|
||||||
|
/// cryptographic operations require access to the server keys.
|
||||||
|
///
|
||||||
|
/// Both private and public keys are specified through file descriptors and both are read from
|
||||||
|
/// their respective file descriptors into process memory. A file descriptor based transport is
|
||||||
|
/// used because of the excessive size of Classic McEliece public keys (100kb and up).
|
||||||
|
///
|
||||||
|
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
|
||||||
|
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
|
||||||
|
/// backed file descriptor if the server keys are not backed by a file system file.
|
||||||
|
fn supply_keypair(
|
||||||
|
&mut self,
|
||||||
|
req: &super::SupplyKeypairRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::SupplyKeypairResponse,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// Supply a new UDP listen socket through file descriptor passing via the API
|
||||||
|
///
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::AddListenSocket] API message.
|
||||||
|
///
|
||||||
|
/// # File descriptors
|
||||||
|
///
|
||||||
|
/// 1. The listen socket; must be backed by a UDP network listen socket
|
||||||
|
///
|
||||||
|
/// # API Return Status
|
||||||
|
///
|
||||||
|
/// 1. [crate::api::add_listen_socket_response_status::OK] - Indicates success
|
||||||
|
/// 2. [add_listen_socket_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||||
|
/// - Missing file descriptors for public key
|
||||||
|
/// - Invalid file descriptor type
|
||||||
|
/// 3. [crate::api::add_listen_socket_response_status::INTERNAL_ERROR] – Some other, non-fatal error
|
||||||
|
/// occured. Check the logs on log
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform
|
||||||
|
/// cryptographic key exchanges via the Rosenpass protocol.
|
||||||
|
fn add_listen_socket(
|
||||||
|
&mut self,
|
||||||
|
req: &super::AddListenSocketRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::AddListenSocketResponse,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
fn add_psk_broker(
|
||||||
|
&mut self,
|
||||||
|
req: &super::AddPskBrokerRequest,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: &mut super::AddPskBrokerResponse,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
fn dispatch<ReqBuf, ResBuf>(
|
||||||
|
&mut self,
|
||||||
|
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
ReqBuf: ByteSlice,
|
||||||
|
ResBuf: ByteSliceMut,
|
||||||
|
{
|
||||||
|
match p {
|
||||||
|
RequestResponsePair::Ping((req, res)) => self.ping(req, req_fds, res),
|
||||||
|
RequestResponsePair::SupplyKeypair((req, res)) => {
|
||||||
|
self.supply_keypair(req, req_fds, res)
|
||||||
|
}
|
||||||
|
RequestResponsePair::AddListenSocket((req, res)) => {
|
||||||
|
self.add_listen_socket(req, req_fds, res)
|
||||||
|
}
|
||||||
|
RequestResponsePair::AddPskBroker((req, res)) => self.add_psk_broker(req, req_fds, res),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_message<ReqBuf, ResBuf>(
|
||||||
|
&mut self,
|
||||||
|
req: ReqBuf,
|
||||||
|
req_fds: &mut VecDeque<OwnedFd>,
|
||||||
|
res: ResBuf,
|
||||||
|
) -> anyhow::Result<usize>
|
||||||
|
where
|
||||||
|
ReqBuf: ByteSlice,
|
||||||
|
ResBuf: ByteSliceMut,
|
||||||
|
{
|
||||||
|
let req = req.parse_request_from_prefix()?;
|
||||||
|
// TODO: This is not pretty; This match should be moved into RequestRef
|
||||||
|
let mut pair = match req {
|
||||||
|
RequestRef::Ping(req) => {
|
||||||
|
let mut res = res.ping_response_from_prefix()?;
|
||||||
|
res.init();
|
||||||
|
RequestResponsePair::Ping((req, res))
|
||||||
|
}
|
||||||
|
RequestRef::SupplyKeypair(req) => {
|
||||||
|
let mut res = res.supply_keypair_response_from_prefix()?;
|
||||||
|
res.init();
|
||||||
|
RequestResponsePair::SupplyKeypair((req, res))
|
||||||
|
}
|
||||||
|
RequestRef::AddListenSocket(req) => {
|
||||||
|
let mut res = res.add_listen_socket_response_from_prefix()?;
|
||||||
|
res.init();
|
||||||
|
RequestResponsePair::AddListenSocket((req, res))
|
||||||
|
}
|
||||||
|
RequestRef::AddPskBroker(req) => {
|
||||||
|
let mut res = res.add_psk_broker_response_from_prefix()?;
|
||||||
|
res.init();
|
||||||
|
RequestResponsePair::AddPskBroker((req, res))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.dispatch(&mut pair, req_fds)?;
|
||||||
|
|
||||||
|
let res_len = pair.response().bytes().len();
|
||||||
|
Ok(res_len)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
rosenpass/src/api/cli.rs
Normal file
40
rosenpass/src/api/cli.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
use crate::config::Rosenpass as RosenpassConfig;
|
||||||
|
|
||||||
|
use super::config::ApiConfig;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ApiCli {
|
||||||
|
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||||
|
/// connections on.
|
||||||
|
#[arg(long)]
|
||||||
|
api_listen_path: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// When rosenpass is called from another process, the other process can open and bind the
|
||||||
|
/// unix socket for the Rosenpass API 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)]
|
||||||
|
api_listen_fd: Vec<i32>,
|
||||||
|
|
||||||
|
/// When rosenpass is called from another process, the other process can connect the unix socket for the API
|
||||||
|
/// themselves, for instance using the `socketpair(2)` system call.
|
||||||
|
#[arg(long)]
|
||||||
|
api_stream_fd: Vec<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiCli {
|
||||||
|
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
||||||
|
self.apply_to_api_config(&mut cfg.api)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
|
||||||
|
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
||||||
|
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
||||||
|
cfg.stream_fd.extend_from_slice(&self.api_stream_fd);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
49
rosenpass/src/api/config.rs
Normal file
49
rosenpass/src/api/config.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use mio::net::UnixListener;
|
||||||
|
use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::app_server::AppServer;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
pub struct ApiConfig {
|
||||||
|
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||||
|
/// connections on
|
||||||
|
pub listen_path: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// When rosenpass is called from another process, the other process can open and bind the
|
||||||
|
/// unix socket for the Rosenpass API 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.
|
||||||
|
pub listen_fd: Vec<i32>,
|
||||||
|
|
||||||
|
/// When rosenpass is called from another process, the other process can connect the unix socket for the API
|
||||||
|
/// themselves, for instance using the `socketpair(2)` system call.
|
||||||
|
pub stream_fd: Vec<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiConfig {
|
||||||
|
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
|
for path in self.listen_path.iter() {
|
||||||
|
srv.add_api_listener(UnixListener::bind(path)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for fd in self.listen_fd.iter() {
|
||||||
|
srv.add_api_listener(UnixListenerExt::claim_fd(*fd)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for fd in self.stream_fd.iter() {
|
||||||
|
srv.add_api_connection(UnixStreamExt::claim_fd(*fd)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count_api_sources(&self) -> usize {
|
||||||
|
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_api_sources(&self) -> bool {
|
||||||
|
self.count_api_sources() > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
321
rosenpass/src/api/mio/connection.rs
Normal file
321
rosenpass/src/api/mio/connection.rs
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
use std::borrow::{Borrow, BorrowMut};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::os::fd::OwnedFd;
|
||||||
|
|
||||||
|
use mio::net::UnixStream;
|
||||||
|
use rosenpass_secret_memory::Secret;
|
||||||
|
use rosenpass_util::mio::ReadWithFileDescriptors;
|
||||||
|
use rosenpass_util::{
|
||||||
|
io::{IoResultKindHintExt, TryIoResultKindHintExt},
|
||||||
|
length_prefix_encoding::{
|
||||||
|
decoder::{self as lpe_decoder, LengthPrefixDecoder},
|
||||||
|
encoder::{self as lpe_encoder, LengthPrefixEncoder},
|
||||||
|
},
|
||||||
|
mio::interest::RW as MIO_RW,
|
||||||
|
};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::api::MAX_REQUEST_FDS;
|
||||||
|
use crate::{api::Server, app_server::AppServer};
|
||||||
|
|
||||||
|
use super::super::{ApiHandler, ApiHandlerContext};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SecretBuffer<const N: usize>(pub Secret<N>);
|
||||||
|
|
||||||
|
impl<const N: usize> SecretBuffer<N> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Secret::zero())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Borrow<[u8]> for SecretBuffer<N> {
|
||||||
|
fn borrow(&self) -> &[u8] {
|
||||||
|
self.0.secret()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> BorrowMut<[u8]> for SecretBuffer<N> {
|
||||||
|
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||||
|
self.0.secret_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Unfortunately, zerocopy is quite particular about alignment, hence the 4096
|
||||||
|
type ReadBuffer = LengthPrefixDecoder<SecretBuffer<4096>>;
|
||||||
|
type WriteBuffer = LengthPrefixEncoder<SecretBuffer<4096>>;
|
||||||
|
type ReadFdBuffer = VecDeque<OwnedFd>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MioConnectionBuffers {
|
||||||
|
read_buffer: ReadBuffer,
|
||||||
|
write_buffer: WriteBuffer,
|
||||||
|
read_fd_buffer: ReadFdBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MioConnection {
|
||||||
|
io: UnixStream,
|
||||||
|
mio_token: mio::Token,
|
||||||
|
invalid_read: bool,
|
||||||
|
buffers: Option<MioConnectionBuffers>,
|
||||||
|
api_handler: ApiHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MioConnection {
|
||||||
|
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
|
||||||
|
let mio_token = app_server.mio_token_dispenser.dispense();
|
||||||
|
app_server
|
||||||
|
.mio_poll
|
||||||
|
.registry()
|
||||||
|
.register(&mut io, mio_token, MIO_RW)?;
|
||||||
|
|
||||||
|
let invalid_read = false;
|
||||||
|
let read_buffer = LengthPrefixDecoder::new(SecretBuffer::new());
|
||||||
|
let write_buffer = LengthPrefixEncoder::from_buffer(SecretBuffer::new());
|
||||||
|
let read_fd_buffer = VecDeque::new();
|
||||||
|
let buffers = Some(MioConnectionBuffers {
|
||||||
|
read_buffer,
|
||||||
|
write_buffer,
|
||||||
|
read_fd_buffer,
|
||||||
|
});
|
||||||
|
let api_state = ApiHandler::new();
|
||||||
|
Ok(Self {
|
||||||
|
io,
|
||||||
|
mio_token,
|
||||||
|
invalid_read,
|
||||||
|
buffers,
|
||||||
|
api_handler: api_state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_close(&self) -> bool {
|
||||||
|
let exhausted = self
|
||||||
|
.buffers
|
||||||
|
.as_ref()
|
||||||
|
.map(|b| b.write_buffer.exhausted())
|
||||||
|
.unwrap_or(false);
|
||||||
|
self.invalid_read && exhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
||||||
|
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mio_token(&self) -> mio::Token {
|
||||||
|
self.mio_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MioConnectionContext {
|
||||||
|
fn mio_connection(&self) -> &MioConnection;
|
||||||
|
fn app_server(&self) -> &AppServer;
|
||||||
|
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
||||||
|
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> anyhow::Result<()> {
|
||||||
|
macro_rules! short {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(()) => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// All of these functions return an error, None ("operation incomplete")
|
||||||
|
// or some ("operation complete, keep processing")
|
||||||
|
short!(self.flush_write_buffer()?); // Flush last message
|
||||||
|
short!(self.recv()?); // Receive new message
|
||||||
|
short!(self.handle_incoming_message()?); // Process new message with API
|
||||||
|
short!(self.flush_write_buffer()?); // Begin flushing response
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
|
self.with_buffers_stolen(|this, bufs| {
|
||||||
|
// Acquire request & response. Caller is responsible to make sure
|
||||||
|
// that read buffer holds a message and that write buffer is cleared.
|
||||||
|
// Hence the unwraps and assertions
|
||||||
|
assert!(bufs.write_buffer.exhausted());
|
||||||
|
let req = bufs.read_buffer.message().unwrap().unwrap();
|
||||||
|
let req_fds = &mut bufs.read_fd_buffer;
|
||||||
|
let res = bufs.write_buffer.buffer_bytes_mut();
|
||||||
|
|
||||||
|
// Call API handler
|
||||||
|
// Transitive trait implementations: MioConnectionContext -> ApiHandlerContext -> as ApiServer
|
||||||
|
let response_len = this.handle_message(req, req_fds, res)?;
|
||||||
|
|
||||||
|
bufs.write_buffer
|
||||||
|
.restart_write_with_new_message(response_len)?;
|
||||||
|
bufs.read_buffer.zeroize(); // clear for new message to read
|
||||||
|
bufs.read_fd_buffer.clear();
|
||||||
|
|
||||||
|
Ok(Some(()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
|
if self.write_buf_mut().exhausted() {
|
||||||
|
return Ok(Some(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
use lpe_encoder::WriteToIoReturn as Ret;
|
||||||
|
use std::io::ErrorKind as K;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let conn = self.mio_connection_mut();
|
||||||
|
let bufs = conn.buffers.as_mut().unwrap();
|
||||||
|
|
||||||
|
let sock = &conn.io;
|
||||||
|
let write_buf = &mut bufs.write_buffer;
|
||||||
|
|
||||||
|
match write_buf.write_to_stdio(sock).io_err_kind_hint() {
|
||||||
|
// Done
|
||||||
|
Ok(Ret { done: true, .. }) => {
|
||||||
|
write_buf.zeroize(); // clear for new message to write
|
||||||
|
break Ok(Some(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Would block
|
||||||
|
Ok(Ret {
|
||||||
|
bytes_written: 0, ..
|
||||||
|
}) => break Ok(None),
|
||||||
|
Err((_e, K::WouldBlock)) => break Ok(None),
|
||||||
|
|
||||||
|
// Just continue
|
||||||
|
Ok(_) => continue, /* Ret { bytes_written > 0, done = false } acc. to previous cases*/
|
||||||
|
Err((_e, K::Interrupted)) => continue,
|
||||||
|
|
||||||
|
// Other errors
|
||||||
|
Err((e, _ek)) => Err(e)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
|
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret};
|
||||||
|
use std::io::ErrorKind as K;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let conn = self.mio_connection_mut();
|
||||||
|
let bufs = conn.buffers.as_mut().unwrap();
|
||||||
|
|
||||||
|
let read_buf = &mut bufs.read_buffer;
|
||||||
|
let read_fd_buf = &mut bufs.read_fd_buffer;
|
||||||
|
|
||||||
|
let sock = &conn.io;
|
||||||
|
let fd_passing_sock = ReadWithFileDescriptors::<MAX_REQUEST_FDS, UnixStream, _, _>::new(
|
||||||
|
sock,
|
||||||
|
read_fd_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
match read_buf
|
||||||
|
.read_from_stdio(fd_passing_sock)
|
||||||
|
.try_io_err_kind_hint()
|
||||||
|
{
|
||||||
|
// We actually received a proper message
|
||||||
|
// (Impl below match to appease borrow checker)
|
||||||
|
Ok(Ret {
|
||||||
|
message: Some(_msg),
|
||||||
|
..
|
||||||
|
}) => break Ok(Some(())),
|
||||||
|
|
||||||
|
// Message does not fit in buffer
|
||||||
|
Err((e @ E::MessageTooLargeError { .. }, _)) => {
|
||||||
|
log::warn!("Received message on API that was too big to fit in our buffers; \
|
||||||
|
looks like the client is broken. Stopping to process messages of the client.\n\
|
||||||
|
Error: {e:?}");
|
||||||
|
conn.invalid_read = true; // Closed mio_manager
|
||||||
|
break Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Would block
|
||||||
|
Ok(Ret { bytes_read: 0, .. }) => break Ok(None),
|
||||||
|
Err((_, Some(K::WouldBlock))) => break Ok(None),
|
||||||
|
|
||||||
|
// Just keep going
|
||||||
|
Ok(Ret { bytes_read: _, .. }) => continue,
|
||||||
|
Err((_, Some(K::Interrupted))) => continue,
|
||||||
|
|
||||||
|
// Other IO Error (just pass on to the caller)
|
||||||
|
Err((E::IoError(e), _)) => {
|
||||||
|
log::warn!(
|
||||||
|
"IO error while trying to read message from API socket. \
|
||||||
|
The connection is broken. Stopping to process messages of the client.\n\
|
||||||
|
Error: {e:?}"
|
||||||
|
);
|
||||||
|
conn.invalid_read = true; // closed later by mio_manager
|
||||||
|
break Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mio_token(&self) -> mio::Token {
|
||||||
|
self.mio_connection().mio_token()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_close(&self) -> bool {
|
||||||
|
self.mio_connection().should_close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait MioConnectionContextPrivate: MioConnectionContext {
|
||||||
|
fn steal_buffers(&mut self) -> MioConnectionBuffers {
|
||||||
|
self.mio_connection_mut().buffers.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_buffers(&mut self, buffers: MioConnectionBuffers) {
|
||||||
|
let opt = &mut self.mio_connection_mut().buffers;
|
||||||
|
assert!(opt.is_none());
|
||||||
|
let _ = opt.insert(buffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_buffers_stolen<R, F: FnOnce(&mut Self, &mut MioConnectionBuffers) -> R>(
|
||||||
|
&mut self,
|
||||||
|
f: F,
|
||||||
|
) -> R {
|
||||||
|
let mut bufs = self.steal_buffers();
|
||||||
|
let res = f(self, &mut bufs);
|
||||||
|
self.return_buffers(bufs);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_buf_mut(&mut self) -> &mut WriteBuffer {
|
||||||
|
self.mio_connection_mut()
|
||||||
|
.buffers
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.write_buffer
|
||||||
|
.borrow_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
||||||
|
|
||||||
|
impl<T> ApiHandlerContext for T
|
||||||
|
where
|
||||||
|
T: ?Sized + MioConnectionContext,
|
||||||
|
{
|
||||||
|
fn api_handler(&self) -> &ApiHandler {
|
||||||
|
&self.mio_connection().api_handler
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_server(&self) -> &AppServer {
|
||||||
|
MioConnectionContext::app_server(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_handler_mut(&mut self) -> &mut ApiHandler {
|
||||||
|
&mut self.mio_connection_mut().api_handler
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_server_mut(&mut self) -> &mut AppServer {
|
||||||
|
MioConnectionContext::app_server_mut(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
173
rosenpass/src/api/mio/manager.rs
Normal file
173
rosenpass/src/api/mio/manager.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
use std::{borrow::BorrowMut, io};
|
||||||
|
|
||||||
|
use mio::net::{UnixListener, UnixStream};
|
||||||
|
|
||||||
|
use rosenpass_util::{
|
||||||
|
functional::ApplyExt, io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app_server::{AppServer, AppServerIoSource};
|
||||||
|
|
||||||
|
use super::{MioConnection, MioConnectionContext};
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct MioManager {
|
||||||
|
listeners: Vec<UnixListener>,
|
||||||
|
connections: Vec<Option<MioConnection>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum MioManagerIoSource {
|
||||||
|
Listener(usize),
|
||||||
|
Connection(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MioManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
||||||
|
ctx: &'a mut T,
|
||||||
|
conn_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
||||||
|
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
||||||
|
Self { ctx, conn_idx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MioManagerContext {
|
||||||
|
fn mio_manager(&self) -> &MioManager;
|
||||||
|
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
||||||
|
fn app_server(&self) -> &AppServer;
|
||||||
|
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||||
|
|
||||||
|
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
|
||||||
|
let srv = self.app_server_mut();
|
||||||
|
let mio_token = srv.mio_token_dispenser.dispense();
|
||||||
|
srv.mio_poll
|
||||||
|
.registry()
|
||||||
|
.register(&mut listener, mio_token, MIO_RW)?;
|
||||||
|
let io_source = self
|
||||||
|
.mio_manager()
|
||||||
|
.listeners
|
||||||
|
.len()
|
||||||
|
.apply(MioManagerIoSource::Listener)
|
||||||
|
.apply(AppServerIoSource::MioManager);
|
||||||
|
self.mio_manager_mut().listeners.push(listener);
|
||||||
|
self.app_server_mut()
|
||||||
|
.register_io_source(mio_token, io_source);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
||||||
|
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
||||||
|
let mio_token = connection.mio_token();
|
||||||
|
let conns: &mut Vec<Option<MioConnection>> =
|
||||||
|
self.mio_manager_mut().connections.borrow_mut();
|
||||||
|
let idx = conns
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, slot)| slot.is_some())
|
||||||
|
.map(|(idx, _)| idx)
|
||||||
|
.unwrap_or(conns.len());
|
||||||
|
conns.insert(idx, Some(connection));
|
||||||
|
let io_source = idx
|
||||||
|
.apply(MioManagerIoSource::Listener)
|
||||||
|
.apply(AppServerIoSource::MioManager);
|
||||||
|
self.app_server_mut()
|
||||||
|
.register_io_source(mio_token, io_source);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
||||||
|
use MioManagerIoSource as S;
|
||||||
|
match io_source {
|
||||||
|
S::Listener(idx) => self.accept_from(idx)?,
|
||||||
|
S::Connection(idx) => self.poll_particular_connection(idx)?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self) -> anyhow::Result<()> {
|
||||||
|
self.accept_connections()?;
|
||||||
|
self.poll_connections()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept_connections(&mut self) -> io::Result<()> {
|
||||||
|
for idx in 0..self.mio_manager_mut().listeners.len() {
|
||||||
|
self.accept_from(idx)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
||||||
|
// Accept connection until the socket would block or returns another error
|
||||||
|
// TODO: This currently only adds connections--we eventually need the ability to remove
|
||||||
|
// them as well, see the note in connection.rs
|
||||||
|
loop {
|
||||||
|
match nonblocking_handle_io_errors(|| self.mio_manager().listeners[idx].accept())? {
|
||||||
|
None => break,
|
||||||
|
Some((conn, _addr)) => {
|
||||||
|
self.add_connection(conn)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
||||||
|
for idx in 0..self.mio_manager().connections.len() {
|
||||||
|
self.poll_particular_connection(idx)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
||||||
|
if self.mio_manager().connections[idx].is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut conn = MioConnectionFocus::new(self, idx);
|
||||||
|
conn.poll()?;
|
||||||
|
|
||||||
|
if conn.should_close() {
|
||||||
|
let conn = self.mio_manager_mut().connections[idx].take().unwrap();
|
||||||
|
let mio_token = conn.mio_token();
|
||||||
|
if let Err(e) = conn.close(self.app_server_mut()) {
|
||||||
|
log::warn!("Error while closing API connection {e:?}");
|
||||||
|
};
|
||||||
|
self.app_server_mut().unregister_io_source(mio_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + MioManagerContext> MioConnectionContext for MioConnectionFocus<'_, T> {
|
||||||
|
fn mio_connection(&self) -> &MioConnection {
|
||||||
|
self.ctx.mio_manager().connections[self.conn_idx]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_server(&self) -> &AppServer {
|
||||||
|
self.ctx.app_server()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mio_connection_mut(&mut self) -> &mut MioConnection {
|
||||||
|
self.ctx.mio_manager_mut().connections[self.conn_idx]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_server_mut(&mut self) -> &mut AppServer {
|
||||||
|
self.ctx.app_server_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
5
rosenpass/src/api/mio/mod.rs
Normal file
5
rosenpass/src/api/mio/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod connection;
|
||||||
|
mod manager;
|
||||||
|
|
||||||
|
pub use connection::*;
|
||||||
|
pub use manager::*;
|
||||||
9
rosenpass/src/api/mod.rs
Normal file
9
rosenpass/src/api/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod api_handler;
|
||||||
|
mod boilerplate;
|
||||||
|
|
||||||
|
pub use api_handler::*;
|
||||||
|
pub use boilerplate::*;
|
||||||
|
|
||||||
|
pub mod cli;
|
||||||
|
pub mod config;
|
||||||
|
pub mod mio;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
@@ -7,7 +8,14 @@ use mio::Interest;
|
|||||||
use mio::Token;
|
use mio::Token;
|
||||||
use rosenpass_secret_memory::Public;
|
use rosenpass_secret_memory::Public;
|
||||||
use rosenpass_secret_memory::Secret;
|
use rosenpass_secret_memory::Secret;
|
||||||
|
use rosenpass_util::build::ConstructionSite;
|
||||||
use rosenpass_util::file::StoreValueB64;
|
use rosenpass_util::file::StoreValueB64;
|
||||||
|
use rosenpass_util::functional::run;
|
||||||
|
use rosenpass_util::functional::ApplyExt;
|
||||||
|
use rosenpass_util::io::IoResultKindHintExt;
|
||||||
|
use rosenpass_util::io::SubstituteForIoErrorKindExt;
|
||||||
|
use rosenpass_util::option::SomeExt;
|
||||||
|
use rosenpass_util::result::OkExt;
|
||||||
use rosenpass_wireguard_broker::WireguardBrokerMio;
|
use rosenpass_wireguard_broker::WireguardBrokerMio;
|
||||||
use rosenpass_wireguard_broker::{WireguardBrokerCfg, WG_KEY_LEN};
|
use rosenpass_wireguard_broker::{WireguardBrokerCfg, WG_KEY_LEN};
|
||||||
use zerocopy::AsBytes;
|
use zerocopy::AsBytes;
|
||||||
@@ -15,8 +23,12 @@ use zerocopy::AsBytes;
|
|||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::io;
|
||||||
|
use std::io::stdout;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
use std::io::Write;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::net::Ipv6Addr;
|
use std::net::Ipv6Addr;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -28,6 +40,7 @@ use std::slice;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use crate::protocol::BuildCryptoServer;
|
||||||
use crate::protocol::HostIdentification;
|
use crate::protocol::HostIdentification;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Verbosity,
|
config::Verbosity,
|
||||||
@@ -63,7 +76,7 @@ pub struct MioTokenDispenser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MioTokenDispenser {
|
impl MioTokenDispenser {
|
||||||
fn dispense(&mut self) -> Token {
|
pub fn dispense(&mut self) -> Token {
|
||||||
let r = self.counter;
|
let r = self.counter;
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
Token(r)
|
Token(r)
|
||||||
@@ -72,7 +85,7 @@ impl MioTokenDispenser {
|
|||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct BrokerStore {
|
pub struct BrokerStore {
|
||||||
store: HashMap<
|
pub store: HashMap<
|
||||||
Public<BROKER_ID_BYTES>,
|
Public<BROKER_ID_BYTES>,
|
||||||
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||||
>,
|
>,
|
||||||
@@ -138,15 +151,28 @@ pub struct AppServerTest {
|
|||||||
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum AppServerIoSource {
|
||||||
|
Socket(usize),
|
||||||
|
PskBroker(Public<BROKER_ID_BYTES>),
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
MioManager(crate::api::mio::MioManagerIoSource),
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_CAPACITY: usize = 20;
|
||||||
|
|
||||||
/// Holds the state of the application, namely the external IO
|
/// Holds the state of the application, namely the external IO
|
||||||
///
|
///
|
||||||
/// Responsible for file IO, network IO
|
/// Responsible for file IO, network IO
|
||||||
// TODO add user control via unix domain socket and stdin/stdout
|
// TODO add user control via unix domain socket and stdin/stdout
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppServer {
|
pub struct AppServer {
|
||||||
pub crypt: CryptoServer,
|
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
|
||||||
pub sockets: Vec<mio::net::UdpSocket>,
|
pub sockets: Vec<mio::net::UdpSocket>,
|
||||||
pub events: mio::Events,
|
pub events: mio::Events,
|
||||||
|
pub short_poll_queue: VecDeque<mio::event::Event>,
|
||||||
|
pub performed_long_poll: bool,
|
||||||
|
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
|
||||||
pub mio_poll: mio::Poll,
|
pub mio_poll: mio::Poll,
|
||||||
pub mio_token_dispenser: MioTokenDispenser,
|
pub mio_token_dispenser: MioTokenDispenser,
|
||||||
pub brokers: BrokerStore,
|
pub brokers: BrokerStore,
|
||||||
@@ -159,6 +185,8 @@ pub struct AppServer {
|
|||||||
pub unpolled_count: usize,
|
pub unpolled_count: usize,
|
||||||
pub last_update_time: Instant,
|
pub last_update_time: Instant,
|
||||||
pub test_helpers: Option<AppServerTest>,
|
pub test_helpers: Option<AppServerTest>,
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
pub api_manager: crate::api::mio::MioManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A socket pointer is an index assigned to a socket;
|
/// A socket pointer is an index assigned to a socket;
|
||||||
@@ -507,15 +535,14 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
|
|
||||||
impl AppServer {
|
impl AppServer {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sk: SSk,
|
keypair: Option<(SSk, SPk)>,
|
||||||
pk: SPk,
|
|
||||||
addrs: Vec<SocketAddr>,
|
addrs: Vec<SocketAddr>,
|
||||||
verbosity: Verbosity,
|
verbosity: Verbosity,
|
||||||
test_helpers: Option<AppServerTest>,
|
test_helpers: Option<AppServerTest>,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
// setup mio
|
// setup mio
|
||||||
let mio_poll = mio::Poll::new()?;
|
let mio_poll = mio::Poll::new()?;
|
||||||
let events = mio::Events::with_capacity(20);
|
let events = mio::Events::with_capacity(EVENT_CAPACITY);
|
||||||
let mut mio_token_dispenser = MioTokenDispenser::default();
|
let mut mio_token_dispenser = MioTokenDispenser::default();
|
||||||
|
|
||||||
// bind each SocketAddr to a socket
|
// bind each SocketAddr to a socket
|
||||||
@@ -590,22 +617,30 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// register all sockets to mio
|
// register all sockets to mio
|
||||||
for socket in sockets.iter_mut() {
|
let mut io_source_index = HashMap::new();
|
||||||
mio_poll.registry().register(
|
for (idx, socket) in sockets.iter_mut().enumerate() {
|
||||||
socket,
|
let mio_token = mio_token_dispenser.dispense();
|
||||||
mio_token_dispenser.dispense(),
|
mio_poll
|
||||||
Interest::READABLE,
|
.registry()
|
||||||
)?;
|
.register(socket, mio_token, Interest::READABLE)?;
|
||||||
|
let prev = io_source_index.insert(mio_token, AppServerIoSource::Socket(idx));
|
||||||
|
assert!(prev.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux
|
let crypto_site = match keypair {
|
||||||
|
Some((sk, pk)) => ConstructionSite::from_product(CryptoServer::new(sk, pk)),
|
||||||
|
None => ConstructionSite::new(BuildCryptoServer::empty()),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
crypt: CryptoServer::new(sk, pk),
|
crypto_site,
|
||||||
peers: Vec::new(),
|
peers: Vec::new(),
|
||||||
verbosity,
|
verbosity,
|
||||||
sockets,
|
sockets,
|
||||||
events,
|
events,
|
||||||
|
short_poll_queue: Default::default(),
|
||||||
|
performed_long_poll: false,
|
||||||
|
io_source_index,
|
||||||
mio_poll,
|
mio_poll,
|
||||||
mio_token_dispenser,
|
mio_token_dispenser,
|
||||||
brokers: BrokerStore::default(),
|
brokers: BrokerStore::default(),
|
||||||
@@ -616,48 +651,78 @@ impl AppServer {
|
|||||||
unpolled_count: 0,
|
unpolled_count: 0,
|
||||||
last_update_time: Instant::now(),
|
last_update_time: Instant::now(),
|
||||||
test_helpers,
|
test_helpers,
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
api_manager: crate::api::mio::MioManager::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
||||||
|
self.crypto_site
|
||||||
|
.product_ref()
|
||||||
|
.context("Cryptography handler not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
||||||
|
self.crypto_site
|
||||||
|
.product_mut()
|
||||||
|
.context("Cryptography handler not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn verbose(&self) -> bool {
|
pub fn verbose(&self) -> bool {
|
||||||
matches!(self.verbosity, Verbosity::Verbose)
|
matches!(self.verbosity, Verbosity::Verbose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
||||||
|
let mio_token = self.mio_token_dispenser.dispense();
|
||||||
|
self.mio_poll
|
||||||
|
.registry()
|
||||||
|
.register(&mut sock, mio_token, mio::Interest::READABLE)?;
|
||||||
|
let io_source = self.sockets.len().apply(AppServerIoSource::Socket);
|
||||||
|
self.sockets.push(sock);
|
||||||
|
self.register_io_source(mio_token, io_source);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
||||||
|
let prev = self.io_source_index.insert(token, io_source);
|
||||||
|
assert!(prev.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
||||||
|
let value = self.io_source_index.remove(&token);
|
||||||
|
assert!(value.is_some(), "Removed IO source that does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn register_broker(
|
pub fn register_broker(
|
||||||
&mut self,
|
&mut self,
|
||||||
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||||
) -> Result<BrokerStorePtr> {
|
) -> Result<BrokerStorePtr> {
|
||||||
let ptr = Public::from_slice((self.brokers.store.len() as u64).as_bytes());
|
let ptr = Public::from_slice((self.brokers.store.len() as u64).as_bytes());
|
||||||
|
|
||||||
if self.brokers.store.insert(ptr, broker).is_some() {
|
if self.brokers.store.insert(ptr, broker).is_some() {
|
||||||
bail!("Broker already registered");
|
bail!("Broker already registered");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mio_token = self.mio_token_dispenser.dispense();
|
||||||
|
let io_source = ptr.apply(AppServerIoSource::PskBroker);
|
||||||
//Register broker
|
//Register broker
|
||||||
self.brokers
|
self.brokers
|
||||||
.store
|
.store
|
||||||
.get_mut(&ptr)
|
.get_mut(&ptr)
|
||||||
.ok_or(anyhow::format_err!("Broker wasn't added to registry"))?
|
.ok_or(anyhow::format_err!("Broker wasn't added to registry"))?
|
||||||
.register(
|
.register(self.mio_poll.registry(), mio_token)?;
|
||||||
self.mio_poll.registry(),
|
self.register_io_source(mio_token, io_source);
|
||||||
self.mio_token_dispenser.dispense(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(BrokerStorePtr(ptr))
|
Ok(BrokerStorePtr(ptr))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
||||||
//Unregister broker
|
let mut broker = self
|
||||||
self.brokers
|
.brokers
|
||||||
.store
|
|
||||||
.get_mut(&ptr.0)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Broker not found"))?
|
|
||||||
.unregister(self.mio_poll.registry())?;
|
|
||||||
|
|
||||||
//Remove broker from store
|
|
||||||
self.brokers
|
|
||||||
.store
|
.store
|
||||||
.remove(&ptr.0)
|
.remove(&ptr.0)
|
||||||
.ok_or_else(|| anyhow::anyhow!("Broker not found"))?;
|
.context("Broker not found")?;
|
||||||
|
self.unregister_io_source(broker.mio_token().unwrap());
|
||||||
|
broker.unregister(self.mio_poll.registry())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,8 +734,13 @@ impl AppServer {
|
|||||||
broker_peer: Option<BrokerPeer>,
|
broker_peer: Option<BrokerPeer>,
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
) -> anyhow::Result<AppPeerPtr> {
|
) -> anyhow::Result<AppPeerPtr> {
|
||||||
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
|
let PeerPtr(pn) = match &mut self.crypto_site {
|
||||||
|
ConstructionSite::Void => bail!("Crypto server construction site is void"),
|
||||||
|
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk),
|
||||||
|
ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?,
|
||||||
|
};
|
||||||
assert!(pn == self.peers.len());
|
assert!(pn == self.peers.len());
|
||||||
|
|
||||||
let initial_endpoint = hostname
|
let initial_endpoint = hostname
|
||||||
.map(Endpoint::discovery_from_hostname)
|
.map(Endpoint::discovery_from_hostname)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
@@ -712,7 +782,7 @@ impl AppServer {
|
|||||||
);
|
);
|
||||||
if tries_left > 0 {
|
if tries_left > 0 {
|
||||||
error!("re-initializing networking in {sleep}! {tries_left} tries left.");
|
error!("re-initializing networking in {sleep}! {tries_left} tries left.");
|
||||||
std::thread::sleep(self.crypt.timebase.dur(sleep));
|
std::thread::sleep(Duration::from_secs_f64(sleep));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,16 +825,31 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.poll(&mut *rx)? {
|
enum CryptoSrv {
|
||||||
#[allow(clippy::redundant_closure_call)]
|
Avail,
|
||||||
SendInitiation(peer) => tx_maybe_with!(peer, || self
|
Missing,
|
||||||
.crypt
|
}
|
||||||
|
|
||||||
|
let poll_result = self.poll(&mut *rx)?;
|
||||||
|
let have_crypto = match self.crypto_site.is_available() {
|
||||||
|
true => CryptoSrv::Avail,
|
||||||
|
false => CryptoSrv::Missing,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::redundant_closure_call)]
|
||||||
|
match (have_crypto, poll_result) {
|
||||||
|
(CryptoSrv::Missing, SendInitiation(_)) => {}
|
||||||
|
(CryptoSrv::Avail, SendInitiation(peer)) => tx_maybe_with!(peer, || self
|
||||||
|
.crypto_server_mut()?
|
||||||
.initiate_handshake(peer.lower(), &mut *tx))?,
|
.initiate_handshake(peer.lower(), &mut *tx))?,
|
||||||
#[allow(clippy::redundant_closure_call)]
|
|
||||||
SendRetransmission(peer) => tx_maybe_with!(peer, || self
|
(CryptoSrv::Missing, SendRetransmission(_)) => {}
|
||||||
.crypt
|
(CryptoSrv::Avail, SendRetransmission(peer)) => tx_maybe_with!(peer, || self
|
||||||
|
.crypto_server_mut()?
|
||||||
.retransmit_handshake(peer.lower(), &mut *tx))?,
|
.retransmit_handshake(peer.lower(), &mut *tx))?,
|
||||||
DeleteKey(peer) => {
|
|
||||||
|
(CryptoSrv::Missing, DeleteKey(_)) => {}
|
||||||
|
(CryptoSrv::Avail, DeleteKey(peer)) => {
|
||||||
self.output_key(peer, Stale, &SymKey::random())?;
|
self.output_key(peer, Stale, &SymKey::random())?;
|
||||||
|
|
||||||
// There was a loss of connection apparently; restart host discovery
|
// There was a loss of connection apparently; restart host discovery
|
||||||
@@ -778,12 +863,15 @@ impl AppServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReceivedMessage(len, endpoint) => {
|
(CryptoSrv::Missing, ReceivedMessage(_, _)) => {}
|
||||||
|
(CryptoSrv::Avail, ReceivedMessage(len, endpoint)) => {
|
||||||
let msg_result = match self.under_load {
|
let msg_result = match self.under_load {
|
||||||
DoSOperation::UnderLoad => {
|
DoSOperation::UnderLoad => {
|
||||||
self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx)
|
self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx)
|
||||||
}
|
}
|
||||||
DoSOperation::Normal => self.crypt.handle_msg(&rx[..len], &mut *tx),
|
DoSOperation::Normal => {
|
||||||
|
self.crypto_server_mut()?.handle_msg(&rx[..len], &mut *tx)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match msg_result {
|
match msg_result {
|
||||||
Err(ref e) => {
|
Err(ref e) => {
|
||||||
@@ -811,7 +899,8 @@ impl AppServer {
|
|||||||
ap.get_app_mut(self).current_endpoint = Some(endpoint);
|
ap.get_app_mut(self).current_endpoint = Some(endpoint);
|
||||||
|
|
||||||
// TODO: Maybe we should rather call the key "rosenpass output"?
|
// TODO: Maybe we should rather call the key "rosenpass output"?
|
||||||
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
|
let osk = &self.crypto_server_mut()?.osk(p)?;
|
||||||
|
self.output_key(ap, Exchanged, osk)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -827,9 +916,9 @@ impl AppServer {
|
|||||||
tx: &mut [u8],
|
tx: &mut [u8],
|
||||||
) -> Result<crate::protocol::HandleMsgResult> {
|
) -> Result<crate::protocol::HandleMsgResult> {
|
||||||
match endpoint {
|
match endpoint {
|
||||||
Endpoint::SocketBoundAddress(socket) => {
|
Endpoint::SocketBoundAddress(socket) => self
|
||||||
self.crypt.handle_msg_under_load(rx, &mut *tx, socket)
|
.crypto_server_mut()?
|
||||||
}
|
.handle_msg_under_load(rx, &mut *tx, socket),
|
||||||
Endpoint::Discovery(_) => {
|
Endpoint::Discovery(_) => {
|
||||||
anyhow::bail!("Host-path discovery is not supported under load")
|
anyhow::bail!("Host-path discovery is not supported under load")
|
||||||
}
|
}
|
||||||
@@ -842,7 +931,7 @@ impl AppServer {
|
|||||||
why: KeyOutputReason,
|
why: KeyOutputReason,
|
||||||
key: &SymKey,
|
key: &SymKey,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let peerid = peer.lower().get(&self.crypt).pidt()?;
|
let peerid = peer.lower().get(self.crypto_server()?).pidt()?;
|
||||||
|
|
||||||
if self.verbose() {
|
if self.verbose() {
|
||||||
let msg = match why {
|
let msg = match why {
|
||||||
@@ -870,10 +959,14 @@ impl AppServer {
|
|||||||
|
|
||||||
// this is intentionally writing to stdout instead of stderr, because
|
// this is intentionally writing to stdout instead of stderr, because
|
||||||
// it is meant to allow external detection of a successful key-exchange
|
// it is meant to allow external detection of a successful key-exchange
|
||||||
println!(
|
let stdout = stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
writeln!(
|
||||||
|
stdout,
|
||||||
"output-key peer {} key-file {of:?} {why}",
|
"output-key peer {} key-file {of:?} {why}",
|
||||||
peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>()
|
peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>()
|
||||||
);
|
)?;
|
||||||
|
stdout.flush()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.set_psk(self, key)?;
|
peer.set_psk(self, key)?;
|
||||||
@@ -884,17 +977,32 @@ impl AppServer {
|
|||||||
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
||||||
use crate::protocol::PollResult as C;
|
use crate::protocol::PollResult as C;
|
||||||
use AppPollResult as A;
|
use AppPollResult as A;
|
||||||
loop {
|
let res = loop {
|
||||||
return Ok(match self.crypt.poll()? {
|
// Call CryptoServer's poll (if available)
|
||||||
C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)),
|
let crypto_poll = self
|
||||||
C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)),
|
.crypto_site
|
||||||
C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)),
|
.product_mut()
|
||||||
C::Sleep(timeout) => match self.try_recv(rx_buf, timeout)? {
|
.map(|crypto| crypto.poll())
|
||||||
Some((len, addr)) => A::ReceivedMessage(len, addr),
|
.transpose()?;
|
||||||
None => continue,
|
|
||||||
},
|
// Map crypto server's poll result to our poll result
|
||||||
});
|
let io_poll_timeout = match crypto_poll {
|
||||||
}
|
Some(C::DeleteKey(PeerPtr(no))) => break A::DeleteKey(AppPeerPtr(no)),
|
||||||
|
Some(C::SendInitiation(PeerPtr(no))) => break A::SendInitiation(AppPeerPtr(no)),
|
||||||
|
Some(C::SendRetransmission(PeerPtr(no))) => {
|
||||||
|
break A::SendRetransmission(AppPeerPtr(no))
|
||||||
|
}
|
||||||
|
Some(C::Sleep(timeout)) => timeout, // No event from crypto-server, do IO
|
||||||
|
None => crate::protocol::UNENDING, // Crypto server is uninitialized, do IO
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform IO (look for a message)
|
||||||
|
if let Some((len, addr)) = self.try_recv(rx_buf, io_poll_timeout)? {
|
||||||
|
break A::ReceivedMessage(len, addr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to receive a new message
|
/// Tries to receive a new message
|
||||||
@@ -932,22 +1040,33 @@ impl AppServer {
|
|||||||
// readiness event seems to be good enough™ for now.
|
// readiness event seems to be good enough™ for now.
|
||||||
|
|
||||||
// only poll if we drained all sockets before
|
// only poll if we drained all sockets before
|
||||||
if self.all_sockets_drained {
|
run(|| -> anyhow::Result<()> {
|
||||||
//Non blocked polling
|
if !self.all_sockets_drained || !self.short_poll_queue.is_empty() {
|
||||||
self.mio_poll
|
self.unpolled_count += 1;
|
||||||
.poll(&mut self.events, Some(Duration::from_secs(0)))?;
|
return Ok(());
|
||||||
|
|
||||||
if self.events.iter().peekable().peek().is_none() {
|
|
||||||
// if there are no events, then add to blocking poll count
|
|
||||||
self.blocking_polls_count += 1;
|
|
||||||
//Execute blocking poll
|
|
||||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
|
||||||
} else {
|
|
||||||
self.non_blocking_polls_count += 1;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.unpolled_count += 1;
|
self.perform_mio_poll_and_register_events(Duration::from_secs(0))?; // Non-blocking poll
|
||||||
}
|
if !self.short_poll_queue.is_empty() {
|
||||||
|
// Got some events in non-blocking mode
|
||||||
|
self.non_blocking_polls_count += 1;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.performed_long_poll {
|
||||||
|
// pass – go perform a full long poll before we enter blocking poll mode
|
||||||
|
// to make sure our experimental short poll feature did not miss any events
|
||||||
|
// due to being buggy.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform and register blocking poll
|
||||||
|
self.blocking_polls_count += 1;
|
||||||
|
self.perform_mio_poll_and_register_events(timeout)?;
|
||||||
|
self.performed_long_poll = false;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
if let Some(AppServerTest {
|
if let Some(AppServerTest {
|
||||||
enable_dos_permanently: true,
|
enable_dos_permanently: true,
|
||||||
@@ -982,26 +1101,58 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Focused polling – i.e. actually using mio::Token – is experimental for now.
|
||||||
|
// The reason for this is that we need to figure out how to integrate load detection
|
||||||
|
// and focused polling for one. Mio event-based polling also does not play nice with
|
||||||
|
// the current function signature and its reentrant design which is focused around receiving UDP socket packages
|
||||||
|
// for processing by the crypto protocol server.
|
||||||
|
// Besides that, there are also some parts of the code which intentionally block
|
||||||
|
// despite available data. This is the correct behavior; e.g. api::mio::Connection blocks
|
||||||
|
// further reads from its unix socket until the write buffer is flushed. In other words
|
||||||
|
// the connection handler makes sure that there is a buffer to put the response in while
|
||||||
|
// before reading further request.
|
||||||
|
// The potential problem with this behavior is that we end up ignoring instructions from
|
||||||
|
// epoll() to read from the particular sockets, so epoll will return information about that
|
||||||
|
// particular – blocked – file descriptor every call. We have only so many event slots and
|
||||||
|
// in theory, the event array could fill up entirely with intentionally blocked sockets.
|
||||||
|
// We need to figure out how to deal with this situation.
|
||||||
|
// Mio uses uses epoll in level-triggered mode, so we could handle taint-tracking for ignored
|
||||||
|
// sockets ourselves. The facilities are available in epoll and Mio, but we need to figure out how mio uses those
|
||||||
|
// facilities and how we can integrate them here.
|
||||||
|
// This will involve rewriting a lot of IO code and we should probably have integration
|
||||||
|
// tests before we approach that.
|
||||||
|
//
|
||||||
|
// This hybrid approach is not without merit though; the short poll implementation covers
|
||||||
|
// all our IO sources, so under contention, rosenpass should generally not hit the long
|
||||||
|
// poll mode below. We keep short polling and calling epoll() in non-blocking mode (timeout
|
||||||
|
// of zero) until we run out of IO events processed. Then, just before we would perform a
|
||||||
|
// blocking poll, we go through all available IO sources to see if we missed anything.
|
||||||
|
{
|
||||||
|
while let Some(ev) = self.short_poll_queue.pop_front() {
|
||||||
|
if let Some(v) = self.try_recv_from_mio_token(buf, ev.token())? {
|
||||||
|
return Ok(Some(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// drain all sockets
|
// drain all sockets
|
||||||
let mut would_block_count = 0;
|
let mut would_block_count = 0;
|
||||||
for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
|
for sock_no in 0..self.sockets.len() {
|
||||||
match socket.recv_from(buf) {
|
match self
|
||||||
Ok((n, addr)) => {
|
.try_recv_from_listen_socket(buf, sock_no)
|
||||||
|
.io_err_kind_hint()
|
||||||
|
{
|
||||||
|
Ok(None) => continue,
|
||||||
|
Ok(Some(v)) => {
|
||||||
// at least one socket was not drained...
|
// at least one socket was not drained...
|
||||||
self.all_sockets_drained = false;
|
self.all_sockets_drained = false;
|
||||||
return Ok(Some((
|
return Ok(Some(v));
|
||||||
n,
|
|
||||||
Endpoint::SocketBoundAddress(SocketBoundEndpoint::new(
|
|
||||||
SocketPtr(sock_no),
|
|
||||||
addr,
|
|
||||||
)),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == ErrorKind::WouldBlock => {
|
Err((_, ErrorKind::WouldBlock)) => {
|
||||||
would_block_count += 1;
|
would_block_count += 1;
|
||||||
}
|
}
|
||||||
// TODO if one socket continuously returns an error, then we never poll, thus we never wait for a timeout, thus we have a spin-lock
|
// TODO if one socket continuously returns an error, then we never poll, thus we never wait for a timeout, thus we have a spin-lock
|
||||||
Err(e) => return Err(e.into()),
|
Err((e, _)) => return Err(e)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,6 +1164,127 @@ impl AppServer {
|
|||||||
broker.process_poll()?;
|
broker.process_poll()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API poll
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
{
|
||||||
|
use crate::api::mio::MioManagerContext;
|
||||||
|
MioManagerFocus(self).poll()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.performed_long_poll = true;
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
||||||
|
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||||
|
// Fill the short poll buffer with the acquired events
|
||||||
|
self.events
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.for_each(|v| self.short_poll_queue.push_back(v));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_from_mio_token(
|
||||||
|
&mut self,
|
||||||
|
buf: &mut [u8],
|
||||||
|
token: mio::Token,
|
||||||
|
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
||||||
|
let io_source = match self.io_source_index.get(&token) {
|
||||||
|
Some(io_source) => *io_source,
|
||||||
|
None => {
|
||||||
|
log::warn!("No IO source assiociated with mio token ({token:?}). Polling using mio tokens directly is an experimental feature and IO handler should recover when all available io sources are polled. This is a developer error. Please report it.");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.try_recv_from_io_source(buf, io_source)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_from_io_source(
|
||||||
|
&mut self,
|
||||||
|
buf: &mut [u8],
|
||||||
|
io_source: AppServerIoSource,
|
||||||
|
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
||||||
|
match io_source {
|
||||||
|
AppServerIoSource::Socket(idx) => self
|
||||||
|
.try_recv_from_listen_socket(buf, idx)
|
||||||
|
.substitute_for_ioerr_wouldblock(None)?
|
||||||
|
.ok(),
|
||||||
|
|
||||||
|
AppServerIoSource::PskBroker(key) => self
|
||||||
|
.brokers
|
||||||
|
.store
|
||||||
|
.get_mut(&key)
|
||||||
|
.with_context(|| format!("No PSK broker under key {key:?}"))?
|
||||||
|
.process_poll()
|
||||||
|
.map(|_| None),
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
AppServerIoSource::MioManager(mmio_src) => {
|
||||||
|
use crate::api::mio::MioManagerContext;
|
||||||
|
|
||||||
|
MioManagerFocus(self)
|
||||||
|
.poll_particular(mmio_src)
|
||||||
|
.map(|_| None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_recv_from_listen_socket(
|
||||||
|
&mut self,
|
||||||
|
buf: &mut [u8],
|
||||||
|
idx: usize,
|
||||||
|
) -> io::Result<Option<(usize, Endpoint)>> {
|
||||||
|
use std::io::ErrorKind as K;
|
||||||
|
let (n, addr) = loop {
|
||||||
|
match self.sockets[idx].recv_from(buf).io_err_kind_hint() {
|
||||||
|
Ok(v) => break v,
|
||||||
|
Err((_, K::Interrupted)) => continue,
|
||||||
|
Err((e, _)) => return Err(e)?,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SocketPtr(idx)
|
||||||
|
.apply(|sp| SocketBoundEndpoint::new(sp, addr))
|
||||||
|
.apply(Endpoint::SocketBoundAddress)
|
||||||
|
.apply(|ep| (n, ep))
|
||||||
|
.some()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
pub fn add_api_connection(&mut self, connection: mio::net::UnixStream) -> std::io::Result<()> {
|
||||||
|
use crate::api::mio::MioManagerContext;
|
||||||
|
MioManagerFocus(self).add_connection(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
pub fn add_api_listener(&mut self, listener: mio::net::UnixListener) -> std::io::Result<()> {
|
||||||
|
use crate::api::mio::MioManagerContext;
|
||||||
|
MioManagerFocus(self).add_listener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
struct MioManagerFocus<'a>(&'a mut AppServer);
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
impl crate::api::mio::MioManagerContext for MioManagerFocus<'_> {
|
||||||
|
fn mio_manager(&self) -> &crate::api::mio::MioManager {
|
||||||
|
&self.0.api_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mio_manager_mut(&mut self) -> &mut crate::api::mio::MioManager {
|
||||||
|
&mut self.0.api_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_server(&self) -> &AppServer {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_server_mut(&mut self) -> &mut AppServer {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
rosenpass/src/bin/gen-ipc-msg-types.rs
Normal file
92
rosenpass/src/bin/gen-ipc-msg-types.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use heck::ToShoutySnakeCase;
|
||||||
|
|
||||||
|
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||||
|
|
||||||
|
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
|
||||||
|
match values.split_first() {
|
||||||
|
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
|
||||||
|
None => Ok(hd.into_value()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_literal(path: &[&str]) -> Result<()> {
|
||||||
|
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
||||||
|
let (last, prefix) = path.split_last().context("developer error!")?;
|
||||||
|
let var_name = last.to_shouty_snake_case();
|
||||||
|
|
||||||
|
print!("// hash domain hash of: ");
|
||||||
|
for n in prefix.iter() {
|
||||||
|
print!("{n} -> ");
|
||||||
|
}
|
||||||
|
println!("{last}");
|
||||||
|
|
||||||
|
let c = hex::encode(val)
|
||||||
|
.chars()
|
||||||
|
.collect::<Vec<char>>()
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|chunk| chunk.iter().collect::<String>())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
println!("const {var_name} : RawMsgType = RawMsgType::from_le_bytes(hex!(\"{} {} {} {} {} {} {} {}\"));",
|
||||||
|
c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Tree {
|
||||||
|
Branch(String, Vec<Tree>),
|
||||||
|
Leaf(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tree {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Branch(name, _) => name,
|
||||||
|
Self::Leaf(name) => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> {
|
||||||
|
let mut path = prefix.to_owned();
|
||||||
|
path.push(self.name());
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Branch(_, ref children) => {
|
||||||
|
for c in children.iter() {
|
||||||
|
c.gen_code_inner(&path)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Leaf(_) => print_literal(&path)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_code(&self) -> Result<()> {
|
||||||
|
self.gen_code_inner(&[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let tree = Tree::Branch(
|
||||||
|
"Rosenpass IPC API".to_owned(),
|
||||||
|
vec![Tree::Branch(
|
||||||
|
"Rosenpass Protocol Server".to_owned(),
|
||||||
|
vec![
|
||||||
|
Tree::Leaf("Ping Request".to_owned()),
|
||||||
|
Tree::Leaf("Ping Response".to_owned()),
|
||||||
|
Tree::Leaf("Supply Keypair Request".to_owned()),
|
||||||
|
Tree::Leaf("Supply Keypair Response".to_owned()),
|
||||||
|
Tree::Leaf("Add Listen Socket Request".to_owned()),
|
||||||
|
Tree::Leaf("Add Listen Socket Response".to_owned()),
|
||||||
|
Tree::Leaf("Add Psk Broker Request".to_owned()),
|
||||||
|
Tree::Leaf("Add Psk Broker Response".to_owned()),
|
||||||
|
],
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("type RawMsgType = u128;");
|
||||||
|
println!();
|
||||||
|
tree.gen_code()
|
||||||
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
use anyhow::{bail, ensure};
|
use anyhow::{bail, ensure, Context};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::kem::StaticKem;
|
||||||
use rosenpass_secret_memory::file::StoreSecret;
|
use rosenpass_secret_memory::file::StoreSecret;
|
||||||
use rosenpass_secret_memory::{
|
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
|
||||||
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
|
|
||||||
};
|
|
||||||
use rosenpass_util::file::{LoadValue, LoadValueB64};
|
|
||||||
use rosenpass_wireguard_broker::brokers::native_unix::{
|
use rosenpass_wireguard_broker::brokers::native_unix::{
|
||||||
NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError,
|
NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError,
|
||||||
};
|
};
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::app_server::AppServerTest;
|
use crate::app_server::AppServerTest;
|
||||||
@@ -18,27 +16,94 @@ use crate::protocol::{SPk, SSk, SymKey};
|
|||||||
|
|
||||||
use super::config;
|
use super::config;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
use {
|
||||||
|
command_fds::{CommandFdExt, FdMapping},
|
||||||
|
log::{error, info},
|
||||||
|
mio::net::UnixStream,
|
||||||
|
rosenpass_util::fd::claim_fd,
|
||||||
|
rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient,
|
||||||
|
rosenpass_wireguard_broker::WireguardBrokerMio,
|
||||||
|
rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType},
|
||||||
|
std::os::fd::AsRawFd,
|
||||||
|
std::os::unix::net,
|
||||||
|
std::process::Command,
|
||||||
|
std::thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// enum representing a choice of interface to a WireGuard broker
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BrokerInterface {
|
||||||
|
Socket(PathBuf),
|
||||||
|
FileDescriptor(i32),
|
||||||
|
SocketPair,
|
||||||
|
}
|
||||||
|
|
||||||
/// struct holding all CLI arguments for `clap` crate to parse
|
/// struct holding all CLI arguments for `clap` crate to parse
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about)]
|
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
/// lowest log level to show – log messages at higher levels will be omitted
|
/// Lowest log level to show
|
||||||
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
|
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
|
||||||
log_level: Option<log::LevelFilter>,
|
log_level: Option<log::LevelFilter>,
|
||||||
|
|
||||||
/// show verbose log output – sets log level to "debug"
|
/// Show verbose log output – sets log level to "debug"
|
||||||
#[arg(short, long, group = "log-level")]
|
#[arg(short, long, group = "log-level")]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
|
||||||
/// show no log output – sets log level to "error"
|
/// Show no log output – sets log level to "error"
|
||||||
#[arg(short, long, group = "log-level")]
|
#[arg(short, long, group = "log-level")]
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
|
||||||
|
#[command(flatten)]
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
api: crate::api::cli::ApiCli,
|
||||||
|
|
||||||
|
/// Path of the `wireguard_psk` broker socket to connect to
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
#[arg(long, group = "psk-broker-specs")]
|
||||||
|
psk_broker_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// File descriptor of the `wireguard_psk` broker socket to connect to
|
||||||
|
///
|
||||||
|
/// When this command is called from another process, the other process can
|
||||||
|
/// open and bind the Unix socket for the PSK broker connection 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
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
#[arg(long, group = "psk-broker-specs")]
|
||||||
|
psk_broker_fd: Option<i32>,
|
||||||
|
|
||||||
|
/// Spawn a PSK broker locally using a socket pair
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
#[arg(short, long, group = "psk-broker-specs")]
|
||||||
|
psk_broker_spawn: bool,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: CliCommand,
|
pub command: Option<CliCommand>,
|
||||||
|
|
||||||
|
/// Generate man pages for the CLI
|
||||||
|
///
|
||||||
|
/// This option is used to generate man pages for Rosenpass in the specified
|
||||||
|
/// directory and exit.
|
||||||
|
#[clap(long, value_name = "out_dir")]
|
||||||
|
pub generate_manpage: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Generate completion file for a shell
|
||||||
|
///
|
||||||
|
/// This option is used to generate completion files for the specified shell
|
||||||
|
#[clap(long, value_name = "shell")]
|
||||||
|
pub print_completions: Option<clap_complete::Shell>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
|
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
self.api.apply_to_config(_cfg)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// returns the log level filter set by CLI args
|
/// returns the log level filter set by CLI args
|
||||||
/// returns `None` if the user did not specify any log level filter via CLI
|
/// returns `None` if the user did not specify any log level filter via CLI
|
||||||
///
|
///
|
||||||
@@ -50,32 +115,54 @@ impl CliArgs {
|
|||||||
return Some(log::LevelFilter::Info);
|
return Some(log::LevelFilter::Info);
|
||||||
}
|
}
|
||||||
if self.quiet {
|
if self.quiet {
|
||||||
return Some(log::LevelFilter::Error);
|
return Some(log::LevelFilter::Warn);
|
||||||
}
|
}
|
||||||
if let Some(level_filter) = self.log_level {
|
if let Some(level_filter) = self.log_level {
|
||||||
return Some(level_filter);
|
return Some(level_filter);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
/// returns the broker interface set by CLI args
|
||||||
|
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||||
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
|
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||||
|
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
||||||
|
} else if let Some(fd) = self.psk_broker_fd {
|
||||||
|
Some(BrokerInterface::FileDescriptor(fd))
|
||||||
|
} else if self.psk_broker_spawn {
|
||||||
|
Some(BrokerInterface::SocketPair)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
|
/// returns the broker interface set by CLI args
|
||||||
|
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||||
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// represents a command specified via CLI
|
/// represents a command specified via CLI
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum CliCommand {
|
pub enum CliCommand {
|
||||||
/// Start Rosenpass in server mode and carry on with the key exchange
|
/// Start Rosenpass key exchanges based on a configuration file
|
||||||
///
|
///
|
||||||
/// This will parse the configuration file and perform the key exchange
|
/// This will parse the configuration file and perform key exchanges with
|
||||||
/// with the specified peers. If a peer's endpoint is specified, this
|
/// the specified peers. If a peer's endpoint is specified, this Rosenpass
|
||||||
/// Rosenpass instance will try to initiate a key exchange with the peer,
|
/// instance will try to initiate a key exchange with the peer; otherwise,
|
||||||
/// otherwise only initiation attempts from the peer will be responded to.
|
/// only initiation attempts from other peers will be responded to.
|
||||||
ExchangeConfig { config_file: PathBuf },
|
ExchangeConfig { config_file: PathBuf },
|
||||||
|
|
||||||
/// Start in daemon mode, performing key exchanges
|
/// Start Rosenpass key exchanges based on command line arguments
|
||||||
///
|
///
|
||||||
/// The configuration is read from the command line. The `peer` token
|
/// The configuration is read from the command line. The `peer` token always
|
||||||
/// always separates multiple peers, e. g. if the token `peer` appears
|
/// separates multiple peers, e.g., if the token `peer` appears in the
|
||||||
/// in the WIREGUARD_EXTRA_ARGS it is not put into the WireGuard arguments
|
/// WIREGUARD_EXTRA_ARGS, it is not put into the WireGuard arguments but
|
||||||
/// but instead a new peer is created.
|
/// instead a new peer is created.
|
||||||
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
||||||
* `Vec<String>`. They are only used to trick clap into displaying some
|
* `Vec<String>`. They are only used to trick clap into displaying some
|
||||||
* guidance on the CLI usage.
|
* guidance on the CLI usage.
|
||||||
@@ -104,7 +191,10 @@ pub enum CliCommand {
|
|||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Generate a demo config file
|
/// Generate a demo config file for Rosenpass
|
||||||
|
///
|
||||||
|
/// The generated config file will contain a single peer and all common
|
||||||
|
/// options.
|
||||||
GenConfig {
|
GenConfig {
|
||||||
config_file: PathBuf,
|
config_file: PathBuf,
|
||||||
|
|
||||||
@@ -113,19 +203,19 @@ pub enum CliCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Generate the keys mentioned in a configFile
|
/// Generate secret & public key for Rosenpass
|
||||||
///
|
///
|
||||||
/// Generates secret- & public-key to their destination. If a config file
|
/// Generates secret & public key to their destination. If a config file is
|
||||||
/// is provided then the key file destination is taken from there.
|
/// provided then the key file destination is taken from there, otherwise
|
||||||
/// Otherwise the
|
/// the destination is taken from the CLI arguments.
|
||||||
GenKeys {
|
GenKeys {
|
||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// where to write public-key to
|
/// Where to write public key to
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
public_key: Option<PathBuf>,
|
public_key: Option<PathBuf>,
|
||||||
|
|
||||||
/// where to write secret-key to
|
/// Where to write secret key to
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
secret_key: Option<PathBuf>,
|
secret_key: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -134,67 +224,57 @@ pub enum CliCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Deprecated - use gen-keys instead
|
/// Validate a configuration file
|
||||||
|
///
|
||||||
|
/// This command will validate the configuration file and print any errors
|
||||||
|
/// it finds. If the configuration file is valid, it will print a success.
|
||||||
|
/// Defined secret & public keys are checked for existence and validity.
|
||||||
|
Validate { config_files: Vec<PathBuf> },
|
||||||
|
|
||||||
|
/// DEPRECATED - use the gen-keys command instead
|
||||||
#[allow(rustdoc::broken_intra_doc_links)]
|
#[allow(rustdoc::broken_intra_doc_links)]
|
||||||
#[allow(rustdoc::invalid_html_tags)]
|
#[allow(rustdoc::invalid_html_tags)]
|
||||||
|
#[command(hide = true)]
|
||||||
Keygen {
|
Keygen {
|
||||||
// NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"!
|
// NOTE yes, the legacy keygen argument initially really accepted
|
||||||
|
// "private-key", not "secret-key"!
|
||||||
/// public-key <PATH> private-key <PATH>
|
/// public-key <PATH> private-key <PATH>
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Validate a configuration
|
|
||||||
Validate { config_files: Vec<PathBuf> },
|
|
||||||
|
|
||||||
/// Show the rosenpass manpage
|
|
||||||
// TODO make this the default, but only after the manpage has been adjusted once the CLI stabilizes
|
|
||||||
Man,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliCommand {
|
impl CliArgs {
|
||||||
/// runs the command specified via CLI
|
/// Runs the command specified via CLI
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// ## TODO
|
||||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||||
pub fn run(self, test_helpers: Option<AppServerTest>) -> anyhow::Result<()> {
|
pub fn run(
|
||||||
//Specify secret policy
|
self,
|
||||||
|
broker_interface: Option<BrokerInterface>,
|
||||||
#[cfg(feature = "enable_memfd_alloc")]
|
test_helpers: Option<AppServerTest>,
|
||||||
secret_policy_try_use_memfd_secrets();
|
) -> anyhow::Result<()> {
|
||||||
#[cfg(not(feature = "enable_memfd_alloc"))]
|
|
||||||
secret_policy_use_only_malloc_secrets();
|
|
||||||
|
|
||||||
use CliCommand::*;
|
use CliCommand::*;
|
||||||
match self {
|
match &self.command {
|
||||||
Man => {
|
Some(GenConfig { config_file, force }) => {
|
||||||
let man_cmd = std::process::Command::new("man")
|
|
||||||
.args(["1", "rosenpass"])
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if !(man_cmd.is_ok() && man_cmd.unwrap().success()) {
|
|
||||||
println!(include_str!(env!("ROSENPASS_MAN")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GenConfig { config_file, force } => {
|
|
||||||
ensure!(
|
ensure!(
|
||||||
force || !config_file.exists(),
|
*force || !config_file.exists(),
|
||||||
"config file {config_file:?} already exists"
|
"config file {config_file:?} already exists"
|
||||||
);
|
);
|
||||||
|
|
||||||
config::Rosenpass::example_config().store(config_file)?;
|
std::fs::write(config_file, config::EXAMPLE_CONFIG)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated - use gen-keys instead
|
// Deprecated - use gen-keys instead
|
||||||
Keygen { args } => {
|
Some(Keygen { args }) => {
|
||||||
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
||||||
|
|
||||||
let mut public_key: Option<PathBuf> = None;
|
let mut public_key: Option<PathBuf> = None;
|
||||||
let mut secret_key: Option<PathBuf> = None;
|
let mut secret_key: Option<PathBuf> = None;
|
||||||
|
|
||||||
// Manual arg parsing, since clap wants to prefix flags with "--"
|
// Manual arg parsing, since clap wants to prefix flags with "--"
|
||||||
let mut args = args.into_iter();
|
let mut args = args.iter();
|
||||||
loop {
|
loop {
|
||||||
match (args.next().as_deref(), args.next()) {
|
match (args.next().map(|x| x.as_str()), args.next()) {
|
||||||
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
|
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
|
||||||
secret_key = Some(opt.into());
|
secret_key = Some(opt.into());
|
||||||
}
|
}
|
||||||
@@ -218,12 +298,12 @@ impl CliCommand {
|
|||||||
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
GenKeys {
|
Some(GenKeys {
|
||||||
config_file,
|
config_file,
|
||||||
public_key,
|
public_key,
|
||||||
secret_key,
|
secret_key,
|
||||||
force,
|
force,
|
||||||
} => {
|
}) => {
|
||||||
// figure out where the key file is specified, in the config file or directly as flag?
|
// 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) {
|
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
||||||
(Some(config_file), _, _) => {
|
(Some(config_file), _, _) => {
|
||||||
@@ -233,10 +313,13 @@ impl CliCommand {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let config = config::Rosenpass::load(config_file)?;
|
let config = config::Rosenpass::load(config_file)?;
|
||||||
|
let keypair = config
|
||||||
|
.keypair
|
||||||
|
.context("Config file present, but no keypair is specified.")?;
|
||||||
|
|
||||||
(config.public_key, config.secret_key)
|
(keypair.public_key, keypair.secret_key)
|
||||||
}
|
}
|
||||||
(_, Some(pkf), Some(skf)) => (pkf, skf),
|
(_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()),
|
||||||
_ => {
|
_ => {
|
||||||
bail!("either a config-file or both public-key and secret-key file are required")
|
bail!("either a config-file or both public-key and secret-key file are required")
|
||||||
}
|
}
|
||||||
@@ -246,12 +329,14 @@ impl CliCommand {
|
|||||||
let mut problems = vec![];
|
let mut problems = vec![];
|
||||||
if !force && pkf.is_file() {
|
if !force && pkf.is_file() {
|
||||||
problems.push(format!(
|
problems.push(format!(
|
||||||
"public-key file {pkf:?} exist, refusing to overwrite it"
|
"public-key file {:?} exists, refusing to overwrite",
|
||||||
|
std::fs::canonicalize(&pkf)?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if !force && skf.is_file() {
|
if !force && skf.is_file() {
|
||||||
problems.push(format!(
|
problems.push(format!(
|
||||||
"secret-key file {skf:?} exist, refusing to overwrite it"
|
"secret-key file {:?} exists, refusing to overwrite",
|
||||||
|
std::fs::canonicalize(&skf)?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if !problems.is_empty() {
|
if !problems.is_empty() {
|
||||||
@@ -262,48 +347,57 @@ impl CliCommand {
|
|||||||
generate_and_save_keypair(skf, pkf)?;
|
generate_and_save_keypair(skf, pkf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExchangeConfig { config_file } => {
|
Some(ExchangeConfig { config_file }) => {
|
||||||
ensure!(
|
ensure!(
|
||||||
config_file.exists(),
|
config_file.exists(),
|
||||||
"config file '{config_file:?}' does not exist"
|
"config file '{config_file:?}' does not exist"
|
||||||
);
|
);
|
||||||
|
|
||||||
let config = config::Rosenpass::load(config_file)?;
|
let mut config = config::Rosenpass::load(config_file)?;
|
||||||
config.validate()?;
|
config.validate()?;
|
||||||
Self::event_loop(config, test_helpers)?;
|
self.apply_to_config(&mut config)?;
|
||||||
|
config.check_usefullness()?;
|
||||||
|
|
||||||
|
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Exchange {
|
Some(Exchange {
|
||||||
first_arg,
|
first_arg,
|
||||||
mut rest_of_args,
|
rest_of_args,
|
||||||
config_file,
|
config_file,
|
||||||
} => {
|
}) => {
|
||||||
rest_of_args.insert(0, first_arg);
|
let mut rest_of_args = rest_of_args.clone();
|
||||||
|
rest_of_args.insert(0, first_arg.clone());
|
||||||
let args = rest_of_args;
|
let args = rest_of_args;
|
||||||
let mut config = config::Rosenpass::parse_args(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.store(p)?;
|
||||||
config.config_file_path = p;
|
config.config_file_path.clone_from(p);
|
||||||
}
|
}
|
||||||
config.validate()?;
|
config.validate()?;
|
||||||
Self::event_loop(config, test_helpers)?;
|
self.apply_to_config(&mut config)?;
|
||||||
|
config.check_usefullness()?;
|
||||||
|
|
||||||
|
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate { config_files } => {
|
Some(Validate { config_files }) => {
|
||||||
for file in config_files {
|
for file in config_files {
|
||||||
match config::Rosenpass::load(&file) {
|
match config::Rosenpass::load(file) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
||||||
match config.validate() {
|
match config.validate() {
|
||||||
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
|
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
|
||||||
Err(_) => eprintln!("{file:?} contains logical errors"),
|
Err(err) => eprintln!("{file:?} contains logical errors: '{err}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&None => {} // calp print help if no command is given
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -311,23 +405,34 @@ impl CliCommand {
|
|||||||
|
|
||||||
fn event_loop(
|
fn event_loop(
|
||||||
config: config::Rosenpass,
|
config: config::Rosenpass,
|
||||||
|
broker_interface: Option<BrokerInterface>,
|
||||||
test_helpers: Option<AppServerTest>,
|
test_helpers: Option<AppServerTest>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
const MAX_PSK_SIZE: usize = 1000;
|
const MAX_PSK_SIZE: usize = 1000;
|
||||||
|
|
||||||
// load own keys
|
// load own keys
|
||||||
let sk = SSk::load(&config.secret_key)?;
|
let keypair = config
|
||||||
let pk = SPk::load(&config.public_key)?;
|
.keypair
|
||||||
|
.as_ref()
|
||||||
|
.map(|kp| -> anyhow::Result<_> {
|
||||||
|
let sk = SSk::load(&kp.secret_key)?;
|
||||||
|
let pk = SPk::load(&kp.public_key)?;
|
||||||
|
Ok((sk, pk))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
// start an application server
|
// start an application server
|
||||||
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
||||||
sk,
|
keypair,
|
||||||
pk,
|
config.listen.clone(),
|
||||||
config.listen,
|
|
||||||
config.verbosity,
|
config.verbosity,
|
||||||
test_helpers,
|
test_helpers,
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
let broker_store_ptr = srv.register_broker(Box::new(NativeUnixBroker::new()))?;
|
config.apply_to_app_server(&mut srv)?;
|
||||||
|
|
||||||
|
let broker = Self::create_broker(broker_interface)?;
|
||||||
|
let broker_store_ptr = srv.register_broker(broker)?;
|
||||||
|
|
||||||
fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error {
|
fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error {
|
||||||
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
||||||
@@ -364,13 +469,102 @@ impl CliCommand {
|
|||||||
|
|
||||||
srv.event_loop()
|
srv.event_loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
fn create_broker(
|
||||||
|
broker_interface: Option<BrokerInterface>,
|
||||||
|
) -> Result<
|
||||||
|
Box<dyn WireguardBrokerMio<MioError = anyhow::Error, Error = anyhow::Error>>,
|
||||||
|
anyhow::Error,
|
||||||
|
> {
|
||||||
|
if let Some(interface) = broker_interface {
|
||||||
|
let socket = Self::get_broker_socket(interface)?;
|
||||||
|
Ok(Box::new(MioBrokerClient::new(socket)))
|
||||||
|
} else {
|
||||||
|
Ok(Box::new(NativeUnixBroker::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
|
fn create_broker(
|
||||||
|
_broker_interface: Option<BrokerInterface>,
|
||||||
|
) -> Result<Box<NativeUnixBroker>, anyhow::Error> {
|
||||||
|
Ok(Box::new(NativeUnixBroker::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||||
|
// Connect to the psk broker unix socket if one was specified
|
||||||
|
// OR OTHERWISE spawn the psk broker and use socketpair(2) to connect with them
|
||||||
|
match broker_interface {
|
||||||
|
BrokerInterface::Socket(broker_path) => Ok(UnixStream::connect(broker_path)?),
|
||||||
|
BrokerInterface::FileDescriptor(broker_fd) => {
|
||||||
|
// mio::net::UnixStream doesn't implement From<OwnedFd>, so we have to go through std
|
||||||
|
let sock = net::UnixStream::from(claim_fd(broker_fd)?);
|
||||||
|
sock.set_nonblocking(true)?;
|
||||||
|
Ok(UnixStream::from_std(sock))
|
||||||
|
}
|
||||||
|
BrokerInterface::SocketPair => {
|
||||||
|
// Form a socketpair for communicating to the broker
|
||||||
|
let (ours, theirs) = socketpair(
|
||||||
|
AddressFamily::UNIX,
|
||||||
|
SocketType::STREAM,
|
||||||
|
SocketFlags::empty(),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Setup our end of the socketpair
|
||||||
|
let ours = net::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:?})");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(UnixStream::from_std(ours))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||||
let mut ssk = crate::protocol::SSk::random();
|
let mut ssk = crate::protocol::SSk::random();
|
||||||
let mut spk = crate::protocol::SPk::random();
|
let mut spk = crate::protocol::SPk::random();
|
||||||
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||||
ssk.store_secret(secret_key)?;
|
ssk.store_secret(secret_key)?;
|
||||||
spk.store(public_key)
|
spk.store(public_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "internal_testing")]
|
||||||
|
pub mod testing {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn generate_and_save_keypair(
|
||||||
|
secret_key: PathBuf,
|
||||||
|
public_key: PathBuf,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
super::generate_and_save_keypair(secret_key, public_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
//! ## TODO
|
//! ## TODO
|
||||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||||
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||||
|
use crate::protocol::{SPk, SSk};
|
||||||
|
use rosenpass_util::file::LoadValue;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
@@ -19,13 +20,28 @@ use anyhow::{bail, ensure};
|
|||||||
use rosenpass_util::file::{fopen_w, Visibility};
|
use rosenpass_util::file::{fopen_w, Visibility};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::app_server::AppServer;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
fn empty_api_config() -> crate::api::config::ApiConfig {
|
||||||
|
crate::api::config::ApiConfig {
|
||||||
|
listen_path: Vec::new(),
|
||||||
|
listen_fd: Vec::new(),
|
||||||
|
stream_fd: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Rosenpass {
|
pub struct Rosenpass {
|
||||||
/// path to the public key file
|
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||||
pub public_key: PathBuf,
|
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub keypair: Option<Keypair>,
|
||||||
|
|
||||||
/// path to the secret key file
|
/// Location of the API listen sockets
|
||||||
pub secret_key: PathBuf,
|
#[cfg(feature = "experiment_api")]
|
||||||
|
#[serde(default = "empty_api_config")]
|
||||||
|
pub api: crate::api::config::ApiConfig,
|
||||||
|
|
||||||
/// list of [`SocketAddr`] to listen on
|
/// list of [`SocketAddr`] to listen on
|
||||||
///
|
///
|
||||||
@@ -52,9 +68,29 @@ pub struct Rosenpass {
|
|||||||
pub config_file_path: PathBuf,
|
pub config_file_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Keypair {
|
||||||
|
/// path to the public key file
|
||||||
|
pub public_key: PathBuf,
|
||||||
|
|
||||||
|
/// path to the secret key file
|
||||||
|
pub secret_key: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keypair {
|
||||||
|
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
||||||
|
let public_key = public_key.as_ref().to_path_buf();
|
||||||
|
let secret_key = secret_key.as_ref().to_path_buf();
|
||||||
|
Self {
|
||||||
|
public_key,
|
||||||
|
secret_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// ## TODO
|
||||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
/// - 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, Copy, Clone)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
Quiet,
|
Quiet,
|
||||||
Verbose,
|
Verbose,
|
||||||
@@ -107,6 +143,12 @@ pub struct WireGuard {
|
|||||||
pub extra_params: Vec<String>,
|
pub extra_params: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Rosenpass {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Rosenpass {
|
impl Rosenpass {
|
||||||
/// load configuration from a TOML file
|
/// load configuration from a TOML file
|
||||||
///
|
///
|
||||||
@@ -122,8 +164,10 @@ impl Rosenpass {
|
|||||||
|
|
||||||
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
|
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
|
||||||
use util::resolve_path_with_tilde;
|
use util::resolve_path_with_tilde;
|
||||||
resolve_path_with_tilde(&mut config.public_key);
|
if let Some(ref mut keypair) = config.keypair {
|
||||||
resolve_path_with_tilde(&mut config.secret_key);
|
resolve_path_with_tilde(&mut keypair.public_key);
|
||||||
|
resolve_path_with_tilde(&mut keypair.secret_key);
|
||||||
|
}
|
||||||
for peer in config.peers.iter_mut() {
|
for peer in config.peers.iter_mut() {
|
||||||
resolve_path_with_tilde(&mut peer.public_key);
|
resolve_path_with_tilde(&mut peer.public_key);
|
||||||
if let Some(ref mut psk) = &mut peer.pre_shared_key {
|
if let Some(ref mut psk) = &mut peer.pre_shared_key {
|
||||||
@@ -157,25 +201,43 @@ impl Rosenpass {
|
|||||||
self.store(&self.config_file_path)
|
self.store(&self.config_file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate a configuration
|
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
///
|
#[cfg(feature = "experiment_api")]
|
||||||
/// ## TODO
|
self.api.apply_to_app_server(_srv)?;
|
||||||
/// - check that files do not just exist but are also readable
|
Ok(())
|
||||||
/// - 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
|
|
||||||
ensure!(
|
|
||||||
self.public_key.is_file(),
|
|
||||||
"could not find public-key file {:?}: no such file",
|
|
||||||
self.public_key
|
|
||||||
);
|
|
||||||
|
|
||||||
// check the secret-key file exists
|
/// Validate a configuration
|
||||||
ensure!(
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
self.secret_key.is_file(),
|
if let Some(ref keypair) = self.keypair {
|
||||||
"could not find secret-key file {:?}: no such file",
|
// check the public key file exists
|
||||||
self.secret_key
|
ensure!(
|
||||||
);
|
keypair.public_key.is_file(),
|
||||||
|
"could not find public-key file {:?}: no such file. Consider running `rosenpass gen-keys` to generate a new keypair.",
|
||||||
|
keypair.public_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the public-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SPk::load(&keypair.public_key).is_ok(),
|
||||||
|
"could not load public-key file {:?}: invalid key",
|
||||||
|
keypair.public_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the secret-key file exists
|
||||||
|
ensure!(
|
||||||
|
keypair.secret_key.is_file(),
|
||||||
|
"could not find secret-key file {:?}: no such file. Consider running `rosenpass gen-keys` to generate a new keypair.",
|
||||||
|
keypair.secret_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the secret-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SSk::load(&keypair.secret_key).is_ok(),
|
||||||
|
"could not load public-key file {:?}: invalid key",
|
||||||
|
keypair.secret_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (i, peer) in self.peers.iter().enumerate() {
|
for (i, peer) in self.peers.iter().enumerate() {
|
||||||
// check peer's public-key file exists
|
// check peer's public-key file exists
|
||||||
@@ -185,6 +247,13 @@ impl Rosenpass {
|
|||||||
peer.public_key
|
peer.public_key
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// check peer's public-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SPk::load(&peer.public_key).is_ok(),
|
||||||
|
"peer {i} public-key file {:?} is invalid",
|
||||||
|
peer.public_key
|
||||||
|
);
|
||||||
|
|
||||||
// check endpoint is usable
|
// check endpoint is usable
|
||||||
if let Some(addr) = peer.endpoint.as_ref() {
|
if let Some(addr) = peer.endpoint.as_ref() {
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -194,18 +263,57 @@ impl Rosenpass {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO warn if neither out_key nor exchange_command is defined
|
// check if `key_out` or `device` and `peer` are defined
|
||||||
|
if peer.key_out.is_none() {
|
||||||
|
if let Some(wg) = &peer.wg {
|
||||||
|
if wg.device.is_empty() || wg.peer.is_empty() {
|
||||||
|
ensure!(
|
||||||
|
false,
|
||||||
|
"peer {i} has neither `key_out` nor valid wireguard config defined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ensure!(
|
||||||
|
false,
|
||||||
|
"peer {i} has neither `key_out` nor valid wireguard config defined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||||
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
|
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
ensure!(
|
||||||
|
self.keypair.is_some() || self.api.has_api_sources(),
|
||||||
|
"{}{}",
|
||||||
|
"Specify a server keypair or some API connections to configure the keypair with.",
|
||||||
|
"Without a keypair, rosenpass can not operate."
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::new(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||||
|
Self::new(Some(Keypair::new(pk, sk)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new configuration
|
/// Creates a new configuration
|
||||||
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(public_key: P1, secret_key: P2) -> Self {
|
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
public_key: PathBuf::from(public_key.as_ref()),
|
keypair,
|
||||||
secret_key: PathBuf::from(secret_key.as_ref()),
|
|
||||||
listen: vec![],
|
listen: vec![],
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
api: crate::api::config::ApiConfig::default(),
|
||||||
verbosity: Verbosity::Quiet,
|
verbosity: Verbosity::Quiet,
|
||||||
peers: vec![],
|
peers: vec![],
|
||||||
config_file_path: PathBuf::new(),
|
config_file_path: PathBuf::new(),
|
||||||
@@ -228,7 +336,7 @@ impl Rosenpass {
|
|||||||
/// from chaotic args
|
/// from chaotic args
|
||||||
/// Quest: the grammar is undecideable, what do we do here?
|
/// Quest: the grammar is undecideable, what do we do here?
|
||||||
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||||
let mut config = Self::new("", "");
|
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
enum State {
|
enum State {
|
||||||
@@ -289,7 +397,7 @@ impl Rosenpass {
|
|||||||
already_set.insert(OwnPublicKey),
|
already_set.insert(OwnPublicKey),
|
||||||
"public-key was already set"
|
"public-key was already set"
|
||||||
);
|
);
|
||||||
config.public_key = pk.into();
|
config.keypair.as_mut().unwrap().public_key = pk.into();
|
||||||
Own
|
Own
|
||||||
}
|
}
|
||||||
(OwnSecretKey, sk, None) => {
|
(OwnSecretKey, sk, None) => {
|
||||||
@@ -297,7 +405,7 @@ impl Rosenpass {
|
|||||||
already_set.insert(OwnSecretKey),
|
already_set.insert(OwnSecretKey),
|
||||||
"secret-key was already set"
|
"secret-key was already set"
|
||||||
);
|
);
|
||||||
config.secret_key = sk.into();
|
config.keypair.as_mut().unwrap().secret_key = sk.into();
|
||||||
Own
|
Own
|
||||||
}
|
}
|
||||||
(OwnListen, l, None) => {
|
(OwnListen, l, None) => {
|
||||||
@@ -416,45 +524,146 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rosenpass {
|
|
||||||
/// Generate an example configuration
|
|
||||||
pub fn example_config() -> Self {
|
|
||||||
let peer = RosenpassPeer {
|
|
||||||
public_key: "/path/to/rp-peer-public-key".into(),
|
|
||||||
endpoint: Some("my-peer.test:9999".into()),
|
|
||||||
key_out: Some("/path/to/rp-key-out.txt".into()),
|
|
||||||
pre_shared_key: Some("additional pre shared key".into()),
|
|
||||||
wg: Some(WireGuard {
|
|
||||||
device: "wirgeguard device e.g. wg0".into(),
|
|
||||||
peer: "wireguard public key".into(),
|
|
||||||
extra_params: vec!["passed to".into(), "wg set".into()],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
public_key: "/path/to/rp-public-key".into(),
|
|
||||||
secret_key: "/path/to/rp-secret-key".into(),
|
|
||||||
peers: vec![peer],
|
|
||||||
..Self::new("", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Verbosity {
|
impl Default for Verbosity {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Quiet
|
Self::Quiet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
||||||
|
secret_key = "/path/to/rp-secret-key"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
# Commented out fields are optional
|
||||||
|
public_key = "/path/to/rp-peer-public-key"
|
||||||
|
endpoint = "127.0.0.1:9998"
|
||||||
|
# pre_shared_key = "/path/to/preshared-key"
|
||||||
|
|
||||||
|
# Choose to store the key in a file via `key_out` or pass it to WireGuard by
|
||||||
|
# defining `device` and `peer`. You may choose to do both.
|
||||||
|
key_out = "/path/to/rp-key-out.txt" # path to store the key
|
||||||
|
# device = "wg0" # WireGuard interface
|
||||||
|
#peer = "RULdRAtUw7SFfVfGD..." # WireGuard public key
|
||||||
|
# extra_params = [] # passed to WireGuard `wg set`
|
||||||
|
"###;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::net::IpAddr;
|
use std::{borrow::Borrow, net::IpAddr};
|
||||||
|
|
||||||
|
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||||
|
toml::from_str(s.borrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toml_ser<S: Serialize>(s: S) -> Result<toml::Table, toml::ser::Error> {
|
||||||
|
toml::Table::try_from(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_toml<L: Serialize, R: Borrow<str>>(l: L, r: R, info: &str) -> anyhow::Result<()> {
|
||||||
|
fn lines_prepend(prefix: &str, s: &str) -> anyhow::Result<String> {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
for line in s.lines() {
|
||||||
|
writeln!(&mut buf, "{prefix}{line}")?;
|
||||||
|
}
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
let l = toml_ser(l)?;
|
||||||
|
let r = toml_des(r.borrow())?;
|
||||||
|
ensure!(
|
||||||
|
l == r,
|
||||||
|
"{}{}TOML value mismatch.\n Have:\n{}\n Expected:\n{}",
|
||||||
|
info,
|
||||||
|
if info.is_empty() { "" } else { ": " },
|
||||||
|
lines_prepend(" ", &toml::to_string_pretty(&l)?)?,
|
||||||
|
lines_prepend(" ", &toml::to_string_pretty(&r)?)?
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_toml_round<'de, L: Serialize + Deserialize<'de>, R: Borrow<str>>(
|
||||||
|
l: L,
|
||||||
|
r: R,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let l = toml_ser(l)?;
|
||||||
|
assert_toml(&l, r.borrow(), "Straight deserialization")?;
|
||||||
|
|
||||||
|
let l: L = l.try_into().unwrap();
|
||||||
|
let l = toml_ser(l).unwrap();
|
||||||
|
assert_toml(l, r.borrow(), "Roundtrip deserialization")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn split_str(s: &str) -> Vec<String> {
|
fn split_str(s: &str) -> Vec<String> {
|
||||||
s.split(' ').map(|s| s.to_string()).collect()
|
s.split(' ').map(|s| s.to_string()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn toml_serialization() -> anyhow::Result<()> {
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
assert_toml_round(
|
||||||
|
Rosenpass::empty(),
|
||||||
|
r#"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Quiet"
|
||||||
|
peers = []
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
|
assert_toml_round(
|
||||||
|
Rosenpass::empty(),
|
||||||
|
r#"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Quiet"
|
||||||
|
peers = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
assert_toml_round(
|
||||||
|
Rosenpass::from_sk_pk("/my/sk", "/my/pk"),
|
||||||
|
r#"
|
||||||
|
public_key = "/my/pk"
|
||||||
|
secret_key = "/my/sk"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Quiet"
|
||||||
|
peers = []
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
|
assert_toml_round(
|
||||||
|
Rosenpass::from_sk_pk("/my/sk", "/my/pk"),
|
||||||
|
r#"
|
||||||
|
public_key = "/my/pk"
|
||||||
|
secret_key = "/my/sk"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Quiet"
|
||||||
|
peers = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_cli_parse() {
|
fn test_simple_cli_parse() {
|
||||||
let args = split_str(
|
let args = split_str(
|
||||||
@@ -465,8 +674,10 @@ mod test {
|
|||||||
|
|
||||||
let config = Rosenpass::parse_args(args).unwrap();
|
let config = Rosenpass::parse_args(args).unwrap();
|
||||||
|
|
||||||
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
assert_eq!(
|
||||||
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
config.keypair,
|
||||||
|
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||||
|
);
|
||||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&config.listen,
|
&config.listen,
|
||||||
@@ -495,8 +706,10 @@ mod test {
|
|||||||
|
|
||||||
let config = Rosenpass::parse_args(args).unwrap();
|
let config = Rosenpass::parse_args(args).unwrap();
|
||||||
|
|
||||||
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
assert_eq!(
|
||||||
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
config.keypair,
|
||||||
|
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||||
|
);
|
||||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||||
assert!(&config.listen.is_empty());
|
assert!(&config.listen.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
pub mod api;
|
||||||
pub mod app_server;
|
pub mod app_server;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
@@ -11,4 +13,8 @@ pub enum RosenpassError {
|
|||||||
BufferSizeMismatch,
|
BufferSizeMismatch,
|
||||||
#[error("invalid message type")]
|
#[error("invalid message type")]
|
||||||
InvalidMessageType(u8),
|
InvalidMessageType(u8),
|
||||||
|
#[error("invalid API message type")]
|
||||||
|
InvalidApiMessageType(u128),
|
||||||
|
#[error("could not parse API message")]
|
||||||
|
InvalidApiMessage,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,59 @@
|
|||||||
|
use clap::CommandFactory;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clap_mangen::roff::{roman, Roff};
|
||||||
use log::error;
|
use log::error;
|
||||||
use rosenpass::cli::CliArgs;
|
use rosenpass::cli::CliArgs;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File) {
|
||||||
|
let mut roff = Roff::default();
|
||||||
|
roff.control("SH", [section]);
|
||||||
|
roff.text([roman(text)]);
|
||||||
|
let _ = roff.to_writer(file);
|
||||||
|
}
|
||||||
|
|
||||||
/// Catches errors, prints them through the logger, then exits
|
/// Catches errors, prints them through the logger, then exits
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// parse CLI arguments
|
// parse CLI arguments
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
|
|
||||||
|
if let Some(shell) = args.print_completions {
|
||||||
|
let mut cmd = CliArgs::command();
|
||||||
|
clap_complete::generate(shell, &mut cmd, "rosenpass", &mut std::io::stdout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(out_dir) = args.generate_manpage {
|
||||||
|
std::fs::create_dir_all(&out_dir).expect("Failed to create man pages directory");
|
||||||
|
|
||||||
|
let cmd = CliArgs::command();
|
||||||
|
let man = clap_mangen::Man::new(cmd.clone());
|
||||||
|
let _ = clap_mangen::generate_to(cmd, &out_dir);
|
||||||
|
|
||||||
|
let file_path = out_dir.join("rosenpass.1");
|
||||||
|
let mut file = std::fs::File::create(file_path).expect("Failed to create man page file");
|
||||||
|
|
||||||
|
let _ = man.render_title(&mut file);
|
||||||
|
let _ = man.render_name_section(&mut file);
|
||||||
|
let _ = man.render_synopsis_section(&mut file);
|
||||||
|
let _ = man.render_subcommands_section(&mut file);
|
||||||
|
let _ = man.render_options_section(&mut file);
|
||||||
|
print_custom_man_section("EXIT STATUS", EXIT_STATUS_MAN, &mut file);
|
||||||
|
print_custom_man_section("SEE ALSO", SEE_ALSO_MAN, &mut file);
|
||||||
|
print_custom_man_section("STANDARDS", STANDARDS_MAN, &mut file);
|
||||||
|
print_custom_man_section("AUTHORS", AUTHORS_MAN, &mut file);
|
||||||
|
print_custom_man_section("BUGS", BUGS_MAN, &mut file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
use rosenpass_secret_memory as SM;
|
||||||
|
#[cfg(feature = "experiment_memfd_secret")]
|
||||||
|
SM::secret_policy_try_use_memfd_secrets();
|
||||||
|
#[cfg(not(feature = "experiment_memfd_secret"))]
|
||||||
|
SM::secret_policy_use_only_malloc_secrets();
|
||||||
|
}
|
||||||
|
|
||||||
// init logging
|
// init logging
|
||||||
{
|
{
|
||||||
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
|
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
|
||||||
@@ -26,11 +72,30 @@ pub fn main() {
|
|||||||
// error!("error dummy");
|
// error!("error dummy");
|
||||||
}
|
}
|
||||||
|
|
||||||
match args.command.run(None) {
|
let broker_interface = args.get_broker_interface();
|
||||||
|
match args.run(broker_interface, None) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{e:?}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static EXIT_STATUS_MAN: &str = r"
|
||||||
|
The rosenpass utility exits 0 on success, and >0 if an error occurs.";
|
||||||
|
|
||||||
|
static SEE_ALSO_MAN: &str = r"
|
||||||
|
rp(1), wg(1)
|
||||||
|
|
||||||
|
Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt, Rosenpass, https://rosenpass.eu/whitepaper.pdf, 2023.";
|
||||||
|
|
||||||
|
static STANDARDS_MAN: &str = r"
|
||||||
|
This tool is the reference implementation of the Rosenpass protocol, as
|
||||||
|
specified within the whitepaper referenced above.";
|
||||||
|
|
||||||
|
static AUTHORS_MAN: &str = r"
|
||||||
|
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske, Marei
|
||||||
|
Peischl, Stephan Ajuvo, and Lisa Schmidt.";
|
||||||
|
|
||||||
|
static BUGS_MAN: &str = r"
|
||||||
|
The bugs are tracked at https://github.com/rosenpass/rosenpass/issues.";
|
||||||
|
|||||||
127
rosenpass/src/protocol/build_crypto_server.rs
Normal file
127
rosenpass/src/protocol/build_crypto_server.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use rosenpass_util::{
|
||||||
|
build::Build,
|
||||||
|
mem::{DiscardResultExt, SwapWithDefaultExt},
|
||||||
|
result::ensure_or,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Keypair {
|
||||||
|
pub sk: SSk,
|
||||||
|
pub pk: SPk,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We need a named tuple derive
|
||||||
|
impl Keypair {
|
||||||
|
pub fn new(sk: SSk, pk: SPk) -> Self {
|
||||||
|
Self { sk, pk }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self::new(SSk::zero(), SPk::zero())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random() -> Self {
|
||||||
|
Self::new(SSk::random(), SPk::random())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_parts(parts: (SSk, SPk)) -> Self {
|
||||||
|
Self::new(parts.0, parts.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_parts(self) -> (SSk, SPk) {
|
||||||
|
(self.sk, self.pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("PSK already set in BuildCryptoServer")]
|
||||||
|
pub struct PskAlreadySet;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("Keypair already set in BuildCryptoServer")]
|
||||||
|
pub struct KeypairAlreadySet;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("Can not construct CryptoServer: Missing keypair")]
|
||||||
|
pub struct MissingKeypair;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct BuildCryptoServer {
|
||||||
|
pub keypair: Option<Keypair>,
|
||||||
|
pub peers: Vec<PeerParams>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Build<CryptoServer> for BuildCryptoServer {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn build(self) -> Result<CryptoServer, Self::Error> {
|
||||||
|
let Some(Keypair { sk, pk }) = self.keypair else {
|
||||||
|
return Err(MissingKeypair)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut srv = CryptoServer::new(sk, pk);
|
||||||
|
|
||||||
|
for (idx, PeerParams { psk, pk }) in self.peers.into_iter().enumerate() {
|
||||||
|
let PeerPtr(idx2) = srv.add_peer(psk, pk)?;
|
||||||
|
assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(srv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PeerParams {
|
||||||
|
pub psk: Option<SymKey>,
|
||||||
|
pub pk: SPk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildCryptoServer {
|
||||||
|
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
|
||||||
|
Self { keypair, peers }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::new(None, Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
|
||||||
|
Self {
|
||||||
|
keypair: parts.0,
|
||||||
|
peers: parts.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||||
|
(self.keypair.take(), self.peers.swap_with_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||||
|
self.take_parts()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
|
||||||
|
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
|
||||||
|
self.keypair.insert(keypair).discard_result();
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self {
|
||||||
|
// TODO: Check here already whether peer was already added
|
||||||
|
self.peers.push(PeerParams { psk, pk });
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr {
|
||||||
|
let id = PeerPtr(self.peers.len());
|
||||||
|
self.with_added_peer(psk, pk);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emancipate(&mut self) -> Self {
|
||||||
|
Self::from_parts(self.take_parts())
|
||||||
|
}
|
||||||
|
}
|
||||||
6
rosenpass/src/protocol/mod.rs
Normal file
6
rosenpass/src/protocol/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
mod build_crypto_server;
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
|
mod protocol;
|
||||||
|
|
||||||
|
pub use build_crypto_server::*;
|
||||||
|
pub use protocol::*;
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
//! [CryptoServer].
|
//! [CryptoServer].
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
//! use std::ops::DerefMut;
|
||||||
//! use rosenpass_secret_memory::policy::*;
|
//! use rosenpass_secret_memory::policy::*;
|
||||||
//! use rosenpass_cipher_traits::Kem;
|
//! use rosenpass_cipher_traits::Kem;
|
||||||
//! use rosenpass_ciphers::kem::StaticKem;
|
//! use rosenpass_ciphers::kem::StaticKem;
|
||||||
@@ -32,11 +33,11 @@
|
|||||||
//!
|
//!
|
||||||
//! // initialize secret and public key for peer a ...
|
//! // initialize secret and public key for peer a ...
|
||||||
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||||
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
|
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||||
//!
|
//!
|
||||||
//! // ... and for peer b
|
//! // ... and for peer b
|
||||||
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||||
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?;
|
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||||
//!
|
//!
|
||||||
//! // initialize server and a pre-shared key
|
//! // initialize server and a pre-shared key
|
||||||
//! let psk = SymKey::random();
|
//! let psk = SymKey::random();
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::{
|
use std::{
|
||||||
collections::hash_map::{
|
collections::hash_map::{
|
||||||
Entry::{Occupied, Vacant},
|
Entry::{Occupied, Vacant},
|
||||||
@@ -88,20 +90,14 @@ use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace
|
|||||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||||
use rosenpass_constant_time as constant_time;
|
use rosenpass_constant_time as constant_time;
|
||||||
use rosenpass_secret_memory::{Public, Secret};
|
use rosenpass_secret_memory::{Public, PublicBox, Secret};
|
||||||
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
|
use rosenpass_util::{cat, mem::cpy_min, time::Timebase};
|
||||||
use zerocopy::{AsBytes, FromBytes, Ref};
|
use zerocopy::{AsBytes, FromBytes, Ref};
|
||||||
|
|
||||||
use crate::{hash_domains, msgs::*, RosenpassError};
|
use crate::{hash_domains, msgs::*, RosenpassError};
|
||||||
|
|
||||||
// CONSTANTS & SETTINGS //////////////////////////
|
// 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>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// A type for time, e.g. for backoff before re-tries
|
/// A type for time, e.g. for backoff before re-tries
|
||||||
pub type Timing = f64;
|
pub type Timing = f64;
|
||||||
|
|
||||||
@@ -138,11 +134,10 @@ pub const PEER_COOKIE_VALUE_EPOCH: Timing = 120.0;
|
|||||||
// decryption for a second epoch
|
// decryption for a second epoch
|
||||||
pub const BISCUIT_EPOCH: Timing = 300.0;
|
pub const BISCUIT_EPOCH: Timing = 300.0;
|
||||||
|
|
||||||
// Retransmission pub constants; will retransmit for up to _ABORT ms; starting with a delay of
|
// Retransmission pub constants; will retransmit for up to _ABORT seconds;
|
||||||
// _DELAY_BEG ms and increasing the delay exponentially by a factor of
|
// starting with a delay of _DELAY_BEGIN seconds and increasing the delay
|
||||||
// _DELAY_GROWTH up to _DELAY_END. An additional jitter factor of ±_DELAY_JITTER
|
// exponentially by a factor of _DELAY_GROWTH up to _DELAY_END.
|
||||||
// is added.
|
// An additional jitter factor of ±_DELAY_JITTER is added.
|
||||||
pub const RETRANSMIT_ABORT: Timing = 120.0;
|
|
||||||
pub const RETRANSMIT_DELAY_GROWTH: Timing = 2.0;
|
pub const RETRANSMIT_DELAY_GROWTH: Timing = 2.0;
|
||||||
pub const RETRANSMIT_DELAY_BEGIN: Timing = 0.5;
|
pub const RETRANSMIT_DELAY_BEGIN: Timing = 0.5;
|
||||||
pub const RETRANSMIT_DELAY_END: Timing = 10.0;
|
pub const RETRANSMIT_DELAY_END: Timing = 10.0;
|
||||||
@@ -163,7 +158,7 @@ pub fn has_happened(ev: Timing, now: Timing) -> bool {
|
|||||||
|
|
||||||
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
|
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
|
||||||
|
|
||||||
pub type SPk = Secret<{ StaticKem::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
|
pub type SPk = PublicBox<{ StaticKem::PK_LEN }>;
|
||||||
pub type SSk = Secret<{ StaticKem::SK_LEN }>;
|
pub type SSk = Secret<{ StaticKem::SK_LEN }>;
|
||||||
pub type EPk = Public<{ EphemeralKem::PK_LEN }>;
|
pub type EPk = Public<{ EphemeralKem::PK_LEN }>;
|
||||||
pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
|
pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
|
||||||
@@ -548,7 +543,7 @@ impl CryptoServer {
|
|||||||
pub fn pidm(&self) -> Result<PeerId> {
|
pub fn pidm(&self) -> Result<PeerId> {
|
||||||
Ok(Public::new(
|
Ok(Public::new(
|
||||||
hash_domains::peerid()?
|
hash_domains::peerid()?
|
||||||
.mix(self.spkm.secret())?
|
.mix(self.spkm.deref())?
|
||||||
.into_value()))
|
.into_value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +703,7 @@ impl Peer {
|
|||||||
pub fn pidt(&self) -> Result<PeerId> {
|
pub fn pidt(&self) -> Result<PeerId> {
|
||||||
Ok(Public::new(
|
Ok(Public::new(
|
||||||
hash_domains::peerid()?
|
hash_domains::peerid()?
|
||||||
.mix(self.spkt.secret())?
|
.mix(self.spkt.deref())?
|
||||||
.into_value()))
|
.into_value()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1017,7 +1012,7 @@ impl CryptoServer {
|
|||||||
|
|
||||||
let cookie_value = active_cookie_value.unwrap();
|
let cookie_value = active_cookie_value.unwrap();
|
||||||
let cookie_key = hash_domains::cookie_key()?
|
let cookie_key = hash_domains::cookie_key()?
|
||||||
.mix(self.spkm.secret())?
|
.mix(self.spkm.deref())?
|
||||||
.into_value();
|
.into_value();
|
||||||
|
|
||||||
let mut msg_out = truncating_cast_into::<CookieReply>(tx_buf)?;
|
let mut msg_out = truncating_cast_into::<CookieReply>(tx_buf)?;
|
||||||
@@ -1477,7 +1472,7 @@ impl IniHsPtr {
|
|||||||
.min(ih.tx_count as f64),
|
.min(ih.tx_count as f64),
|
||||||
)
|
)
|
||||||
* RETRANSMIT_DELAY_JITTER
|
* RETRANSMIT_DELAY_JITTER
|
||||||
* (rand::random::<f64>() + 1.0); // TODO: Replace with the rand crate
|
* (rand::random::<f64>() + 1.0);
|
||||||
ih.tx_count += 1;
|
ih.tx_count += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1509,7 +1504,7 @@ where
|
|||||||
/// Calculate the message authentication code (`mac`) and also append cookie value
|
/// Calculate the message authentication code (`mac`) and also append cookie value
|
||||||
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
|
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
|
||||||
let mac = hash_domains::mac()?
|
let mac = hash_domains::mac()?
|
||||||
.mix(peer.get(srv).spkt.secret())?
|
.mix(peer.get(srv).spkt.deref())?
|
||||||
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
|
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
|
||||||
self.mac.copy_from_slice(mac.into_value()[..16].as_ref());
|
self.mac.copy_from_slice(mac.into_value()[..16].as_ref());
|
||||||
self.seal_cookie(peer, srv)?;
|
self.seal_cookie(peer, srv)?;
|
||||||
@@ -1536,7 +1531,7 @@ where
|
|||||||
/// Check the message authentication code
|
/// Check the message authentication code
|
||||||
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
|
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
|
||||||
let expected = hash_domains::mac()?
|
let expected = hash_domains::mac()?
|
||||||
.mix(srv.spkm.secret())?
|
.mix(srv.spkm.deref())?
|
||||||
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
|
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
|
||||||
Ok(constant_time::memcmp(
|
Ok(constant_time::memcmp(
|
||||||
&self.mac,
|
&self.mac,
|
||||||
@@ -1641,7 +1636,7 @@ impl HandshakeState {
|
|||||||
|
|
||||||
// calculate ad contents
|
// calculate ad contents
|
||||||
let ad = hash_domains::biscuit_ad()?
|
let ad = hash_domains::biscuit_ad()?
|
||||||
.mix(srv.spkm.secret())?
|
.mix(srv.spkm.deref())?
|
||||||
.mix(self.sidi.as_slice())?
|
.mix(self.sidi.as_slice())?
|
||||||
.mix(self.sidr.as_slice())?
|
.mix(self.sidr.as_slice())?
|
||||||
.into_value();
|
.into_value();
|
||||||
@@ -1676,7 +1671,7 @@ impl HandshakeState {
|
|||||||
|
|
||||||
// Calculate additional data fields
|
// Calculate additional data fields
|
||||||
let ad = hash_domains::biscuit_ad()?
|
let ad = hash_domains::biscuit_ad()?
|
||||||
.mix(srv.spkm.secret())?
|
.mix(srv.spkm.deref())?
|
||||||
.mix(sidi.as_slice())?
|
.mix(sidi.as_slice())?
|
||||||
.mix(sidr.as_slice())?
|
.mix(sidr.as_slice())?
|
||||||
.into_value();
|
.into_value();
|
||||||
@@ -1763,7 +1758,7 @@ impl CryptoServer {
|
|||||||
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
|
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
|
||||||
|
|
||||||
// IHI1
|
// IHI1
|
||||||
hs.core.init(peer.get(self).spkt.secret())?;
|
hs.core.init(peer.get(self).spkt.deref())?;
|
||||||
|
|
||||||
// IHI2
|
// IHI2
|
||||||
hs.core.sidi.randomize();
|
hs.core.sidi.randomize();
|
||||||
@@ -1780,7 +1775,7 @@ impl CryptoServer {
|
|||||||
hs.core
|
hs.core
|
||||||
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||||
ih.sctr.as_mut_slice(),
|
ih.sctr.as_mut_slice(),
|
||||||
peer.get(self).spkt.secret(),
|
peer.get(self).spkt.deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// IHI6
|
// IHI6
|
||||||
@@ -1789,7 +1784,7 @@ impl CryptoServer {
|
|||||||
|
|
||||||
// IHI7
|
// IHI7
|
||||||
hs.core
|
hs.core
|
||||||
.mix(self.spkm.secret())?
|
.mix(self.spkm.deref())?
|
||||||
.mix(peer.get(self).psk.secret())?;
|
.mix(peer.get(self).psk.secret())?;
|
||||||
|
|
||||||
// IHI8
|
// IHI8
|
||||||
@@ -1807,7 +1802,7 @@ impl CryptoServer {
|
|||||||
core.sidi = SessionId::from_slice(&ih.sidi);
|
core.sidi = SessionId::from_slice(&ih.sidi);
|
||||||
|
|
||||||
// IHR1
|
// IHR1
|
||||||
core.init(self.spkm.secret())?;
|
core.init(self.spkm.deref())?;
|
||||||
|
|
||||||
// IHR4
|
// IHR4
|
||||||
core.mix(&ih.sidi)?.mix(&ih.epki)?;
|
core.mix(&ih.sidi)?.mix(&ih.epki)?;
|
||||||
@@ -1815,7 +1810,7 @@ impl CryptoServer {
|
|||||||
// IHR5
|
// IHR5
|
||||||
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||||
self.sskm.secret(),
|
self.sskm.secret(),
|
||||||
self.spkm.secret(),
|
self.spkm.deref(),
|
||||||
&ih.sctr,
|
&ih.sctr,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -1828,7 +1823,7 @@ impl CryptoServer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// IHR7
|
// IHR7
|
||||||
core.mix(peer.get(self).spkt.secret())?
|
core.mix(peer.get(self).spkt.deref())?
|
||||||
.mix(peer.get(self).psk.secret())?;
|
.mix(peer.get(self).psk.secret())?;
|
||||||
|
|
||||||
// IHR8
|
// IHR8
|
||||||
@@ -1848,7 +1843,7 @@ impl CryptoServer {
|
|||||||
// RHR5
|
// RHR5
|
||||||
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||||
&mut rh.scti,
|
&mut rh.scti,
|
||||||
peer.get(self).spkt.secret(),
|
peer.get(self).spkt.deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// RHR6
|
// RHR6
|
||||||
@@ -1909,14 +1904,14 @@ impl CryptoServer {
|
|||||||
// RHI4
|
// RHI4
|
||||||
core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
|
core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
|
||||||
hs!().eski.secret(),
|
hs!().eski.secret(),
|
||||||
&*hs!().epki,
|
hs!().epki.deref(),
|
||||||
&rh.ecti,
|
&rh.ecti,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// RHI5
|
// RHI5
|
||||||
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||||
self.sskm.secret(),
|
self.sskm.secret(),
|
||||||
self.spkm.secret(),
|
self.spkm.deref(),
|
||||||
&rh.scti,
|
&rh.scti,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -2014,8 +2009,7 @@ impl CryptoServer {
|
|||||||
|
|
||||||
// Send ack – Implementing sending the empty acknowledgement here
|
// Send ack – Implementing sending the empty acknowledgement here
|
||||||
// instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either<EmptyData, Data>
|
// instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either<EmptyData, Data>
|
||||||
// because data transmission is a stub currently. This software is supposed to be used
|
// because data transmission is a stub currently.
|
||||||
// as a key exchange service feeding a PSK into some classical (i.e. non post quantum)
|
|
||||||
let ses = peer
|
let ses = peer
|
||||||
.session()
|
.session()
|
||||||
.get_mut(self)
|
.get_mut(self)
|
||||||
@@ -2113,7 +2107,7 @@ impl CryptoServer {
|
|||||||
),
|
),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let spkt = peer.get(self).spkt.secret();
|
let spkt = peer.get(self).spkt.deref();
|
||||||
let cookie_key = hash_domains::cookie_key()?.mix(spkt)?.into_value();
|
let cookie_key = hash_domains::cookie_key()?.mix(spkt)?.into_value();
|
||||||
let cookie_value = peer.cv().update_mut(self).unwrap();
|
let cookie_value = peer.cv().update_mut(self).unwrap();
|
||||||
|
|
||||||
@@ -2146,7 +2140,7 @@ fn truncating_cast_into_nomut<T: FromBytes>(buf: &[u8]) -> Result<Ref<&[u8], T>,
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::{net::SocketAddrV4, thread::sleep, time::Duration};
|
use std::{net::SocketAddrV4, ops::DerefMut, thread::sleep, time::Duration};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
@@ -2255,7 +2249,7 @@ mod test {
|
|||||||
fn keygen() -> Result<(SSk, SPk)> {
|
fn keygen() -> Result<(SSk, SPk)> {
|
||||||
// TODO: Copied from the benchmark; deduplicate
|
// TODO: Copied from the benchmark; deduplicate
|
||||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
|
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||||
Ok((sk, pk))
|
Ok((sk, pk))
|
||||||
}
|
}
|
||||||
|
|
||||||
332
rosenpass/tests/api-integration-tests-api-setup.rs
Normal file
332
rosenpass/tests/api-integration-tests-api-setup.rs
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
|
io::{BufRead, BufReader, Write},
|
||||||
|
os::unix::net::UnixStream,
|
||||||
|
process::Stdio,
|
||||||
|
thread::sleep,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
|
use command_fds::{CommandFdExt, FdMapping};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use rosenpass::api::{
|
||||||
|
self, add_listen_socket_response_status, add_psk_broker_response_status,
|
||||||
|
supply_keypair_response_status,
|
||||||
|
};
|
||||||
|
use rosenpass_util::{
|
||||||
|
b64::B64Display,
|
||||||
|
file::LoadValueB64,
|
||||||
|
io::IoErrorKind,
|
||||||
|
length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder},
|
||||||
|
mem::{DiscardResultExt, MoveExt},
|
||||||
|
mio::WriteWithFileDescriptors,
|
||||||
|
zerocopy::ZerocopySliceExt,
|
||||||
|
};
|
||||||
|
use std::os::fd::{AsFd, AsRawFd};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
|
use rosenpass::protocol::SymKey;
|
||||||
|
|
||||||
|
struct KillChild(std::process::Child);
|
||||||
|
|
||||||
|
impl Drop for KillChild {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.kill().discard_result();
|
||||||
|
self.0.wait().discard_result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn api_integration_api_setup() -> anyhow::Result<()> {
|
||||||
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
|
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
||||||
|
|
||||||
|
macro_rules! tempfile {
|
||||||
|
($($lst:expr),+) => {{
|
||||||
|
let mut buf = dir.path().to_path_buf();
|
||||||
|
$(buf.push($lst);)*
|
||||||
|
buf
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
let peer_a_endpoint = "[::1]:0";
|
||||||
|
let peer_a_listen = std::net::UdpSocket::bind(peer_a_endpoint)?;
|
||||||
|
let peer_a_endpoint = format!("{}", peer_a_listen.local_addr()?);
|
||||||
|
let peer_a_keypair = config::Keypair::new(tempfile!("a.pk"), tempfile!("a.sk"));
|
||||||
|
|
||||||
|
let peer_b_osk = tempfile!("b.osk");
|
||||||
|
let peer_b_wg_device = "mock_device";
|
||||||
|
let peer_b_wg_peer_id = hex!(
|
||||||
|
"
|
||||||
|
93 0f ee 77 0c 6b 54 7e 13 5f 13 92 21 97 26 53
|
||||||
|
7d 77 4a 6a 0f 6c eb 1a dd 6e 5b c4 1b 92 cd 99
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
use rosenpass::config;
|
||||||
|
let peer_a = config::Rosenpass {
|
||||||
|
config_file_path: tempfile!("a.config"),
|
||||||
|
keypair: None,
|
||||||
|
listen: vec![], // TODO: This could collide by accident
|
||||||
|
verbosity: config::Verbosity::Verbose,
|
||||||
|
api: api::config::ApiConfig {
|
||||||
|
listen_path: vec![tempfile!("a.sock")],
|
||||||
|
listen_fd: vec![],
|
||||||
|
stream_fd: vec![],
|
||||||
|
},
|
||||||
|
peers: vec![config::RosenpassPeer {
|
||||||
|
public_key: tempfile!("b.pk"),
|
||||||
|
key_out: None,
|
||||||
|
endpoint: None,
|
||||||
|
pre_shared_key: None,
|
||||||
|
wg: Some(config::WireGuard {
|
||||||
|
device: peer_b_wg_device.to_string(),
|
||||||
|
peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()),
|
||||||
|
extra_params: vec![],
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let peer_b_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk"));
|
||||||
|
let peer_b = config::Rosenpass {
|
||||||
|
config_file_path: tempfile!("b.config"),
|
||||||
|
keypair: Some(peer_b_keypair.clone()),
|
||||||
|
listen: vec![],
|
||||||
|
verbosity: config::Verbosity::Verbose,
|
||||||
|
api: api::config::ApiConfig {
|
||||||
|
listen_path: vec![tempfile!("b.sock")],
|
||||||
|
listen_fd: vec![],
|
||||||
|
stream_fd: vec![],
|
||||||
|
},
|
||||||
|
peers: vec![config::RosenpassPeer {
|
||||||
|
public_key: tempfile!("a.pk"),
|
||||||
|
key_out: Some(peer_b_osk.clone()),
|
||||||
|
endpoint: Some(peer_a_endpoint.to_owned()),
|
||||||
|
pre_shared_key: None,
|
||||||
|
wg: None,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate the keys
|
||||||
|
rosenpass::cli::testing::generate_and_save_keypair(
|
||||||
|
peer_a_keypair.secret_key.clone(),
|
||||||
|
peer_a_keypair.public_key.clone(),
|
||||||
|
)?;
|
||||||
|
rosenpass::cli::testing::generate_and_save_keypair(
|
||||||
|
peer_b_keypair.secret_key.clone(),
|
||||||
|
peer_b_keypair.public_key.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Write the configuration files
|
||||||
|
peer_a.commit()?;
|
||||||
|
peer_b.commit()?;
|
||||||
|
|
||||||
|
let (deliberate_fail_api_client, deliberate_fail_api_server) =
|
||||||
|
std::os::unix::net::UnixStream::pair()?;
|
||||||
|
let deliberate_fail_child_fd = 3;
|
||||||
|
|
||||||
|
// Start peer a
|
||||||
|
let _proc_a = KillChild(
|
||||||
|
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||||
|
.args(["--api-stream-fd", &deliberate_fail_child_fd.to_string()])
|
||||||
|
.fd_mappings(vec![FdMapping {
|
||||||
|
parent_fd: deliberate_fail_api_server.move_here().as_raw_fd(),
|
||||||
|
child_fd: 3,
|
||||||
|
}])?
|
||||||
|
.args([
|
||||||
|
"exchange-config",
|
||||||
|
peer_a.config_file_path.to_str().context("")?,
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.spawn()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start peer b
|
||||||
|
let mut proc_b = KillChild(
|
||||||
|
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||||
|
.args([
|
||||||
|
"exchange-config",
|
||||||
|
peer_b.config_file_path.to_str().context("")?,
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Acquire stdout
|
||||||
|
let mut out_b = BufReader::new(proc_b.0.stdout.take().context("")?).lines();
|
||||||
|
|
||||||
|
// Now connect to the peers
|
||||||
|
let api_path = peer_a.api.listen_path[0].as_path();
|
||||||
|
|
||||||
|
// Wait for the socket to be created
|
||||||
|
let attempt = 0;
|
||||||
|
while !api_path.exists() {
|
||||||
|
sleep(Duration::from_millis(200));
|
||||||
|
assert!(
|
||||||
|
attempt < 50,
|
||||||
|
"Api failed to be created even after 50 seconds"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let api = UnixStream::connect(api_path)?;
|
||||||
|
let (psk_broker_sock, psk_broker_server_sock) = UnixStream::pair()?;
|
||||||
|
|
||||||
|
// Send AddListenSocket request
|
||||||
|
{
|
||||||
|
let fd = peer_a_listen.as_fd();
|
||||||
|
|
||||||
|
let mut fds = vec![&fd].into();
|
||||||
|
let mut api = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(&api, &mut fds);
|
||||||
|
LengthPrefixEncoder::from_message(api::AddListenSocketRequest::new().as_bytes())
|
||||||
|
.write_all_to_stdio(&mut api)?;
|
||||||
|
assert!(fds.is_empty(), "Failed to write all file descriptors");
|
||||||
|
std::mem::forget(peer_a_listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
{
|
||||||
|
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||||
|
let res = decoder.read_all_from_stdio(&api)?;
|
||||||
|
let res = res.zk_parse::<api::AddListenSocketResponse>()?;
|
||||||
|
assert_eq!(
|
||||||
|
*res,
|
||||||
|
api::AddListenSocketResponse::new(add_listen_socket_response_status::OK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliberately break API connection given via FD; this checks that the
|
||||||
|
// API connections are closed when invalid data is received and it also
|
||||||
|
// implicitly checks that other connections are unaffected
|
||||||
|
{
|
||||||
|
use std::io::ErrorKind as K;
|
||||||
|
let client = deliberate_fail_api_client;
|
||||||
|
let err = loop {
|
||||||
|
if let Err(e) = client.borrow().write(&[0xffu8; 16]) {
|
||||||
|
break e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// NotConnected happens on Mac
|
||||||
|
assert!(matches!(
|
||||||
|
err.io_error_kind(),
|
||||||
|
K::ConnectionReset | K::BrokenPipe | K::NotConnected
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send SupplyKeypairRequest
|
||||||
|
{
|
||||||
|
use rustix::fs::{open, Mode, OFlags};
|
||||||
|
let sk = open(peer_a_keypair.secret_key, OFlags::RDONLY, Mode::empty())?;
|
||||||
|
let pk = open(peer_a_keypair.public_key, OFlags::RDONLY, Mode::empty())?;
|
||||||
|
|
||||||
|
let mut fds = vec![&sk, &pk].into();
|
||||||
|
let mut api = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(&api, &mut fds);
|
||||||
|
LengthPrefixEncoder::from_message(api::SupplyKeypairRequest::new().as_bytes())
|
||||||
|
.write_all_to_stdio(&mut api)?;
|
||||||
|
assert!(fds.is_empty(), "Failed to write all file descriptors");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
{
|
||||||
|
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||||
|
let res = decoder.read_all_from_stdio(&api)?;
|
||||||
|
let res = res.zk_parse::<api::SupplyKeypairResponse>()?;
|
||||||
|
assert_eq!(
|
||||||
|
*res,
|
||||||
|
api::SupplyKeypairResponse::new(supply_keypair_response_status::OK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send AddPskBroker request
|
||||||
|
{
|
||||||
|
let mut fds = vec![psk_broker_server_sock.as_fd()].into();
|
||||||
|
let mut api = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(&api, &mut fds);
|
||||||
|
LengthPrefixEncoder::from_message(api::AddPskBrokerRequest::new().as_bytes())
|
||||||
|
.write_all_to_stdio(&mut api)?;
|
||||||
|
assert!(fds.is_empty(), "Failed to write all file descriptors");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
{
|
||||||
|
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||||
|
let res = decoder.read_all_from_stdio(&api)?;
|
||||||
|
let res = res.zk_parse::<api::AddPskBrokerResponse>()?;
|
||||||
|
assert_eq!(
|
||||||
|
*res,
|
||||||
|
api::AddPskBrokerResponse::new(add_psk_broker_response_status::OK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the keys to successfully exchange a key
|
||||||
|
let mut attempt = 0;
|
||||||
|
loop {
|
||||||
|
// Read OSK generated by A
|
||||||
|
let osk_a = {
|
||||||
|
use rosenpass_wireguard_broker::api::msgs as M;
|
||||||
|
type SetPskReqPkg = M::Envelope<M::SetPskRequest>;
|
||||||
|
type SetPskResPkg = M::Envelope<M::SetPskResponse>;
|
||||||
|
|
||||||
|
// Receive request
|
||||||
|
let mut decoder = LengthPrefixDecoder::new([0u8; M::REQUEST_MSG_BUFFER_SIZE]);
|
||||||
|
let req = decoder.read_all_from_stdio(&psk_broker_sock)?;
|
||||||
|
|
||||||
|
let req = req.zk_parse::<SetPskReqPkg>()?;
|
||||||
|
assert_eq!(req.msg_type, M::MsgType::SetPsk as u8);
|
||||||
|
assert_eq!(req.payload.peer_id, peer_b_wg_peer_id);
|
||||||
|
assert_eq!(req.payload.iface()?, peer_b_wg_device);
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
let res = SetPskResPkg {
|
||||||
|
msg_type: M::MsgType::SetPsk as u8,
|
||||||
|
reserved: [0u8; 3],
|
||||||
|
payload: M::SetPskResponse {
|
||||||
|
return_code: M::SetPskResponseReturnCode::Success as u8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
LengthPrefixEncoder::from_message(res.as_bytes())
|
||||||
|
.write_all_to_stdio(&psk_broker_sock)?;
|
||||||
|
|
||||||
|
SymKey::from_slice(&req.payload.psk)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read OSK generated by B
|
||||||
|
let osk_b = {
|
||||||
|
let line = out_b.next().context("")??;
|
||||||
|
let words = line.split(' ').collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// FIXED FIXED PEER-ID FIXED FILENAME STATUS
|
||||||
|
// output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged
|
||||||
|
let peer_id = words
|
||||||
|
.get(2)
|
||||||
|
.with_context(|| format!("Bad rosenpass output: `{line}`"))?;
|
||||||
|
assert_eq!(
|
||||||
|
line,
|
||||||
|
format!(
|
||||||
|
"output-key peer {peer_id} key-file \"{}\" exchanged",
|
||||||
|
peer_b_osk.to_str().context("")?
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SymKey::load_b64::<64, _>(peer_b_osk.clone())?
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This may be flaky. Both rosenpass instances are not guaranteed to produce
|
||||||
|
// the same number of output events; they merely guarantee eventual consistency of OSK.
|
||||||
|
// Correctly, we should use tokio to read any number of generated OSKs and indicate
|
||||||
|
// success on consensus
|
||||||
|
match osk_a.secret() == osk_b.secret() {
|
||||||
|
true => break,
|
||||||
|
false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"),
|
||||||
|
false => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
attempt += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
194
rosenpass/tests/api-integration-tests.rs
Normal file
194
rosenpass/tests/api-integration-tests.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
use std::{
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
net::ToSocketAddrs,
|
||||||
|
os::unix::net::UnixStream,
|
||||||
|
process::Stdio,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
|
use rosenpass::api;
|
||||||
|
use rosenpass_to::{ops::copy_slice_least_src, To};
|
||||||
|
use rosenpass_util::{
|
||||||
|
file::LoadValueB64,
|
||||||
|
length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder},
|
||||||
|
};
|
||||||
|
use rosenpass_util::{mem::DiscardResultExt, zerocopy::ZerocopySliceExt};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
|
use rosenpass::protocol::SymKey;
|
||||||
|
|
||||||
|
struct KillChild(std::process::Child);
|
||||||
|
|
||||||
|
impl Drop for KillChild {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.kill().discard_result();
|
||||||
|
self.0.wait().discard_result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn api_integration_test() -> anyhow::Result<()> {
|
||||||
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
|
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
||||||
|
|
||||||
|
macro_rules! tempfile {
|
||||||
|
($($lst:expr),+) => {{
|
||||||
|
let mut buf = dir.path().to_path_buf();
|
||||||
|
$(buf.push($lst);)*
|
||||||
|
buf
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
let peer_a_endpoint = "[::1]:61423";
|
||||||
|
let peer_a_osk = tempfile!("a.osk");
|
||||||
|
let peer_b_osk = tempfile!("b.osk");
|
||||||
|
|
||||||
|
use rosenpass::config;
|
||||||
|
|
||||||
|
let peer_a_keypair = config::Keypair::new(tempfile!("a.pk"), tempfile!("a.sk"));
|
||||||
|
let peer_a = config::Rosenpass {
|
||||||
|
config_file_path: tempfile!("a.config"),
|
||||||
|
keypair: Some(peer_a_keypair.clone()),
|
||||||
|
listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident
|
||||||
|
verbosity: config::Verbosity::Verbose,
|
||||||
|
api: api::config::ApiConfig {
|
||||||
|
listen_path: vec![tempfile!("a.sock")],
|
||||||
|
listen_fd: vec![],
|
||||||
|
stream_fd: vec![],
|
||||||
|
},
|
||||||
|
peers: vec![config::RosenpassPeer {
|
||||||
|
public_key: tempfile!("b.pk"),
|
||||||
|
key_out: Some(peer_a_osk.clone()),
|
||||||
|
endpoint: None,
|
||||||
|
pre_shared_key: None,
|
||||||
|
wg: None,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let peer_b_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk"));
|
||||||
|
let peer_b = config::Rosenpass {
|
||||||
|
config_file_path: tempfile!("b.config"),
|
||||||
|
keypair: Some(peer_b_keypair.clone()),
|
||||||
|
listen: vec![],
|
||||||
|
verbosity: config::Verbosity::Verbose,
|
||||||
|
api: api::config::ApiConfig {
|
||||||
|
listen_path: vec![tempfile!("b.sock")],
|
||||||
|
listen_fd: vec![],
|
||||||
|
stream_fd: vec![],
|
||||||
|
},
|
||||||
|
peers: vec![config::RosenpassPeer {
|
||||||
|
public_key: tempfile!("a.pk"),
|
||||||
|
key_out: Some(peer_b_osk.clone()),
|
||||||
|
endpoint: Some(peer_a_endpoint.to_owned()),
|
||||||
|
pre_shared_key: None,
|
||||||
|
wg: None,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate the keys
|
||||||
|
rosenpass::cli::testing::generate_and_save_keypair(
|
||||||
|
peer_a_keypair.secret_key.clone(),
|
||||||
|
peer_a_keypair.public_key.clone(),
|
||||||
|
)?;
|
||||||
|
rosenpass::cli::testing::generate_and_save_keypair(
|
||||||
|
peer_b_keypair.secret_key.clone(),
|
||||||
|
peer_b_keypair.public_key.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Write the configuration files
|
||||||
|
peer_a.commit()?;
|
||||||
|
peer_b.commit()?;
|
||||||
|
|
||||||
|
// Start peer a
|
||||||
|
let mut proc_a = KillChild(
|
||||||
|
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||||
|
.args([
|
||||||
|
"exchange-config",
|
||||||
|
peer_a.config_file_path.to_str().context("")?,
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start peer b
|
||||||
|
let mut proc_b = KillChild(
|
||||||
|
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||||
|
.args([
|
||||||
|
"exchange-config",
|
||||||
|
peer_b.config_file_path.to_str().context("")?,
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Acquire stdout
|
||||||
|
let mut out_a = BufReader::new(proc_a.0.stdout.take().context("")?).lines();
|
||||||
|
let mut out_b = BufReader::new(proc_b.0.stdout.take().context("")?).lines();
|
||||||
|
|
||||||
|
// Wait for the keys to successfully exchange a key
|
||||||
|
let mut attempt = 0;
|
||||||
|
loop {
|
||||||
|
let line_a = out_a.next().context("")??;
|
||||||
|
let line_b = out_b.next().context("")??;
|
||||||
|
|
||||||
|
let words_a = line_a.split(' ').collect::<Vec<_>>();
|
||||||
|
let words_b = line_b.split(' ').collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// FIXED FIXED PEER-ID FIXED FILENAME STATUS
|
||||||
|
// output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged
|
||||||
|
let peer_a_id = words_b
|
||||||
|
.get(2)
|
||||||
|
.with_context(|| format!("Bad rosenpass output: `{line_b}`"))?;
|
||||||
|
let peer_b_id = words_a
|
||||||
|
.get(2)
|
||||||
|
.with_context(|| format!("Bad rosenpass output: `{line_a}`"))?;
|
||||||
|
assert_eq!(
|
||||||
|
line_a,
|
||||||
|
format!(
|
||||||
|
"output-key peer {peer_b_id} key-file \"{}\" exchanged",
|
||||||
|
peer_a_osk.to_str().context("")?
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
line_b,
|
||||||
|
format!(
|
||||||
|
"output-key peer {peer_a_id} key-file \"{}\" exchanged",
|
||||||
|
peer_b_osk.to_str().context("")?
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read OSKs
|
||||||
|
let osk_a = SymKey::load_b64::<64, _>(peer_a_osk.clone())?;
|
||||||
|
let osk_b = SymKey::load_b64::<64, _>(peer_b_osk.clone())?;
|
||||||
|
match osk_a.secret() == osk_b.secret() {
|
||||||
|
true => break,
|
||||||
|
false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"),
|
||||||
|
false => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
attempt += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now connect to the peers
|
||||||
|
let api_a = UnixStream::connect(&peer_a.api.listen_path[0])?;
|
||||||
|
let api_b = UnixStream::connect(&peer_b.api.listen_path[0])?;
|
||||||
|
|
||||||
|
for conn in ([api_a, api_b]).iter() {
|
||||||
|
let mut echo = [0u8; 256];
|
||||||
|
copy_slice_least_src("Hello World".as_bytes()).to(&mut echo);
|
||||||
|
|
||||||
|
let req = api::PingRequest::new(echo);
|
||||||
|
LengthPrefixEncoder::from_message(req.as_bytes()).write_all_to_stdio(conn)?;
|
||||||
|
|
||||||
|
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||||
|
let res = decoder.read_all_from_stdio(conn)?;
|
||||||
|
let res = res.zk_parse::<api::PingResponse>()?;
|
||||||
|
assert_eq!(*res, api::PingResponse::new(echo));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::fs::File;
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
@@ -5,9 +6,10 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rosenpass::{app_server::AppServerTestBuilder, cli::CliArgs};
|
use rosenpass::{app_server::AppServerTestBuilder, cli::CliArgs, config::EXAMPLE_CONFIG};
|
||||||
use rosenpass_secret_memory::{Public, Secret};
|
use rosenpass_secret_memory::{Public, Secret};
|
||||||
use rosenpass_wireguard_broker::{WireguardBrokerMio, WG_KEY_LEN, WG_PEER_LEN};
|
use rosenpass_wireguard_broker::{WireguardBrokerMio, WG_KEY_LEN, WG_PEER_LEN};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
@@ -15,9 +17,19 @@ use std::io::Write;
|
|||||||
|
|
||||||
const BIN: &str = "rosenpass";
|
const BIN: &str = "rosenpass";
|
||||||
|
|
||||||
|
fn setup_tests() {
|
||||||
|
use rosenpass_secret_memory as SM;
|
||||||
|
#[cfg(feature = "experiment_memfd_secret")]
|
||||||
|
SM::secret_policy_try_use_memfd_secrets();
|
||||||
|
#[cfg(not(feature = "experiment_memfd_secret"))]
|
||||||
|
SM::secret_policy_use_only_malloc_secrets();
|
||||||
|
}
|
||||||
|
|
||||||
// check that we can generate keys
|
// check that we can generate keys
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_keys() {
|
fn generate_keys() {
|
||||||
|
setup_tests();
|
||||||
|
|
||||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen");
|
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen");
|
||||||
fs::create_dir_all(&tmpdir).unwrap();
|
fs::create_dir_all(&tmpdir).unwrap();
|
||||||
|
|
||||||
@@ -94,14 +106,11 @@ fn run_server_client_exchange(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
cli.command
|
let test_helpers = server_test_builder
|
||||||
.run(Some(
|
.termination_handler(Some(server_terminate_rx))
|
||||||
server_test_builder
|
.build()
|
||||||
.termination_handler(Some(server_terminate_rx))
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
cli.run(None, Some(test_helpers)).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
let cli = CliArgs::try_parse_from(
|
let cli = CliArgs::try_parse_from(
|
||||||
@@ -112,14 +121,11 @@ fn run_server_client_exchange(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
cli.command
|
let test_helpers = client_test_builder
|
||||||
.run(Some(
|
.termination_handler(Some(client_terminate_rx))
|
||||||
client_test_builder
|
.build()
|
||||||
.termination_handler(Some(client_terminate_rx))
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
cli.run(None, Some(test_helpers)).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
// give them some time to do the key exchange under load
|
// give them some time to do the key exchange under load
|
||||||
@@ -130,10 +136,51 @@ fn run_server_client_exchange(
|
|||||||
client_terminate.send(()).unwrap();
|
client_terminate.send(()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify that EXAMPLE_CONFIG is correct
|
||||||
|
#[test]
|
||||||
|
fn check_example_config() {
|
||||||
|
setup_tests();
|
||||||
|
setup_logging();
|
||||||
|
|
||||||
|
let tmp_dir = tempdir().unwrap();
|
||||||
|
let config_path = tmp_dir.path().join("config.toml");
|
||||||
|
let mut config_file = File::create(config_path.to_owned()).unwrap();
|
||||||
|
|
||||||
|
config_file
|
||||||
|
.write_all(
|
||||||
|
EXAMPLE_CONFIG
|
||||||
|
.replace("/path/to", tmp_dir.path().to_str().unwrap())
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let output = test_bin::get_test_bin(BIN)
|
||||||
|
.args(["gen-keys"])
|
||||||
|
.arg(&config_path)
|
||||||
|
.output()
|
||||||
|
.expect("EXAMPLE_CONFIG not valid");
|
||||||
|
|
||||||
|
fs::copy(
|
||||||
|
tmp_dir.path().join("rp-public-key"),
|
||||||
|
tmp_dir.path().join("rp-peer-public-key"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let output = test_bin::get_test_bin(BIN)
|
||||||
|
.args(["validate"])
|
||||||
|
.arg(&config_path)
|
||||||
|
.output()
|
||||||
|
.expect("EXAMPLE_CONFIG not valid");
|
||||||
|
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert!(stderr.contains("has passed all logical checks"));
|
||||||
|
}
|
||||||
|
|
||||||
// check that we can exchange keys
|
// check that we can exchange keys
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn check_exchange_under_normal() {
|
fn check_exchange_under_normal() {
|
||||||
|
setup_tests();
|
||||||
setup_logging();
|
setup_logging();
|
||||||
|
|
||||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
|
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
|
||||||
@@ -206,6 +253,7 @@ fn check_exchange_under_normal() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn check_exchange_under_dos() {
|
fn check_exchange_under_dos() {
|
||||||
|
setup_tests();
|
||||||
setup_logging();
|
setup_logging();
|
||||||
|
|
||||||
//Generate binary with responder with feature integration_test
|
//Generate binary with responder with feature integration_test
|
||||||
@@ -283,9 +331,11 @@ struct MockBrokerInner {
|
|||||||
interface: Option<String>,
|
interface: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct MockBroker {
|
struct MockBroker {
|
||||||
inner: Arc<Mutex<MockBrokerInner>>,
|
inner: Arc<Mutex<MockBrokerInner>>,
|
||||||
|
mio_token: Option<mio::Token>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WireguardBrokerMio for MockBroker {
|
impl WireguardBrokerMio for MockBroker {
|
||||||
@@ -294,8 +344,9 @@ impl WireguardBrokerMio for MockBroker {
|
|||||||
fn register(
|
fn register(
|
||||||
&mut self,
|
&mut self,
|
||||||
_registry: &mio::Registry,
|
_registry: &mio::Registry,
|
||||||
_token: mio::Token,
|
token: mio::Token,
|
||||||
) -> Result<(), Self::MioError> {
|
) -> Result<(), Self::MioError> {
|
||||||
|
self.mio_token = Some(token);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,8 +355,13 @@ impl WireguardBrokerMio for MockBroker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn unregister(&mut self, _registry: &mio::Registry) -> Result<(), Self::MioError> {
|
fn unregister(&mut self, _registry: &mio::Registry) -> Result<(), Self::MioError> {
|
||||||
|
self.mio_token = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mio_token(&self) -> Option<mio::Token> {
|
||||||
|
self.mio_token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker {
|
impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker {
|
||||||
@@ -321,7 +377,7 @@ impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker {
|
|||||||
if let Ok(ref mut mutex) = lock {
|
if let Ok(ref mut mutex) = lock {
|
||||||
**mutex = MockBrokerInner {
|
**mutex = MockBrokerInner {
|
||||||
psk: Some(config.psk.clone()),
|
psk: Some(config.psk.clone()),
|
||||||
peer_id: Some(config.peer_id.clone()),
|
peer_id: Some(*config.peer_id),
|
||||||
interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()),
|
interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()),
|
||||||
};
|
};
|
||||||
break Ok(());
|
break Ok(());
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ repository = "https://github.com/rosenpass/rosenpass"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64ct = { workspace = true }
|
base64ct = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
toml = { workspace = true }
|
||||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
@@ -20,9 +22,9 @@ rosenpass-ciphers = { workspace = true }
|
|||||||
rosenpass-cipher-traits = { workspace = true }
|
rosenpass-cipher-traits = { workspace = true }
|
||||||
rosenpass-secret-memory = { workspace = true }
|
rosenpass-secret-memory = { workspace = true }
|
||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
rosenpass-wireguard-broker = {workspace = true}
|
rosenpass-wireguard-broker = { workspace = true }
|
||||||
|
|
||||||
tokio = {workspace = true}
|
tokio = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||||
ctrlc-async = "3.2"
|
ctrlc-async = "3.2"
|
||||||
@@ -35,8 +37,9 @@ netlink-packet-generic = "0.3"
|
|||||||
netlink-packet-wireguard = "0.2"
|
netlink-packet-wireguard = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = {workspace = true}
|
tempfile = { workspace = true }
|
||||||
stacker = {workspace = true}
|
stacker = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
enable_memfd_alloc = []
|
experiment_memfd_secret = []
|
||||||
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ pub enum Command {
|
|||||||
public_keys_dir: PathBuf,
|
public_keys_dir: PathBuf,
|
||||||
},
|
},
|
||||||
Exchange(ExchangeOptions),
|
Exchange(ExchangeOptions),
|
||||||
|
ExchangeConfig {
|
||||||
|
config_file: PathBuf,
|
||||||
|
},
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ enum CommandType {
|
|||||||
GenKey,
|
GenKey,
|
||||||
PubKey,
|
PubKey,
|
||||||
Exchange,
|
Exchange,
|
||||||
|
ExchangeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -32,9 +36,10 @@ fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
|||||||
Some(command) => match command {
|
Some(command) => match command {
|
||||||
CommandType::GenKey => Err(format!("{}\nUsage: rp genkey PRIVATE_KEYS_DIR", note)),
|
CommandType::GenKey => Err(format!("{}\nUsage: rp genkey PRIVATE_KEYS_DIR", note)),
|
||||||
CommandType::PubKey => Err(format!("{}\nUsage: rp pubkey PRIVATE_KEYS_DIR PUBLIC_KEYS_DIR", note)),
|
CommandType::PubKey => Err(format!("{}\nUsage: rp pubkey PRIVATE_KEYS_DIR PUBLIC_KEYS_DIR", note)),
|
||||||
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [ip <ip1>/<cidr1>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
||||||
|
CommandType::ExchangeConfig => Err(format!("{}\nUsage: rp exchange-config <CONFIG_FILE>", note)),
|
||||||
},
|
},
|
||||||
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange [ARGS]...", note)),
|
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange|exchange-config [ARGS]...", note)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +149,13 @@ impl ExchangeOptions {
|
|||||||
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"ip" => {
|
||||||
|
if let Some(ip) = args.next() {
|
||||||
|
options.ip = Some(ip);
|
||||||
|
} else {
|
||||||
|
return fatal("is option requires parameter", Some(CommandType::Exchange));
|
||||||
|
}
|
||||||
|
}
|
||||||
"listen" => {
|
"listen" => {
|
||||||
if let Some(addr) = args.next() {
|
if let Some(addr) = args.next() {
|
||||||
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
||||||
@@ -246,6 +258,21 @@ impl Cli {
|
|||||||
let options = ExchangeOptions::parse(&mut args)?;
|
let options = ExchangeOptions::parse(&mut args)?;
|
||||||
cli.command = Some(Command::Exchange(options));
|
cli.command = Some(Command::Exchange(options));
|
||||||
}
|
}
|
||||||
|
"exchange-config" => {
|
||||||
|
if cli.command.is_some() {
|
||||||
|
return fatal("Too many commands supplied", None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(config_file) = args.next() {
|
||||||
|
let config_file = PathBuf::from(config_file);
|
||||||
|
cli.command = Some(Command::ExchangeConfig { config_file });
|
||||||
|
} else {
|
||||||
|
return fatal(
|
||||||
|
"Required position argument: CONFIG_FILE",
|
||||||
|
Some(CommandType::ExchangeConfig),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
"help" => {
|
"help" => {
|
||||||
cli.command = Some(Command::Help);
|
cli.command = Some(Command::Help);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
use std::{net::SocketAddr, path::PathBuf};
|
use anyhow::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{net::SocketAddr, path::PathBuf, process::Command};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
use crate::key::WG_B64_LEN;
|
use crate::key::WG_B64_LEN;
|
||||||
#[derive(Default)]
|
|
||||||
|
#[derive(Default, Deserialize)]
|
||||||
pub struct ExchangePeer {
|
pub struct ExchangePeer {
|
||||||
pub public_keys_dir: PathBuf,
|
pub public_keys_dir: PathBuf,
|
||||||
pub endpoint: Option<SocketAddr>,
|
pub endpoint: Option<SocketAddr>,
|
||||||
@@ -11,11 +19,12 @@ pub struct ExchangePeer {
|
|||||||
pub allowed_ips: Option<String>,
|
pub allowed_ips: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct ExchangeOptions {
|
pub struct ExchangeOptions {
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
pub private_keys_dir: PathBuf,
|
pub private_keys_dir: PathBuf,
|
||||||
pub dev: Option<String>,
|
pub dev: Option<String>,
|
||||||
|
pub ip: Option<String>,
|
||||||
pub listen: Option<SocketAddr>,
|
pub listen: Option<SocketAddr>,
|
||||||
pub peers: Vec<ExchangePeer>,
|
pub peers: Vec<ExchangePeer>,
|
||||||
}
|
}
|
||||||
@@ -129,6 +138,27 @@ mod netlink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
|
struct CleanupHandlers(
|
||||||
|
Arc<::futures::lock::Mutex<Vec<Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>>>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
|
impl CleanupHandlers {
|
||||||
|
fn new() -> Self {
|
||||||
|
CleanupHandlers(Arc::new(::futures::lock::Mutex::new(vec![])))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn enqueue(&self, handler: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>) {
|
||||||
|
self.0.lock().await.push(Box::pin(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(self) -> Result<Vec<()>, Error> {
|
||||||
|
futures::future::try_join_all(self.0.lock().await.deref_mut()).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -149,15 +179,50 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||||
tokio::spawn(connection);
|
tokio::spawn(connection);
|
||||||
|
|
||||||
let link_name = options.dev.unwrap_or("rosenpass0".to_string());
|
let link_name = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||||
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
||||||
|
|
||||||
|
let cleanup_handlers = CleanupHandlers::new();
|
||||||
|
let final_cleanup_handlers = (&cleanup_handlers).clone();
|
||||||
|
|
||||||
|
cleanup_handlers
|
||||||
|
.enqueue(Box::pin(async move {
|
||||||
|
netlink::link_cleanup_standalone(link_index).await
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
ctrlc_async::set_async_handler(async move {
|
ctrlc_async::set_async_handler(async move {
|
||||||
netlink::link_cleanup_standalone(link_index)
|
final_cleanup_handlers
|
||||||
|
.run()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to clean up");
|
.expect("Failed to clean up");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(ip) = options.ip {
|
||||||
|
let dev = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("address")
|
||||||
|
.arg("add")
|
||||||
|
.arg(ip.clone())
|
||||||
|
.arg("dev")
|
||||||
|
.arg(dev.clone())
|
||||||
|
.status()
|
||||||
|
.expect("failed to configure ip");
|
||||||
|
cleanup_handlers
|
||||||
|
.enqueue(Box::pin(async move {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("address")
|
||||||
|
.arg("del")
|
||||||
|
.arg(ip)
|
||||||
|
.arg("dev")
|
||||||
|
.arg(dev)
|
||||||
|
.status()
|
||||||
|
.expect("failed to remove ip");
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
// Deploy the classic wireguard private key
|
// Deploy the classic wireguard private key
|
||||||
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
||||||
tokio::spawn(connection);
|
tokio::spawn(connection);
|
||||||
@@ -186,8 +251,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
let pk = SPk::load(&pqpk)?;
|
let pk = SPk::load(&pqpk)?;
|
||||||
|
|
||||||
let mut srv = Box::new(AppServer::new(
|
let mut srv = Box::new(AppServer::new(
|
||||||
sk,
|
Some((sk, pk)),
|
||||||
pk,
|
|
||||||
if let Some(listen) = options.listen {
|
if let Some(listen) = options.listen {
|
||||||
vec![listen]
|
vec![listen]
|
||||||
} else {
|
} else {
|
||||||
@@ -253,6 +317,29 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
broker_peer,
|
broker_peer,
|
||||||
peer.endpoint.map(|x| x.to_string()),
|
peer.endpoint.map(|x| x.to_string()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Configure routes
|
||||||
|
if let Some(allowed_ips) = peer.allowed_ips {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("replace")
|
||||||
|
.arg(allowed_ips.clone())
|
||||||
|
.arg("dev")
|
||||||
|
.arg(options.dev.clone().unwrap_or("rosenpass0".to_string()))
|
||||||
|
.status()
|
||||||
|
.expect("failed to configure route");
|
||||||
|
cleanup_handlers
|
||||||
|
.enqueue(Box::pin(async move {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("del")
|
||||||
|
.arg(allowed_ips)
|
||||||
|
.status()
|
||||||
|
.expect("failed to remove ip");
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = srv.event_loop();
|
let out = srv.event_loop();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{self, DirBuilder},
|
fs::{self, DirBuilder},
|
||||||
|
ops::DerefMut,
|
||||||
os::unix::fs::{DirBuilderExt, PermissionsExt},
|
os::unix::fs::{DirBuilderExt, PermissionsExt},
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use rosenpass_util::file::{LoadValueB64, StoreValueB64};
|
use rosenpass_util::file::{LoadValueB64, StoreValue, StoreValueB64};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use rosenpass::protocol::{SPk, SSk};
|
use rosenpass::protocol::{SPk, SSk};
|
||||||
@@ -56,8 +57,8 @@ pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
|||||||
if !pqsk_path.exists() && !pqpk_path.exists() {
|
if !pqsk_path.exists() && !pqpk_path.exists() {
|
||||||
let mut pqsk = SSk::random();
|
let mut pqsk = SSk::random();
|
||||||
let mut pqpk = SPk::random();
|
let mut pqpk = SPk::random();
|
||||||
StaticKem::keygen(pqsk.secret_mut(), pqpk.secret_mut())?;
|
StaticKem::keygen(pqsk.secret_mut(), pqpk.deref_mut())?;
|
||||||
pqpk.store_secret(pqpk_path)?;
|
pqpk.store(pqpk_path)?;
|
||||||
pqsk.store_secret(pqsk_path)?;
|
pqsk.store_secret(pqsk_path)?;
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::process::exit;
|
use std::{fs, process::exit};
|
||||||
|
|
||||||
use cli::{Cli, Command};
|
use cli::{Cli, Command};
|
||||||
use exchange::exchange;
|
use exchange::exchange;
|
||||||
@@ -11,9 +11,9 @@ mod key;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
#[cfg(feature = "enable_memfd_alloc")]
|
#[cfg(feature = "experiment_memfd_secret")]
|
||||||
policy::secret_policy_try_use_memfd_secrets();
|
policy::secret_policy_try_use_memfd_secrets();
|
||||||
#[cfg(not(feature = "enable_memfd_alloc"))]
|
#[cfg(not(feature = "experiment_memfd_secret"))]
|
||||||
policy::secret_policy_use_only_malloc_secrets();
|
policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
let cli = match Cli::parse(std::env::args().peekable()) {
|
let cli = match Cli::parse(std::env::args().peekable()) {
|
||||||
@@ -36,6 +36,13 @@ async fn main() {
|
|||||||
options.verbose = cli.verbose;
|
options.verbose = cli.verbose;
|
||||||
exchange(options).await
|
exchange(options).await
|
||||||
}
|
}
|
||||||
|
Command::ExchangeConfig { config_file } => {
|
||||||
|
let s: String = fs::read_to_string(config_file).expect("cannot read config");
|
||||||
|
let mut options: exchange::ExchangeOptions =
|
||||||
|
toml::from_str::<exchange::ExchangeOptions>(&s).expect("cannot parse config");
|
||||||
|
options.verbose = options.verbose || cli.verbose;
|
||||||
|
exchange(options).await
|
||||||
|
}
|
||||||
Command::Help => {
|
Command::Help => {
|
||||||
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ log = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
allocator-api2-tests = { workspace = true }
|
allocator-api2-tests = { workspace = true }
|
||||||
tempfile = {workspace = true}
|
tempfile = { workspace = true }
|
||||||
base64ct = {workspace = true}
|
base64ct = { workspace = true }
|
||||||
procspawn = {workspace = true}
|
procspawn = { workspace = true }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub mod alloc;
|
|||||||
|
|
||||||
mod public;
|
mod public;
|
||||||
pub use crate::public::Public;
|
pub use crate::public::Public;
|
||||||
|
pub use crate::public::PublicBox;
|
||||||
|
|
||||||
mod secret;
|
mod secret;
|
||||||
pub use crate::secret::Secret;
|
pub use crate::secret::Secret;
|
||||||
|
|||||||
@@ -172,12 +172,154 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct PublicBox<const N: usize> {
|
||||||
|
pub inner: Box<Public<N>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> PublicBox<N> {
|
||||||
|
/// Create a new [PublicBox] from a byte slice
|
||||||
|
pub fn from_slice(value: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::new(Public::from_slice(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [PublicBox] from a byte array
|
||||||
|
pub fn new(value: [u8; N]) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::new(Public::new(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a zero initialized [PublicBox]
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::new(Public::zero()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a random initialized [PublicBox]
|
||||||
|
pub fn random() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::new(Public::random()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Randomize all bytes in an existing [PublicBox]
|
||||||
|
pub fn randomize(&mut self) {
|
||||||
|
self.inner.randomize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Randomize for PublicBox<N> {
|
||||||
|
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||||
|
self.inner.try_fill(rng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> fmt::Debug for PublicBox<N> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
debug_crypto_array(&**self, fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Deref for PublicBox<N> {
|
||||||
|
type Target = [u8; N];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[u8; N] {
|
||||||
|
self.inner.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> DerefMut for PublicBox<N> {
|
||||||
|
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||||
|
self.inner.deref_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
|
||||||
|
fn borrow(&self) -> &[u8] {
|
||||||
|
self.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
|
||||||
|
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||||
|
self.deref_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LoadValue for PublicBox<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||||
|
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
|
let mut p = Self::random();
|
||||||
|
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValue for PublicBox<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
|
self.inner.store(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||||
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// A vector is used here to ensure heap allocation without copy from stack
|
||||||
|
let mut f = vec![0u8; F];
|
||||||
|
let mut v = PublicBox::zero();
|
||||||
|
let p = path.as_ref();
|
||||||
|
|
||||||
|
let len = fopen_r(p)?
|
||||||
|
.read_slice_to_end(&mut f)
|
||||||
|
.with_context(|| format!("Could not load file {p:?}"))?;
|
||||||
|
|
||||||
|
b64_decode(&f[0..len], v.deref_mut())
|
||||||
|
.with_context(|| format!("Could not decode base64 file {p:?}"))?;
|
||||||
|
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValueB64 for PublicBox<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
|
self.inner.store_b64::<F, P>(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
writer: W,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.inner.store_b64_writer::<F, W>(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::Public;
|
use crate::{Public, PublicBox};
|
||||||
use rosenpass_util::{
|
use rosenpass_util::{
|
||||||
b64::b64_encode,
|
b64::b64_encode,
|
||||||
file::{
|
file::{
|
||||||
@@ -185,32 +327,35 @@ mod tests {
|
|||||||
Visibility,
|
Visibility,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{fs, os::unix::fs::PermissionsExt};
|
use std::{fs, ops::Deref, os::unix::fs::PermissionsExt};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
/// test loading a public from an example file, and then storing it again in a different file
|
/// Number of bytes in payload for load and store tests
|
||||||
#[test]
|
const N: usize = 100;
|
||||||
fn test_public_load_store() {
|
|
||||||
const N: usize = 100;
|
|
||||||
|
|
||||||
|
/// Convenience function for running a load/store test
|
||||||
|
fn run_load_store_test<
|
||||||
|
T: LoadValue<Error = anyhow::Error>
|
||||||
|
+ StoreValue<Error = anyhow::Error>
|
||||||
|
+ Deref<Target = [u8; N]>,
|
||||||
|
>() {
|
||||||
// Generate original random bytes
|
// Generate original random bytes
|
||||||
let original_bytes: [u8; N] = [rand::random(); N];
|
let original_bytes: [u8; N] = [rand::random(); N];
|
||||||
|
|
||||||
// Create a temporary directory
|
// Create a temporary directory
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
|
|
||||||
// Store the original public to an example file in the temporary directory
|
// Store the original bytes to an example file in the temporary directory
|
||||||
let example_file = temp_dir.path().join("example_file");
|
let example_file = temp_dir.path().join("example_file");
|
||||||
std::fs::write(example_file.clone(), &original_bytes).unwrap();
|
std::fs::write(&example_file, original_bytes).unwrap();
|
||||||
|
|
||||||
// Load the public from the example file
|
// Load the value from the example file into our generic type
|
||||||
|
let loaded_public = T::load(&example_file).unwrap();
|
||||||
|
|
||||||
let loaded_public = Public::load(&example_file).unwrap();
|
// Check that the loaded value matches the original bytes
|
||||||
|
assert_eq!(loaded_public.deref(), &original_bytes);
|
||||||
|
|
||||||
// Check that the loaded public matches the original bytes
|
// Store the loaded value to a different file in the temporary directory
|
||||||
assert_eq!(&loaded_public.value, &original_bytes);
|
|
||||||
|
|
||||||
// Store the loaded public to a different file in the temporary directory
|
|
||||||
let new_file = temp_dir.path().join("new_file");
|
let new_file = temp_dir.path().join("new_file");
|
||||||
loaded_public.store(&new_file).unwrap();
|
loaded_public.store(&new_file).unwrap();
|
||||||
|
|
||||||
@@ -224,10 +369,13 @@ mod tests {
|
|||||||
assert_eq!(new_file_contents, original_file_contents);
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// test loading a base64 encoded public from an example file, and then storing it again in a different file
|
/// Convenience function for running a base64 load/store test
|
||||||
#[test]
|
fn run_base64_load_store_test<
|
||||||
fn test_public_load_store_base64() {
|
T: LoadValueB64<Error = anyhow::Error>
|
||||||
const N: usize = 100;
|
+ StoreValueB64<Error = anyhow::Error>
|
||||||
|
+ StoreValueB64Writer<Error = anyhow::Error>
|
||||||
|
+ Deref<Target = [u8; N]>,
|
||||||
|
>() {
|
||||||
// Generate original random bytes
|
// Generate original random bytes
|
||||||
let original_bytes: [u8; N] = [rand::random(); N];
|
let original_bytes: [u8; N] = [rand::random(); N];
|
||||||
// Create a temporary directory
|
// Create a temporary directory
|
||||||
@@ -238,9 +386,9 @@ mod tests {
|
|||||||
std::fs::write(&example_file, encoded_public).unwrap();
|
std::fs::write(&example_file, encoded_public).unwrap();
|
||||||
|
|
||||||
// Load the public from the example file
|
// Load the public from the example file
|
||||||
let loaded_public = Public::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
|
let loaded_public = T::load_b64::<{ N * 2 }, _>(&example_file).unwrap();
|
||||||
// Check that the loaded public matches the original bytes
|
// Check that the loaded public matches the original bytes
|
||||||
assert_eq!(&loaded_public.value, &original_bytes);
|
assert_eq!(loaded_public.deref(), &original_bytes);
|
||||||
|
|
||||||
// Store the loaded public to a different file in the temporary directory
|
// Store the loaded public to a different file in the temporary directory
|
||||||
let new_file = temp_dir.path().join("new_file");
|
let new_file = temp_dir.path().join("new_file");
|
||||||
@@ -253,7 +401,7 @@ mod tests {
|
|||||||
// Check that the contents of the new file match the original file
|
// Check that the contents of the new file match the original file
|
||||||
assert_eq!(new_file_contents, original_file_contents);
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
|
||||||
//Check new file permissions are public
|
// Check new file permissions are public
|
||||||
let metadata = fs::metadata(&new_file).unwrap();
|
let metadata = fs::metadata(&new_file).unwrap();
|
||||||
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
|
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
|
||||||
|
|
||||||
@@ -271,9 +419,35 @@ mod tests {
|
|||||||
// Check that the contents of the new file match the original file
|
// Check that the contents of the new file match the original file
|
||||||
assert_eq!(new_file_contents, original_file_contents);
|
assert_eq!(new_file_contents, original_file_contents);
|
||||||
|
|
||||||
//Check new file permissions are public
|
// Check new file permissions are public
|
||||||
let metadata = fs::metadata(&new_file).unwrap();
|
let metadata = fs::metadata(&new_file).unwrap();
|
||||||
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
|
assert_eq!(metadata.permissions().mode() & 0o000777, 0o644);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test loading a [Public] from an example file, and then storing it again in a new file
|
||||||
|
#[test]
|
||||||
|
fn test_public_load_store() {
|
||||||
|
run_load_store_test::<Public<N>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test loading a [PublicBox] from an example file, and then storing it again in a new file
|
||||||
|
#[test]
|
||||||
|
fn test_public_box_load_store() {
|
||||||
|
run_load_store_test::<PublicBox<N>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test loading a base64-encoded [Public] from an example file, and then storing it again
|
||||||
|
/// in a different file
|
||||||
|
#[test]
|
||||||
|
fn test_public_load_store_base64() {
|
||||||
|
run_base64_load_store_test::<Public<N>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test loading a base64-encoded [PublicBox] from an example file, and then storing it
|
||||||
|
/// again in a different file
|
||||||
|
#[test]
|
||||||
|
fn test_public_box_load_store_base64() {
|
||||||
|
run_base64_load_store_test::<PublicBox<N>>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -387,7 +386,7 @@ mod test {
|
|||||||
|
|
||||||
// Store the original secret to an example file in the temporary directory
|
// Store the original secret to an example file in the temporary directory
|
||||||
let example_file = temp_dir.path().join("example_file");
|
let example_file = temp_dir.path().join("example_file");
|
||||||
std::fs::write(example_file.clone(), &original_bytes).unwrap();
|
std::fs::write(&example_file, original_bytes).unwrap();
|
||||||
|
|
||||||
// Load the secret from the example file
|
// Load the secret from the example file
|
||||||
let loaded_secret = Secret::load(&example_file).unwrap();
|
let loaded_secret = Secret::load(&example_file).unwrap();
|
||||||
|
|||||||
2
systemd/rosenpass.target
Normal file
2
systemd/rosenpass.target
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rosenpass target
|
||||||
47
systemd/rosenpass@.service
Normal file
47
systemd/rosenpass@.service
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rosenpass key exchange for %I
|
||||||
|
Documentation=man:rosenpass(1)
|
||||||
|
Documentation=https://rosenpass.eu/docs
|
||||||
|
|
||||||
|
After=network-online.target nss-lookup.target sys-devices-virtual-net-%i.device
|
||||||
|
Wants=network-online.target nss-lookup.target
|
||||||
|
BindsTo=sys-devices-virtual-net-%i.device
|
||||||
|
PartOf=rosenpass.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=rosenpass exchange-config /etc/rosenpass/%i.toml
|
||||||
|
LoadCredential=pqsk:/etc/rosenpass/%i/pqsk
|
||||||
|
|
||||||
|
AmbientCapabilities=CAP_NET_ADMIN
|
||||||
|
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
|
||||||
|
DynamicUser=true
|
||||||
|
LockPersonality=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
|
RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
48
systemd/rp@.service
Normal file
48
systemd/rp@.service
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rosenpass key exchange for %I
|
||||||
|
Documentation=man:rosenpass(1)
|
||||||
|
Documentation=https://rosenpass.eu/docs
|
||||||
|
|
||||||
|
After=network-online.target nss-lookup.target
|
||||||
|
Wants=network-online.target nss-lookup.target
|
||||||
|
PartOf=rosenpass.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=rp exchange-config /etc/rosenpass/%i.toml
|
||||||
|
LoadCredential=pqpk:/etc/rosenpass/%i/pqpk
|
||||||
|
LoadCredential=pqsk:/etc/rosenpass/%i/pqsk
|
||||||
|
LoadCredential=wgsk:/etc/rosenpass/%i/wgsk
|
||||||
|
|
||||||
|
AmbientCapabilities=CAP_NET_ADMIN
|
||||||
|
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
|
||||||
|
DynamicUser=true
|
||||||
|
LockPersonality=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
|
RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
183
tests/systemd/rosenpass.nix
Normal file
183
tests/systemd/rosenpass.nix
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# This test is largely inspired from:
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/rosenpass.nix
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/wireguard/basic.nix
|
||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
server = {
|
||||||
|
ip4 = "192.168.0.1";
|
||||||
|
ip6 = "fd00::1";
|
||||||
|
wg = {
|
||||||
|
ip4 = "10.23.42.1";
|
||||||
|
ip6 = "fc00::1";
|
||||||
|
public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
|
||||||
|
secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
|
||||||
|
listen = 10000;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client = {
|
||||||
|
ip4 = "192.168.0.2";
|
||||||
|
ip6 = "fd00::2";
|
||||||
|
wg = {
|
||||||
|
ip4 = "10.23.42.2";
|
||||||
|
ip6 = "fc00::2";
|
||||||
|
public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
|
||||||
|
secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
server_config = {
|
||||||
|
listen = [ "0.0.0.0:9999" ];
|
||||||
|
public_key = "/etc/rosenpass/rp0/pqpk";
|
||||||
|
secret_key = "/run/credentials/rosenpass@rp0.service/pqsk";
|
||||||
|
verbosity = "Verbose";
|
||||||
|
peers = [{
|
||||||
|
device = "rp0";
|
||||||
|
peer = client.wg.public;
|
||||||
|
public_key = "/etc/rosenpass/rp0/peers/client/pqpk";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
client_config = {
|
||||||
|
listen = [ "0.0.0.0:9999" ]; # TODO: Should not be necessary to set, but wouldn't parse.
|
||||||
|
public_key = "/etc/rosenpass/rp0/pqpk";
|
||||||
|
secret_key = "/run/credentials/rosenpass@rp0.service/pqsk";
|
||||||
|
verbosity = "Verbose";
|
||||||
|
peers = [{
|
||||||
|
device = "rp0";
|
||||||
|
peer = server.wg.public;
|
||||||
|
public_key = "/etc/rosenpass/rp0/peers/server/pqpk";
|
||||||
|
endpoint = "${server.ip4}:9999";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
config = pkgs.runCommand "config" { } ''
|
||||||
|
mkdir -pv $out
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "rp0.toml" server_config} $out/server
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "rp0.toml" client_config} $out/client
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "rosenpass unit";
|
||||||
|
|
||||||
|
nodes =
|
||||||
|
let
|
||||||
|
shared = peer: { config, modulesPath, pkgs, ... }: {
|
||||||
|
# Need to work around a problem in recent systemd changes.
|
||||||
|
# It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific
|
||||||
|
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767
|
||||||
|
# This can potentially be removed in future nixpkgs updates
|
||||||
|
systemd.packages = [
|
||||||
|
(pkgs.runCommand "rosenpass" { } ''
|
||||||
|
mkdir -p $out/lib/systemd/system
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass@.service \
|
||||||
|
sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.wireguard-tools}/bin@' |
|
||||||
|
sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' |
|
||||||
|
sed 's@^ExecStart=rosenpass @ExecStart='"${pkgs.rosenpass}"'/bin/rosenpass @' > $out/lib/systemd/system/rosenpass@.service
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
networking.wireguard = {
|
||||||
|
enable = true;
|
||||||
|
interfaces.rp0 = {
|
||||||
|
ips = [ "${peer.wg.ip4}/32" "${peer.wg.ip6}/128" ];
|
||||||
|
privateKeyFile = "/etc/wireguard/wgsk";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
environment.etc."wireguard/wgsk".text = peer.wg.secret;
|
||||||
|
networking.interfaces.eth1 = {
|
||||||
|
ipv4.addresses = [{
|
||||||
|
address = peer.ip4;
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
ipv6.addresses = [{
|
||||||
|
address = peer.ip6;
|
||||||
|
prefixLength = 64;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
server = {
|
||||||
|
imports = [ (shared server) ];
|
||||||
|
networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ];
|
||||||
|
networking.wireguard.interfaces.rp0 = {
|
||||||
|
listenPort = server.wg.listen;
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
allowedIPs = [ client.wg.ip4 client.wg.ip6 ];
|
||||||
|
publicKey = client.wg.public;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
client = {
|
||||||
|
imports = [ (shared client) ];
|
||||||
|
networking.wireguard.interfaces.rp0 = {
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
allowedIPs = [ "10.23.42.0/24" "fc00::/64" ];
|
||||||
|
publicKey = server.wg.public;
|
||||||
|
endpoint = "${server.ip4}:${toString server.wg.listen}";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = { ... }: ''
|
||||||
|
from os import system
|
||||||
|
rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
|
with subtest("Key, Config, and Service Setup"):
|
||||||
|
for name, machine, remote in [("server", server, client), ("client", client, server)]:
|
||||||
|
# generate all the keys
|
||||||
|
system(f"{rosenpass} gen-keys --public-key {name}-pqpk --secret-key {name}-pqsk")
|
||||||
|
|
||||||
|
# copy private keys to our side
|
||||||
|
machine.copy_from_host(f"{name}-pqsk", "/etc/rosenpass/rp0/pqsk")
|
||||||
|
machine.copy_from_host(f"{name}-pqpk", "/etc/rosenpass/rp0/pqpk")
|
||||||
|
|
||||||
|
# copy public keys to other side
|
||||||
|
remote.copy_from_host(f"{name}-pqpk", f"/etc/rosenpass/rp0/peers/{name}/pqpk")
|
||||||
|
|
||||||
|
machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/rp0.toml")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("wireguard-rp0.service")
|
||||||
|
|
||||||
|
with subtest("wg network test"):
|
||||||
|
client.succeed("wg show all preshared-keys | grep none", timeout=5);
|
||||||
|
client.succeed("ping -c5 ${server.wg.ip4}")
|
||||||
|
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||||
|
|
||||||
|
with subtest("Set up rosenpass"):
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.succeed("systemctl start rosenpass@rp0.service")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("rosenpass@rp0.service")
|
||||||
|
|
||||||
|
|
||||||
|
with subtest("compare preshared keys"):
|
||||||
|
client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
|
||||||
|
def get_psk(m):
|
||||||
|
psk = m.succeed("wg show rp0 preshared-keys | awk '{print $2}'")
|
||||||
|
psk = psk.strip()
|
||||||
|
assert len(psk.split()) == 1, "Only one PSK"
|
||||||
|
return psk
|
||||||
|
|
||||||
|
assert get_psk(client) == get_psk(server), "preshared keys need to match"
|
||||||
|
|
||||||
|
with subtest("rosenpass network test"):
|
||||||
|
client.succeed("ping -c5 ${server.wg.ip4}")
|
||||||
|
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||||
|
'';
|
||||||
|
}
|
||||||
139
tests/systemd/rp.nix
Normal file
139
tests/systemd/rp.nix
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
server = {
|
||||||
|
ip4 = "192.168.0.1";
|
||||||
|
ip6 = "fd00::1";
|
||||||
|
wg = {
|
||||||
|
ip6 = "fc00::1";
|
||||||
|
listen = 10000;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client = {
|
||||||
|
ip4 = "192.168.0.2";
|
||||||
|
ip6 = "fd00::2";
|
||||||
|
wg = {
|
||||||
|
ip6 = "fc00::2";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
server_config = {
|
||||||
|
listen = "${server.ip4}:9999";
|
||||||
|
private_keys_dir = "/run/credentials/rp@test-rp-device0.service";
|
||||||
|
verbose = true;
|
||||||
|
dev = "test-rp-device0";
|
||||||
|
ip = "fc00::1/64";
|
||||||
|
peers = [{
|
||||||
|
public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/client";
|
||||||
|
allowed_ips = "fc00::2";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
client_config = {
|
||||||
|
private_keys_dir = "/run/credentials/rp@test-rp-device0.service";
|
||||||
|
verbose = true;
|
||||||
|
dev = "test-rp-device0";
|
||||||
|
ip = "fc00::2/128";
|
||||||
|
peers = [{
|
||||||
|
public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/server";
|
||||||
|
endpoint = "${server.ip4}:9999";
|
||||||
|
allowed_ips = "fc00::/64";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
config = pkgs.runCommand "config" { } ''
|
||||||
|
mkdir -pv $out
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" server_config} $out/server
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" client_config} $out/client
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "rp systemd unit";
|
||||||
|
|
||||||
|
nodes =
|
||||||
|
let
|
||||||
|
shared = peer: { config, modulesPath, pkgs, ... }: {
|
||||||
|
# Need to work around a problem in recent systemd changes.
|
||||||
|
# It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific
|
||||||
|
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767
|
||||||
|
# This can potentially be removed in future nixpkgs updates
|
||||||
|
systemd.packages = [
|
||||||
|
(pkgs.runCommand "rp@.service" { } ''
|
||||||
|
mkdir -p $out/lib/systemd/system
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rp@.service \
|
||||||
|
sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.iproute2}/bin:${pkgs.wireguard-tools}/bin@' |
|
||||||
|
sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' |
|
||||||
|
sed 's@^ExecStart=rp @ExecStart='"${pkgs.rosenpass}"'/bin/rp @' > $out/lib/systemd/system/rp@.service
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
environment.systemPackages = [ pkgs.wireguard-tools ];
|
||||||
|
networking.interfaces.eth1 = {
|
||||||
|
ipv4.addresses = [{
|
||||||
|
address = peer.ip4;
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
ipv6.addresses = [{
|
||||||
|
address = peer.ip6;
|
||||||
|
prefixLength = 64;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
server = {
|
||||||
|
imports = [ (shared server) ];
|
||||||
|
networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ];
|
||||||
|
};
|
||||||
|
client = {
|
||||||
|
imports = [ (shared client) ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = { ... }: ''
|
||||||
|
from os import system
|
||||||
|
rp = "${pkgs.rosenpass}/bin/rp"
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
|
with subtest("Key, Config, and Service Setup"):
|
||||||
|
for name, machine, remote in [("server", server, client), ("client", client, server)]:
|
||||||
|
# create all the keys
|
||||||
|
system(f"{rp} genkey {name}-sk")
|
||||||
|
system(f"{rp} pubkey {name}-sk {name}-pk")
|
||||||
|
|
||||||
|
# copy secret keys to our side
|
||||||
|
for file in ["pqpk", "pqsk", "wgsk"]:
|
||||||
|
machine.copy_from_host(f"{name}-sk/{file}", f"/etc/rosenpass/test-rp-device0/{file}")
|
||||||
|
# copy public keys to other side
|
||||||
|
for file in ["pqpk", "wgpk"]:
|
||||||
|
remote.copy_from_host(f"{name}-pk/{file}", f"/etc/rosenpass/test-rp-device0/peers/{name}/{file}")
|
||||||
|
|
||||||
|
machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/test-rp-device0.toml")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.succeed("systemctl start rp@test-rp-device0.service")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("rp@test-rp-device0.service")
|
||||||
|
|
||||||
|
with subtest("compare preshared keys"):
|
||||||
|
client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
|
||||||
|
def get_psk(m):
|
||||||
|
psk = m.succeed("wg show test-rp-device0 preshared-keys | awk '{print $2}'")
|
||||||
|
psk = psk.strip()
|
||||||
|
assert len(psk.split()) == 1, "Only one PSK"
|
||||||
|
return psk
|
||||||
|
|
||||||
|
assert get_psk(client) == get_psk(server), "preshared keys need to match"
|
||||||
|
|
||||||
|
with subtest("network test"):
|
||||||
|
client.succeed("ping -c5 ${server.wg.ip6}")
|
||||||
|
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
#![recursion_limit = "256"]
|
||||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||||
|
|
||||||
#[cfg(doctest)]
|
#[cfg(doctest)]
|
||||||
|
|||||||
@@ -5,23 +5,70 @@ use crate::CondenseBeside;
|
|||||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||||
|
|
||||||
impl<Val, Ret> Beside<Val, Ret> {
|
impl<Val, Ret> Beside<Val, Ret> {
|
||||||
|
/// Get an immutable reference to the destination value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let beside = Beside(1, 2);
|
||||||
|
/// assert_eq!(beside.dest(), &1);
|
||||||
|
/// ```
|
||||||
pub fn dest(&self) -> &Val {
|
pub fn dest(&self) -> &Val {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an immutable reference to the return value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let beside = Beside(1, 2);
|
||||||
|
/// assert_eq!(beside.ret(), &2);
|
||||||
|
/// ```
|
||||||
pub fn ret(&self) -> &Ret {
|
pub fn ret(&self) -> &Ret {
|
||||||
&self.1
|
&self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the destination value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let mut beside = Beside(1, 2);
|
||||||
|
/// *beside.dest_mut() = 3;
|
||||||
|
/// assert_eq!(beside.dest(), &3);
|
||||||
|
/// ```
|
||||||
pub fn dest_mut(&mut self) -> &mut Val {
|
pub fn dest_mut(&mut self) -> &mut Val {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the return value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let mut beside = Beside(1, 2);
|
||||||
|
/// *beside.ret_mut() = 3;
|
||||||
|
/// assert_eq!(beside.ret(), &3);
|
||||||
|
/// ```
|
||||||
pub fn ret_mut(&mut self) -> &mut Ret {
|
pub fn ret_mut(&mut self) -> &mut Ret {
|
||||||
&mut self.1
|
&mut self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform beside condensation. See [CondenseBeside]
|
/// Perform beside condensation. See [CondenseBeside]
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
/// use rosenpass_to::CondenseBeside;
|
||||||
|
///
|
||||||
|
/// let beside = Beside(1, ());
|
||||||
|
/// assert_eq!(beside.condense(), 1);
|
||||||
|
/// ```
|
||||||
pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||||
where
|
where
|
||||||
Ret: CondenseBeside<Val>,
|
Ret: CondenseBeside<Val>,
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||||
/// condense trait.
|
/// condense trait.
|
||||||
pub trait CondenseBeside<Val> {
|
pub trait CondenseBeside<Val> {
|
||||||
|
/// The type that results from condensation.
|
||||||
type Condensed;
|
type Condensed;
|
||||||
|
|
||||||
|
/// Takes ownership of `self` and condenses it with the given value.
|
||||||
fn condense(self, ret: Val) -> Self::Condensed;
|
fn condense(self, ret: Val) -> Self::Condensed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/// Helper performing explicit unsized coercion.
|
/// Helper performing explicit unsized coercion.
|
||||||
/// Used by the [to](crate::to()) function.
|
/// Used by the [to](crate::to()) function.
|
||||||
pub trait DstCoercion<Dst: ?Sized> {
|
pub trait DstCoercion<Dst: ?Sized> {
|
||||||
|
/// Performs an explicit coercion to the destination type.
|
||||||
fn coerce_dest(&mut self) -> &mut Dst;
|
fn coerce_dest(&mut self) -> &mut Dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
||||||
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to
|
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to
|
||||||
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) – Some value that
|
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) – Some value that
|
||||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
|
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
|
||||||
//! `Dst` (e.g. `[u8; 64]`).
|
//! `Dst` (e.g. `[u8; 64]`).
|
||||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
|
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
|
||||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
||||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
use crate::{Beside, CondenseBeside};
|
use crate::{Beside, CondenseBeside};
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
|
|
||||||
// The To trait is the core of the to crate; most functions with destinations will either return
|
/// The To trait is the core of the to crate; most functions with destinations will either return
|
||||||
// an object that is an instance of this trait or they will return `-> impl To<Destination,
|
/// an object that is an instance of this trait or they will return `-> impl To<Destination,
|
||||||
// Return_value`.
|
/// Return_value`.
|
||||||
//
|
///
|
||||||
// A quick way to implement a function with destination is to use the
|
/// A quick way to implement a function with destination is to use the
|
||||||
// [with_destination(|param: &mut Type| ...)] higher order function.
|
/// [with_destination(|param: &mut Type| ...)] higher order function.
|
||||||
pub trait To<Dst: ?Sized, Ret>: Sized {
|
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||||
|
/// Writes self to the destination `out` and returns a value of type `Ret`.
|
||||||
|
///
|
||||||
|
/// This is the core method that must be implemented by all types implementing `To`.
|
||||||
fn to(self, out: &mut Dst) -> Ret;
|
fn to(self, out: &mut Dst) -> Ret;
|
||||||
|
|
||||||
/// Generate a destination on the fly with a lambda.
|
/// Generate a destination on the fly with a lambda.
|
||||||
|
|||||||
@@ -1,20 +1,38 @@
|
|||||||
use crate::To;
|
use crate::To;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// A struct that wraps a closure and implements the `To` trait
|
||||||
|
///
|
||||||
|
/// This allows passing closures that operate on a destination type `Dst`
|
||||||
|
/// and return `Ret`.
|
||||||
|
///
|
||||||
|
/// # Type Parameters
|
||||||
|
/// * `Dst` - The destination type the closure operates on
|
||||||
|
/// * `Ret` - The return type of the closure
|
||||||
|
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
||||||
struct ToClosure<Dst, Ret, Fun>
|
struct ToClosure<Dst, Ret, Fun>
|
||||||
where
|
where
|
||||||
Dst: ?Sized,
|
Dst: ?Sized,
|
||||||
Fun: FnOnce(&mut Dst) -> Ret,
|
Fun: FnOnce(&mut Dst) -> Ret,
|
||||||
{
|
{
|
||||||
|
/// The function to call.
|
||||||
fun: Fun,
|
fun: Fun,
|
||||||
|
/// Phantom data to hold the destination type
|
||||||
_val: PhantomData<Box<Dst>>,
|
_val: PhantomData<Box<Dst>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implementation of the `To` trait for ToClosure
|
||||||
|
///
|
||||||
|
/// This enables calling the wrapped closure with a destination reference.
|
||||||
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
||||||
where
|
where
|
||||||
Dst: ?Sized,
|
Dst: ?Sized,
|
||||||
Fun: FnOnce(&mut Dst) -> Ret,
|
Fun: FnOnce(&mut Dst) -> Ret,
|
||||||
{
|
{
|
||||||
|
/// Execute the wrapped closure with the given destination
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `out` - Mutable reference to the destination
|
||||||
fn to(self, out: &mut Dst) -> Ret {
|
fn to(self, out: &mut Dst) -> Ret {
|
||||||
(self.fun)(out)
|
(self.fun)(out)
|
||||||
}
|
}
|
||||||
@@ -22,6 +40,14 @@ where
|
|||||||
|
|
||||||
/// Used to create a function with destination.
|
/// Used to create a function with destination.
|
||||||
///
|
///
|
||||||
|
/// Creates a wrapper that implements the `To` trait for a closure that
|
||||||
|
/// operates on a destination type.
|
||||||
|
///
|
||||||
|
/// # Type Parameters
|
||||||
|
/// * `Dst` - The destination type the closure operates on
|
||||||
|
/// * `Ret` - The return type of the closure
|
||||||
|
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
||||||
|
///
|
||||||
/// See the tutorial in [readme.me]..
|
/// See the tutorial in [readme.me]..
|
||||||
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -16,5 +16,14 @@ base64ct = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
typenum = { workspace = true }
|
typenum = { workspace = true }
|
||||||
static_assertions = { workspace = true }
|
static_assertions = { workspace = true }
|
||||||
rustix = {workspace = true}
|
rustix = { workspace = true }
|
||||||
zeroize = {workspace = true}
|
zeroize = { workspace = true }
|
||||||
|
zerocopy = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
mio = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
experiment_file_descriptor_passing = ["uds"]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user