mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-09 06:10:30 -08:00
Compare commits
337 Commits
dev/karo/a
...
dev/blipp/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf67344e86 | ||
|
|
d2539e445f | ||
|
|
6dc58cc6c1 | ||
|
|
e3d16966c9 | ||
|
|
a5e6af4b49 | ||
|
|
24a71977f0 | ||
|
|
5f0ac579d7 | ||
|
|
4df994b5f0 | ||
|
|
e4e0a9e661 | ||
|
|
742e037936 | ||
|
|
b5848af799 | ||
|
|
4982e40084 | ||
|
|
c1ae3268c6 | ||
|
|
524ec68f3f | ||
|
|
184603aa2c | ||
|
|
ec6706ffeb | ||
|
|
7571670e71 | ||
|
|
0d7dd99d96 | ||
|
|
c78a9cb777 | ||
|
|
dd0db53e8b | ||
|
|
422acf9891 | ||
|
|
877c15a018 | ||
|
|
55d7f8b1c1 | ||
|
|
199ff63a06 | ||
|
|
47b556e317 | ||
|
|
f87e2cb31b | ||
|
|
58e1c8fbff | ||
|
|
c89c7d7acf | ||
|
|
a5b876f119 | ||
|
|
c2f50f47b3 | ||
|
|
53168dc62d | ||
|
|
2cfe703118 | ||
|
|
a2d7c3aaa6 | ||
|
|
1aa111570e | ||
|
|
a91d61f9f0 | ||
|
|
ff7827c24e | ||
|
|
255e377d29 | ||
|
|
50505d81cc | ||
|
|
10484cc6d4 | ||
|
|
d27e602f43 | ||
|
|
73f6b33dbb | ||
|
|
a279dfc0b1 | ||
|
|
caf2f6bfec | ||
|
|
d398ad369e | ||
|
|
00696321ff | ||
|
|
d807a1bca7 | ||
|
|
4daf97b2ee | ||
|
|
b394e302ab | ||
|
|
198bc2d5f2 | ||
|
|
fc2f535eae | ||
|
|
302e249f08 | ||
|
|
d8fe3eba5f | ||
|
|
35519e7baa | ||
|
|
78af5d1dc4 | ||
|
|
61b8b28e86 | ||
|
|
26f77924f8 | ||
|
|
2e0e2cfa0c | ||
|
|
9cc860fdeb | ||
|
|
a537eb3e1b | ||
|
|
ea233bf137 | ||
|
|
db8796ab40 | ||
|
|
0353c82729 | ||
|
|
ae3fbde0a3 | ||
|
|
51d4dede15 | ||
|
|
4725a2d628 | ||
|
|
a6bac74d48 | ||
|
|
b9a34f4238 | ||
|
|
46e855b266 | ||
|
|
c0b91fd729 | ||
|
|
97dff8453d | ||
|
|
a3d4686104 | ||
|
|
cee0678817 | ||
|
|
a996f194c7 | ||
|
|
447be89414 | ||
|
|
ef4f550abc | ||
|
|
4737cd2b2a | ||
|
|
9336794e4d | ||
|
|
096bac6ee5 | ||
|
|
161826979a | ||
|
|
c435b772d2 | ||
|
|
8805ef7c38 | ||
|
|
cca02dc8d1 | ||
|
|
d4350195eb | ||
|
|
1c5e4ecf95 | ||
|
|
b15947b815 | ||
|
|
cacbf8535c | ||
|
|
f6d9da4a18 | ||
|
|
68f73e264d | ||
|
|
d5f68dcbd2 | ||
|
|
96581ed118 | ||
|
|
553b058759 | ||
|
|
85286c146f | ||
|
|
0f58b36c5b | ||
|
|
737781c8bc | ||
|
|
4ea1c76b81 | ||
|
|
5251721bcf | ||
|
|
a789f801ab | ||
|
|
be06f8adec | ||
|
|
03d3c70e2e | ||
|
|
94ba99d89b | ||
|
|
667a994253 | ||
|
|
9561ea4a47 | ||
|
|
fb641f8568 | ||
|
|
6e16956bc7 | ||
|
|
eeb738b649 | ||
|
|
2d20ad6335 | ||
|
|
df3d1821c8 | ||
|
|
6048ebd3d9 | ||
|
|
cd7558594f | ||
|
|
022cdc4ffa | ||
|
|
06d4e289a5 | ||
|
|
f9dce3fc9a | ||
|
|
d9f3c8fb96 | ||
|
|
0ea9f1061e | ||
|
|
737f0bc9f9 | ||
|
|
32ebd18107 | ||
|
|
f04cff6d57 | ||
|
|
2c1a0a7451 | ||
|
|
74fdb44680 | ||
|
|
c3adbb7cf3 | ||
|
|
fa583ec6ae | ||
|
|
aa76db1e1c | ||
|
|
c5699b5259 | ||
|
|
d3c52fdf64 | ||
|
|
b18f05ae19 | ||
|
|
d8839ba341 | ||
|
|
7022a93378 | ||
|
|
c9da9b8591 | ||
|
|
b483612cb7 | ||
|
|
a30805f8a0 | ||
|
|
a9b0a90ab5 | ||
|
|
2bc138e614 | ||
|
|
f97781039f | ||
|
|
5eda161cf2 | ||
|
|
a473fe6d9b | ||
|
|
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 |
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
|
||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
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:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Clone rosenpass-website repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: rosenpass/rosenpass-website
|
||||
ref: main
|
||||
|
||||
205
.github/workflows/nix.yaml
vendored
205
.github/workflows/nix.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
i686-linux---default:
|
||||
name: Build i686-linux.default
|
||||
@@ -14,11 +19,11 @@ jobs:
|
||||
needs:
|
||||
- i686-linux---rosenpass
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -30,11 +35,11 @@ jobs:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -47,11 +52,11 @@ jobs:
|
||||
needs:
|
||||
- i686-linux---rosenpass
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -62,11 +67,11 @@ jobs:
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -79,11 +84,11 @@ jobs:
|
||||
needs:
|
||||
- x86_64-darwin---rosenpass
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -98,11 +103,11 @@ jobs:
|
||||
- x86_64-darwin---rp
|
||||
- x86_64-darwin---rosenpass-oci-image
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -114,11 +119,11 @@ jobs:
|
||||
- macos-13
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -130,11 +135,11 @@ jobs:
|
||||
- macos-13
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -147,11 +152,11 @@ jobs:
|
||||
needs:
|
||||
- x86_64-darwin---rosenpass
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -162,11 +167,11 @@ jobs:
|
||||
runs-on:
|
||||
- macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -179,11 +184,11 @@ jobs:
|
||||
needs:
|
||||
- x86_64-linux---rosenpass
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -196,11 +201,11 @@ jobs:
|
||||
needs:
|
||||
- x86_64-linux---proverif-patched
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -212,11 +217,11 @@ jobs:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -231,51 +236,51 @@ jobs:
|
||||
- x86_64-linux---rosenpass-static-oci-image
|
||||
- x86_64-linux---rp-static
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
|
||||
aarch64-linux---release-package:
|
||||
name: Build aarch64-linux.release-package
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
needs:
|
||||
- aarch64-linux---rosenpass-oci-image
|
||||
- aarch64-linux---rosenpass
|
||||
- aarch64-linux---rp
|
||||
steps:
|
||||
- run: |
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
extra_nix_config: |
|
||||
system = aarch64-linux
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
||||
# aarch64-linux---release-package:
|
||||
# name: Build aarch64-linux.release-package
|
||||
# runs-on:
|
||||
# - ubuntu-latest
|
||||
# needs:
|
||||
# - aarch64-linux---rosenpass-oci-image
|
||||
# - aarch64-linux---rosenpass
|
||||
# - aarch64-linux---rp
|
||||
# steps:
|
||||
# - run: |
|
||||
# DEBIAN_FRONTEND=noninteractive
|
||||
# sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: cachix/install-nix-action@v30
|
||||
# with:
|
||||
# nix_path: nixpkgs=channel:nixos-unstable
|
||||
# extra_nix_config: |
|
||||
# system = aarch64-linux
|
||||
# - uses: cachix/cachix-action@v15
|
||||
# with:
|
||||
# name: rosenpass
|
||||
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
# - name: Build
|
||||
# run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
||||
x86_64-linux---rosenpass:
|
||||
name: Build x86_64-linux.rosenpass
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -290,13 +295,13 @@ jobs:
|
||||
- run: |
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
extra_nix_config: |
|
||||
system = aarch64-linux
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -311,13 +316,13 @@ jobs:
|
||||
- run: |
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
extra_nix_config: |
|
||||
system = aarch64-linux
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -330,11 +335,11 @@ jobs:
|
||||
needs:
|
||||
- x86_64-linux---rosenpass
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -350,13 +355,13 @@ jobs:
|
||||
- run: |
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
extra_nix_config: |
|
||||
system = aarch64-linux
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -368,11 +373,11 @@ jobs:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -384,11 +389,11 @@ jobs:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -401,11 +406,11 @@ jobs:
|
||||
needs:
|
||||
- x86_64-linux---rosenpass-static
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -417,11 +422,11 @@ jobs:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -432,11 +437,11 @@ jobs:
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -447,11 +452,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -460,7 +465,7 @@ jobs:
|
||||
- name: Build
|
||||
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
||||
- name: Deploy PDF artifacts
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: result/
|
||||
|
||||
59
.github/workflows/qc.yaml
vendored
59
.github/workflows/qc.yaml
vendored
@@ -4,6 +4,10 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
@@ -12,8 +16,8 @@ jobs:
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actionsx/prettier@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actionsx/prettier@v3
|
||||
with:
|
||||
args: --check .
|
||||
|
||||
@@ -21,7 +25,7 @@ jobs:
|
||||
name: Shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
|
||||
@@ -29,15 +33,15 @@ jobs:
|
||||
name: Rust Format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Rust Formatting Script
|
||||
run: bash format_rust_code.sh --mode check
|
||||
|
||||
cargo-bench:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -57,16 +61,14 @@ jobs:
|
||||
steps:
|
||||
- name: Install mandoc
|
||||
run: sudo apt-get install -y mandoc
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check rosenpass.1
|
||||
run: doc/check.sh doc/rosenpass.1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check rp.1
|
||||
run: doc/check.sh doc/rp.1
|
||||
|
||||
cargo-audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -74,8 +76,8 @@ jobs:
|
||||
cargo-clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -93,8 +95,8 @@ jobs:
|
||||
cargo-doc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -117,8 +119,8 @@ jobs:
|
||||
# - ubuntu is x86-64
|
||||
# - macos-13 is also x86-64 architecture
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -136,8 +138,8 @@ jobs:
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -146,10 +148,10 @@ jobs:
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: cachix/install-nix-action@v21
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
@@ -158,8 +160,8 @@ jobs:
|
||||
cargo-fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -191,17 +193,20 @@ jobs:
|
||||
codecov:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup default nightly
|
||||
- run: rustup component add llvm-tools-preview
|
||||
- run: |
|
||||
cargo install cargo-llvm-cov || true
|
||||
cargo llvm-cov --lcov --output-path coverage.lcov
|
||||
cargo install grcov || true
|
||||
./coverage_report.sh
|
||||
# If using tarapulin
|
||||
#- run: cargo install cargo-tarpaulin
|
||||
#- run: cargo tarpaulin --out Xml
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4.0.1
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./coverage.lcov
|
||||
files: ./target/grcov/lcov
|
||||
verbose: true
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
20
.github/workflows/regressions.yml
vendored
20
.github/workflows/regressions.yml
vendored
@@ -1,9 +1,13 @@
|
||||
name: QC
|
||||
name: Regressions
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
@@ -12,10 +16,22 @@ jobs:
|
||||
multi-peer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build release
|
||||
run: nix build .#release-package --print-build-logs
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||
@@ -32,18 +32,18 @@ jobs:
|
||||
runs-on:
|
||||
- macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build release
|
||||
run: nix build .#release-package --print-build-logs
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||
@@ -53,18 +53,18 @@ jobs:
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v12
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build release
|
||||
run: nix build .#release-package --print-build-logs
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.direnv/
|
||||
flake.lock
|
||||
papers/whitepaper.md
|
||||
target/
|
||||
src/usage.md
|
||||
target/
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
**Making a new Release of Rosenpass — Cooking Recipe**
|
||||
# Contributing to Rosenpass
|
||||
|
||||
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
|
||||
## Common operations
|
||||
|
||||
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`
|
||||
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!
|
||||
### Apply code formatting
|
||||
|
||||
**Frequently Asked Questions (FAQ)**
|
||||
Format rust code:
|
||||
|
||||
- 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.
|
||||
```bash
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
Format rust code in markdown files:
|
||||
|
||||
```bash
|
||||
./format_rust_code.sh --mode fix
|
||||
```
|
||||
|
||||
### Spawn a development environment with nix
|
||||
|
||||
```bash
|
||||
nix develop .#fullEnv
|
||||
```
|
||||
|
||||
You need to [install this nix package manager](https://wiki.archlinux.org/title/Nix) first.
|
||||
|
||||
### Run our test
|
||||
|
||||
Make sure to increase the stack size available; some of our cryptography operations require a lot of stack memory.
|
||||
|
||||
```bash
|
||||
RUST_MIN_STACK=8388608 cargo test --workspace --all-features
|
||||
```
|
||||
|
||||
### Generate coverage reports
|
||||
|
||||
Keep in mind that many of Rosenpass' tests are doctests, so to get an accurate read on our code coverage, you have to include doctests:
|
||||
|
||||
```bash
|
||||
./coverage_report.sh
|
||||
```
|
||||
|
||||
1063
Cargo.lock
generated
1063
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
47
Cargo.toml
47
Cargo.toml
@@ -32,55 +32,62 @@ rosenpass-secret-memory = { path = "secret-memory" }
|
||||
rosenpass-oqs = { path = "oqs" }
|
||||
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
||||
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"
|
||||
memoffset = "0.9.1"
|
||||
thiserror = "1.0.63"
|
||||
thiserror = "1.0.69"
|
||||
paste = "1.0.15"
|
||||
env_logger = "0.10.2"
|
||||
toml = "0.7.8"
|
||||
static_assertions = "1.1.0"
|
||||
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"
|
||||
typenum = "1.17.0"
|
||||
log = { version = "0.4.22" }
|
||||
clap = { version = "4.5.13", features = ["derive"] }
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||
anyhow = { version = "1.0.86", features = ["backtrace", "std"] }
|
||||
mio = { version = "1.0.1", features = ["net", "os-poll"] }
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
clap_mangen = "0.2.24"
|
||||
clap_complete = "4.5.40"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
arbitrary = { version = "1.4.1", features = ["derive"] }
|
||||
anyhow = { version = "1.0.95", features = ["backtrace", "std"] }
|
||||
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
||||
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||
'classic_mceliece',
|
||||
'kyber',
|
||||
'classic_mceliece',
|
||||
'kyber',
|
||||
] }
|
||||
blake2 = "0.10.6"
|
||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
||||
"std",
|
||||
"heapless",
|
||||
"std",
|
||||
"heapless",
|
||||
] }
|
||||
zerocopy = { version = "0.7.35", features = ["derive"] }
|
||||
home = "0.5.9"
|
||||
derive_builder = "0.20.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }
|
||||
postcard= {version = "1.0.8", features = ["alloc"]}
|
||||
derive_builder = "0.20.1"
|
||||
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] }
|
||||
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" }
|
||||
heck = { version = "0.5.0" }
|
||||
libc = { version = "0.2" }
|
||||
uds = { git = "https://github.com/rosenpass/uds" }
|
||||
signal-hook = "0.3.17"
|
||||
|
||||
#Dev dependencies
|
||||
serial_test = "3.1.1"
|
||||
serial_test = "3.2.0"
|
||||
tempfile = "3"
|
||||
stacker = "0.1.15"
|
||||
stacker = "0.1.17"
|
||||
libfuzzer-sys = "0.4"
|
||||
test_bin = "0.4.0"
|
||||
criterion = "0.4.0"
|
||||
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)
|
||||
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||
command-fds = "0.2.3"
|
||||
rustix = { version = "0.38.27", features = ["net", "fs"] }
|
||||
rustix = { version = "0.38.42", features = ["net", "fs", "process"] }
|
||||
|
||||
@@ -10,3 +10,8 @@ repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
rosenpass-oqs = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
anyhow = {workspace = true}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Rosenpass internal library providing traits for cryptographic primitives.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
This is an internal library; no guarantee is made about its API at this point in time.
|
||||
|
||||
@@ -5,10 +5,128 @@
|
||||
//!
|
||||
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
|
||||
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
|
||||
//!
|
||||
//! encapsulation.
|
||||
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
|
||||
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
|
||||
//!
|
||||
//! The [Kem] Trait describes the basic API offered by a Key Encapsulation
|
||||
//! Mechanism. Two implementations for it are provided:
|
||||
//! [Kyber512](../../rosenpass_oqs/kyber_512/enum.Kyber512.html) and
|
||||
//! [ClassicMceliece460896](../../rosenpass_oqs/classic_mceliece_460896/enum.ClassicMceliece460896.html).
|
||||
//!
|
||||
//! An example where Alice generates a keypair and gives her public key to Bob, for Bob to
|
||||
//! encapsulate a symmetric key and Alice to decapsulate it would look as follows.
|
||||
//! In the example, we are using Kyber512, but any KEM that correctly implements the [Kem]
|
||||
//! trait could be used as well.
|
||||
//!```rust
|
||||
//! use rosenpass_cipher_traits::Kem;
|
||||
//! use rosenpass_oqs::Kyber512;
|
||||
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
//!
|
||||
//! type MyKem = Kyber512;
|
||||
//! secret_policy_use_only_malloc_secrets();
|
||||
//! let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
|
||||
//! let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
|
||||
//! MyKem::keygen(alice_sk.secret_mut(), &mut alice_pk)?;
|
||||
//!
|
||||
//! let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
|
||||
//! MyKem::encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||
//!
|
||||
//! let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! MyKem::decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||
//!
|
||||
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||
//! # Ok::<(), anyhow::Error>(())
|
||||
//!```
|
||||
//!
|
||||
//! Implementing the [Kem]-trait for a KEM is easy. Mostly, you must format the KEM's
|
||||
//! keys, and ciphertext as `u8` slices. Below, we provide an example for how the trait can
|
||||
//! be implemented using a **HORRIBLY INSECURE** DummyKem that only uses static values for keys
|
||||
//! and ciphertexts as an example.
|
||||
//!```rust
|
||||
//!# use rosenpass_cipher_traits::Kem;
|
||||
//!
|
||||
//! struct DummyKem {}
|
||||
//! impl Kem for DummyKem {
|
||||
//!
|
||||
//! // For this DummyKem, using String for errors is sufficient.
|
||||
//! type Error = String;
|
||||
//!
|
||||
//! // For this DummyKem, we will use a single `u8` for everything
|
||||
//! const SK_LEN: usize = 1;
|
||||
//! const PK_LEN: usize = 1;
|
||||
//! const CT_LEN: usize = 1;
|
||||
//! const SHK_LEN: usize = 1;
|
||||
//!
|
||||
//! fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), Self::Error> {
|
||||
//! if sk.len() != Self::SK_LEN {
|
||||
//! return Err("sk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if pk.len() != Self::PK_LEN {
|
||||
//! return Err("pk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! sk[0] = 42;
|
||||
//! pk[0] = 21;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), Self::Error> {
|
||||
//! if pk.len() != Self::PK_LEN {
|
||||
//! return Err("pk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if ct.len() != Self::CT_LEN {
|
||||
//! return Err("ct does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if shk.len() != Self::SHK_LEN {
|
||||
//! return Err("shk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if pk[0] != 21 {
|
||||
//! return Err("Invalid public key!".to_string());
|
||||
//! }
|
||||
//! ct[0] = 7;
|
||||
//! shk[0] = 17;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), Self::Error> {
|
||||
//! if sk.len() != Self::SK_LEN {
|
||||
//! return Err("sk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if ct.len() != Self::CT_LEN {
|
||||
//! return Err("ct does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if shk.len() != Self::SHK_LEN {
|
||||
//! return Err("shk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if sk[0] != 42 {
|
||||
//! return Err("Invalid public key!".to_string());
|
||||
//! }
|
||||
//! if ct[0] != 7 {
|
||||
//! return Err("Invalid ciphertext!".to_string());
|
||||
//! }
|
||||
//! shk[0] = 17;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
//! #
|
||||
//! # type MyKem = DummyKem;
|
||||
//! # secret_policy_use_only_malloc_secrets();
|
||||
//! # let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
|
||||
//! # let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
|
||||
//! # MyKem::keygen(alice_sk.secret_mut(), &mut alice_pk)?;
|
||||
//!
|
||||
//! # let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! # let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
|
||||
//! # MyKem::encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||
//! #
|
||||
//! # let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! # MyKem::decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||
//! #
|
||||
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||
//! #
|
||||
//! # Ok::<(), String>(())
|
||||
//!```
|
||||
//!
|
||||
|
||||
/// Key Encapsulation Mechanism
|
||||
///
|
||||
|
||||
@@ -23,4 +23,4 @@ static_assertions = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
chacha20poly1305 = { workspace = true }
|
||||
blake2 = { workspace = true }
|
||||
libcrux = { workspace = true, optional = true }
|
||||
libcrux = { workspace = true, optional = true }
|
||||
|
||||
@@ -2,100 +2,192 @@ use anyhow::Result;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_to::To;
|
||||
|
||||
use crate::subtle::incorrect_hmac_blake2b as hash;
|
||||
use crate::keyed_hash as hash;
|
||||
|
||||
pub use hash::KEY_LEN;
|
||||
|
||||
///
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::hash_domain::{HashDomain, HashDomainNamespace, SecretHashDomain, SecretHashDomainNamespace};
|
||||
/// use rosenpass_secret_memory::Secret;
|
||||
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
|
||||
/// // create use once hash domain for the protocol identifier
|
||||
/// let mut hash_domain = HashDomain::zero();
|
||||
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
|
||||
/// // upgrade to reusable hash domain
|
||||
/// let hash_domain_namespace: HashDomainNamespace = hash_domain.dup();
|
||||
/// // derive new key
|
||||
/// let key_identifier = "my_key_identifier";
|
||||
/// let key = hash_domain_namespace.mix(key_identifier.as_bytes())?.into_value();
|
||||
/// // derive a new key based on a secret
|
||||
/// const MY_SECRET_LEN: usize = 21;
|
||||
/// let my_secret_bytes = "my super duper secret".as_bytes();
|
||||
/// let my_secret: Secret<21> = Secret::from_slice("my super duper secret".as_bytes());
|
||||
/// let secret_hash_domain: SecretHashDomain = hash_domain_namespace.mix_secret(my_secret)?;
|
||||
/// // derive a new key based on the secret key
|
||||
/// let new_key_identifier = "my_new_key_identifier".as_bytes();
|
||||
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
|
||||
///
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
///```
|
||||
///
|
||||
|
||||
// TODO Use a proper Dec interface
|
||||
/// A use-once hash domain for a specified key that can be used directly.
|
||||
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
|
||||
/// use [SecretHashDomain] instead.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HashDomain([u8; KEY_LEN]);
|
||||
/// A reusable hash domain for a namespace identified by the key.
|
||||
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
|
||||
/// use [SecretHashDomainNamespace] instead.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HashDomainNamespace([u8; KEY_LEN]);
|
||||
/// A use-once hash domain for a specified key that can be used directly
|
||||
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretHashDomain(Secret<KEY_LEN>);
|
||||
/// A reusable secure hash domain for a namespace identified by the key and that keeps the key secure
|
||||
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
|
||||
|
||||
impl HashDomain {
|
||||
/// Creates a nw [HashDomain] initialized with a all-zeros key.
|
||||
pub fn zero() -> Self {
|
||||
Self([0u8; KEY_LEN])
|
||||
}
|
||||
|
||||
/// Turns this [HashDomain] into a [HashDomainNamespace], keeping the key.
|
||||
pub fn dup(self) -> HashDomainNamespace {
|
||||
HashDomainNamespace(self.0)
|
||||
}
|
||||
|
||||
/// Turns this [HashDomain] into a [SecretHashDomain] by wrapping the key into a [Secret]
|
||||
/// and creating a new [SecretHashDomain] from it.
|
||||
pub fn turn_secret(self) -> SecretHashDomain {
|
||||
SecretHashDomain(Secret::from_slice(&self.0))
|
||||
}
|
||||
|
||||
// TODO: Protocol! Use domain separation to ensure that
|
||||
/// Creates a new [HashDomain] by mixing in a new key `v`. Specifically,
|
||||
/// it evaluates [hash::hash] with this HashDomain's key as the key and `v`
|
||||
/// as the `data` and uses the result as the key for the new [HashDomain].
|
||||
///
|
||||
pub fn mix(self, v: &[u8]) -> Result<Self> {
|
||||
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||
/// by calling [SecretHashDomain::invoke_primitive] with this
|
||||
/// [HashDomain]'s key as `k` and `v` as `d`.
|
||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
||||
}
|
||||
|
||||
/// Gets the key of this [HashDomain].
|
||||
pub fn into_value(self) -> [u8; KEY_LEN] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HashDomainNamespace {
|
||||
/// Creates a new [HashDomain] by mixing in a new key `v`. Specifically,
|
||||
/// it evaluates [hash::hash] with the key of this HashDomainNamespace key as the key and `v`
|
||||
/// as the `data` and uses the result as the key for the new [HashDomain].
|
||||
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
|
||||
Ok(HashDomain(
|
||||
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
|
||||
/// [HashDomainNamespace] as `k` and `v` as `d`.
|
||||
///
|
||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretHashDomain {
|
||||
/// Create a new [SecretHashDomain] with the given key `k` and data `d` by calling
|
||||
/// [hash::hash] with `k` as the `key` and `d` s the `data`, and using the result
|
||||
/// as the content for the new [SecretHashDomain].
|
||||
/// Both `k` and `d` have to be exactly [KEY_LEN] bytes in length.
|
||||
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
|
||||
let mut r = SecretHashDomain(Secret::zero());
|
||||
hash::hash(k, d).to(r.0.secret_mut())?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] that is initialized with an all zeros key.
|
||||
pub fn zero() -> Self {
|
||||
Self(Secret::zero())
|
||||
}
|
||||
|
||||
/// Turns this [SecretHashDomain] into a [SecretHashDomainNamespace].
|
||||
pub fn dup(self) -> SecretHashDomainNamespace {
|
||||
SecretHashDomainNamespace(self.0)
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] from a [Secret] `k`.
|
||||
///
|
||||
/// It requires that `k` consist of exactly [KEY_LEN] bytes.
|
||||
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
|
||||
Self(k)
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
|
||||
/// it evaluates [hash::hash] with this [SecretHashDomain]'s key as the key and `v`
|
||||
/// as the `data` and uses the result as the key for the new [SecretHashDomain].
|
||||
///
|
||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||
Self::invoke_primitive(self.0.secret(), v)
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
|
||||
/// [HashDomainNamespace] as `k` and `v` as `d`.
|
||||
///
|
||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
Self::invoke_primitive(self.0.secret(), v.secret())
|
||||
}
|
||||
|
||||
/// Get the secret key data from this [SecretHashDomain].
|
||||
pub fn into_secret(self) -> Secret<KEY_LEN> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Evaluate [hash::hash] with this [SecretHashDomain]'s data as the `key` and
|
||||
/// `dst` as the `data` and stores the result as the new data for this [SecretHashDomain].
|
||||
///
|
||||
/// It requires that both `v` and `d` consist of exactly [KEY_LEN] many bytes.
|
||||
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
|
||||
hash::hash(v, dst).to(self.0.secret_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretHashDomainNamespace {
|
||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
|
||||
/// it evaluates [hash::hash] with the key of this HashDomainNamespace key as the key and `v`
|
||||
/// as the `data` and uses the result as the key for the new [HashDomain].
|
||||
///
|
||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(self.0.secret(), v)
|
||||
}
|
||||
|
||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
|
||||
/// [HashDomainNamespace] as `k` and `v` as `d`.
|
||||
///
|
||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
|
||||
}
|
||||
@@ -103,6 +195,7 @@ impl SecretHashDomainNamespace {
|
||||
// TODO: This entire API is not very nice; we need this for biscuits, but
|
||||
// it might be better to extract a special "biscuit"
|
||||
// labeled subkey and reinitialize the chain with this
|
||||
/// Get the secret key data from this [SecretHashDomain].
|
||||
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
|
||||
self.0
|
||||
}
|
||||
|
||||
@@ -2,12 +2,25 @@ use static_assertions::const_assert;
|
||||
|
||||
pub mod subtle;
|
||||
|
||||
/// All keyed primitives in this crate use 32 byte keys
|
||||
pub const KEY_LEN: usize = 32;
|
||||
const_assert!(KEY_LEN == aead::KEY_LEN);
|
||||
const_assert!(KEY_LEN == xaead::KEY_LEN);
|
||||
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
||||
|
||||
/// Keyed hashing
|
||||
///
|
||||
/// This should only be used for implementation details; anything with relevance
|
||||
/// to the cryptographic protocol should use the facilities in [hash_domain], (though
|
||||
/// hash domain uses this module internally)
|
||||
pub mod keyed_hash {
|
||||
pub use crate::subtle::incorrect_hmac_blake2b::{
|
||||
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
|
||||
};
|
||||
}
|
||||
|
||||
/// Authenticated encryption with associated data
|
||||
/// Chacha20poly1305 is used.
|
||||
pub mod aead {
|
||||
#[cfg(not(feature = "experiment_libcrux"))]
|
||||
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||
@@ -18,6 +31,7 @@ pub mod aead {
|
||||
}
|
||||
|
||||
/// Authenticated encryption with associated data with a constant nonce
|
||||
/// XChacha20poly1305 is used.
|
||||
pub mod xaead {
|
||||
pub use crate::subtle::xchacha20poly1305_ietf::{
|
||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||
@@ -26,6 +40,13 @@ pub mod xaead {
|
||||
|
||||
pub mod hash_domain;
|
||||
|
||||
/// This crate includes two key encapsulation mechanisms.
|
||||
/// Namely ClassicMceliece460896 (also referred to as `StaticKem` sometimes) and
|
||||
/// Kyber512 (also referred to as `EphemeralKem` sometimes).
|
||||
///
|
||||
/// See [rosenpass_oqs::ClassicMceliece460896]
|
||||
/// and [rosenpass_oqs::Kyber512] for more details on the specific KEMS.
|
||||
///
|
||||
pub mod kem {
|
||||
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
||||
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
||||
|
||||
@@ -9,19 +9,43 @@ use blake2::Blake2bMac;
|
||||
use rosenpass_to::{ops::copy_slice, with_destination, To};
|
||||
use rosenpass_util::typenum2const;
|
||||
|
||||
/// Specify that the used implementation of BLAKE2b is the MAC version of BLAKE2b
|
||||
/// with output and key length of 32 bytes (see [Blake2bMac]).
|
||||
type Impl = Blake2bMac<U32>;
|
||||
|
||||
type KeyLen = <Impl as KeySizeUser>::KeySize;
|
||||
type OutLen = <Impl as OutputSizeUser>::OutputSize;
|
||||
|
||||
/// The key length for BLAKE2b supported by this API. Currently 32 Bytes.
|
||||
const KEY_LEN: usize = typenum2const! { KeyLen };
|
||||
/// The output length for BLAKE2b supported by this API. Currently 32 Bytes.
|
||||
const OUT_LEN: usize = typenum2const! { OutLen };
|
||||
|
||||
/// Minimal key length supported by this API.
|
||||
pub const KEY_MIN: usize = KEY_LEN;
|
||||
/// maximal key length supported by this API.
|
||||
pub const KEY_MAX: usize = KEY_LEN;
|
||||
/// minimal output length supported by this API.
|
||||
pub const OUT_MIN: usize = OUT_LEN;
|
||||
/// maximal output length supported by this API.
|
||||
pub const OUT_MAX: usize = OUT_LEN;
|
||||
|
||||
/// Hashes the given `data` with the [Blake2bMac] hash function under the given `key`.
|
||||
/// The both the length of the output the length of the key 32 bytes (or 256 bits).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::blake2b::hash;
|
||||
/// use rosenpass_to::To;
|
||||
/// let zero_key: [u8; 32] = [0; 32];
|
||||
/// let data: [u8; 32] = [255; 32];
|
||||
/// // buffer for the hash output
|
||||
/// let mut hash_data: [u8; 32] = [0u8; 32];
|
||||
///
|
||||
/// assert!(hash(&zero_key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||
///```
|
||||
///
|
||||
#[inline]
|
||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
||||
with_destination(|out: &mut [u8]| {
|
||||
@@ -36,7 +60,6 @@ pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<(
|
||||
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
||||
h.finalize_into(tmp);
|
||||
copy_slice(tmp.as_ref()).to(out);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,10 +6,39 @@ use chacha20poly1305::aead::generic_array::GenericArray;
|
||||
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
|
||||
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
|
||||
|
||||
/// The key length is 32 bytes or 256 bits.
|
||||
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
|
||||
/// The MAC tag length is 16 bytes or 128 bits.
|
||||
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
|
||||
/// The nonce length is 12 bytes or 96 bits.
|
||||
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
|
||||
|
||||
/// Encrypts using ChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
||||
/// `key` MUST be chosen (pseudo-)randomly and `nonce` MOST NOT be reused. The `key` slice MUST have
|
||||
/// a length of [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
|
||||
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
|
||||
/// `plaintext.len()` + [TAG_LEN].
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||
///
|
||||
/// const PLAINTEXT_LEN: usize = 43;
|
||||
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||
/// let mut ciphertext_buffer = [0u8;PLAINTEXT_LEN + TAG_LEN];
|
||||
///
|
||||
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
|
||||
/// assert!(res.is_ok());
|
||||
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
||||
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
||||
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
||||
/// # 8, 114, 85, 4, 25];
|
||||
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
|
||||
///```
|
||||
#[inline]
|
||||
pub fn encrypt(
|
||||
ciphertext: &mut [u8],
|
||||
@@ -26,6 +55,33 @@ pub fn encrypt(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
||||
/// `ad`. using ChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
||||
///
|
||||
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
|
||||
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
||||
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
||||
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
||||
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
|
||||
/// const PLAINTEXT_LEN: usize = 43;
|
||||
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
|
||||
///
|
||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
||||
///
|
||||
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
|
||||
/// assert!(res.is_ok());
|
||||
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||
/// assert_eq!(expected_plaintext, plaintext_buffer);
|
||||
///
|
||||
///```
|
||||
#[inline]
|
||||
pub fn decrypt(
|
||||
plaintext: &mut [u8],
|
||||
|
||||
@@ -3,10 +3,40 @@ use rosenpass_to::To;
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// The key length is 32 bytes or 256 bits.
|
||||
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
|
||||
/// The MAC tag length is 16 bytes or 128 bits.
|
||||
pub const TAG_LEN: usize = 16;
|
||||
/// The nonce length is 12 bytes or 96 bits.
|
||||
pub const NONCE_LEN: usize = 12;
|
||||
|
||||
/// Encrypts using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
|
||||
/// Key and nonce MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
|
||||
/// [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
|
||||
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
|
||||
/// `plaintext.len()` + [TAG_LEN].
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||
///
|
||||
/// const PLAINTEXT_LEN: usize = 43;
|
||||
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||
/// let mut ciphertext_buffer = [0u8; PLAINTEXT_LEN + TAG_LEN];
|
||||
///
|
||||
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
|
||||
/// assert!(res.is_ok());
|
||||
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
||||
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
||||
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
||||
/// # 8, 114, 85, 4, 25];
|
||||
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
|
||||
///```
|
||||
///
|
||||
#[inline]
|
||||
pub fn encrypt(
|
||||
ciphertext: &mut [u8],
|
||||
@@ -33,6 +63,33 @@ pub fn encrypt(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
||||
/// `ad`. using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
|
||||
///
|
||||
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
|
||||
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
||||
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
||||
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
||||
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
|
||||
/// const PLAINTEXT_LEN: usize = 43;
|
||||
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
|
||||
///
|
||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
||||
///
|
||||
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
|
||||
/// assert!(res.is_ok());
|
||||
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||
/// assert_eq!(expected_plaintext, plaintext_buffer);
|
||||
///
|
||||
///```
|
||||
#[inline]
|
||||
pub fn decrypt(
|
||||
plaintext: &mut [u8],
|
||||
|
||||
@@ -6,10 +6,15 @@ use rosenpass_to::{ops::copy_slice, with_destination, To};
|
||||
|
||||
use crate::subtle::blake2b;
|
||||
|
||||
/// The key length, 32 bytes or 256 bits.
|
||||
pub const KEY_LEN: usize = 32;
|
||||
/// The minimal key length, identical to [KEY_LEN]
|
||||
pub const KEY_MIN: usize = KEY_LEN;
|
||||
/// The maximal key length, identical to [KEY_LEN]
|
||||
pub const KEY_MAX: usize = KEY_LEN;
|
||||
/// The minimal output length, see [blake2b::OUT_MIN]
|
||||
pub const OUT_MIN: usize = blake2b::OUT_MIN;
|
||||
/// The maximal output length, see [blake2b::OUT_MAX]
|
||||
pub const OUT_MAX: usize = blake2b::OUT_MAX;
|
||||
|
||||
/// This is a woefully incorrect implementation of hmac_blake2b.
|
||||
@@ -19,6 +24,22 @@ pub const OUT_MAX: usize = blake2b::OUT_MAX;
|
||||
///
|
||||
/// This will be replaced, likely by Kekkac at some point soon.
|
||||
/// <https://github.com/rosenpass/rosenpass/pull/145>
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::hash;
|
||||
/// use rosenpass_to::To;
|
||||
/// let key: [u8; 32] = [0; 32];
|
||||
/// let data: [u8; 32] = [255; 32];
|
||||
/// // buffer for the hash output
|
||||
/// let mut hash_data: [u8; 32] = [0u8; 32];
|
||||
///
|
||||
/// assert!(hash(&key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||
/// # let expected_hash: &[u8] = &[5, 152, 135, 141, 151, 106, 147, 8, 220, 95, 38, 66, 29, 33, 3,
|
||||
/// 104, 250, 114, 131, 119, 27, 56, 59, 44, 11, 67, 230, 113, 112, 20, 80, 103];
|
||||
/// # assert_eq!(hash_data, expected_hash);
|
||||
///```
|
||||
///
|
||||
#[inline]
|
||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
||||
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/// This module provides the following cryptographic schemes:
|
||||
/// - [blake2b]: The blake2b hash function
|
||||
/// - [chacha20poly1305_ietf]: The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305) (only used when the feature `experiment_libcrux` is disabled).
|
||||
/// - [chacha20poly1305_ietf_libcrux]: The Chacha20Poly1305 AEAD as implemented in [libcrux](https://github.com/cryspen/libcrux) (only used when the feature `experiment_libcrux` is enabled).
|
||||
/// - [incorrect_hmac_blake2b]: An (incorrect) hmac based on [blake2b].
|
||||
/// - [xchacha20poly1305_ietf] The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305)
|
||||
pub mod blake2b;
|
||||
#[cfg(not(feature = "experiment_libcrux"))]
|
||||
pub mod chacha20poly1305_ietf;
|
||||
|
||||
@@ -6,10 +6,41 @@ use chacha20poly1305::aead::generic_array::GenericArray;
|
||||
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
|
||||
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
|
||||
|
||||
/// The key length is 32 bytes or 256 bits.
|
||||
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
|
||||
/// The MAC tag length is 16 bytes or 128 bits.
|
||||
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
|
||||
/// The nonce length is 24 bytes or 192 bits.
|
||||
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
|
||||
|
||||
/// Encrypts using XChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
||||
/// `key` and `nonce` MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
|
||||
/// [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN].
|
||||
/// In contrast to [chacha20poly1305_ietf::encrypt](crate::subtle::chacha20poly1305_ietf::encrypt) and
|
||||
/// [chacha20poly1305_ietf_libcrux::encrypt](crate::subtle::chacha20poly1305_ietf_libcrux::encrypt),
|
||||
/// `nonce` is also written into `ciphertext` and therefore ciphertext MUST have a length
|
||||
/// of at least [NONCE_LEN] + `plaintext.len()` + [TAG_LEN].
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||
/// const PLAINTEXT_LEN: usize = 43;
|
||||
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||
/// let mut ciphertext_buffer = [0u8; NONCE_LEN + PLAINTEXT_LEN + TAG_LEN];
|
||||
///
|
||||
///
|
||||
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
|
||||
/// # assert!(res.is_ok());
|
||||
/// # let expected_ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/// # 0, 0, 0, 0, 8, 241, 229, 253, 200, 81, 248, 30, 183, 149, 134, 168, 149, 87, 109, 49, 159, 108,
|
||||
/// # 206, 89, 51, 232, 232, 197, 163, 253, 254, 208, 73, 76, 253, 13, 247, 162, 133, 184, 177, 44,
|
||||
/// # 73, 138, 176, 193, 61, 248, 61, 183, 164, 192, 214, 168, 4, 1, 62, 243, 36, 48, 149, 164, 6];
|
||||
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
|
||||
///```
|
||||
#[inline]
|
||||
pub fn encrypt(
|
||||
ciphertext: &mut [u8],
|
||||
@@ -28,6 +59,38 @@ pub fn encrypt(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
||||
/// `ad`. using XChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
||||
///
|
||||
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
|
||||
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN] - [NONCE_LEN].
|
||||
///
|
||||
/// In contrast to [chacha20poly1305_ietf::decrypt](crate::subtle::chacha20poly1305_ietf::decrypt) and
|
||||
/// [chacha20poly1305_ietf_libcrux::decrypt](crate::subtle::chacha20poly1305_ietf_libcrux::decrypt),
|
||||
/// `ciperhtext` MUST include the as it is not given otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///```rust
|
||||
/// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||
/// let ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/// # 0, 0, 0, 0, 8, 241, 229, 253, 200, 81, 248, 30, 183, 149, 134, 168, 149, 87, 109, 49, 159, 108,
|
||||
/// # 206, 89, 51, 232, 232, 197, 163, 253, 254, 208, 73, 76, 253, 13, 247, 162, 133, 184, 177, 44,
|
||||
/// # 73, 138, 176, 193, 61, 248, 61, 183, 164, 192, 214, 168, 4, 1, 62, 243, 36, 48, 149, 164, 6];
|
||||
/// // this is the ciphertext generated by the example for the encryption
|
||||
/// const PLAINTEXT_LEN: usize = 43;
|
||||
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN + NONCE_LEN, ciphertext.len());
|
||||
///
|
||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
||||
///
|
||||
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, additional_data, ciphertext);
|
||||
/// assert!(res.is_ok());
|
||||
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||
/// assert_eq!(expected_plaintext, plaintext_buffer);
|
||||
///
|
||||
///```
|
||||
#[inline]
|
||||
pub fn decrypt(
|
||||
plaintext: &mut [u8],
|
||||
|
||||
@@ -20,3 +20,6 @@ memsec = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
//! Constant-time comparison
|
||||
|
||||
use core::ptr;
|
||||
|
||||
/// Little endian memcmp version of quinier/memsec
|
||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
||||
/// Little endian memcmp version of [quinier/memsec](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.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// let a = [1, 2, 3, 4];
|
||||
/// let b = [1, 2, 3, 4];
|
||||
/// let c = [1, 2, 2, 5];
|
||||
/// let d = [1, 2, 2, 4];
|
||||
///
|
||||
/// unsafe {
|
||||
/// use rosenpass_constant_time::memcmp_le;
|
||||
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
|
||||
/// assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
|
||||
/// assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
|
||||
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(never)]
|
||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
let mut res = 0;
|
||||
@@ -13,6 +36,16 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
((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
|
||||
/// the slices
|
||||
///
|
||||
@@ -32,8 +65,50 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
/// <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]
|
||||
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::compare::memcmp_le;
|
||||
|
||||
#[test]
|
||||
fn memcmp_le_test() {
|
||||
let a = [1, 2, 3, 4];
|
||||
let b = [1, 2, 3, 4];
|
||||
let c = [1, 2, 2, 5];
|
||||
let d = [1, 2, 2, 4];
|
||||
|
||||
unsafe {
|
||||
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
|
||||
assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
|
||||
assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
|
||||
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
//! Incrementing numbers
|
||||
|
||||
use core::hint::black_box;
|
||||
|
||||
/// Interpret the given slice as a little-endian unsigned integer
|
||||
/// and increment that integer.
|
||||
///
|
||||
/// # Leaks
|
||||
/// TODO: mention here if this function leaks any information, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
/// This function may leak timing information in the following ways:
|
||||
///
|
||||
/// - The function execution time is linearly proportional to the input length
|
||||
/// - The number of carry operations that occur may affect timing slightly
|
||||
/// - Memory access patterns are sequential and predictable
|
||||
///
|
||||
/// The carry operation timing variation is mitigated through the use of black_box,
|
||||
/// but the linear scaling with input size is inherent to the operation.
|
||||
/// These timing characteristics are generally considered acceptable for most
|
||||
/// cryptographic counter implementations.
|
||||
///
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
//! constant-time implementations of some primitives
|
||||
//!
|
||||
//! Rosenpass internal library providing basic constant-time operations.
|
||||
@@ -5,6 +7,32 @@
|
||||
//! ## TODO
|
||||
//! Figure out methodology to ensure that code is actually constant time, see
|
||||
//! <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rosenpass_constant_time::{memcmp, compare};
|
||||
//!
|
||||
//! let a = [1, 2, 3, 4];
|
||||
//! let b = [1, 2, 3, 4];
|
||||
//! let c = [1, 2, 3, 5];
|
||||
//!
|
||||
//! // Compare for equality
|
||||
//! assert!(memcmp(&a, &b));
|
||||
//! assert!(!memcmp(&a, &c));
|
||||
//!
|
||||
//! // Compare lexicographically
|
||||
//! assert_eq!(compare(&a, &c), -1); // a < c
|
||||
//! assert_eq!(compare(&c, &a), 1); // c > a
|
||||
//! assert_eq!(compare(&a, &b), 0); // a == b
|
||||
//! ```
|
||||
//!
|
||||
//! # Security Notes
|
||||
//!
|
||||
//! While these functions aim to be constant-time, they may leak timing information in some cases:
|
||||
//!
|
||||
//! - Length mismatches between inputs are immediately detectable
|
||||
//! - Execution time scales linearly with input size
|
||||
|
||||
mod compare;
|
||||
mod increment;
|
||||
@@ -12,6 +40,7 @@ mod memcmp;
|
||||
mod xor;
|
||||
|
||||
pub use compare::compare;
|
||||
pub use compare::memcmp_le;
|
||||
pub use increment::increment;
|
||||
pub use memcmp::memcmp;
|
||||
pub use xor::xor;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! memcmp
|
||||
|
||||
/// compares two sclices of memory content and returns whether they are equal
|
||||
///
|
||||
/// ## Leaks
|
||||
@@ -7,6 +9,18 @@
|
||||
///
|
||||
/// The execution time of the function grows approx. linear with the length of the input. This is
|
||||
/// considered safe.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// 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]
|
||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
@@ -99,9 +113,10 @@ mod tests {
|
||||
// Pearson correlation
|
||||
let correlation = cv / (sd_x * sd_y);
|
||||
println!("correlation: {:.6?}", correlation);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
correlation.abs() < 0.01,
|
||||
"execution time correlates with result"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
//! xor
|
||||
|
||||
use core::hint::black_box;
|
||||
use rosenpass_to::{with_destination, To};
|
||||
|
||||
/// Xors the source into the destination
|
||||
///
|
||||
/// Performs a constant-time XOR operation between two byte slices
|
||||
///
|
||||
/// Takes a source slice and XORs it with the destination slice in-place using the
|
||||
/// rosenpass_to trait for destination management.
|
||||
///
|
||||
/// # Panics
|
||||
/// If source and destination are of different sizes.
|
||||
///
|
||||
/// # Leaks
|
||||
/// TODO: mention here if this function leaks any information, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
/// This function may leak timing information in the following ways:
|
||||
///
|
||||
/// - The function execution time is linearly proportional to the input length
|
||||
/// - Length mismatches between source and destination are immediately detectable via panic
|
||||
/// - Memory access patterns follow a predictable sequential pattern
|
||||
///
|
||||
/// These leaks are generally considered acceptable in most cryptographic contexts
|
||||
/// as they don't reveal information about the actual content being XORed.
|
||||
///
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
|
||||
46
coverage_report.sh
Executable file
46
coverage_report.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
OUTPUT_DIR="target/grcov"
|
||||
|
||||
log() {
|
||||
echo >&2 "$@"
|
||||
}
|
||||
|
||||
exc() {
|
||||
echo '$' "$@"
|
||||
"$@"
|
||||
}
|
||||
|
||||
main() {
|
||||
exc cd "$(dirname "$0")"
|
||||
|
||||
local open="0"
|
||||
if [[ "$1" == "--open" ]]; then
|
||||
open="1"
|
||||
fi
|
||||
|
||||
exc cargo llvm-cov --all-features --workspace --doctests --branch
|
||||
|
||||
exc rm -rf target/llvm-cov-target/debug/deps/doctestbins
|
||||
exc mv -v target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/
|
||||
exc rm -rf "${OUTPUT_DIR}"
|
||||
exc mkdir -p "${OUTPUT_DIR}"
|
||||
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
|
||||
--binary-path ./target/llvm-cov-target/debug/deps \
|
||||
--ignore-not-existing --ignore '../*' --ignore "/*" \
|
||||
--excl-line '^\s*#\[(derive|repr)\(' \
|
||||
-t lcov,html,markdown -o "${OUTPUT_DIR}"
|
||||
|
||||
if (( "${open}" == 1 )); then
|
||||
xdg-open "${PWD}/${OUTPUT_DIR}/html/index.html"
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "Generated reports in \"${PWD}/${OUTPUT_DIR}\"."
|
||||
log "Open \"${PWD}/${OUTPUT_DIR}/html/index.html\" to view HTML report."
|
||||
log ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
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 Clara Engler
|
||||
.Sh BUGS
|
||||
The bugs are tracked at
|
||||
.Lk https://github.com/rosenpass/rosenpass/issues .
|
||||
49
flake.lock
generated
49
flake.lock
generated
@@ -2,15 +2,17 @@
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": ["nixpkgs"],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712298178,
|
||||
"narHash": "sha256-590fpCPXYAkaAeBz/V91GX4/KGzPObdYtqsTWzT6AhI=",
|
||||
"lastModified": 1728282832,
|
||||
"narHash": "sha256-I7AbcwGggf+CHqpyd/9PiAjpIBGTGx5woYHqtwxaV7I=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "569b5b5781395da08e7064e825953c548c26af76",
|
||||
"rev": "1ec71be1f4b8f3105c5d38da339cb061fefc43f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -24,11 +26,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -37,36 +39,18 @@
|
||||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1712168706,
|
||||
"narHash": "sha256-XP24tOobf6GGElMd0ux90FEBalUtw6NkBSVh/RlA6ik=",
|
||||
"lastModified": 1728193676,
|
||||
"narHash": "sha256-PbDWAIjKJdlVg+qQRhzdSor04bAPApDqIv2DofTyynk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1487bdea619e4a7a53a4590c475deabb5a9d1bfb",
|
||||
"rev": "ecbc1ca8ffd6aea8372ad16be9ebbb39889e55b6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -75,18 +59,17 @@
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1712156296,
|
||||
"narHash": "sha256-St7ZQrkrr5lmQX9wC1ZJAFxL8W7alswnyZk9d1se3Us=",
|
||||
"lastModified": 1728249780,
|
||||
"narHash": "sha256-J269DvCI5dzBmPrXhAAtj566qt0b22TJtF3TIK+tMsI=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "8e581ac348e223488622f4d3003cb2bd412bf27e",
|
||||
"rev": "2b750da1a1a2c1d2c70896108d7096089842d877",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
429
flake.nix
429
flake.nix
@@ -1,12 +1,8 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
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
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -15,6 +11,15 @@
|
||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
||||
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 ###
|
||||
#
|
||||
@@ -30,310 +35,39 @@
|
||||
]
|
||||
(system:
|
||||
let
|
||||
scoped = (scope: scope.result);
|
||||
lib = nixpkgs.lib;
|
||||
|
||||
# normal nixpkgs
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
|
||||
# parsed Cargo.toml
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
|
||||
|
||||
# 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" ];
|
||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
in
|
||||
rec {
|
||||
packages = rec {
|
||||
default = rosenpass;
|
||||
rosenpass = rosenpassDerivation pkgs;
|
||||
rp = rpDerivation pkgs;
|
||||
rosenpass-oci-image = rosenpassOCI "rosenpass";
|
||||
{
|
||||
packages = {
|
||||
default = pkgs.rosenpass;
|
||||
rosenpass = pkgs.rosenpass;
|
||||
rosenpass-oci-image = pkgs.rosenpass-oci-image;
|
||||
rp = pkgs.rp;
|
||||
|
||||
# derivation for the release
|
||||
release-package =
|
||||
let
|
||||
version = cargoToml.package.version;
|
||||
package =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rosenpass-static
|
||||
else packages.rosenpass;
|
||||
rp =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rp-static
|
||||
else packages.rp;
|
||||
oci-image =
|
||||
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 { });
|
||||
release-package = pkgs.release-package;
|
||||
|
||||
# for good measure, we also offer to cross compile to Linux on Arm
|
||||
aarch64-linux-rosenpass-static =
|
||||
pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass;
|
||||
aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp;
|
||||
}
|
||||
//
|
||||
# We only offer static builds for linux, as this is not supported on OS X
|
||||
(nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux {
|
||||
rosenpass-static = pkgs.pkgsStatic.rosenpass;
|
||||
rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image;
|
||||
rp-static = pkgs.pkgsStatic.rp;
|
||||
});
|
||||
}
|
||||
))
|
||||
|
||||
|
||||
#
|
||||
### Linux specifics ###
|
||||
#
|
||||
@@ -341,92 +75,69 @@
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
packages = self.packages.${system};
|
||||
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 ###
|
||||
#
|
||||
packages.proverif-patched = pkgs.proverif.overrideAttrs (old: {
|
||||
postInstall = ''
|
||||
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
|
||||
'';
|
||||
};
|
||||
packages.proverif-patched = pkgs.proverif-patched;
|
||||
packages.proof-proverif = pkgs.proof-proverif;
|
||||
|
||||
|
||||
#
|
||||
### Devshells ###
|
||||
#
|
||||
devShells.default = pkgs.mkShell {
|
||||
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ packages.default ];
|
||||
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
inputs.fenix.packages.${system}.complete.toolchain
|
||||
cmake # override the fakecmake from the main step above
|
||||
cargo-release
|
||||
clippy
|
||||
rustfmt
|
||||
nodePackages.prettier
|
||||
nushell # for the .ci/gen-workflow-files.nu script
|
||||
packages.proverif-patched
|
||||
proverif-patched
|
||||
];
|
||||
};
|
||||
# TODO: Write this as a patched version of the default environment
|
||||
devShells.fullEnv = pkgs.mkShell {
|
||||
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cargo-release
|
||||
rustfmt
|
||||
nodePackages.prettier
|
||||
nushell # for the .ci/gen-workflow-files.nu script
|
||||
proverif-patched
|
||||
inputs.fenix.packages.${system}.complete.toolchain
|
||||
pkgs.cargo-llvm-cov
|
||||
pkgs.grcov
|
||||
];
|
||||
};
|
||||
devShells.coverage = pkgs.mkShell {
|
||||
inputsFrom = [ packages.default ];
|
||||
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = [
|
||||
inputs.fenix.packages.${system}.complete.toolchain
|
||||
pkgs.cargo-llvm-cov
|
||||
pkgs.grcov
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
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"
|
||||
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
||||
|
||||
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 "$@"
|
||||
@@ -14,3 +14,7 @@ rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
oqs-sys = { 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 {
|
||||
($name:ident) => { ::paste::paste!{
|
||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||
mod [< $name:snake >] {
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
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 >] {}
|
||||
|
||||
/// # 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 {
|
||||
($name:path, $($args:expr),*) => {{
|
||||
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 { };
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
template: rosenpass
|
||||
title: Rosenpass
|
||||
author:
|
||||
- Karolin Varner = Independent Researcher
|
||||
- Benjamin Lipp = Max Planck Institute for Security and Privacy (MPI-SP)
|
||||
- Karolin Varner = Rosenpass e.V., Max Planck Institute for Security and Privacy (MPI-SP)
|
||||
- Benjamin Lipp = Rosenpass e.V., Max Planck Institute for Security and Privacy (MPI-SP)
|
||||
- Wanja Zaeske
|
||||
- Lisa Schmidt = {Scientific Illustrator – \\url{mullana.de}}
|
||||
- Prabhpreet Dua
|
||||
@@ -383,9 +383,18 @@ fn load_biscuit(nct) {
|
||||
"biscuit additional data",
|
||||
spkr, sidi, sidr);
|
||||
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
|
||||
|
||||
// Find the peer and apply retransmission protection
|
||||
lookup_peer(pt.peerid);
|
||||
assert(pt.biscuit_no <= peer.biscuit_used);
|
||||
|
||||
// In December 2024, the InitConf retransmission mechanisim was redesigned
|
||||
// in a backwards-compatible way. See the changelog.
|
||||
//
|
||||
// -- 2024-11-30, Karolin Varner
|
||||
if (protocol_version!(< "0.3.0")) {
|
||||
// Ensure that the biscuit is used only once
|
||||
assert(pt.biscuit_no <= peer.biscuit_used);
|
||||
}
|
||||
|
||||
// Restore the chaining key
|
||||
ck ← pt.ck;
|
||||
@@ -501,7 +510,7 @@ LAST_UNDER_LOAD_WINDOW = 1 //seconds
|
||||
|
||||
The initiator deals with packet loss by storing the messages it sends to the responder and retransmitting them in randomized, exponentially increasing intervals until they get a response. Receiving RespHello terminates retransmission of InitHello. A Data or EmptyData message serves as acknowledgement of receiving InitConf and terminates its retransmission.
|
||||
|
||||
The responder does not need to do anything special to handle RespHello retransmission – if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
|
||||
The responder uses less complex form of the same mechanism: The responder never retransmits RespHello, instead the responder generates a new RespHello message if InitHello is retransmitted. Responder confirmation messages of completed handshake (EmptyData) messages are retransmitted by storing the most recent InitConf messages (or their hashes) and caching the associated EmptyData messages. Through this cache, InitConf retransmission is detected and the associated EmptyData message is retransmitted.
|
||||
|
||||
### Interaction with cookie reply system
|
||||
|
||||
@@ -515,6 +524,76 @@ When the responder is under load and it recieves an InitConf message, the messag
|
||||
|
||||
# Changelog
|
||||
|
||||
### 0.3.x
|
||||
|
||||
#### 2024-10-30 – InitConf retransmission updates
|
||||
|
||||
\vspace{0.5em}
|
||||
|
||||
Author: Karolin Varner
|
||||
Issue: [#331](https://github.com/rosenpass/rosenpass/issues/331)
|
||||
PR: [#513](https://github.com/rosenpass/rosenpass/pull/513)
|
||||
|
||||
\vspace{0.5em}
|
||||
|
||||
We redesign the InitConf retransmission mechanism to use a hash table. This avoids the need for the InitConf handling code to account for InitConf retransmission specifically and moves the retransmission logic into less-sensitive code.
|
||||
|
||||
Previously, we would specifically account for InitConf retransmission in the InitConf handling code by checking the biscuit number: If the biscuit number was higher than any previously seen biscuit number, then this must be a new key-exchange being completed; if the biscuit number was exactly the highest seen biscuit number, then the InitConf message is interpreted as an InitConf retransmission; in this case, an entirely new EmptyData (responder confirmation) message was generated as confirmation that InitConf has been received and that the initiator can now cease opportunistic retransmission of InitConf.
|
||||
|
||||
This mechanism was a bit brittle; even leading to a very minor but still relevant security issue, necessitating the release of Rosenpass maintenance version 0.2.2 with a [fix for the problem](https://github.com/rosenpass/rosenpass/pull/329). We had processed the InitConf message, correctly identifying that InitConf was a retransmission, but we failed to pass this information on to the rest of the code base, leading to double emission of the same "hey, we have a new cryptographic session key" even if the `outfile` option was used to integrate Rosenpass into some external application. If this event was used anywhere to reset a nonce, then this could have led to a nonce-misuse, although for the use with WireGuard this is not an issue.
|
||||
|
||||
By removing all retransmission handling code from the cryptographic protocol, we are taking structural measures to exclude the possibilities of similar issues.
|
||||
|
||||
- In section "Dealing With Package Loss" we replace
|
||||
|
||||
\begin{quote}
|
||||
The responder does not need to do anything special to handle RespHello retransmission – if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
|
||||
\end{quote}
|
||||
|
||||
by
|
||||
|
||||
\begin{quote}
|
||||
The responder uses less complex form of the same mechanism: The responder never retransmits RespHello, instead the responder generates a new RespHello message if InitHello is retransmitted. Responder confirmation messages of completed handshake (EmptyData) messages are retransmitted by storing the most recent InitConf messages (or their hashes) and caching the associated EmptyData messages. Through this cache, InitConf retransmission is detected and the associated EmptyData message is retransmitted.
|
||||
\end{quote}
|
||||
|
||||
- In function `load_biscuit` we replace
|
||||
|
||||
``` {=tex}
|
||||
\begin{quote}
|
||||
\begin{minted}{pseudorust}
|
||||
assert(pt.biscuit_no <= peer.biscuit_used);
|
||||
\end{minted}
|
||||
\end{quote}
|
||||
```
|
||||
|
||||
by
|
||||
|
||||
``` {=tex}
|
||||
\begin{quote}
|
||||
\begin{minted}{pseudorust}
|
||||
// In December 2024, the InitConf retransmission mechanisim was redesigned
|
||||
// in a backwards-compatible way. See the changelog.
|
||||
//
|
||||
// -- 2024-11-30, Karolin Varner
|
||||
if (protocol_version!(< "0.3.0")) {
|
||||
// Ensure that the biscuit is used only once
|
||||
assert(pt.biscuit_no <= peer.biscuit_used);
|
||||
}
|
||||
\end{minted}
|
||||
\end{quote}
|
||||
```
|
||||
|
||||
#### 2024-04-16 – Denial of Service Mitigation
|
||||
|
||||
\vspace{0.5em}
|
||||
|
||||
Author: Prabhpreet Dua
|
||||
Issue: [#137](https://github.com/rosenpass/rosenpass/issues/137)
|
||||
PR: [#142](https://github.com/rosenpass/rosenpass/pull/142)
|
||||
|
||||
\vspace{0.5em}
|
||||
|
||||
- Added denial of service mitigation using the WireGuard cookie mechanism
|
||||
- Added section "Denial of Service Mitigation and Cookies", and modify "Dealing with Packet Loss" for DoS cookie mechanism
|
||||
|
||||
\printbibliography
|
||||
|
||||
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" ];
|
||||
}
|
||||
89
pkgs/rosenpass.nix
Normal file
89
pkgs/rosenpass.nix
Normal file
@@ -0,0 +1,89 @@
|
||||
{ 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 ];
|
||||
|
||||
buildFeatures = [ "experiment_api" ];
|
||||
|
||||
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/
|
||||
'';
|
||||
}
|
||||
@@ -23,6 +23,12 @@ rosenpass help
|
||||
|
||||
Follow [quick start instructions](https://rosenpass.eu/#start) to get a VPN up and running.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are generally welcome. Join our [Matrix Chat](https://matrix.to/#/#rosenpass:matrix.org) if you are looking for guidance on how to contribute or for people to collaborate with.
|
||||
|
||||
We also have a – as of now, very minimal – [contributors guide](CONTRIBUTING.md).
|
||||
|
||||
## Software architecture
|
||||
|
||||
The [rosenpass tool](./src/) is written in Rust and uses liboqs[^liboqs]. The tool establishes a symmetric key and provides it to WireGuard. Since it supplies WireGuard with key through the PSK feature using Rosenpass+WireGuard is cryptographically no less secure than using WireGuard on its own ("hybrid security"). Rosenpass refreshes the symmetric key every two minutes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rosenpass"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0-dev"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -22,6 +22,14 @@ required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"]
|
||||
name = "api-integration-tests"
|
||||
required-features = ["experiment_api", "internal_testing"]
|
||||
|
||||
[[test]]
|
||||
name = "api-integration-tests-api-setup"
|
||||
required-features = ["experiment_api", "internal_testing"]
|
||||
|
||||
[[test]]
|
||||
name = "gen-ipc-msg-types"
|
||||
required-features = ["experiment_api", "internal_testing", "internal_bin_gen_ipc_msg_types"]
|
||||
|
||||
[[bench]]
|
||||
name = "handshake"
|
||||
harness = false
|
||||
@@ -43,16 +51,22 @@ env_logger = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = { workspace = true }
|
||||
clap_mangen = { workspace = true }
|
||||
mio = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
zerocopy = { workspace = true }
|
||||
home = { workspace = true }
|
||||
derive_builder = {workspace = true}
|
||||
rosenpass-wireguard-broker = {workspace = true}
|
||||
derive_builder = { 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"] }
|
||||
signal-hook = { workspace = true, optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
@@ -61,14 +75,26 @@ anyhow = { workspace = true }
|
||||
criterion = { workspace = true }
|
||||
test_bin = { workspace = true }
|
||||
stacker = { workspace = true }
|
||||
serial_test = {workspace = true}
|
||||
procspawn = {workspace = true}
|
||||
serial_test = { workspace = true }
|
||||
procspawn = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
|
||||
[features]
|
||||
enable_broker_api = ["rosenpass-wireguard-broker/enable_broker_api"]
|
||||
experiment_memfd_secret = []
|
||||
default = []
|
||||
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||
experiment_api = ["hex-literal"]
|
||||
internal_testing = []
|
||||
experiment_api = [
|
||||
"hex-literal",
|
||||
"uds",
|
||||
"command-fds",
|
||||
"rustix",
|
||||
"rosenpass-util/experiment_file_descriptor_passing",
|
||||
"rosenpass-wireguard-broker/experiment_api",
|
||||
]
|
||||
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
||||
internal_testing = []
|
||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -4,113 +4,259 @@ use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt};
|
||||
|
||||
use super::{
|
||||
PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef,
|
||||
ResponseMsgType, ResponseRef,
|
||||
ResponseMsgType, ResponseRef, SupplyKeypairRequest, SupplyKeypairResponse,
|
||||
};
|
||||
|
||||
pub trait ByteSliceRefExt: ByteSlice {
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
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()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and
|
||||
/// [RefMakerRawMsgTypeExt::parse_request_msg_type]
|
||||
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker().parse_request_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_prefix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
|
||||
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_prefix()?
|
||||
.parse_request_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_suffix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
|
||||
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_suffix()?
|
||||
.parse_request_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker().parse_response_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_prefix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_prefix()?
|
||||
.parse_response_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_suffix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_suffix()?
|
||||
.parse_response_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [RequestRef::parse] in chaining.
|
||||
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [RequestRef::parse_from_prefix] in chaining.
|
||||
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse_from_prefix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [RequestRef::parse_from_suffix] in chaining.
|
||||
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse_from_suffix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [ResponseRef::parse] in chaining.
|
||||
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [ResponseRef::parse_from_prefix] in chaining.
|
||||
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse_from_prefix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [ResponseRef::parse_from_suffix] in chaining.
|
||||
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse_from_suffix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn supply_keypair_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn supply_keypair_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_listen_socket_request_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_listen_socket_request_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_listen_socket_response(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_listen_socket_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_listen_socket_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_psk_broker_request_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_psk_broker_request_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_psk_broker_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_psk_broker_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> ByteSliceRefExt for B {}
|
||||
|
||||
@@ -4,16 +4,41 @@ use rosenpass_util::zerocopy::RefMaker;
|
||||
|
||||
use super::RawMsgType;
|
||||
|
||||
/// Trait implemented by all the Rosenpass API message types.
|
||||
///
|
||||
/// Implemented by the message as including the message envelope; e.g.
|
||||
/// [crate::api::PingRequest] but not by [crate::api::PingRequestPayload].
|
||||
pub trait Message {
|
||||
/// The payload this API message contains. E.g. this is [crate::api::PingRequestPayload] for [[crate::api::PingRequest].
|
||||
type Payload;
|
||||
/// Either [crate::api::RequestMsgType] or [crate::api::ResponseMsgType]
|
||||
type MessageClass: Into<RawMsgType>;
|
||||
/// The specific message type in the [Self::MessageClass].
|
||||
/// E.g. this is [crate::api::RequestMsgType::Ping] for [crate::api::PingRequest]
|
||||
const MESSAGE_TYPE: Self::MessageClass;
|
||||
|
||||
/// Wraps the payload into the envelope
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [crate::api::PingRequest::from_payload]
|
||||
fn from_payload(payload: Self::Payload) -> Self;
|
||||
/// Initialize the message;
|
||||
/// just sets the message type [crate::api::Envelope::msg_type].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [crate::api::PingRequest::init]
|
||||
fn init(&mut self);
|
||||
/// Initialize the message from a raw buffer: Zeroize the buffer and then call [Self::init].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [crate::api::PingRequest::setup]
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
|
||||
}
|
||||
|
||||
/// Additional convenience functions for working with [rosenpass_util::zerocopy::RefMaker]
|
||||
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
||||
}
|
||||
@@ -23,6 +48,27 @@ where
|
||||
B: ByteSliceMut,
|
||||
T: Message,
|
||||
{
|
||||
/// Initialize the message using [Message::setup].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::api::{
|
||||
/// PingRequest, ZerocopyResponseMakerSetupMessageExt, PING_REQUEST,
|
||||
/// };
|
||||
/// use rosenpass_util::zerocopy::RefMaker;
|
||||
/// use std::mem::size_of;
|
||||
///
|
||||
/// let mut buf = [0u8; { size_of::<PingRequest>() }];
|
||||
///
|
||||
/// let rm = RefMaker::<&mut [u8], PingRequest>::new(&mut buf);
|
||||
/// let msg: zerocopy::Ref<_, PingRequest> = rm.setup_msg()?;
|
||||
///
|
||||
/// let t = msg.msg_type; // Deal with unaligned read
|
||||
/// assert_eq!(t, PING_REQUEST);
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
||||
T::setup(self.into_buf())
|
||||
}
|
||||
|
||||
@@ -14,24 +14,60 @@ pub const PING_REQUEST: RawMsgType =
|
||||
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"));
|
||||
|
||||
/// Message properties global to the message type
|
||||
pub trait MessageAttributes {
|
||||
/// Get the size of the message
|
||||
///
|
||||
/// # Exampleds
|
||||
fn message_size(&self) -> usize;
|
||||
}
|
||||
|
||||
/// API request message types as an enum
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum RequestMsgType {
|
||||
Ping,
|
||||
SupplyKeypair,
|
||||
AddListenSocket,
|
||||
AddPskBroker,
|
||||
}
|
||||
|
||||
/// API response messages types as an enum
|
||||
#[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>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +76,9 @@ 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>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +90,9 @@ impl TryFrom<RawMsgType> for RequestMsgType {
|
||||
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)),
|
||||
})
|
||||
}
|
||||
@@ -61,6 +103,9 @@ impl From<RequestMsgType> for RawMsgType {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +117,9 @@ impl TryFrom<RawMsgType> for ResponseMsgType {
|
||||
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)),
|
||||
})
|
||||
}
|
||||
@@ -82,12 +130,24 @@ impl From<ResponseMsgType> for RawMsgType {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [RawMsgType].
|
||||
///
|
||||
/// We are using an extension trait rather than just using methods
|
||||
/// because [RawMsgType] is a type alias, so we can not define methods
|
||||
/// on it.
|
||||
pub trait RawMsgTypeExt {
|
||||
/// Try to convert this to a [RequestMsgType]; alias for the appropriate [TryFrom]
|
||||
/// implementation
|
||||
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
|
||||
/// Try to convert this to a [ResponseMsgType]; alias for the appropriate [TryFrom]
|
||||
/// implementation
|
||||
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
||||
}
|
||||
|
||||
@@ -101,8 +161,11 @@ impl RawMsgTypeExt for RawMsgType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
|
||||
pub trait RefMakerRawMsgTypeExt {
|
||||
/// Parse a request message type from bytes
|
||||
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
||||
/// Parse a response message type from bytes
|
||||
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! Boring, repetitive code related to message parsing for the API.
|
||||
//!
|
||||
//! Most of this should be automatically generated though some derive macro at some point.
|
||||
|
||||
mod byte_slice_ext;
|
||||
mod message_trait;
|
||||
mod message_type;
|
||||
|
||||
@@ -3,10 +3,14 @@ use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
|
||||
|
||||
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
||||
|
||||
/// Size required to fit any message in binary form
|
||||
/// Size required to fit any request message in binary form
|
||||
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
|
||||
/// Size required to fit any response message in binary form
|
||||
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
|
||||
/// Maximum number of file descriptors that can be sent in a request.
|
||||
pub const MAX_REQUEST_FDS: usize = 2;
|
||||
|
||||
/// Message envelope for API messages
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
@@ -16,9 +20,12 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
pub payload: M,
|
||||
}
|
||||
|
||||
/// Message envelope for API requests
|
||||
pub type RequestEnvelope<M> = Envelope<M>;
|
||||
/// Message envelope for API responses
|
||||
pub type ResponseEnvelope<M> = Envelope<M>;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct PingRequestPayload {
|
||||
@@ -26,9 +33,11 @@ pub struct PingRequestPayload {
|
||||
pub echo: [u8; 256],
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
||||
|
||||
impl PingRequest {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(echo: [u8; 256]) -> Self {
|
||||
Self::from_payload(PingRequestPayload { echo })
|
||||
}
|
||||
@@ -57,6 +66,7 @@ impl Message for PingRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct PingResponsePayload {
|
||||
@@ -64,9 +74,11 @@ pub struct PingResponsePayload {
|
||||
pub echo: [u8; 256],
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
||||
|
||||
impl PingResponse {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(echo: [u8; 256]) -> Self {
|
||||
Self::from_payload(PingResponsePayload { echo })
|
||||
}
|
||||
@@ -94,3 +106,293 @@ impl Message for PingResponse {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct SupplyKeypairRequestPayload {}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
||||
|
||||
impl Default for SupplyKeypairRequest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SupplyKeypairRequest {
|
||||
#[allow(missing_docs)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod supply_keypair_response_status {
|
||||
#[allow(missing_docs)]
|
||||
pub const OK: u128 = 0;
|
||||
#[allow(missing_docs)]
|
||||
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
||||
/// TODO: This is not actually part of the API. Remove.
|
||||
#[allow(missing_docs)]
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
#[allow(missing_docs)]
|
||||
pub const INVALID_REQUEST: u128 = 3;
|
||||
/// TODO: Deprectaed, remove
|
||||
#[allow(missing_docs)]
|
||||
pub const IO_ERROR: u128 = 4;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct SupplyKeypairResponsePayload {
|
||||
#[allow(missing_docs)]
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
||||
|
||||
impl SupplyKeypairResponse {
|
||||
#[allow(missing_docs)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddListenSocketRequestPayload {}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
||||
|
||||
impl Default for AddListenSocketRequest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddListenSocketRequest {
|
||||
#[allow(missing_docs)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod add_listen_socket_response_status {
|
||||
#[allow(missing_docs)]
|
||||
pub const OK: u128 = 0;
|
||||
#[allow(missing_docs)]
|
||||
pub const INVALID_REQUEST: u128 = 1;
|
||||
#[allow(missing_docs)]
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddListenSocketResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
||||
|
||||
impl AddListenSocketResponse {
|
||||
#[allow(missing_docs)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddPskBrokerRequestPayload {}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
||||
|
||||
impl Default for AddPskBrokerRequest {
|
||||
#[allow(missing_docs)]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddPskBrokerRequest {
|
||||
#[allow(missing_docs)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod add_psk_broker_response_status {
|
||||
#[allow(missing_docs)]
|
||||
pub const OK: u128 = 0;
|
||||
#[allow(missing_docs)]
|
||||
pub const INVALID_REQUEST: u128 = 1;
|
||||
#[allow(missing_docs)]
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddPskBrokerResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
||||
|
||||
impl AddPskBrokerResponse {
|
||||
#[allow(missing_docs)]
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,27 +4,69 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
||||
|
||||
/// Helper for producing API message request references, [RequestRef].
|
||||
///
|
||||
/// This is to [RequestRef] as [rosenpass_util::zerocopy::RefMaker] is to
|
||||
/// [zerocopy::Ref].
|
||||
struct RequestRefMaker<B> {
|
||||
buf: B,
|
||||
msg_type: RequestMsgType,
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> RequestRef<B> {
|
||||
/// Produce a [RequestRef] from a raw message buffer,
|
||||
/// reading the type from the buffer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zerocopy::AsBytes;
|
||||
///
|
||||
/// use rosenpass::api::{PingRequest, RequestRef, RequestMsgType};
|
||||
///
|
||||
/// let msg = PingRequest::new([0u8; 256]);
|
||||
///
|
||||
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
|
||||
/// let typ = msg.msg_type;
|
||||
/// assert_eq!(typ, rosenpass::api::PING_REQUEST);
|
||||
///
|
||||
/// let buf = msg.as_bytes();
|
||||
/// let msg_ref = RequestRef::parse(buf)?;
|
||||
/// assert!(matches!(msg_ref, RequestRef::Ping(_)));
|
||||
///
|
||||
/// assert_eq!(msg_ref.message_type(), RequestMsgType::Ping);
|
||||
///
|
||||
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
||||
}
|
||||
|
||||
/// Get the message type [Self] contains
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +77,24 @@ impl<B> From<Ref<B, PingRequest>> for RequestRef<B> {
|
||||
}
|
||||
}
|
||||
|
||||
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()?;
|
||||
@@ -48,6 +108,15 @@ impl<B: ByteSlice> RequestRefMaker<B> {
|
||||
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()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,17 +149,29 @@ impl<B: ByteSlice> RequestRefMaker<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a API message response, typed as an enum.
|
||||
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,
|
||||
{
|
||||
/// Access the byte data of this reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::parse].
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,9 +180,13 @@ impl<B> RequestRef<B>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
{
|
||||
/// Access the byte data of this reference; mutably
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,23 +6,29 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
use super::{Message, PingRequest, PingResponse};
|
||||
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
||||
|
||||
/// Extension trait for [Message]s that are requests messages
|
||||
pub trait RequestMsg: Sized + Message {
|
||||
/// The response message belonging to this request message
|
||||
type ResponseMsg: ResponseMsg;
|
||||
|
||||
/// Construct a response make for this particular message
|
||||
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
||||
buf.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Setup a response maker (through [Message::setup]) for this request message type
|
||||
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).setup_msg()
|
||||
}
|
||||
|
||||
/// Setup a response maker from a buffer prefix (through [Message::setup]) for this request message type
|
||||
fn setup_response_from_prefix<B: ByteSliceMut>(
|
||||
buf: B,
|
||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||
}
|
||||
|
||||
/// Setup a response maker from a buffer suffix (through [Message::setup]) for this request message type
|
||||
fn setup_response_from_suffix<B: ByteSliceMut>(
|
||||
buf: B,
|
||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
@@ -30,6 +36,7 @@ pub trait RequestMsg: Sized + Message {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [Message]s that are response messages
|
||||
pub trait ResponseMsg: Message {
|
||||
type RequestMsg: RequestMsg;
|
||||
}
|
||||
@@ -42,10 +49,54 @@ impl ResponseMsg for PingResponse {
|
||||
type RequestMsg = PingRequest;
|
||||
}
|
||||
|
||||
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
||||
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;
|
||||
}
|
||||
|
||||
/// Request and response for the [crate::api::RequestMsgType::Ping] message type
|
||||
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
||||
/// Request and response for the [crate::api::RequestMsgType::SupplyKeypair] message type
|
||||
pub type SupplyKeypairPair<B1, B2> = (
|
||||
Ref<B1, super::SupplyKeypairRequest>,
|
||||
Ref<B2, super::SupplyKeypairResponse>,
|
||||
);
|
||||
/// Request and response for the [crate::api::RequestMsgType::AddListenSocket] message type
|
||||
pub type AddListenSocketPair<B1, B2> = (
|
||||
Ref<B1, super::AddListenSocketRequest>,
|
||||
Ref<B2, super::AddListenSocketResponse>,
|
||||
);
|
||||
/// Request and response for the [crate::api::RequestMsgType::AddPskBroker] message type
|
||||
pub type AddPskBrokerPair<B1, B2> = (
|
||||
Ref<B1, super::AddPskBrokerRequest>,
|
||||
Ref<B2, super::AddPskBrokerResponse>,
|
||||
);
|
||||
|
||||
/// A pair of references to messages; request and response each.
|
||||
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> {
|
||||
@@ -54,11 +105,30 @@ impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
{
|
||||
/// Returns a tuple to both the request and the response message
|
||||
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
||||
match self {
|
||||
Self::Ping((req, res)) => {
|
||||
@@ -66,13 +136,30 @@ where
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the request message
|
||||
pub fn request(&self) -> RequestRef<&[u8]> {
|
||||
self.both().0
|
||||
}
|
||||
|
||||
/// Returns the response message
|
||||
pub fn response(&self) -> ResponseRef<&[u8]> {
|
||||
self.both().1
|
||||
}
|
||||
@@ -83,6 +170,7 @@ where
|
||||
B1: ByteSliceMut,
|
||||
B2: ByteSliceMut,
|
||||
{
|
||||
/// Returns a mutable tuple to both the request and the response message
|
||||
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
||||
match self {
|
||||
Self::Ping((req, res)) => {
|
||||
@@ -90,13 +178,30 @@ where
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the request message, mutably
|
||||
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
||||
self.both_mut().0
|
||||
}
|
||||
|
||||
/// Returns the response message, mutably
|
||||
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
||||
self.both_mut().1
|
||||
}
|
||||
|
||||
@@ -5,27 +5,72 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
||||
|
||||
/// Helper for producing API message response references, [ResponseRef].
|
||||
///
|
||||
/// This is to [ResponseRef] as [rosenpass_util::zerocopy::RefMaker] is to
|
||||
/// [zerocopy::Ref].
|
||||
struct ResponseRefMaker<B> {
|
||||
/// Buffer we are referencing
|
||||
buf: B,
|
||||
/// Message type we are producing
|
||||
msg_type: ResponseMsgType,
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> ResponseRef<B> {
|
||||
/// Produce a [ResponseRef] from a raw message buffer,
|
||||
/// reading the type from the buffer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zerocopy::AsBytes;
|
||||
///
|
||||
/// use rosenpass::api::{PingResponse, ResponseRef, ResponseMsgType};
|
||||
/// // Produce the original PingResponse
|
||||
/// let msg = PingResponse::new([0u8; 256]);
|
||||
///
|
||||
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
|
||||
/// let typ = msg.msg_type;
|
||||
/// assert_eq!(typ, rosenpass::api::PING_RESPONSE);
|
||||
///
|
||||
/// // Parse as a message type
|
||||
/// let buf = msg.as_bytes();
|
||||
/// let msg_ref = ResponseRef::parse(buf)?;
|
||||
/// assert!(matches!(msg_ref, ResponseRef::Ping(_)));
|
||||
///
|
||||
/// // Buffers and message types of course match what we expect
|
||||
/// assert_eq!(msg_ref.message_type(), ResponseMsgType::Ping);
|
||||
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
||||
}
|
||||
|
||||
/// Get the message type [Self] contains
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +81,24 @@ impl<B> From<Ref<B, PingResponse>> for ResponseRef<B> {
|
||||
}
|
||||
}
|
||||
|
||||
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()?;
|
||||
@@ -49,6 +112,15 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
|
||||
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()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,17 +153,29 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a API message response, typed.
|
||||
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,
|
||||
{
|
||||
/// Access the byte data of this reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::parse].
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,9 +184,13 @@ impl<B> ResponseRef<B>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
{
|
||||
/// Access the byte data of this reference; mutably
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,186 @@
|
||||
use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair};
|
||||
use std::{collections::VecDeque, os::fd::OwnedFd};
|
||||
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||
|
||||
use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair};
|
||||
|
||||
/// The rosenpass API implementation functions.
|
||||
///
|
||||
/// Implemented by [crate::api::ApiHandler].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
pub trait Server {
|
||||
fn ping(&mut self, req: &PingRequest, res: &mut PingResponse) -> anyhow::Result<()>;
|
||||
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
||||
///
|
||||
/// It merely takes a buffer and returns that same buffer.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
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.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
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 key
|
||||
/// key exchanges using the Rosenpass protocol.
|
||||
/// cryptographic key exchanges via the Rosenpass protocol.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
fn add_listen_socket(
|
||||
&mut self,
|
||||
req: &super::AddListenSocketRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::AddListenSocketResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Supply a new PSK broker listen socket through file descriptor passing via the API
|
||||
///
|
||||
/// This implements the handler for the [crate::api::RequestMsgType::AddPskBroker] API message.
|
||||
///
|
||||
/// # File descriptors
|
||||
///
|
||||
/// 1. The listen socket; must be backed by a unix domain stream socket
|
||||
///
|
||||
/// # API Return Status
|
||||
///
|
||||
/// 1. [crate::api::add_psk_broker_response_status::OK] - Indicates success
|
||||
/// 2. [crate::api::add_psk_broker_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||
/// - Missing file descriptors for public key
|
||||
/// - Invalid file descriptor type
|
||||
/// 3. [crate::api::add_psk_broker_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 transmit
|
||||
/// cryptographic keys exchanged to WireGuard.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
fn add_psk_broker(
|
||||
&mut self,
|
||||
req: &super::AddPskBrokerRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::AddPskBrokerResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Similar to [Self::handle_message], but takes a [RequestResponsePair]
|
||||
/// instead of taking to separate byte buffers.
|
||||
///
|
||||
/// I.e. this function uses the explicit type tag encoded in [RequestResponsePair]
|
||||
/// rather than reading the type tag from the request buffer.
|
||||
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, res),
|
||||
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, res: ResBuf) -> anyhow::Result<usize>
|
||||
/// Called by [crate::api::mio::MioConnection] when a new API request was received.
|
||||
///
|
||||
/// The parameters are:
|
||||
///
|
||||
/// - `req` – A buffer containing the request
|
||||
/// - `res_fds` – A list of file descriptors received during the API call (i.e. this is used
|
||||
/// with unix socket file descriptor passing)
|
||||
/// - `res` – The buffer to store the response in.
|
||||
fn handle_message<ReqBuf, ResBuf>(
|
||||
&mut self,
|
||||
req: ReqBuf,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: ResBuf,
|
||||
) -> anyhow::Result<usize>
|
||||
where
|
||||
ReqBuf: ByteSlice,
|
||||
ResBuf: ByteSliceMut,
|
||||
@@ -31,10 +193,25 @@ pub trait Server {
|
||||
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)?;
|
||||
self.dispatch(&mut pair, req_fds)?;
|
||||
|
||||
let res_len = pair.request().bytes().len();
|
||||
let res_len = pair.response().bytes().len();
|
||||
Ok(res_len)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::config::Rosenpass as RosenpassConfig;
|
||||
|
||||
use super::config::ApiConfig;
|
||||
|
||||
/// Additional command line arguments for the API
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ApiCli {
|
||||
@@ -27,10 +28,14 @@ pub struct ApiCli {
|
||||
}
|
||||
|
||||
impl ApiCli {
|
||||
/// Copy the parameters set here into the [RosenpassConfig].
|
||||
/// Forwards to [Self::apply_to_api_config]:
|
||||
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
||||
self.apply_to_api_config(&mut cfg.api)
|
||||
}
|
||||
|
||||
/// Fills the values from [ApiConfig::listen_path], [ApiConfig::listen_fd], and
|
||||
/// [ApiConfig::stream_fd] with the values from [Self]
|
||||
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);
|
||||
|
||||
@@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app_server::AppServer;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
/// Configuration options for the Rosenpass API
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
|
||||
pub struct ApiConfig {
|
||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||
/// connections on
|
||||
@@ -23,6 +24,10 @@ pub struct ApiConfig {
|
||||
}
|
||||
|
||||
impl ApiConfig {
|
||||
/// Construct appropriate [UnixListener]s for each of the API
|
||||
/// listeners and connections configured in [Self] and invoke
|
||||
/// [AppServer::add_api_listener] for each to add them to the
|
||||
/// [AppServer].
|
||||
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)?)?;
|
||||
@@ -38,4 +43,14 @@ impl ApiConfig {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sum of all the API sources configured in here
|
||||
pub fn count_api_sources(&self) -> usize {
|
||||
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
||||
}
|
||||
|
||||
/// Checks if [Self::count_api_sources] is greater than zero
|
||||
pub fn has_api_sources(&self) -> bool {
|
||||
self.count_api_sources() > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use rosenpass_to::{ops::copy_slice, To};
|
||||
|
||||
use crate::protocol::CryptoServer;
|
||||
|
||||
use super::Server as ApiServer;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CryptoServerApiState {
|
||||
_dummy: (),
|
||||
}
|
||||
|
||||
impl CryptoServerApiState {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self { _dummy: () }
|
||||
}
|
||||
|
||||
pub fn acquire_backend<'a>(
|
||||
&'a mut self,
|
||||
crypto: &'a mut Option<CryptoServer>,
|
||||
) -> CryptoServerApiHandler<'a> {
|
||||
let state = self;
|
||||
CryptoServerApiHandler { state, crypto }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CryptoServerApiHandler<'a> {
|
||||
#[allow(unused)] // TODO: Remove
|
||||
crypto: &'a mut Option<CryptoServer>,
|
||||
#[allow(unused)] // TODO: Remove
|
||||
state: &'a mut CryptoServerApiState,
|
||||
}
|
||||
|
||||
impl<'a> ApiServer for CryptoServerApiHandler<'a> {
|
||||
fn ping(
|
||||
&mut self,
|
||||
req: &super::PingRequest,
|
||||
res: &mut super::PingResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
let (req, res) = (&req.payload, &mut res.payload);
|
||||
copy_slice(&req.echo).to(&mut res.echo);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,205 @@
|
||||
use mio::{net::UnixStream, Interest};
|
||||
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::Server, app_server::MioTokenDispenser, protocol::CryptoServer};
|
||||
use crate::api::MAX_REQUEST_FDS;
|
||||
use crate::{api::Server, app_server::AppServer};
|
||||
|
||||
use super::super::{CryptoServerApiState, MAX_REQUEST_LEN, MAX_RESPONSE_LEN};
|
||||
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)]
|
||||
/// Represents a single connection with an API client.
|
||||
/// Includes the necessary buffers, the [ApiHandler],
|
||||
/// and the [UnixStream] that is used for communication.
|
||||
pub struct MioConnection {
|
||||
io: UnixStream,
|
||||
mio_token: mio::Token,
|
||||
invalid_read: bool,
|
||||
read_buffer: LengthPrefixDecoder<[u8; MAX_REQUEST_LEN]>,
|
||||
write_buffer: LengthPrefixEncoder<[u8; MAX_RESPONSE_LEN]>,
|
||||
api_state: CryptoServerApiState,
|
||||
buffers: Option<MioConnectionBuffers>,
|
||||
api_handler: ApiHandler,
|
||||
}
|
||||
|
||||
impl MioConnection {
|
||||
pub fn new(
|
||||
mut io: UnixStream,
|
||||
registry: &mio::Registry,
|
||||
token_dispenser: &mut MioTokenDispenser, // TODO: We should actually start using tokens…
|
||||
) -> std::io::Result<Self> {
|
||||
registry.register(
|
||||
&mut io,
|
||||
token_dispenser.dispense(),
|
||||
Interest::READABLE | Interest::WRITABLE,
|
||||
)?;
|
||||
/// Construct a new [Self] for the given app server from the unix socket stream
|
||||
/// to communicate on.
|
||||
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([0u8; MAX_REQUEST_LEN]);
|
||||
let write_buffer = LengthPrefixEncoder::from_buffer([0u8; MAX_RESPONSE_LEN]);
|
||||
let api_state = CryptoServerApiState::new();
|
||||
Ok(Self {
|
||||
io,
|
||||
invalid_read,
|
||||
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,
|
||||
api_state,
|
||||
read_fd_buffer,
|
||||
});
|
||||
let api_state = ApiHandler::new();
|
||||
Ok(Self {
|
||||
io,
|
||||
mio_token,
|
||||
invalid_read,
|
||||
buffers,
|
||||
api_handler: api_state,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn poll(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
|
||||
self.flush_write_buffer()?;
|
||||
if self.write_buffer.exhausted() {
|
||||
self.recv(crypto)?;
|
||||
}
|
||||
/// Checks if this unix stream should be closed by the enclosing
|
||||
/// structure
|
||||
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
|
||||
}
|
||||
|
||||
/// Close and deregister this particular API connection
|
||||
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
||||
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is *exclusively* called by recv if the read_buffer holds a message
|
||||
fn handle_incoming_message(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
|
||||
// Unwrap is allowed because recv() confirms before the call that a message was
|
||||
// received
|
||||
let req = self.read_buffer.message().unwrap().unwrap();
|
||||
/// Retrieve the mio token
|
||||
pub fn mio_token(&self) -> mio::Token {
|
||||
self.mio_token
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The API should not return anyhow::Result
|
||||
let response_len = self
|
||||
.api_state
|
||||
.acquire_backend(crypto)
|
||||
.handle_message(req, self.write_buffer.buffer_bytes_mut())?;
|
||||
self.read_buffer.zeroize(); // clear for new message to read
|
||||
self.write_buffer
|
||||
.restart_write_with_new_message(response_len)?;
|
||||
/// We require references to both [MioConnection] and to the [AppServer] that contains it.
|
||||
pub trait MioConnectionContext {
|
||||
/// Reference to the [MioConnection] we are focusing on
|
||||
fn mio_connection(&self) -> &MioConnection;
|
||||
/// Reference to the [AppServer] that contains the [Self::mio_connection]
|
||||
fn app_server(&self) -> &AppServer;
|
||||
/// Mutable reference to the [MioConnection] we are focusing on
|
||||
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
||||
/// Mutable reference to the [AppServer] that contains the [Self::mio_connection]
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
|
||||
/// Called by [AppServer::poll] regularly to process any incoming (and outgoing) API messages
|
||||
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
|
||||
|
||||
self.flush_write_buffer()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush_write_buffer(&mut self) -> anyhow::Result<()> {
|
||||
if self.write_buffer.exhausted() {
|
||||
return Ok(());
|
||||
/// Called by [Self::poll] to process incoming messages
|
||||
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(()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Called by [Self::poll] to write data in the send buffer to the unix stream
|
||||
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 {
|
||||
use lpe_encoder::WriteToIoReturn as Ret;
|
||||
use std::io::ErrorKind as K;
|
||||
let conn = self.mio_connection_mut();
|
||||
let bufs = conn.buffers.as_mut().unwrap();
|
||||
|
||||
match self
|
||||
.write_buffer
|
||||
.write_to_stdio(&self.io)
|
||||
.io_err_kind_hint()
|
||||
{
|
||||
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, .. }) => {
|
||||
self.write_buffer.zeroize(); // clear for new message to write
|
||||
break;
|
||||
write_buf.zeroize(); // clear for new message to write
|
||||
break Ok(Some(()));
|
||||
}
|
||||
|
||||
// Would block
|
||||
Ok(Ret {
|
||||
bytes_written: 0, ..
|
||||
}) => break,
|
||||
Err((_e, K::WouldBlock)) => break,
|
||||
}) => break Ok(None),
|
||||
Err((_e, K::WouldBlock)) => break Ok(None),
|
||||
|
||||
// Just continue
|
||||
Ok(_) => continue, /* Ret { bytes_written > 0, done = false } acc. to previous cases*/
|
||||
@@ -107,22 +209,32 @@ impl MioConnection {
|
||||
Err((e, _ek)) => Err(e)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
|
||||
if !self.write_buffer.exhausted() || self.invalid_read {
|
||||
return Ok(());
|
||||
/// Called by [Self::poll] to check for messages to receive
|
||||
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
||||
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
loop {
|
||||
use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret};
|
||||
use std::io::ErrorKind as K;
|
||||
use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret};
|
||||
use std::io::ErrorKind as K;
|
||||
|
||||
match self
|
||||
.read_buffer
|
||||
.read_from_stdio(&self.io)
|
||||
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
|
||||
@@ -130,38 +242,101 @@ impl MioConnection {
|
||||
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:?}");
|
||||
// TODO: We should properly close down the socket in this case, but to do that,
|
||||
// we need to have the facilities in the Rosenpass IO handling system to close
|
||||
// open connections.
|
||||
// Just leaving the API connections dangling for now.
|
||||
// This should be fixed for non-experimental use of the API.
|
||||
self.invalid_read = true;
|
||||
break;
|
||||
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,
|
||||
Err((_, Some(K::WouldBlock))) => break,
|
||||
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), _)) => Err(e)?,
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
self.handle_incoming_message(crypto)?;
|
||||
break; // Handle just one message, leave some room for other IO handlers
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// Forwards to [MioConnection::mio_token]
|
||||
fn mio_token(&self) -> mio::Token {
|
||||
self.mio_connection().mio_token()
|
||||
}
|
||||
|
||||
/// Forwards to [MioConnection::should_close]
|
||||
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 {}
|
||||
|
||||
/// Every [MioConnectionContext] is also a [ApiHandlerContext]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,143 @@
|
||||
use std::io;
|
||||
use std::{borrow::BorrowMut, io};
|
||||
|
||||
use mio::net::{UnixListener, UnixStream};
|
||||
|
||||
use rosenpass_util::{io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW};
|
||||
use rosenpass_util::{
|
||||
functional::ApplyExt, io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW,
|
||||
};
|
||||
|
||||
use crate::{app_server::MioTokenDispenser, protocol::CryptoServer};
|
||||
use crate::app_server::{AppServer, AppServerIoSource};
|
||||
|
||||
use super::MioConnection;
|
||||
use super::{MioConnection, MioConnectionContext};
|
||||
|
||||
/// This is in essence a unix listener for API connections.
|
||||
///
|
||||
/// It contains a number of [UnixListener]s and the associated [MioConnection]s encapsulating [mio::net::UnixListener]s.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MioManager {
|
||||
listeners: Vec<UnixListener>,
|
||||
connections: Vec<MioConnection>,
|
||||
connections: Vec<Option<MioConnection>>,
|
||||
}
|
||||
|
||||
/// Points at a particular source of IO events inside [MioManager]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum MioManagerIoSource {
|
||||
// Source of IO events is the Nth unix socket listener (see [MioManager::listeners])
|
||||
Listener(usize),
|
||||
// Source of IO events is the Nth unix socket listener (see [MioManager::connections])
|
||||
Connection(usize),
|
||||
}
|
||||
|
||||
impl MioManager {
|
||||
/// Construct an empty [Self]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Focus in on a particular [MioConnection] inside a [MioManager]
|
||||
///
|
||||
/// This is mainly used to implement [MioConnectionContext].
|
||||
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
||||
/// [MioConnectionContext] to access the [MioManager] instance and [AppServer]
|
||||
ctx: &'a mut T,
|
||||
/// Index of the connection referenced to by [Self]
|
||||
conn_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
||||
/// Produce a MioConnectionContext from the [MioConnectionContext] and the connection index
|
||||
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
||||
Self { ctx, conn_idx }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MioManagerContext {
|
||||
/// Reference to the [MioManager]
|
||||
fn mio_manager(&self) -> &MioManager;
|
||||
/// Reference to the [MioManager], mutably
|
||||
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
||||
/// Reference to the [AppServer] this [MioManager] is associated with
|
||||
fn app_server(&self) -> &AppServer;
|
||||
/// Mutable reference to the [AppServer] this [MioManager] is associated with
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
|
||||
/// Add a new [UnixListener] to listen for API connections on
|
||||
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);
|
||||
|
||||
pub fn add_listener(
|
||||
&mut self,
|
||||
mut listener: UnixListener,
|
||||
registry: &mio::Registry,
|
||||
token_dispenser: &mut MioTokenDispenser,
|
||||
) -> io::Result<()> {
|
||||
registry.register(&mut listener, token_dispenser.dispense(), MIO_RW)?;
|
||||
self.listeners.push(listener);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_connection(
|
||||
&mut self,
|
||||
connection: UnixStream,
|
||||
registry: &mio::Registry,
|
||||
token_dispenser: &mut MioTokenDispenser,
|
||||
) -> io::Result<()> {
|
||||
let connection = MioConnection::new(connection, registry, token_dispenser)?;
|
||||
self.connections.push(connection);
|
||||
/// Add a new connection to an API client
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn poll(
|
||||
&mut self,
|
||||
crypto: &mut Option<CryptoServer>,
|
||||
registry: &mio::Registry,
|
||||
token_dispenser: &mut MioTokenDispenser,
|
||||
) -> anyhow::Result<()> {
|
||||
self.accept_connections(registry, token_dispenser)?;
|
||||
self.poll_connections(crypto)?;
|
||||
/// Poll a particular [MioManagerIoSource] in this [MioManager]
|
||||
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 accept_connections(
|
||||
&mut self,
|
||||
registry: &mio::Registry,
|
||||
token_dispenser: &mut MioTokenDispenser,
|
||||
) -> io::Result<()> {
|
||||
for idx in 0..self.listeners.len() {
|
||||
self.accept_from(idx, registry, token_dispenser)?;
|
||||
/// Check for new connections and poll all the [MioConnectionContext]s managed by [Self]
|
||||
fn poll(&mut self) -> anyhow::Result<()> {
|
||||
self.accept_connections()?;
|
||||
self.poll_connections()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check all the [UnixListener]s managed by this [MioManager] for new connections
|
||||
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,
|
||||
registry: &mio::Registry,
|
||||
token_dispenser: &mut MioTokenDispenser,
|
||||
) -> io::Result<()> {
|
||||
/// Check a particular [UnixListener] managed by this for new connections.
|
||||
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.listeners[idx].accept())? {
|
||||
match nonblocking_handle_io_errors(|| self.mio_manager().listeners[idx].accept())? {
|
||||
None => break,
|
||||
Some((conn, _addr)) => {
|
||||
self.add_connection(conn, registry, token_dispenser)?;
|
||||
self.add_connection(conn)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -84,10 +145,54 @@ impl MioManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_connections(&mut self, crypto: &mut Option<CryptoServer>) -> anyhow::Result<()> {
|
||||
for conn in self.connections.iter_mut() {
|
||||
conn.poll(crypto)?
|
||||
/// Call [MioConnectionContext::poll] on all the [MioConnection]s in This
|
||||
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
||||
for idx in 0..self.mio_manager().connections.len() {
|
||||
self.poll_particular_connection(idx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call [MioConnectionContext::poll] on a particular connection
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
mod boilerplate;
|
||||
mod crypto_server_api_handler;
|
||||
//! The bulk code relating to the Rosenpass unix socket API
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
#![doc = "```ignore"]
|
||||
#![doc = include_str!("../../tests/api-integration-tests-api-setup.rs")]
|
||||
#![doc = "```"]
|
||||
|
||||
mod api_handler;
|
||||
mod boilerplate;
|
||||
|
||||
pub use api_handler::*;
|
||||
pub use boilerplate::*;
|
||||
pub use crypto_server_api_handler::*;
|
||||
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ use heck::ToShoutySnakeCase;
|
||||
|
||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||
|
||||
/// Recursively calculate a concrete hash value for an API message type
|
||||
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),
|
||||
@@ -10,6 +11,7 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]
|
||||
}
|
||||
}
|
||||
|
||||
/// Print a hash literal for pasting into the Rosenpass source code
|
||||
fn print_literal(path: &[&str]) -> Result<()> {
|
||||
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
||||
let (last, prefix) = path.split_last().context("developer error!")?;
|
||||
@@ -33,6 +35,8 @@ fn print_literal(path: &[&str]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tree of domain separators where each leaf represents
|
||||
/// an API message ID
|
||||
#[derive(Debug, Clone)]
|
||||
enum Tree {
|
||||
Branch(String, Vec<Tree>),
|
||||
@@ -68,6 +72,7 @@ impl Tree {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for generating hash-based message IDs for the IPC API
|
||||
fn main() -> Result<()> {
|
||||
let tree = Tree::Branch(
|
||||
"Rosenpass IPC API".to_owned(),
|
||||
@@ -76,6 +81,12 @@ fn main() -> Result<()> {
|
||||
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()),
|
||||
],
|
||||
)],
|
||||
);
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use anyhow::{bail, ensure};
|
||||
//! Contains the code used to parse command line parameters for rosenpass.
|
||||
//!
|
||||
//! [CliArgs::run] is called by the rosenpass main function and contains the
|
||||
//! bulk of our boostrapping code while the main function just sets up the basic environment
|
||||
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use clap::{Parser, Subcommand};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
@@ -16,19 +21,52 @@ use crate::protocol::{SPk, SSk, SymKey};
|
||||
|
||||
use super::config;
|
||||
|
||||
/// struct holding all CLI arguments for `clap` crate to parse
|
||||
#[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,
|
||||
};
|
||||
|
||||
/// How to reach a WireGuard PSK Broker
|
||||
#[derive(Debug)]
|
||||
pub enum BrokerInterface {
|
||||
/// The PSK Broker is listening on a unix socket at the given path
|
||||
Socket(PathBuf),
|
||||
/// The PSK Broker broker is already connected to this process; a
|
||||
/// unix socket stream can be reached at the given file descriptor.
|
||||
///
|
||||
/// This is generally used with file descriptor passing.
|
||||
FileDescriptor(i32),
|
||||
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
|
||||
/// and hand one end of the socketpair to the broker process via file
|
||||
/// descriptor passing to the subprocess
|
||||
SocketPair,
|
||||
}
|
||||
|
||||
/// Command line arguments to the Rosenpass binary.
|
||||
///
|
||||
/// Used for parsing with [clap].
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about)]
|
||||
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
quiet: bool,
|
||||
|
||||
@@ -36,11 +74,50 @@ pub struct CliArgs {
|
||||
#[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,
|
||||
|
||||
/// The subcommand to be invoked
|
||||
#[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 {
|
||||
/// Apply the command line parameters to the Rosenpass configuration struct
|
||||
///
|
||||
/// Generally the flow of control here is that all the command line parameters
|
||||
/// are merged into the configuration file to avoid much code duplication.
|
||||
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_config(_cfg)?;
|
||||
@@ -58,32 +135,57 @@ impl CliArgs {
|
||||
return Some(log::LevelFilter::Info);
|
||||
}
|
||||
if self.quiet {
|
||||
return Some(log::LevelFilter::Error);
|
||||
return Some(log::LevelFilter::Warn);
|
||||
}
|
||||
if let Some(level_filter) = self.log_level {
|
||||
return Some(level_filter);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Return the WireGuard PSK broker interface configured.
|
||||
///
|
||||
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the WireGuard PSK broker interface configured.
|
||||
///
|
||||
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// represents a command specified via CLI
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum CliCommand {
|
||||
/// Start Rosenpass in server mode and carry on with the key exchange
|
||||
/// Start Rosenpass key exchanges based on a configuration file
|
||||
///
|
||||
/// This will parse the configuration file and perform the key exchange
|
||||
/// with the specified peers. If a peer's endpoint is specified, this
|
||||
/// Rosenpass instance will try to initiate a key exchange with the peer,
|
||||
/// otherwise only initiation attempts from the peer will be responded to.
|
||||
/// This will parse the configuration file and perform key exchanges with
|
||||
/// the specified peers. If a peer's endpoint is specified, this Rosenpass
|
||||
/// instance will try to initiate a key exchange with the peer; otherwise,
|
||||
/// only initiation attempts from other peers will be responded to.
|
||||
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
|
||||
/// always separates multiple peers, e. g. if the token `peer` appears
|
||||
/// in the WIREGUARD_EXTRA_ARGS it is not put into the WireGuard arguments
|
||||
/// but instead a new peer is created.
|
||||
/// The configuration is read from the command line. The `peer` token always
|
||||
/// separates multiple peers, e.g., if the token `peer` appears in the
|
||||
/// WIREGUARD_EXTRA_ARGS, it is not put into the WireGuard arguments but
|
||||
/// instead a new peer is created.
|
||||
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
||||
* `Vec<String>`. They are only used to trick clap into displaying some
|
||||
* guidance on the CLI usage.
|
||||
@@ -112,7 +214,10 @@ pub enum CliCommand {
|
||||
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 {
|
||||
config_file: PathBuf,
|
||||
|
||||
@@ -121,19 +226,19 @@ pub enum CliCommand {
|
||||
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
|
||||
/// is provided then the key file destination is taken from there.
|
||||
/// Otherwise the
|
||||
/// Generates secret & public key to their destination. If a config file is
|
||||
/// provided then the key file destination is taken from there, otherwise
|
||||
/// the destination is taken from the CLI arguments.
|
||||
GenKeys {
|
||||
config_file: Option<PathBuf>,
|
||||
|
||||
/// where to write public-key to
|
||||
/// Where to write public key to
|
||||
#[clap(short, long)]
|
||||
public_key: Option<PathBuf>,
|
||||
|
||||
/// where to write secret-key to
|
||||
/// Where to write secret key to
|
||||
#[clap(short, long)]
|
||||
secret_key: Option<PathBuf>,
|
||||
|
||||
@@ -142,51 +247,50 @@ pub enum CliCommand {
|
||||
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::invalid_html_tags)]
|
||||
#[command(hide = true)]
|
||||
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>
|
||||
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 CliArgs {
|
||||
/// runs the command specified via CLI
|
||||
/// Run Rosenpass with the given command line parameters
|
||||
///
|
||||
/// ## TODO
|
||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
pub fn run(self, test_helpers: Option<AppServerTest>) -> anyhow::Result<()> {
|
||||
/// This contains the bulk of our startup logic with
|
||||
/// the main function just setting up the basic environment
|
||||
/// and then calling this function.
|
||||
pub fn run(
|
||||
self,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
use CliCommand::*;
|
||||
match &self.command {
|
||||
Man => {
|
||||
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 } => {
|
||||
Some(GenConfig { config_file, force }) => {
|
||||
ensure!(
|
||||
*force || !config_file.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
|
||||
Keygen { args } => {
|
||||
Some(Keygen { args }) => {
|
||||
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
||||
|
||||
let mut public_key: Option<PathBuf> = None;
|
||||
@@ -219,12 +323,12 @@ impl CliArgs {
|
||||
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
||||
}
|
||||
|
||||
GenKeys {
|
||||
Some(GenKeys {
|
||||
config_file,
|
||||
public_key,
|
||||
secret_key,
|
||||
force,
|
||||
} => {
|
||||
}) => {
|
||||
// figure out where the key file is specified, in the config file or directly as flag?
|
||||
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
||||
(Some(config_file), _, _) => {
|
||||
@@ -234,8 +338,11 @@ impl CliArgs {
|
||||
);
|
||||
|
||||
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.clone(), skf.clone()),
|
||||
_ => {
|
||||
@@ -247,12 +354,14 @@ impl CliArgs {
|
||||
let mut problems = vec![];
|
||||
if !force && pkf.is_file() {
|
||||
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() {
|
||||
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() {
|
||||
@@ -263,7 +372,7 @@ impl CliArgs {
|
||||
generate_and_save_keypair(skf, pkf)?;
|
||||
}
|
||||
|
||||
ExchangeConfig { config_file } => {
|
||||
Some(ExchangeConfig { config_file }) => {
|
||||
ensure!(
|
||||
config_file.exists(),
|
||||
"config file '{config_file:?}' does not exist"
|
||||
@@ -272,15 +381,16 @@ impl CliArgs {
|
||||
let mut config = config::Rosenpass::load(config_file)?;
|
||||
config.validate()?;
|
||||
self.apply_to_config(&mut config)?;
|
||||
config.check_usefullness()?;
|
||||
|
||||
Self::event_loop(config, test_helpers)?;
|
||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||
}
|
||||
|
||||
Exchange {
|
||||
Some(Exchange {
|
||||
first_arg,
|
||||
rest_of_args,
|
||||
config_file,
|
||||
} => {
|
||||
}) => {
|
||||
let mut rest_of_args = rest_of_args.clone();
|
||||
rest_of_args.insert(0, first_arg.clone());
|
||||
let args = rest_of_args;
|
||||
@@ -292,43 +402,54 @@ impl CliArgs {
|
||||
}
|
||||
config.validate()?;
|
||||
self.apply_to_config(&mut config)?;
|
||||
config.check_usefullness()?;
|
||||
|
||||
Self::event_loop(config, test_helpers)?;
|
||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||
}
|
||||
|
||||
Validate { config_files } => {
|
||||
Some(Validate { config_files }) => {
|
||||
for file in config_files {
|
||||
match config::Rosenpass::load(file) {
|
||||
Ok(config) => {
|
||||
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
||||
match config.validate() {
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&None => {} // calp print help if no command is given
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used by [Self::run] to start the Rosenpass key exchange server
|
||||
fn event_loop(
|
||||
config: config::Rosenpass,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<()> {
|
||||
const MAX_PSK_SIZE: usize = 1000;
|
||||
|
||||
// load own keys
|
||||
let sk = SSk::load(&config.secret_key)?;
|
||||
let pk = SPk::load(&config.public_key)?;
|
||||
let keypair = config
|
||||
.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
|
||||
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
||||
sk,
|
||||
pk,
|
||||
keypair,
|
||||
config.listen.clone(),
|
||||
config.verbosity,
|
||||
test_helpers,
|
||||
@@ -336,7 +457,8 @@ impl CliArgs {
|
||||
|
||||
config.apply_to_app_server(&mut srv)?;
|
||||
|
||||
let broker_store_ptr = srv.register_broker(Box::new(NativeUnixBroker::new()))?;
|
||||
let broker = Self::create_broker(broker_interface)?;
|
||||
let broker_store_ptr = srv.register_broker(broker)?;
|
||||
|
||||
fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error {
|
||||
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
||||
@@ -373,10 +495,117 @@ impl CliArgs {
|
||||
|
||||
srv.event_loop()
|
||||
}
|
||||
|
||||
/// Create the WireGuard PSK broker to be used by
|
||||
/// [crate::app_server::AppServer].
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is set, then this communicates with a PSK broker
|
||||
/// running in a different process as configured via
|
||||
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||
/// sending pre-shared keys directly to WireGuard from within this
|
||||
/// process.
|
||||
#[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()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the WireGuard PSK broker to be used by
|
||||
/// [crate::app_server::AppServer].
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is set, then this communicates with a PSK broker
|
||||
/// running in a different process as configured via
|
||||
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||
/// sending pre-shared keys directly to WireGuard from within this
|
||||
/// process.
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
fn create_broker(
|
||||
_broker_interface: Option<BrokerInterface>,
|
||||
) -> Result<Box<NativeUnixBroker>, anyhow::Error> {
|
||||
Ok(Box::new(NativeUnixBroker::new()))
|
||||
}
|
||||
|
||||
/// Used by [Self::create_broker] if the `experiment_api` is configured
|
||||
/// to set up the connection with the PSK broker process as configured
|
||||
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
#[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
|
||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
let mut ssk = crate::protocol::SSk::random();
|
||||
let mut spk = crate::protocol::SPk::random();
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
//! [`Rosenpass`] which holds such a configuration.
|
||||
//!
|
||||
//! ## TODO
|
||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||
//! - TODO: 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::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
@@ -21,22 +23,37 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app_server::AppServer;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Rosenpass {
|
||||
/// path to the public key file
|
||||
pub public_key: PathBuf,
|
||||
#[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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// path to the secret key file
|
||||
pub secret_key: PathBuf,
|
||||
/// Configuration for the Rosenpass key exchange
|
||||
///
|
||||
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Rosenpass {
|
||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||
#[serde(flatten)]
|
||||
pub keypair: Option<Keypair>,
|
||||
|
||||
/// Location of the API listen sockets
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[serde(default = "empty_api_config")]
|
||||
pub api: crate::api::config::ApiConfig,
|
||||
|
||||
/// list of [`SocketAddr`] to listen on
|
||||
///
|
||||
/// Examples:
|
||||
/// - `0.0.0.0:123`
|
||||
///
|
||||
/// - `0.0.0.0:123` – Listen on any interface using IPv4, port 123
|
||||
/// - `[::1]:1234` – Listen on IPv6 localhost, port 1234
|
||||
/// - `[::]:4476` – Listen on any IPv4 or IPv6 interface, port 4476
|
||||
pub listen: Vec<SocketAddr>,
|
||||
|
||||
/// log verbosity
|
||||
@@ -58,61 +75,99 @@ pub struct Rosenpass {
|
||||
pub config_file_path: PathBuf,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
/// Public key and secret key locations.
|
||||
#[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 {
|
||||
/// Construct a keypair from its fields
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Level of verbosity for [crate::app_server::AppServer]
|
||||
///
|
||||
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
|
||||
/// for details.
|
||||
///
|
||||
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum Verbosity {
|
||||
Quiet,
|
||||
Verbose,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - examples
|
||||
/// - documentation
|
||||
/// Configuration data for a single Rosenpass peer
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RosenpassPeer {
|
||||
/// path to the public key of the peer
|
||||
pub public_key: PathBuf,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// The hostname and port to connect to
|
||||
///
|
||||
/// Can be a
|
||||
///
|
||||
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
|
||||
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
|
||||
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
|
||||
pub endpoint: Option<String>,
|
||||
|
||||
/// path to the pre-shared key with the peer
|
||||
/// path to the pre-shared key shared with the peer
|
||||
///
|
||||
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
|
||||
pub pre_shared_key: Option<PathBuf>,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
|
||||
/// to the given file and write a notification to standard out to let the calling application
|
||||
/// know that a new key was exchanged
|
||||
#[serde(default)]
|
||||
pub key_out: Option<PathBuf>,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
||||
/// Information for supplying exchanged keys directly to WireGuard
|
||||
#[serde(flatten)]
|
||||
pub wg: Option<WireGuard>,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Information for supplying exchanged keys directly to WireGuard
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct WireGuard {
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
|
||||
/// key exchange
|
||||
pub device: String,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// WireGuard public key of the peer to supply with pre-shared keys
|
||||
pub peer: String,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Extra parameters passed to the `wg` command
|
||||
#[serde(default)]
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Rosenpass {
|
||||
/// Generate an empty configuration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rosenpass {
|
||||
/// load configuration from a TOML file
|
||||
///
|
||||
@@ -120,16 +175,25 @@ impl Rosenpass {
|
||||
/// checked whether they even exist.
|
||||
///
|
||||
/// ## TODO
|
||||
///
|
||||
/// - consider using a different algorithm to determine home directory – the below one may
|
||||
/// behave unexpectedly on Windows
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||
// read file and deserialize
|
||||
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||
|
||||
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
|
||||
use util::resolve_path_with_tilde;
|
||||
resolve_path_with_tilde(&mut config.public_key);
|
||||
resolve_path_with_tilde(&mut config.secret_key);
|
||||
if let Some(ref mut keypair) = config.keypair {
|
||||
resolve_path_with_tilde(&mut keypair.public_key);
|
||||
resolve_path_with_tilde(&mut keypair.secret_key);
|
||||
}
|
||||
for peer in config.peers.iter_mut() {
|
||||
resolve_path_with_tilde(&mut peer.public_key);
|
||||
if let Some(ref mut psk) = &mut peer.pre_shared_key {
|
||||
@@ -147,7 +211,13 @@ impl Rosenpass {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Write a config to a file
|
||||
/// Encode a configuration object as toml and write it to a file
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||
let serialized_config =
|
||||
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
||||
@@ -156,6 +226,12 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
/// Commit the configuration to where it came from, overwriting the original file
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn commit(&self) -> anyhow::Result<()> {
|
||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||
@@ -163,31 +239,51 @@ impl Rosenpass {
|
||||
self.store(&self.config_file_path)
|
||||
}
|
||||
|
||||
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
|
||||
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_app_server(_srv)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a configuration
|
||||
/// Check that the configuration is sound, ensuring
|
||||
/// for instance that the referenced files exist
|
||||
///
|
||||
/// ## TODO
|
||||
/// - check that files do not just exist but are also readable
|
||||
/// - warn if neither out_key nor exchange_command of a peer is defined (v.i.)
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||
#[doc = "```"]
|
||||
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
|
||||
);
|
||||
if let Some(ref keypair) = self.keypair {
|
||||
// check the public key file exists
|
||||
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 secret-key file exists
|
||||
ensure!(
|
||||
self.secret_key.is_file(),
|
||||
"could not find secret-key file {:?}: no such file",
|
||||
self.secret_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() {
|
||||
// check peer's public-key file exists
|
||||
@@ -197,6 +293,13 @@ impl Rosenpass {
|
||||
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
|
||||
if let Some(addr) = peer.endpoint.as_ref() {
|
||||
ensure!(
|
||||
@@ -206,17 +309,92 @@ 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(())
|
||||
}
|
||||
|
||||
/// Creates a new configuration
|
||||
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(public_key: P1, secret_key: P2) -> Self {
|
||||
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
|
||||
/// and the configuration values.
|
||||
///
|
||||
/// This was introduced when we introduced a unix-socket API feature allowing the server
|
||||
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
|
||||
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
|
||||
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
|
||||
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
|
||||
/// enabled in the configuration.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||
#[doc = "```"]
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Produce an empty confuguration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
|
||||
/// Produce configuration from the keypair
|
||||
///
|
||||
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||
Self::new(Some(Keypair::new(pk, sk)))
|
||||
}
|
||||
|
||||
/// Initialize a minimal configuration with the [Self::keypair] field supplied
|
||||
/// as a parameter
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||
Self {
|
||||
public_key: PathBuf::from(public_key.as_ref()),
|
||||
secret_key: PathBuf::from(secret_key.as_ref()),
|
||||
keypair,
|
||||
listen: vec![],
|
||||
#[cfg(feature = "experiment_api")]
|
||||
api: crate::api::config::ApiConfig::default(),
|
||||
@@ -227,6 +405,14 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
||||
///
|
||||
/// I.e. listen on any interface.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn add_if_any(&mut self, port: u16) {
|
||||
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||
@@ -239,10 +425,22 @@ impl Rosenpass {
|
||||
self.listen.push(ipv6_any);
|
||||
}
|
||||
|
||||
/// from chaotic args
|
||||
/// Quest: the grammar is undecideable, what do we do here?
|
||||
/// Parser for the old, IP style grammar.
|
||||
///
|
||||
/// See out manual page rosenpass-exchange(1) on details about the grammar.
|
||||
///
|
||||
/// This function parses the grammar and turns it into an instance of the configuration
|
||||
/// struct.
|
||||
///
|
||||
/// TODO: the grammar is undecidable, what do we do here?
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
|
||||
#[doc = "```"]
|
||||
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)]
|
||||
enum State {
|
||||
@@ -303,7 +501,7 @@ impl Rosenpass {
|
||||
already_set.insert(OwnPublicKey),
|
||||
"public-key was already set"
|
||||
);
|
||||
config.public_key = pk.into();
|
||||
config.keypair.as_mut().unwrap().public_key = pk.into();
|
||||
Own
|
||||
}
|
||||
(OwnSecretKey, sk, None) => {
|
||||
@@ -311,7 +509,7 @@ impl Rosenpass {
|
||||
already_set.insert(OwnSecretKey),
|
||||
"secret-key was already set"
|
||||
);
|
||||
config.secret_key = sk.into();
|
||||
config.keypair.as_mut().unwrap().secret_key = sk.into();
|
||||
Own
|
||||
}
|
||||
(OwnListen, l, None) => {
|
||||
@@ -430,72 +628,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 {
|
||||
/// Self::Quiet
|
||||
fn default() -> Self {
|
||||
Self::Quiet
|
||||
}
|
||||
}
|
||||
|
||||
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
|
||||
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)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use std::net::IpAddr;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
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> {
|
||||
s.split(' ').map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_cli_parse() {
|
||||
let args = split_str(
|
||||
"public-key /my/public-key secret-key /my/secret-key verbose \
|
||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||
peer.test:9999 outfile /peer/rp-out",
|
||||
);
|
||||
fn toml_serialization() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
assert_toml_round(
|
||||
Rosenpass::empty(),
|
||||
r#"
|
||||
listen = []
|
||||
verbosity = "Quiet"
|
||||
peers = []
|
||||
|
||||
let config = Rosenpass::parse_args(args).unwrap();
|
||||
[api]
|
||||
listen_path = []
|
||||
listen_fd = []
|
||||
stream_fd = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
||||
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers,
|
||||
vec![RosenpassPeer {
|
||||
public_key: PathBuf::from("/peer/public-key"),
|
||||
endpoint: Some("peer.test:9999".into()),
|
||||
pre_shared_key: None,
|
||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||
..Default::default()
|
||||
}]
|
||||
)
|
||||
#[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]
|
||||
@@ -509,8 +781,10 @@ mod test {
|
||||
|
||||
let config = Rosenpass::parse_args(args).unwrap();
|
||||
|
||||
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
||||
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert!(&config.listen.is_empty());
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,13 +1,68 @@
|
||||
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
|
||||
//! ensures their uniqueness
|
||||
//! ensures their uniqueness.
|
||||
//!
|
||||
//! This ensures [domain separation](https://en.wikipedia.org/wiki/Domain_separation) is used
|
||||
//! across the Rosenpass protocol.
|
||||
//!
|
||||
//! There is a chart containing all hash domains used in Rosenpass in the
|
||||
//! [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository).
|
||||
//!
|
||||
//! # Tutorial
|
||||
//!
|
||||
//! ```
|
||||
//! use rosenpass::{hash_domain, hash_domain_ns};
|
||||
//! use rosenpass::hash_domains::protocol;
|
||||
//!
|
||||
//! // Declaring a custom hash domain
|
||||
//! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label");
|
||||
//!
|
||||
//! // Declaring a custom hashers
|
||||
//! hash_domain_ns!(custom_domain, hashers, "hashers");
|
||||
//! hash_domain_ns!(hashers, hasher1, "1");
|
||||
//! hash_domain_ns!(hashers, hasher2, "2");
|
||||
//!
|
||||
//! // Declaring specific domain separators
|
||||
//! hash_domain_ns!(custom_domain, domain_separators, "domain separators");
|
||||
//! hash_domain!(domain_separators, sep1, "1");
|
||||
//! hash_domain!(domain_separators, sep2, "2");
|
||||
//!
|
||||
//! // Generating values under hasher1 with both domain separators
|
||||
//! let h1 = hasher1()?.mix(b"some data")?.dup();
|
||||
//! let h1v1 = h1.mix(&sep1()?)?.mix(b"More data")?.into_value();
|
||||
//! let h1v2 = h1.mix(&sep2()?)?.mix(b"More data")?.into_value();
|
||||
//!
|
||||
//! // Generating values under hasher2 with both domain separators
|
||||
//! let h2 = hasher2()?.mix(b"some data")?.dup();
|
||||
//! let h2v1 = h2.mix(&sep1()?)?.mix(b"More data")?.into_value();
|
||||
//! let h2v2 = h2.mix(&sep2()?)?.mix(b"More data")?.into_value();
|
||||
//!
|
||||
//! // All of the domain separators are now different, random strings
|
||||
//! let values = [h1v1, h1v2, h2v1, h2v2];
|
||||
//! for i in 0..values.len() {
|
||||
//! for j in (i+1)..values.len() {
|
||||
//! assert_ne!(values[i], values[j]);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! Ok::<(), anyhow::Error>(())
|
||||
//! ```
|
||||
|
||||
use anyhow::Result;
|
||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||
use rosenpass_ciphers::hash_domain::HashDomain;
|
||||
|
||||
/// Declare a hash function
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source file for details about how this is used concretely.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general
|
||||
// TODO Use labels that can serve as identifiers
|
||||
#[macro_export]
|
||||
macro_rules! hash_domain_ns {
|
||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
||||
pub fn $name() -> Result<HashDomain> {
|
||||
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
||||
$(#[$($attrss)*])*
|
||||
pub fn $name() -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> {
|
||||
let t = $base()?;
|
||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||
Ok(t)
|
||||
@@ -15,9 +70,18 @@ macro_rules! hash_domain_ns {
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare a concrete hash value
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source file for details about how this is used concretely.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general
|
||||
#[macro_export]
|
||||
macro_rules! hash_domain {
|
||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
||||
pub fn $name() -> Result<[u8; KEY_LEN]> {
|
||||
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
||||
$(#[$($attrss)*])*
|
||||
pub fn $name() -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> {
|
||||
let t = $base()?;
|
||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||
Ok(t.into_value())
|
||||
@@ -25,24 +89,227 @@ macro_rules! hash_domain {
|
||||
}
|
||||
}
|
||||
|
||||
/// The hash domain containing the protocol string.
|
||||
///
|
||||
/// This serves as a global [domain separator](https://en.wikipedia.org/wiki/Domain_separation)
|
||||
/// used in various places in the rosenpass protocol.
|
||||
///
|
||||
/// This is generally used to create further hash-domains for specific purposes. See
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source file for details about how this is used concretely.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general
|
||||
pub fn protocol() -> Result<HashDomain> {
|
||||
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
|
||||
}
|
||||
|
||||
hash_domain_ns!(protocol, mac, "mac");
|
||||
hash_domain_ns!(protocol, cookie, "cookie");
|
||||
hash_domain_ns!(protocol, cookie_value, "cookie-value");
|
||||
hash_domain_ns!(protocol, cookie_key, "cookie-key");
|
||||
hash_domain_ns!(protocol, peerid, "peer id");
|
||||
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
|
||||
hash_domain_ns!(protocol, ckinit, "chaining key init");
|
||||
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
|
||||
hash_domain_ns!(
|
||||
/// Hash domain based on [protocol] for calculating [crate::msgs::Envelope::mac].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source of [crate::msgs::Envelope::seal] and [crate::msgs::Envelope::check_seal]
|
||||
/// to figure out how this is concretely used.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, mac, "mac");
|
||||
hash_domain_ns!(
|
||||
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source of [crate::msgs::Envelope::seal_cookie],
|
||||
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
|
||||
/// [crate::protocol::CryptoServer::handle_cookie_reply]
|
||||
/// to figure out how this is concretely used.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, cookie, "cookie");
|
||||
hash_domain_ns!(
|
||||
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source of [crate::msgs::Envelope::seal_cookie],
|
||||
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
|
||||
/// [crate::protocol::CryptoServer::handle_cookie_reply]
|
||||
/// to figure out how this is concretely used.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, cookie_value, "cookie-value");
|
||||
hash_domain_ns!(
|
||||
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source of [crate::msgs::Envelope::seal_cookie],
|
||||
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
|
||||
/// [crate::protocol::CryptoServer::handle_cookie_reply]
|
||||
/// to figure out how this is concretely used.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, cookie_key, "cookie-key");
|
||||
hash_domain_ns!(
|
||||
/// Hash domain based on [protocol] for calculating the peer id as transmitted (encrypted)
|
||||
/// in [crate::msgs::InitHello::pidic].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the source of [crate::protocol::CryptoServer::pidm] and
|
||||
/// [crate::protocol::Peer::pidt]
|
||||
/// to figure out how this is concretely used.
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, peerid, "peer id");
|
||||
hash_domain_ns!(
|
||||
/// Hash domain based on [protocol] for calculating the additional data
|
||||
/// during [crate::msgs::Biscuit] encryption, storing the biscuit into
|
||||
/// [crate::msgs::RespHello::biscuit].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the biscuit is used, it is best to read
|
||||
/// the code of [crate::protocol::HandshakeState::store_biscuit] and
|
||||
/// [crate::protocol::HandshakeState::load_biscuit]
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, biscuit_ad, "biscuit additional data");
|
||||
hash_domain_ns!(
|
||||
/// This hash domain begins our actual handshake procedure, initializing the
|
||||
/// chaining key [crate::protocol::HandshakeState::ck].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the chaining key is used, study
|
||||
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||
/// and [crate::protocol::HandshakeState::mix].
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, ckinit, "chaining key init");
|
||||
hash_domain_ns!(
|
||||
/// Namespace for chaining key usage domain separators.
|
||||
///
|
||||
/// During the execution of the Rosenpass protocol, we use the chaining key for multiple
|
||||
/// purposes, so to make sure that we have unique value domains, we mix a domain separator
|
||||
/// into the chaining key before using it for any particular purpose.
|
||||
///
|
||||
/// We could use the full domain separation strings, but using a hash value here is nice
|
||||
/// because it does not lead to any constraints about domain separator format and we can
|
||||
/// even allow third parties to define their own separators by claiming a namespace.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the chaining key is used, study
|
||||
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||
/// and [crate::protocol::HandshakeState::mix].
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
protocol, _ckextract, "chaining key extract");
|
||||
|
||||
hash_domain!(_ckextract, mix, "mix");
|
||||
hash_domain!(_ckextract, hs_enc, "handshake encryption");
|
||||
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
|
||||
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
|
||||
hash_domain!(
|
||||
/// Used to mix in further values into the chaining key during the handshake.
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the chaining key is used, study
|
||||
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||
/// and [crate::protocol::HandshakeState::mix].
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_ckextract, mix, "mix");
|
||||
hash_domain!(
|
||||
/// Chaining key domain separator for generating encryption keys that can
|
||||
/// encrypt parts of the handshake.
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Encryption of data during the handshake happens in
|
||||
/// [crate::protocol::HandshakeState::encrypt_and_mix] and decryption happens in
|
||||
/// [crate::protocol::HandshakeState::decrypt_and_mix]. See their source code
|
||||
/// for details.
|
||||
///
|
||||
/// To understand how the chaining key is used, study
|
||||
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||
/// and [crate::protocol::HandshakeState::mix].
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_ckextract, hs_enc, "handshake encryption");
|
||||
hash_domain!(
|
||||
/// Chaining key domain separator for live data encryption.
|
||||
/// Live data encryption is only used to send confirmation of handshake
|
||||
/// done in [crate::msgs::EmptyData].
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This domain separator finds use in [crate::protocol::HandshakeState::enter_live].
|
||||
///
|
||||
/// To understand how the chaining key is used, study
|
||||
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||
/// and [crate::protocol::HandshakeState::mix].
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_ckextract, ini_enc, "initiator handshake encryption");
|
||||
hash_domain!(
|
||||
/// Chaining key domain separator for live data encryption.
|
||||
/// Live data encryption is only used to send confirmation of handshake
|
||||
/// done in [crate::msgs::EmptyData].
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This domain separator finds use in [crate::protocol::HandshakeState::enter_live].
|
||||
/// Check out its source code!
|
||||
///
|
||||
/// To understand how the chaining key is used, study
|
||||
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||
/// and [crate::protocol::HandshakeState::mix].
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_ckextract, res_enc, "responder handshake encryption");
|
||||
|
||||
hash_domain_ns!(_ckextract, _user, "user");
|
||||
hash_domain_ns!(_user, _rp, "rosenpass.eu");
|
||||
hash_domain!(_rp, osk, "wireguard psk");
|
||||
hash_domain_ns!(
|
||||
/// Chaining key domain separator for any usage specific purposes.
|
||||
///
|
||||
/// We do recommend that third parties base their specific domain separators
|
||||
/// on a internet domain and/or mix in much more specific information.
|
||||
///
|
||||
/// We only really use this to derive a output key for wireguard; see [osk].
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_ckextract, _user, "user");
|
||||
hash_domain_ns!(
|
||||
/// Chaining key domain separator for any rosenpass specific purposes.
|
||||
///
|
||||
/// We only really use this to derive a output key for wireguard; see [osk].
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_user, _rp, "rosenpass.eu");
|
||||
hash_domain!(
|
||||
/// Chaining key domain separator for deriving the key sent to WireGuard.
|
||||
///
|
||||
/// See [_ckextract].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This domain separator finds use in [crate::protocol::CryptoServer::osk].
|
||||
/// Check out its source code!
|
||||
///
|
||||
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||
_rp, osk, "wireguard psk");
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
//! This is the central rosenpass crate implementing the rosenpass protocol.
|
||||
//!
|
||||
//! - [crate::app_server] contains the business logic of rosenpass, handling networking
|
||||
//! - [crate::cli] contains the cli parsing logic and contains quite a bit of startup logic; the
|
||||
//! main function quickly hands over to [crate::cli::CliArgs::run] which contains quite a bit
|
||||
//! of our startup logic
|
||||
//! - [crate::config] has the code to parse and generate configuration files
|
||||
//! - [crate::hash_domains] lists the different hash function domains used in the Rosenpass
|
||||
//! protocol
|
||||
//! - [crate::msgs] provides declarations of the Rosenpass protocol network messages and facilities
|
||||
//! to parse those messages through the [::zerocopy] crate
|
||||
//! - [crate::protocol] this is where the bulk of our code lives; this module contains the actual
|
||||
//! cryptographic protocol logic
|
||||
//! - crate::api implements the Rosenpass unix socket API, if feature "experiment_api" is active
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
pub mod api;
|
||||
pub mod app_server;
|
||||
@@ -7,14 +22,25 @@ pub mod hash_domains;
|
||||
pub mod msgs;
|
||||
pub mod protocol;
|
||||
|
||||
/// Error types used in diverse places across Rosenpass
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RosenpassError {
|
||||
/// Usually indicates that parsing a struct through the
|
||||
/// [::zerocopy] crate failed
|
||||
#[error("buffer size mismatch")]
|
||||
BufferSizeMismatch,
|
||||
/// Mostly raised by the `TryFrom<u8>` implementation for [crate::msgs::MsgType]
|
||||
/// to indicate that a message type is not defined
|
||||
#[error("invalid message type")]
|
||||
InvalidMessageType(u8),
|
||||
InvalidMessageType(
|
||||
/// The message type that could not be parsed
|
||||
u8,
|
||||
),
|
||||
/// Raised by the `TryFrom<RawMsgType>` (crate::api::RawMsgType) implementation for crate::api::RequestMsgType
|
||||
/// and crate::api::RequestMsgType to indicate that a message type is not defined
|
||||
#[error("invalid API message type")]
|
||||
InvalidApiMessageType(u128),
|
||||
#[error("could not parse API message")]
|
||||
InvalidApiMessage,
|
||||
InvalidApiMessageType(
|
||||
/// The message type that could not be parsed
|
||||
u128,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,13 +1,56 @@
|
||||
//! For the main function
|
||||
|
||||
use clap::CommandFactory;
|
||||
use clap::Parser;
|
||||
use clap_mangen::roff::{roman, Roff};
|
||||
use log::error;
|
||||
use rosenpass::cli::CliArgs;
|
||||
use std::process::exit;
|
||||
|
||||
/// Printing custom man sections when generating the man page
|
||||
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
|
||||
///
|
||||
/// The bulk of the command line logic is handled inside [crate::cli::CliArgs::run].
|
||||
pub fn main() {
|
||||
// parse CLI arguments
|
||||
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")]
|
||||
@@ -34,7 +77,8 @@ pub fn main() {
|
||||
// error!("error dummy");
|
||||
}
|
||||
|
||||
match args.run(None) {
|
||||
let broker_interface = args.get_broker_interface();
|
||||
match args.run(broker_interface, None) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
@@ -42,3 +86,27 @@ pub fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom main page section: Exit Status
|
||||
static EXIT_STATUS_MAN: &str = r"
|
||||
The rosenpass utility exits 0 on success, and >0 if an error occurs.";
|
||||
|
||||
/// Custom main page section: See also.
|
||||
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.";
|
||||
|
||||
/// Custom main page section: Standards.
|
||||
static STANDARDS_MAN: &str = r"
|
||||
This tool is the reference implementation of the Rosenpass protocol, as
|
||||
specified within the whitepaper referenced above.";
|
||||
|
||||
/// Custom main page section: Authors.
|
||||
static AUTHORS_MAN: &str = r"
|
||||
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske, Marei
|
||||
Peischl, Stephan Ajuvo, and Lisa Schmidt.";
|
||||
|
||||
/// Custom main page section: Bugs.
|
||||
static BUGS_MAN: &str = r"
|
||||
The bugs are tracked at https://github.com/rosenpass/rosenpass/issues.";
|
||||
|
||||
@@ -9,20 +9,75 @@
|
||||
//! To achieve this we utilize the zerocopy library.
|
||||
//!
|
||||
use std::mem::size_of;
|
||||
use std::u8;
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
use super::RosenpassError;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||
pub const MSG_SIZE_LEN: usize = 1;
|
||||
pub const RESERVED_LEN: usize = 3;
|
||||
pub const MAC_SIZE: usize = 16;
|
||||
pub const COOKIE_SIZE: usize = 16;
|
||||
pub const SID_LEN: usize = 4;
|
||||
|
||||
/// Length of a session ID such as [InitHello::sidi]
|
||||
pub const SESSION_ID_LEN: usize = 4;
|
||||
/// Length of a biscuit ID; i.e. size of the value in [Biscuit::biscuit_no]
|
||||
pub const BISCUIT_ID_LEN: usize = 12;
|
||||
|
||||
/// TODO: Unused, remove!
|
||||
pub const WIRE_ENVELOPE_LEN: usize = 1 + 3 + 16 + 16; // TODO verify this
|
||||
|
||||
/// Size required to fit any message in binary form
|
||||
pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
|
||||
|
||||
/// length in bytes of an unencrypted Biscuit (plain text)
|
||||
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
||||
|
||||
/// Length in bytes of an encrypted Biscuit (cipher text)
|
||||
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
|
||||
|
||||
/// Size of the field [Envelope::mac]
|
||||
pub const MAC_SIZE: usize = 16;
|
||||
/// Size of the field [Envelope::cookie]
|
||||
pub const COOKIE_SIZE: usize = MAC_SIZE;
|
||||
|
||||
/// Type of the mac field in [Envelope]
|
||||
pub type MsgEnvelopeMac = [u8; MAC_SIZE];
|
||||
|
||||
/// Type of the cookie field in [Envelope]
|
||||
pub type MsgEnvelopeCookie = [u8; COOKIE_SIZE];
|
||||
|
||||
/// Header and footer included in all our packages,
|
||||
/// including a type field.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::msgs::{Envelope, InitHello};
|
||||
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||
/// use memoffset::offset_of;
|
||||
///
|
||||
/// // Zero-initialization
|
||||
/// let mut ih = Envelope::<InitHello>::new_zeroed();
|
||||
///
|
||||
/// // Edit fields normally
|
||||
/// ih.mac[0] = 1;
|
||||
///
|
||||
/// // Edit as binary
|
||||
/// ih.as_bytes_mut()[offset_of!(Envelope<InitHello>, msg_type)] = 23;
|
||||
/// assert_eq!(ih.msg_type, 23);;
|
||||
///
|
||||
/// // Conversion to bytes
|
||||
/// let mut ih2 = ih.as_bytes().to_owned();
|
||||
///
|
||||
/// // Setting msg_type field, again
|
||||
/// ih2[0] = 42;
|
||||
///
|
||||
/// // Zerocopy parsing
|
||||
/// let ih3 = Ref::<&mut [u8], Envelope<InitHello>>::new(&mut ih2).unwrap();
|
||||
/// assert_ne!(ih.as_bytes(), ih3.as_bytes());
|
||||
/// assert_eq!(ih3.msg_type, 42);
|
||||
/// ```
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes, Clone)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
/// [MsgType] of this message
|
||||
pub msg_type: u8,
|
||||
@@ -32,11 +87,45 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
pub payload: M,
|
||||
/// Message Authentication Code (mac) over all bytes until (exclusive)
|
||||
/// `mac` itself
|
||||
pub mac: [u8; 16],
|
||||
pub mac: MsgEnvelopeMac,
|
||||
/// Currently unused, TODO: do something with this
|
||||
pub cookie: [u8; 16],
|
||||
pub cookie: MsgEnvelopeCookie,
|
||||
}
|
||||
|
||||
/// This is the first message sent by the initiator to the responder
|
||||
/// during the execution of the Rosenpass protocol.
|
||||
///
|
||||
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Check out the code of [crate::protocol::CryptoServer::handle_initiation] (generation on
|
||||
/// iniatiator side) and [crate::protocol::CryptoServer::handle_init_hello] (processing on
|
||||
/// responder side) to understand how this is used.
|
||||
///
|
||||
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::msgs::{Envelope, InitHello};
|
||||
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||
/// use memoffset::span_of;
|
||||
///
|
||||
/// // Zero initialization
|
||||
/// let mut ih = Envelope::<InitHello>::new_zeroed();
|
||||
///
|
||||
/// // Conversion to byte representation
|
||||
/// let ih = ih.as_bytes_mut();
|
||||
///
|
||||
/// // Set value on byte representation
|
||||
/// ih[span_of!(Envelope<InitHello>, payload)][span_of!(InitHello, sidi)]
|
||||
/// .copy_from_slice(&[1,2,3,4]);
|
||||
///
|
||||
/// // Conversion from bytes
|
||||
/// let ih = Ref::<&mut [u8], Envelope<InitHello>>::new(ih).unwrap();
|
||||
///
|
||||
/// // Check that write above on byte representation was effective
|
||||
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
|
||||
/// ```
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct InitHello {
|
||||
@@ -52,6 +141,40 @@ pub struct InitHello {
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
}
|
||||
|
||||
/// This is the second message sent by the responder to the initiator
|
||||
/// during the execution of the Rosenpass protocol in response to [InitHello].
|
||||
///
|
||||
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Check out the code of [crate::protocol::CryptoServer::handle_init_hello] (generation on
|
||||
/// responder side) and [crate::protocol::CryptoServer::handle_resp_hello] (processing on
|
||||
/// initiator side) to understand how this is used.
|
||||
///
|
||||
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::msgs::{Envelope, RespHello};
|
||||
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||
/// use memoffset::span_of;
|
||||
///
|
||||
/// // Zero initialization
|
||||
/// let mut ih = Envelope::<RespHello>::new_zeroed();
|
||||
///
|
||||
/// // Conversion to byte representation
|
||||
/// let ih = ih.as_bytes_mut();
|
||||
///
|
||||
/// // Set value on byte representation
|
||||
/// ih[span_of!(Envelope<RespHello>, payload)][span_of!(RespHello, sidi)]
|
||||
/// .copy_from_slice(&[1,2,3,4]);
|
||||
///
|
||||
/// // Conversion from bytes
|
||||
/// let ih = Ref::<&mut [u8], Envelope<RespHello>>::new(ih).unwrap();
|
||||
///
|
||||
/// // Check that write above on byte representation was effective
|
||||
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
|
||||
/// ```
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct RespHello {
|
||||
@@ -69,8 +192,42 @@ pub struct RespHello {
|
||||
pub biscuit: [u8; BISCUIT_CT_LEN],
|
||||
}
|
||||
|
||||
/// This is the third message sent by the initiator to the responder
|
||||
/// during the execution of the Rosenpass protocol in response to [RespHello].
|
||||
///
|
||||
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Check out the code of [crate::protocol::CryptoServer::handle_resp_hello] (generation on
|
||||
/// initiator side) and [crate::protocol::CryptoServer::handle_init_conf] (processing on
|
||||
/// responder side) to understand how this is used.
|
||||
///
|
||||
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::msgs::{Envelope, InitConf};
|
||||
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||
/// use memoffset::span_of;
|
||||
///
|
||||
/// // Zero initialization
|
||||
/// let mut ih = Envelope::<InitConf>::new_zeroed();
|
||||
///
|
||||
/// // Conversion to byte representation
|
||||
/// let ih = ih.as_bytes_mut();
|
||||
///
|
||||
/// // Set value on byte representation
|
||||
/// ih[span_of!(Envelope<InitConf>, payload)][span_of!(InitConf, sidi)]
|
||||
/// .copy_from_slice(&[1,2,3,4]);
|
||||
///
|
||||
/// // Conversion from bytes
|
||||
/// let ih = Ref::<&mut [u8], Envelope<InitConf>>::new(ih).unwrap();
|
||||
///
|
||||
/// // Check that write above on byte representation was effective
|
||||
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
|
||||
/// ```
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
|
||||
pub struct InitConf {
|
||||
/// Copied from InitHello
|
||||
pub sidi: [u8; 4],
|
||||
@@ -82,8 +239,53 @@ pub struct InitConf {
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
}
|
||||
|
||||
/// This is the fourth message sent by the initiator to the responder
|
||||
/// during the execution of the Rosenpass protocol in response to [RespHello].
|
||||
///
|
||||
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||
///
|
||||
/// This message does not serve a cryptographic purpose; it just tells the initiator
|
||||
/// to stop package retransmission.
|
||||
///
|
||||
/// This message should really be called `RespConf`, but when we wrote the protocol,
|
||||
/// we initially designed the protocol we still though Rosenpass itself should do
|
||||
/// payload transmission at some point so `EmptyData` could have served as a more generic
|
||||
/// mechanism.
|
||||
///
|
||||
/// We might add payload transmission in the future again, but we will treat
|
||||
/// it as a protocol extension if we do.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Check out the code of [crate::protocol::CryptoServer::handle_init_conf] (generation on
|
||||
/// responder side) and [crate::protocol::CryptoServer::handle_resp_conf] (processing on
|
||||
/// initiator side) to understand how this is used.
|
||||
///
|
||||
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::msgs::{Envelope, EmptyData};
|
||||
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||
/// use memoffset::span_of;
|
||||
///
|
||||
/// // Zero initialization
|
||||
/// let mut ih = Envelope::<EmptyData>::new_zeroed();
|
||||
///
|
||||
/// // Conversion to byte representation
|
||||
/// let ih = ih.as_bytes_mut();
|
||||
///
|
||||
/// // Set value on byte representation
|
||||
/// ih[span_of!(Envelope<EmptyData>, payload)][span_of!(EmptyData, sid)]
|
||||
/// .copy_from_slice(&[1,2,3,4]);
|
||||
///
|
||||
/// // Conversion from bytes
|
||||
/// let ih = Ref::<&mut [u8], Envelope<EmptyData>>::new(ih).unwrap();
|
||||
///
|
||||
/// // Check that write above on byte representation was effective
|
||||
/// assert_eq!(ih.payload.sid, [1,2,3,4]);
|
||||
/// ```
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Copy)]
|
||||
pub struct EmptyData {
|
||||
/// Copied from RespHello
|
||||
pub sid: [u8; 4],
|
||||
@@ -93,6 +295,22 @@ pub struct EmptyData {
|
||||
pub auth: [u8; aead::TAG_LEN],
|
||||
}
|
||||
|
||||
/// Cookie encrypted and sent to the initiator by the responder in [RespHello]
|
||||
/// and returned by the initiator in [InitConf].
|
||||
///
|
||||
/// The encryption key is randomly chosen by the responder and frequently regenerated.
|
||||
/// Using this biscuit value in the protocol allows us to make sure that the responder
|
||||
/// is mostly stateless until full initiator authentication is achieved, which is needed
|
||||
/// to prevent denial of service attacks. See the [whitepaper](https://rosenpass.eu/whitepaper.pdf)
|
||||
/// ([/papers/whitepaper.md] in this repository).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the biscuit is used, it is best to read
|
||||
/// the code of [crate::protocol::HandshakeState::store_biscuit] and
|
||||
/// [crate::protocol::HandshakeState::load_biscuit]
|
||||
///
|
||||
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct Biscuit {
|
||||
@@ -104,12 +322,20 @@ pub struct Biscuit {
|
||||
pub ck: [u8; KEY_LEN],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct DataMsg {
|
||||
pub dummy: [u8; 4],
|
||||
}
|
||||
|
||||
/// Specialized message for use in the cookie mechanism.
|
||||
///
|
||||
/// See the [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository) for details.
|
||||
///
|
||||
/// Generally used together with [CookieReply] which brings this up to the size
|
||||
/// of [InitHello] to avoid amplification Denial of Service attacks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the biscuit is used, it is best to read
|
||||
/// the code of [crate::protocol::CryptoServer::handle_cookie_reply] and
|
||||
/// [crate::protocol::CryptoServer::handle_msg_under_load].
|
||||
///
|
||||
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct CookieReplyInner {
|
||||
@@ -123,6 +349,20 @@ pub struct CookieReplyInner {
|
||||
pub cookie_encrypted: [u8; xaead::NONCE_LEN + COOKIE_SIZE + xaead::TAG_LEN],
|
||||
}
|
||||
|
||||
/// Specialized message for use in the cookie mechanism.
|
||||
///
|
||||
/// This just brings [CookieReplyInner] up to the size
|
||||
/// of [InitHello] to avoid amplification Denial of Service attacks.
|
||||
///
|
||||
/// See the [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository) for details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// To understand how the biscuit is used, it is best to read
|
||||
/// the code of [crate::protocol::CryptoServer::handle_cookie_reply] and
|
||||
/// [crate::protocol::CryptoServer::handle_msg_under_load].
|
||||
///
|
||||
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct CookieReply {
|
||||
@@ -130,33 +370,46 @@ pub struct CookieReply {
|
||||
pub padding: [u8; size_of::<Envelope<InitHello>>() - size_of::<CookieReplyInner>()],
|
||||
}
|
||||
|
||||
// Traits /////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub trait WireMsg: std::fmt::Debug {
|
||||
const MSG_TYPE: MsgType;
|
||||
const MSG_TYPE_U8: u8 = Self::MSG_TYPE as u8;
|
||||
const BYTES: usize;
|
||||
}
|
||||
|
||||
// Constants //////////////////////////////////////////////////////////////////
|
||||
|
||||
pub const SESSION_ID_LEN: usize = 4;
|
||||
pub const BISCUIT_ID_LEN: usize = 12;
|
||||
|
||||
pub const WIRE_ENVELOPE_LEN: usize = 1 + 3 + 16 + 16; // TODO verify this
|
||||
|
||||
/// Size required to fit any message in binary form
|
||||
pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
|
||||
|
||||
/// Recognized message types
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::msgs::MsgType;
|
||||
/// use rosenpass::msgs::MsgType as M;
|
||||
///
|
||||
/// let values = [M::InitHello, M::RespHello, M::InitConf, M::EmptyData, M::CookieReply];
|
||||
/// let values_u8 = values.map(|v| -> u8 { v.into() });
|
||||
///
|
||||
/// // Can be converted to and from u8 using [::std::convert::Into] or [::std::convert::From]
|
||||
/// for v in values.iter().copied() {
|
||||
/// let v_u8 : u8 = v.into();
|
||||
/// let v2 : MsgType = v_u8.try_into()?;
|
||||
/// assert_eq!(v, v2);
|
||||
/// }
|
||||
///
|
||||
/// // Converting an unsupported type produces an error
|
||||
/// let invalid_values = (u8::MIN..=u8::MAX)
|
||||
/// .filter(|v| !values_u8.contains(v));
|
||||
/// for v in invalid_values {
|
||||
/// let res : Result<MsgType, _> = v.try_into();
|
||||
/// assert!(res.is_err());
|
||||
/// }
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
#[repr(u8)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum MsgType {
|
||||
/// MsgType for [InitHello]
|
||||
InitHello = 0x81,
|
||||
/// MsgType for [RespHello]
|
||||
RespHello = 0x82,
|
||||
/// MsgType for [InitConf]
|
||||
InitConf = 0x83,
|
||||
/// MsgType for [EmptyData]
|
||||
EmptyData = 0x84,
|
||||
DataMsg = 0x85,
|
||||
/// MsgType for [CookieReply]
|
||||
CookieReply = 0x86,
|
||||
}
|
||||
|
||||
@@ -169,7 +422,6 @@ impl TryFrom<u8> for MsgType {
|
||||
0x82 => MsgType::RespHello,
|
||||
0x83 => MsgType::InitConf,
|
||||
0x84 => MsgType::EmptyData,
|
||||
0x85 => MsgType::DataMsg,
|
||||
0x86 => MsgType::CookieReply,
|
||||
_ => return Err(RosenpassError::InvalidMessageType(value)),
|
||||
})
|
||||
@@ -182,12 +434,6 @@ impl From<MsgType> for u8 {
|
||||
}
|
||||
}
|
||||
|
||||
/// length in bytes of an unencrypted Biscuit (plain text)
|
||||
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
||||
|
||||
/// Length in bytes of an encrypted Biscuit (cipher text)
|
||||
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_constants {
|
||||
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
394
rosenpass/src/protocol/build_crypto_server.rs
Normal file
394
rosenpass/src/protocol/build_crypto_server.rs
Normal file
@@ -0,0 +1,394 @@
|
||||
use rosenpass_util::{
|
||||
build::Build,
|
||||
mem::{DiscardResultExt, SwapWithDefaultExt},
|
||||
result::ensure_or,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A pair of matching public/secret keys used to launch the crypto server.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Decomposing a key pair into its individual components, then recreating it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass::protocol::Keypair;
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let random_pair = Keypair::random();
|
||||
/// let random_copy = random_pair.clone();
|
||||
/// let (sk_copy, pk_copy) = random_copy.into_parts();
|
||||
///
|
||||
/// // Re-assemble the key pair from the original secret/public key
|
||||
/// // Note that it doesn't have to be the exact same keys;
|
||||
/// // you could just as easily use a completely different pair here
|
||||
/// let reconstructed_pair = Keypair::from_parts((sk_copy, pk_copy));
|
||||
///
|
||||
/// assert_eq!(random_pair.sk.secret(), reconstructed_pair.sk.secret());
|
||||
/// assert_eq!(random_pair.pk, reconstructed_pair.pk);
|
||||
/// ```
|
||||
pub struct Keypair {
|
||||
/// Secret key matching the crypto server's public key.
|
||||
pub sk: SSk,
|
||||
/// Public key identifying the crypto server instance.
|
||||
pub pk: SPk,
|
||||
}
|
||||
|
||||
// TODO: We need a named tuple derive
|
||||
impl Keypair {
|
||||
/// Creates a new key pair from the given secret/public key components.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass::protocol::{Keypair, SSk, SPk};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let random_sk = SSk::random();
|
||||
/// let random_pk = SPk::random();
|
||||
/// let random_pair = Keypair::new(random_sk.clone(), random_pk.clone());
|
||||
///
|
||||
/// assert_eq!(random_sk.secret(), random_pair.sk.secret());
|
||||
/// assert_eq!(random_pk, random_pair.pk);
|
||||
/// ```
|
||||
pub fn new(sk: SSk, pk: SPk) -> Self {
|
||||
Self { sk, pk }
|
||||
}
|
||||
|
||||
/// Creates a new "empty" key pair. All bytes are initialized to zero.
|
||||
///
|
||||
/// See [SSk:zero()][crate::protocol::SSk::zero] and [SPk:zero()][crate::protocol::SPk::zero], respectively.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass::protocol::{Keypair, SSk, SPk};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let zero_sk = SSk::zero();
|
||||
/// let zero_pk = SPk::zero();
|
||||
/// let zero_pair = Keypair::zero();
|
||||
///
|
||||
/// assert_eq!(zero_sk.secret(), zero_pair.sk.secret());
|
||||
/// assert_eq!(zero_pk, zero_pair.pk);
|
||||
/// ```
|
||||
pub fn zero() -> Self {
|
||||
Self::new(SSk::zero(), SPk::zero())
|
||||
}
|
||||
|
||||
/// Creates a new (securely-)random key pair. The mechanism is described in [rosenpass_secret_memory::Secret].
|
||||
///
|
||||
/// See [SSk:random()][crate::protocol::SSk::random] and [SPk:random()][crate::protocol::SPk::random], respectively.
|
||||
pub fn random() -> Self {
|
||||
Self::new(SSk::random(), SPk::random())
|
||||
}
|
||||
|
||||
/// Creates a new key pair from the given public/secret key components.
|
||||
pub fn from_parts(parts: (SSk, SPk)) -> Self {
|
||||
Self::new(parts.0, parts.1)
|
||||
}
|
||||
|
||||
/// Deconstructs the key pair, yielding the individual public/secret key components.
|
||||
pub fn into_parts(self) -> (SSk, SPk) {
|
||||
(self.sk, self.pk)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("PSK already set in BuildCryptoServer")]
|
||||
/// Error indicating that the PSK is already set.
|
||||
/// Unused in the current version of the protocol.
|
||||
pub struct PskAlreadySet;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Keypair already set in BuildCryptoServer")]
|
||||
/// Error type indicating that the public/secret key pair has already been set.
|
||||
pub struct KeypairAlreadySet;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Can not construct CryptoServer: Missing keypair")]
|
||||
/// Error type indicating that no public/secret key pair has been provided.
|
||||
pub struct MissingKeypair;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
/// Builder for setting up a [CryptoServer] (with deferred initialization).
|
||||
///
|
||||
/// There are multiple ways of creating a crypto server:
|
||||
///
|
||||
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][crate::protocol::CryptoServer::new])
|
||||
/// 2. Provide the key pair at a later time (using [BuildCryptoServer::empty])
|
||||
///
|
||||
/// With BuildCryptoServer, you can gradually configure parameters as they become available.
|
||||
/// This may be useful when they depend on runtime conditions or have to be fetched asynchronously.
|
||||
/// It's possible to use the builder multiple times; it then serves as a "blueprint" for new
|
||||
/// instances, several of which may be spawned with the same base configuration (or variations thereof).
|
||||
///
|
||||
/// Note that the server won't actually launch without a key pair (expect a [MissingKeypair] error).
|
||||
/// The setup will be much simplified if one is provided, at the cost of some flexibility.
|
||||
/// It's however possible to defer this step in case your application requires it.
|
||||
///
|
||||
/// For additional details or examples, see [AppServer::crypto_site][crate::app_server::AppServer::crypto_site] and [ConstructionSite][rosenpass_util::build::ConstructionSite].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let keypair = Keypair::random();
|
||||
/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random() };
|
||||
/// let peer2 = PeerParams { psk: None, pk: SPk::random() };
|
||||
///
|
||||
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]);
|
||||
/// builder.add_peer(peer2.psk.clone(), peer2.pk);
|
||||
///
|
||||
/// let server = builder.build().expect("build failed");
|
||||
/// assert_eq!(server.peers.len(), 2);
|
||||
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(server.spkm, keypair.pk);
|
||||
/// ```
|
||||
pub struct BuildCryptoServer {
|
||||
/// The key pair (secret/public key) identifying the crypto server instance.
|
||||
pub keypair: Option<Keypair>,
|
||||
/// A list of network peers that should be registered when launching the server.
|
||||
pub peers: Vec<PeerParams>,
|
||||
}
|
||||
|
||||
impl Build<CryptoServer> for BuildCryptoServer {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
/// Creates a crypto server, adding all peers that have previously been registered.
|
||||
///
|
||||
/// You must provide a key pair at the time of instantiation.
|
||||
/// If the list of peers is outdated, building the server will fail.
|
||||
///
|
||||
/// In this case, make sure to remove or re-add any peers that may have changed.
|
||||
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)]
|
||||
/// Cryptographic key(s) identifying the connected [peer][crate::protocol::Peer] ("client")
|
||||
/// for a given session that is being managed by the crypto server.
|
||||
///
|
||||
/// Each peer must be identified by a [public key (SPk)][crate::protocol::SPk].
|
||||
/// Optionally, a [symmetric key (SymKey)][crate::protocol::SymKey]
|
||||
/// can be provided when setting up the connection.
|
||||
/// For more information on the intended usage and security considerations, see [Peer::psk][crate::protocol::Peer::psk] and [Peer::spkt][crate::protocol::Peer::spkt].
|
||||
pub struct PeerParams {
|
||||
/// Pre-shared (symmetric) encryption keys that should be used with this peer.
|
||||
pub psk: Option<SymKey>,
|
||||
/// Public key identifying the peer.
|
||||
pub pk: SPk,
|
||||
}
|
||||
|
||||
impl BuildCryptoServer {
|
||||
/// Creates a new builder instance using the given key pair and peer list.
|
||||
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
|
||||
Self { keypair, peers }
|
||||
}
|
||||
|
||||
/// Creates an "incomplete" builder instance, without assigning a key pair.
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None, Vec::new())
|
||||
}
|
||||
|
||||
/// Creates a builder instance from the given key pair and peer list components.
|
||||
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
|
||||
Self {
|
||||
keypair: parts.0,
|
||||
peers: parts.1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Deconstructs the current builder instance, taking ownership of its key pair and peer list.
|
||||
///
|
||||
/// Replaces all parameters with their default values, which allows extracting them
|
||||
/// while leaving the builder in a reusable state.
|
||||
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||
(self.keypair.take(), self.peers.swap_with_default())
|
||||
}
|
||||
|
||||
/// Deconstructs the builder instance, yielding the assigned key pair and peer list.
|
||||
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||
self.take_parts()
|
||||
}
|
||||
|
||||
/// Creates a new builder instance, assigning the given keypair to it.
|
||||
///
|
||||
/// Note that only one key pair can be assigned (expect [KeypairAlreadySet] on failure).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Adding key pairs to an existing builder
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
|
||||
///
|
||||
/// // Deferred initialization: Create builder first, add the key pair later
|
||||
/// let mut builder = BuildCryptoServer::empty();
|
||||
/// // Do something with the builder ...
|
||||
///
|
||||
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
|
||||
/// // Now we've got a key pair that should be added to the configuration
|
||||
/// let keypair = Keypair::random();
|
||||
/// builder.with_keypair(keypair.clone()).expect("build with key pair failed");
|
||||
///
|
||||
/// // New server instances can now make use of the assigned key pair
|
||||
/// let server = builder.build().expect("build failed");
|
||||
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(server.spkm, keypair.pk);
|
||||
/// ```
|
||||
///
|
||||
/// ## Basic error handling: Re-assigning key pairs
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, KeypairAlreadySet};
|
||||
///
|
||||
/// // In this case, we'll create a functional builder from its various components
|
||||
/// // These could be salvaged from another builder, or obtained from disk/network (etc.)
|
||||
/// let keypair = Keypair::random();
|
||||
/// let mut builder = BuildCryptoServer::from_parts((Some(keypair.clone()), Vec::new()));
|
||||
///
|
||||
/// // The builder has already been assigned a key pair, so this won't work
|
||||
/// let err = builder.with_keypair(keypair).expect_err("should fail to reassign key pair");
|
||||
/// assert!(matches!(err, KeypairAlreadySet));
|
||||
/// ```
|
||||
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)
|
||||
}
|
||||
|
||||
/// Creates a new builder instance, adding a new entry to the list of registered peers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Adding peers to an existing builder:
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
|
||||
///
|
||||
/// // Deferred initialization: Create builder first, add some peers later
|
||||
/// let keypair_option = Some(Keypair::random());
|
||||
/// let mut builder = BuildCryptoServer::new(keypair_option, Vec::new());
|
||||
/// assert!(builder.peers.is_empty());
|
||||
///
|
||||
/// // Do something with the builder ...
|
||||
///
|
||||
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
|
||||
/// // Now we've found a peer that should be added to the configuration
|
||||
/// let pre_shared_key = SymKey::random();
|
||||
/// let public_key = SPk::random();
|
||||
/// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone());
|
||||
///
|
||||
/// // New server instances will then start with the peer being registered already
|
||||
/// let server = builder.build().expect("build failed");
|
||||
/// assert_eq!(server.peers.len(), 1);
|
||||
/// let peer = &server.peers[0];
|
||||
/// let peer_psk = Some(peer.psk.clone()).expect("PSK is None");
|
||||
/// assert_eq!(peer.spkt, public_key);
|
||||
/// assert_eq!(peer_psk.secret(), pre_shared_key.secret());
|
||||
/// ```
|
||||
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
|
||||
}
|
||||
|
||||
/// Add a new entry to the list of registered peers, with or without a pre-shared key.
|
||||
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
|
||||
}
|
||||
|
||||
/// Creates a new builder, taking ownership of another instance's key pair and peer list.
|
||||
/// Allows duplicating the current set of launch parameters, which can then be used to
|
||||
/// start multiple servers with the exact same configuration (or variants using it as a base).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Extracting the server configuration from a builder:
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
|
||||
///
|
||||
/// let keypair = Keypair::random();
|
||||
/// let peer_pk = SPk::random();
|
||||
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]);
|
||||
/// builder.add_peer(None, peer_pk);
|
||||
///
|
||||
/// // Extract configuration parameters from the decomissioned builder
|
||||
/// let (keypair_option, peers) = builder.take_parts();
|
||||
/// let extracted_keypair = keypair_option.unwrap();
|
||||
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(extracted_keypair.pk, keypair.pk);
|
||||
/// assert_eq!(peers.len(), 1);
|
||||
///
|
||||
/// // Now we can create a new builder with the same configuration
|
||||
/// let parts = (Some(extracted_keypair), peers);
|
||||
/// let mut reassembled_builder = BuildCryptoServer::from_parts(parts);
|
||||
/// let new_builder = reassembled_builder.emancipate();
|
||||
///
|
||||
/// // Do something with the new builder ...
|
||||
///
|
||||
/// // ... and now, deconstruct this one as well - still using the same parts
|
||||
/// let (keypair_option, peers) = new_builder.into_parts();
|
||||
/// let extracted_keypair = keypair_option.unwrap();
|
||||
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(extracted_keypair.pk, keypair.pk);
|
||||
/// assert_eq!(peers.len(), 1);
|
||||
/// ```
|
||||
pub fn emancipate(&mut self) -> Self {
|
||||
Self::from_parts(self.take_parts())
|
||||
}
|
||||
}
|
||||
82
rosenpass/src/protocol/mod.rs
Normal file
82
rosenpass/src/protocol/mod.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
//! Module containing the cryptographic protocol implementation
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! The most important types in this module probably are [PollResult]
|
||||
//! & [CryptoServer]. Once a [CryptoServer] is created, the server is
|
||||
//! provided with new messages via the [CryptoServer::handle_msg] method.
|
||||
//! The [CryptoServer::poll] method can be used to let the server work, which
|
||||
//! will eventually yield a [PollResult]. Said [PollResult] contains
|
||||
//! prescriptive activities to be carried out. [CryptoServer::osk] can than
|
||||
//! be used to extract the shared key for two peers, once a key-exchange was
|
||||
//! successful.
|
||||
//!
|
||||
//! TODO explain briefly the role of epki
|
||||
//!
|
||||
//! # Example Handshake
|
||||
//!
|
||||
//! This example illustrates a minimal setup for a key-exchange between two
|
||||
//! [CryptoServer]s; this is what we use for some testing purposes but it is not
|
||||
//! what should be used in a real world application, as timing-based events
|
||||
//! are handled by [CryptoServer::poll].
|
||||
//!
|
||||
//! See [CryptoServer::poll] on how to use crypto server in polling mode for production usage.
|
||||
//!
|
||||
//! ```
|
||||
//! use std::ops::DerefMut;
|
||||
//! use rosenpass_secret_memory::policy::*;
|
||||
//! use rosenpass_cipher_traits::Kem;
|
||||
//! use rosenpass_ciphers::kem::StaticKem;
|
||||
//! use rosenpass::{
|
||||
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
|
||||
//! };
|
||||
//! # fn main() -> anyhow::Result<()> {
|
||||
//! // Set security policy for storing secrets
|
||||
//!
|
||||
//! secret_policy_try_use_memfd_secrets();
|
||||
//!
|
||||
//! // initialize secret and public key for peer a ...
|
||||
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||
//!
|
||||
//! // ... and for peer b
|
||||
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||
//!
|
||||
//! // initialize server and a pre-shared key
|
||||
//! let psk = SymKey::random();
|
||||
//! let mut a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
||||
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
||||
//!
|
||||
//! // introduce peers to each other
|
||||
//! a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
||||
//! b.add_peer(Some(psk), peer_a_pk)?;
|
||||
//!
|
||||
//! // declare buffers for message exchange
|
||||
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
|
||||
//!
|
||||
//! // let a initiate a handshake
|
||||
//! let mut maybe_len = Some(a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice())?);
|
||||
//!
|
||||
//! // let a and b communicate
|
||||
//! while let Some(len) = maybe_len {
|
||||
//! maybe_len = b.handle_msg(&a_buf[..len], &mut b_buf[..])?.resp;
|
||||
//! std::mem::swap(&mut a, &mut b);
|
||||
//! std::mem::swap(&mut a_buf, &mut b_buf);
|
||||
//! }
|
||||
//!
|
||||
//! // all done! Extract the shared keys and ensure they are identical
|
||||
//! let a_key = a.osk(PeerPtr(0))?;
|
||||
//! let b_key = b.osk(PeerPtr(0))?;
|
||||
//! assert_eq!(a_key.secret(), b_key.secret(),
|
||||
//! "the key exchanged failed to establish a shared secret");
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
mod build_crypto_server;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod protocol;
|
||||
|
||||
pub use build_crypto_server::*;
|
||||
pub use protocol::*;
|
||||
4427
rosenpass/src/protocol/protocol.rs
Normal file
4427
rosenpass/src/protocol/protocol.rs
Normal file
File diff suppressed because it is too large
Load Diff
340
rosenpass/tests/api-integration-tests-api-setup.rs
Normal file
340
rosenpass/tests/api-integration-tests-api-setup.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
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) {
|
||||
use rustix::process::{kill_process, Pid, Signal::Term};
|
||||
let pid = Pid::from_child(&self.0);
|
||||
// We seriously need to start handling signals with signalfd, our current signal handling
|
||||
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||
// so the signal is absorbed
|
||||
loop {
|
||||
kill_process(pid, Term).discard_result();
|
||||
if self.0.try_wait().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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())
|
||||
.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(())
|
||||
}
|
||||
@@ -8,16 +8,34 @@ use std::{
|
||||
use anyhow::{bail, Context};
|
||||
use rosenpass::api;
|
||||
use rosenpass_to::{ops::copy_slice_least_src, To};
|
||||
use rosenpass_util::zerocopy::ZerocopySliceExt;
|
||||
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) {
|
||||
use rustix::process::{kill_process, Pid, Signal::Term};
|
||||
let pid = Pid::from_child(&self.0);
|
||||
// We seriously need to start handling signals with signalfd, our current signal handling
|
||||
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||
// so the signal is absorbed
|
||||
loop {
|
||||
kill_process(pid, Term).discard_result();
|
||||
if self.0.try_wait().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_integration_test() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
@@ -37,10 +55,11 @@ fn api_integration_test() -> anyhow::Result<()> {
|
||||
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"),
|
||||
secret_key: tempfile!("a.sk"),
|
||||
public_key: tempfile!("a.pk"),
|
||||
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 {
|
||||
@@ -57,10 +76,10 @@ fn api_integration_test() -> anyhow::Result<()> {
|
||||
}],
|
||||
};
|
||||
|
||||
let peer_b_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk"));
|
||||
let peer_b = config::Rosenpass {
|
||||
config_file_path: tempfile!("b.config"),
|
||||
secret_key: tempfile!("b.sk"),
|
||||
public_key: tempfile!("b.pk"),
|
||||
keypair: Some(peer_b_keypair.clone()),
|
||||
listen: vec![],
|
||||
verbosity: config::Verbosity::Verbose,
|
||||
api: api::config::ApiConfig {
|
||||
@@ -79,12 +98,12 @@ fn api_integration_test() -> anyhow::Result<()> {
|
||||
|
||||
// Generate the keys
|
||||
rosenpass::cli::testing::generate_and_save_keypair(
|
||||
peer_a.secret_key.clone(),
|
||||
peer_a.public_key.clone(),
|
||||
peer_a_keypair.secret_key.clone(),
|
||||
peer_a_keypair.public_key.clone(),
|
||||
)?;
|
||||
rosenpass::cli::testing::generate_and_save_keypair(
|
||||
peer_b.secret_key.clone(),
|
||||
peer_b.public_key.clone(),
|
||||
peer_b_keypair.secret_key.clone(),
|
||||
peer_b_keypair.public_key.clone(),
|
||||
)?;
|
||||
|
||||
// Write the configuration files
|
||||
@@ -92,28 +111,32 @@ fn api_integration_test() -> anyhow::Result<()> {
|
||||
peer_b.commit()?;
|
||||
|
||||
// Start peer a
|
||||
let proc_a = 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()?;
|
||||
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 proc_b = 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()?;
|
||||
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.stdout.context("")?).lines();
|
||||
let mut out_b = BufReader::new(proc_b.stdout.context("")?).lines();
|
||||
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;
|
||||
|
||||
130
rosenpass/tests/app_server_example.rs
Normal file
130
rosenpass/tests/app_server_example.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
ops::DerefMut,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
thread::{self, sleep},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::ensure;
|
||||
use rosenpass::{
|
||||
app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE},
|
||||
protocol::{SPk, SSk, SymKey},
|
||||
};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
|
||||
|
||||
#[test]
|
||||
fn key_exchange_with_app_server() -> anyhow::Result<()> {
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
let outfile_a = tmpdir.path().join("osk_a");
|
||||
let outfile_b = tmpdir.path().join("osk_b");
|
||||
|
||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
// Introduce the servers to each other
|
||||
let psk_a = SymKey::random();
|
||||
let psk_b = psk_a.clone();
|
||||
|
||||
let (tx_a, rx_b) = mpsc::sync_channel(1);
|
||||
let (tx_b, rx_a) = mpsc::sync_channel(1);
|
||||
|
||||
let (tx_term_a, rx_term_a) = mpsc::channel();
|
||||
let (tx_term_b, rx_term_b) = mpsc::channel();
|
||||
|
||||
let configs = [
|
||||
(false, outfile_a.clone(), psk_a, tx_a, rx_a, rx_term_a),
|
||||
(true, outfile_b.clone(), psk_b, tx_b, rx_b, rx_term_b),
|
||||
];
|
||||
|
||||
for (is_client, osk, psk, tx, rx, rx_term) in configs {
|
||||
thread::spawn(move || {
|
||||
run(move || -> anyhow::Result<()> {
|
||||
let mut srv = TestServer::new(rx_term)?;
|
||||
|
||||
tx.send((srv.loopback_port()?, srv.public_key()?.clone()))?;
|
||||
let (otr_port, otr_pk) = rx.recv()?;
|
||||
|
||||
let psk = Some(psk);
|
||||
let broker_peer = None;
|
||||
let pk = otr_pk;
|
||||
let outfile = Some(osk);
|
||||
let port = otr_port;
|
||||
let hostname = is_client.then(|| format!("[::1]:{port}"));
|
||||
srv.app_srv
|
||||
.add_peer(psk, pk, outfile, broker_peer, hostname)?;
|
||||
|
||||
srv.app_srv.event_loop()
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
// Busy wait for both keys to be exchanged
|
||||
let mut successful_exchange = false;
|
||||
for _ in 0..2000 {
|
||||
// 40s
|
||||
sleep(Duration::from_millis(20));
|
||||
run(|| -> anyhow::Result<()> {
|
||||
let osk_a = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_a)?;
|
||||
let osk_b = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_b)?;
|
||||
successful_exchange = rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret());
|
||||
Ok(())
|
||||
})
|
||||
.discard_result();
|
||||
if successful_exchange {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the parties to terminate
|
||||
tx_term_a.send(())?;
|
||||
tx_term_b.send(())?;
|
||||
|
||||
assert!(
|
||||
successful_exchange,
|
||||
"Test did not complete successfully within the deadline"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TestServer {
|
||||
app_srv: AppServer,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
fn new(termination_queue: mpsc::Receiver<()>) -> anyhow::Result<Self> {
|
||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||
|
||||
let keypair = Some((sk, pk));
|
||||
let addrs = vec![
|
||||
SocketAddr::from_str("[::1]:0")?, // Localhost, any port. For connecting to the test server.
|
||||
// ipv4_any_binding(), // any IPv4 interface
|
||||
// ipv6_any_binding(), // any IPv6 interface
|
||||
];
|
||||
let verbosity = rosenpass::config::Verbosity::Verbose;
|
||||
let test_helpers = Some(AppServerTest {
|
||||
enable_dos_permanently: false,
|
||||
termination_handler: Some(termination_queue),
|
||||
});
|
||||
|
||||
let app_srv = AppServer::new(keypair, addrs, verbosity, test_helpers)?;
|
||||
|
||||
Self { app_srv }.ok()
|
||||
}
|
||||
|
||||
fn loopback_port(&self) -> anyhow::Result<u16> {
|
||||
self.app_srv.sockets[0].local_addr()?.port().ok()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> anyhow::Result<&SPk> {
|
||||
Ok(&self.app_srv.crypto_server()?.spkm)
|
||||
}
|
||||
}
|
||||
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use rosenpass::config::Rosenpass;
|
||||
|
||||
#[test]
|
||||
fn config_Rosenpass_add_if_any_example() {
|
||||
let mut v = Rosenpass::empty();
|
||||
v.add_if_any(4000);
|
||||
|
||||
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
|
||||
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
|
||||
}
|
||||
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use rosenpass::config::{Keypair, Rosenpass};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_new() {
|
||||
let (sk, pk) = ("./example.sk", "./example.pk");
|
||||
|
||||
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
|
||||
assert_eq!(Rosenpass::empty(), Rosenpass::default());
|
||||
|
||||
assert_eq!(
|
||||
Rosenpass::from_sk_pk(sk, pk),
|
||||
Rosenpass::new(Some(Keypair::new(pk, sk)))
|
||||
);
|
||||
|
||||
let mut v = Rosenpass::empty();
|
||||
v.keypair = Some(Keypair::new(pk, sk));
|
||||
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
|
||||
}
|
||||
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
|
||||
|
||||
#[test]
|
||||
fn parse_simple() {
|
||||
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
|
||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||
peer.test:9999 outfile /peer/rp-out";
|
||||
let argv = argv.split(' ').map(|s| s.to_string()).collect();
|
||||
|
||||
let config = Rosenpass::parse_args(argv).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers,
|
||||
vec![RosenpassPeer {
|
||||
public_key: PathBuf::from("/peer/public-key"),
|
||||
endpoint: Some("peer.test:9999".into()),
|
||||
pre_shared_key: None,
|
||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||
..Default::default()
|
||||
}]
|
||||
);
|
||||
}
|
||||
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rosenpass::config::{Rosenpass, Verbosity};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_store() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let sk = tmpdir.path().join("example.sk");
|
||||
let pk = tmpdir.path().join("example.pk");
|
||||
let cfg = tmpdir.path().join("config.toml");
|
||||
|
||||
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
|
||||
|
||||
// Can not commit config, path not known
|
||||
assert!(c.commit().is_err());
|
||||
|
||||
// We can store it to an explicit path though
|
||||
c.store(&cfg)?;
|
||||
|
||||
// Storing does not set commitment path
|
||||
assert!(c.commit().is_err());
|
||||
|
||||
// We can reload the config now and the configurations
|
||||
// are equal if we adjust the commitment path
|
||||
let mut c2 = Rosenpass::load(&cfg)?;
|
||||
c.config_file_path = PathBuf::from(&cfg);
|
||||
assert_eq!(c, c2);
|
||||
|
||||
// And this loaded config can now be committed
|
||||
c2.verbosity = Verbosity::Verbose;
|
||||
c2.commit()?;
|
||||
|
||||
// And the changes actually made it to disk
|
||||
let c3 = Rosenpass::load(cfg)?;
|
||||
assert_eq!(c2, c3);
|
||||
assert_ne!(c, c3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fs;
|
||||
|
||||
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
// Empty validates OK
|
||||
assert!(Rosenpass::empty().validate().is_ok());
|
||||
|
||||
// Missing secret key does not pass usefulness
|
||||
assert!(Rosenpass::empty().check_usefullness().is_err());
|
||||
|
||||
let sk = tmpdir.path().join("example.sk");
|
||||
let pk = tmpdir.path().join("example.pk");
|
||||
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
|
||||
|
||||
// Missing secret key does not validate
|
||||
assert!(cfg.validate().is_err());
|
||||
|
||||
// But passes usefulness (the configuration is useful but invalid)
|
||||
assert!(cfg.check_usefullness().is_ok());
|
||||
|
||||
// Providing empty key files does not help
|
||||
fs::write(&sk, b"")?;
|
||||
fs::write(&pk, b"")?;
|
||||
assert!(cfg.validate().is_err());
|
||||
|
||||
// But after providing proper key files, the configuration validates
|
||||
generate_and_save_keypair(sk, pk)?;
|
||||
assert!(cfg.validate().is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
15
rosenpass/tests/gen-ipc-msg-types.rs
Normal file
15
rosenpass/tests/gen-ipc-msg-types.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::{borrow::Borrow, process::Command};
|
||||
|
||||
#[test]
|
||||
fn test_gen_ipc_msg_types() -> anyhow::Result<()> {
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_rosenpass-gen-ipc-msg-types")).output()?;
|
||||
assert!(out.status.success());
|
||||
|
||||
let stdout = String::from_utf8(out.stdout)?;
|
||||
|
||||
// Smoke tests only
|
||||
assert!(stdout.contains("type RawMsgType = u128;"));
|
||||
assert!(stdout.contains("const SUPPLY_KEYPAIR_RESPONSE : RawMsgType = RawMsgType::from_le_bytes(hex!(\"f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9\"));"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::fs::File;
|
||||
use std::{
|
||||
fs,
|
||||
net::UdpSocket,
|
||||
@@ -5,9 +6,10 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
|
||||
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_wireguard_broker::{WireguardBrokerMio, WG_KEY_LEN, WG_PEER_LEN};
|
||||
use serial_test::serial;
|
||||
@@ -108,7 +110,7 @@ fn run_server_client_exchange(
|
||||
.termination_handler(Some(server_terminate_rx))
|
||||
.build()
|
||||
.unwrap();
|
||||
cli.run(Some(test_helpers)).unwrap();
|
||||
cli.run(None, Some(test_helpers)).unwrap();
|
||||
});
|
||||
|
||||
let cli = CliArgs::try_parse_from(
|
||||
@@ -123,7 +125,7 @@ fn run_server_client_exchange(
|
||||
.termination_handler(Some(client_terminate_rx))
|
||||
.build()
|
||||
.unwrap();
|
||||
cli.run(Some(test_helpers)).unwrap();
|
||||
cli.run(None, Some(test_helpers)).unwrap();
|
||||
});
|
||||
|
||||
// give them some time to do the key exchange under load
|
||||
@@ -134,6 +136,49 @@ fn run_server_client_exchange(
|
||||
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");
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert_eq!(stderr, "");
|
||||
|
||||
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
|
||||
#[test]
|
||||
#[serial]
|
||||
@@ -293,6 +338,7 @@ struct MockBrokerInner {
|
||||
#[derive(Debug, Default)]
|
||||
struct MockBroker {
|
||||
inner: Arc<Mutex<MockBrokerInner>>,
|
||||
mio_token: Option<mio::Token>,
|
||||
}
|
||||
|
||||
impl WireguardBrokerMio for MockBroker {
|
||||
@@ -301,8 +347,9 @@ impl WireguardBrokerMio for MockBroker {
|
||||
fn register(
|
||||
&mut self,
|
||||
_registry: &mio::Registry,
|
||||
_token: mio::Token,
|
||||
token: mio::Token,
|
||||
) -> Result<(), Self::MioError> {
|
||||
self.mio_token = Some(token);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -311,8 +358,13 @@ impl WireguardBrokerMio for MockBroker {
|
||||
}
|
||||
|
||||
fn unregister(&mut self, _registry: &mio::Registry) -> Result<(), Self::MioError> {
|
||||
self.mio_token = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mio_token(&self) -> Option<mio::Token> {
|
||||
self.mio_token
|
||||
}
|
||||
}
|
||||
|
||||
impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker {
|
||||
|
||||
99
rosenpass/tests/main-fn-generates-manpages.rs
Normal file
99
rosenpass/tests/main-fn-generates-manpages.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use rosenpass_util::functional::ApplyExt;
|
||||
|
||||
fn expect_section(manpage: &str, section: &str) -> anyhow::Result<()> {
|
||||
anyhow::ensure!(manpage.lines().any(|line| { line.starts_with(section) }));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expect_sections(manpage: &str, sections: &[&str]) -> anyhow::Result<()> {
|
||||
for section in sections.iter().copied() {
|
||||
expect_section(manpage, section)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expect_contents(manpage: &str, patterns: &[&str]) -> anyhow::Result<()> {
|
||||
for pat in patterns.iter().copied() {
|
||||
anyhow::ensure!(manpage.contains(pat))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_backspace(str: &str) -> anyhow::Result<String> {
|
||||
let mut out = String::new();
|
||||
for chr in str.chars() {
|
||||
if chr == '\x08' {
|
||||
anyhow::ensure!(out.pop().is_some());
|
||||
} else {
|
||||
out.push(chr);
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Spot tests about man page generation; these are by far not exhaustive.
|
||||
#[test]
|
||||
fn main_fn_generates_manpages() -> anyhow::Result<()> {
|
||||
let dir = tempfile::TempDir::with_prefix("rosenpass-test-main-fn-generates-mangapges")?;
|
||||
let cmd_out = test_bin::get_test_bin("rosenpass")
|
||||
.args(["--generate-manpage", dir.path().to_str().unwrap()])
|
||||
.output()?;
|
||||
assert!(cmd_out.status.success());
|
||||
|
||||
let expected_manpages = [
|
||||
"rosenpass.1",
|
||||
"rosenpass-exchange.1",
|
||||
"rosenpass-exchange-config.1",
|
||||
"rosenpass-gen-config.1",
|
||||
"rosenpass-gen-keys.1",
|
||||
"rosenpass-keygen.1",
|
||||
"rosenpass-validate.1",
|
||||
];
|
||||
|
||||
let man_texts: std::collections::HashMap<&str, String> = expected_manpages
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|name| (name, dir.path().join(name)))
|
||||
.map(|(name, path)| {
|
||||
let res = std::process::Command::new("man").arg(path).output()?;
|
||||
assert!(res.status.success());
|
||||
let body = res
|
||||
.stdout
|
||||
.apply(String::from_utf8)?
|
||||
.apply(|s| filter_backspace(&s))?;
|
||||
Ok((name, body))
|
||||
})
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
for (name, body) in man_texts.iter() {
|
||||
expect_sections(body, &["NAME", "SYNOPSIS", "OPTIONS"])?;
|
||||
|
||||
if *name != "rosenpass.1" {
|
||||
expect_section(body, "DESCRIPTION")?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let body = man_texts.get("rosenpass.1").unwrap();
|
||||
expect_sections(
|
||||
body,
|
||||
&["EXIT STATUS", "SEE ALSO", "STANDARDS", "AUTHORS", "BUGS"],
|
||||
)?;
|
||||
expect_contents(
|
||||
body,
|
||||
&[
|
||||
"[--log-level]",
|
||||
"rosenpass-exchange-config(1)",
|
||||
"Start Rosenpass key exchanges based on a configuration file",
|
||||
"https://rosenpass.eu/whitepaper.pdf",
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
{
|
||||
let body = man_texts.get("rosenpass-exchange.1").unwrap();
|
||||
expect_contents(body, &["[-c|--config-file]", "PSK := preshared-key"])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
10
rosenpass/tests/main-fn-prints-errors.rs
Normal file
10
rosenpass/tests/main-fn-prints-errors.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#[test]
|
||||
fn main_fn_prints_errors() -> anyhow::Result<()> {
|
||||
let out = test_bin::get_test_bin("rosenpass")
|
||||
.args(["exchange-config", "/"])
|
||||
.output()?;
|
||||
assert!(!out.status.success());
|
||||
assert!(String::from_utf8(out.stderr)?.contains("Is a directory (os error 21)"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
642
rosenpass/tests/poll_example.rs
Normal file
642
rosenpass/tests/poll_example.rs
Normal file
@@ -0,0 +1,642 @@
|
||||
/// This file contains a correct simulation of a two-party key exchange using Poll
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
collections::VecDeque,
|
||||
ops::DerefMut,
|
||||
};
|
||||
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_util::result::OkExt;
|
||||
|
||||
use rosenpass::protocol::{
|
||||
testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult,
|
||||
SPk, SSk, SymKey, Timing, UNENDING,
|
||||
};
|
||||
|
||||
// TODO: Most of the utility functions in here should probably be moved to
|
||||
// rosenpass::protocol::testutils;
|
||||
|
||||
#[test]
|
||||
fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
|
||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let mut sim = RosenpassSimulator::new()?;
|
||||
sim.poll_loop(150)?; // Poll 75 times
|
||||
let transcript = sim.transcript;
|
||||
|
||||
let _completions: Vec<_> = transcript
|
||||
.iter()
|
||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
!_completions.is_empty(),
|
||||
"\
|
||||
Should have performed a successful key exchanged!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions[0].0 < 20.0,
|
||||
"\
|
||||
First key exchange should happen in under twenty seconds!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions.len() >= 3,
|
||||
"\
|
||||
Should have at least two renegotiations!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
(110.0..175.0).contains(&_completions[1].0),
|
||||
"\
|
||||
First renegotiation should happen in between two and three minutes!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
|
||||
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
// Create the simulator
|
||||
let mut sim = RosenpassSimulator::new()?;
|
||||
|
||||
// Make sure the servers are set to under load condition
|
||||
sim.srv_a.under_load = true;
|
||||
sim.srv_b.under_load = false; // See Issue #539 -- https://github.com/rosenpass/rosenpass/issues/539
|
||||
|
||||
// Perform the key exchanges
|
||||
let mut pkg_counter = 0usize;
|
||||
for _ in 0..300 {
|
||||
let ev = sim.poll()?;
|
||||
if let TranscriptEvent::ServerEvent {
|
||||
source,
|
||||
event: ServerEvent::Transmit(_, _),
|
||||
} = ev
|
||||
{
|
||||
// Drop every fifth package
|
||||
if pkg_counter % 10 == 0 {
|
||||
source.drop_outgoing_packet(&mut sim);
|
||||
}
|
||||
|
||||
pkg_counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let transcript = sim.transcript;
|
||||
let _completions: Vec<_> = transcript
|
||||
.iter()
|
||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
!_completions.is_empty(),
|
||||
"\
|
||||
Should have performed a successful key exchanged!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions[0].0 < 10.0,
|
||||
"\
|
||||
First key exchange should happen in under twenty seconds!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions.len() >= 3,
|
||||
"\
|
||||
Should have at least two renegotiations!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
(110.0..175.0).contains(&_completions[1].0),
|
||||
"\
|
||||
First renegotiation should happen in between two and three minutes!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
|
||||
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type MessageType = u8;
|
||||
|
||||
/// Lets record the events that are produced by Rosenpass
|
||||
#[derive(Debug)]
|
||||
#[allow(unused)]
|
||||
enum TranscriptEvent {
|
||||
Wait(Timing),
|
||||
ServerEvent {
|
||||
source: ServerPtr,
|
||||
event: ServerEvent,
|
||||
},
|
||||
CompletedExchange(SymKey),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(unused)]
|
||||
enum ServerEvent {
|
||||
DeleteKey,
|
||||
SendInitiationRequested,
|
||||
SendRetransmissionRequested,
|
||||
Exchanged(SymKey),
|
||||
DiscardInvalidMessage(anyhow::Error),
|
||||
Transmit(MessageType, SendMsgReason),
|
||||
Receive(Option<MessageType>),
|
||||
DroppedPackage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum SendMsgReason {
|
||||
Initiation,
|
||||
Response,
|
||||
Retransmission,
|
||||
}
|
||||
|
||||
impl TranscriptEvent {
|
||||
fn hibernate() -> Self {
|
||||
Self::Wait(UNENDING)
|
||||
}
|
||||
|
||||
fn begin_poll() -> Self {
|
||||
Self::hibernate()
|
||||
}
|
||||
|
||||
fn transmit(source: ServerPtr, buf: &[u8], reason: SendMsgReason) -> Self {
|
||||
assert!(!buf.is_empty());
|
||||
let msg_type = buf[0];
|
||||
ServerEvent::Transmit(msg_type, reason).into_transcript_event(source)
|
||||
}
|
||||
|
||||
fn receive(source: ServerPtr, buf: &[u8]) -> Self {
|
||||
let msg_type = (!buf.is_empty()).then(|| buf[0]);
|
||||
ServerEvent::Receive(msg_type).into_transcript_event(source)
|
||||
}
|
||||
|
||||
pub fn try_fold_with<F: FnOnce() -> anyhow::Result<TranscriptEvent>>(
|
||||
self,
|
||||
f: F,
|
||||
) -> anyhow::Result<TranscriptEvent> {
|
||||
let wait_time_a = match self {
|
||||
Self::Wait(wait_time_a) => wait_time_a,
|
||||
els => return (els).ok(),
|
||||
};
|
||||
|
||||
let wait_time_b = match f()? {
|
||||
Self::Wait(wait_time_b) => wait_time_b,
|
||||
els => return els.ok(),
|
||||
};
|
||||
|
||||
let min_wt = if wait_time_a <= wait_time_b {
|
||||
wait_time_a
|
||||
} else {
|
||||
wait_time_b
|
||||
};
|
||||
Self::Wait(min_wt).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerEvent {
|
||||
fn into_transcript_event(self, source: ServerPtr) -> TranscriptEvent {
|
||||
let event = self;
|
||||
TranscriptEvent::ServerEvent { source, event }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RosenpassSimulator {
|
||||
transcript: Vec<(Timing, TranscriptEvent)>,
|
||||
srv_a: SimulatorServer,
|
||||
srv_b: SimulatorServer,
|
||||
poll_focus: ServerPtr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum UpcomingPollResult {
|
||||
IssueEvent(TranscriptEvent),
|
||||
SendMessage(Vec<u8>, TranscriptEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SimulatorServer {
|
||||
/// We sometimes return multiple multiple events in one call,
|
||||
/// but [ServerPtr::poll] should return just one event per call
|
||||
upcoming_poll_results: VecDeque<UpcomingPollResult>,
|
||||
srv: CryptoServer,
|
||||
rx_queue: VecDeque<Vec<u8>>,
|
||||
other_peer: PeerPtr,
|
||||
under_load: bool,
|
||||
}
|
||||
|
||||
impl RosenpassSimulator {
|
||||
/// Set up the simulator
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
// Set up the first server
|
||||
let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||
let mut srv_a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
||||
|
||||
// …and the second server.
|
||||
let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||
let mut srv_b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
||||
|
||||
// Generate a PSK and introduce the Peers to each other.
|
||||
let psk = SymKey::random();
|
||||
let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
||||
let peer_b = srv_b.add_peer(Some(psk), peer_a_pk)?;
|
||||
|
||||
// Set up the individual server data structures
|
||||
let srv_a = SimulatorServer::new(srv_a, peer_b);
|
||||
let srv_b = SimulatorServer::new(srv_b, peer_a);
|
||||
|
||||
// Initialize transcript and polling state
|
||||
let transcript = Vec::new();
|
||||
let poll_focus = ServerPtr::A;
|
||||
|
||||
// Construct the simulator itself
|
||||
Self {
|
||||
transcript,
|
||||
poll_focus,
|
||||
srv_a,
|
||||
srv_b,
|
||||
}
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Call [poll] a fixed number of times
|
||||
fn poll_loop(&mut self, times: u64) -> anyhow::Result<()> {
|
||||
for _ in 0..times {
|
||||
self.poll()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Every call to poll produces one [TranscriptEvent] and
|
||||
/// and implicitly adds it to [Self:::transcript]
|
||||
fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> {
|
||||
let ev = TranscriptEvent::begin_poll()
|
||||
.try_fold_with(|| self.poll_focus.poll(self))?
|
||||
.try_fold_with(|| {
|
||||
self.poll_focus = self.poll_focus.other();
|
||||
self.poll_focus.poll(self)
|
||||
})?;
|
||||
|
||||
// Generate up a time stamp
|
||||
let now = self.srv_a.srv.timebase.now();
|
||||
|
||||
// Push the event onto the transcript
|
||||
self.transcript.push((now, ev));
|
||||
// We can unwrap; we just pushed the event ourselves
|
||||
let ev = self.transcript.last().unwrap().1.borrow();
|
||||
|
||||
// Time travel instead of waiting
|
||||
if let TranscriptEvent::Wait(secs) = ev {
|
||||
time_travel_forward(&mut self.srv_a.srv, *secs);
|
||||
time_travel_forward(&mut self.srv_b.srv, *secs);
|
||||
}
|
||||
|
||||
ev.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl SimulatorServer {
|
||||
fn new(srv: CryptoServer, other_peer: PeerPtr) -> Self {
|
||||
let upcoming_poll_results = VecDeque::new();
|
||||
let rx_queue = VecDeque::new();
|
||||
let under_load = false;
|
||||
Self {
|
||||
upcoming_poll_results,
|
||||
srv,
|
||||
rx_queue,
|
||||
other_peer,
|
||||
under_load,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Straightforward way of accessing either of the two servers
|
||||
/// with associated data
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ServerPtr {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ServerPtr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("{:?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl HostIdentification for ServerPtr {
|
||||
fn encode(&self) -> &[u8] {
|
||||
match *self {
|
||||
Self::A => b"ServerPtr::A",
|
||||
Self::B => b"ServerPtr::B",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerPtr {
|
||||
fn poll(self, sim: &mut RosenpassSimulator) -> anyhow::Result<TranscriptEvent> {
|
||||
TranscriptEvent::begin_poll()
|
||||
.try_fold_with(|| self.flush_upcoming_events(sim).ok())?
|
||||
.try_fold_with(|| self.poll_for_timed_events(sim))?
|
||||
.try_fold_with(|| self.process_incoming_messages(sim))
|
||||
}
|
||||
|
||||
/// Returns and applies the first upcoming event
|
||||
fn flush_upcoming_events(self, sim: &mut RosenpassSimulator) -> TranscriptEvent {
|
||||
use UpcomingPollResult as R;
|
||||
match self.get_mut(sim).upcoming_poll_results.pop_front() {
|
||||
None => TranscriptEvent::hibernate(),
|
||||
Some(R::IssueEvent(ev)) => ev,
|
||||
Some(R::SendMessage(msg, ev)) => {
|
||||
self.transmit(sim, msg);
|
||||
ev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_for_timed_events(
|
||||
self,
|
||||
sim: &mut RosenpassSimulator,
|
||||
) -> anyhow::Result<TranscriptEvent> {
|
||||
use PollResult as P;
|
||||
use ServerEvent as SE;
|
||||
use TranscriptEvent as TE;
|
||||
|
||||
let other_peer = self.peer(sim);
|
||||
|
||||
// Check if there are events to process from poll()
|
||||
loop {
|
||||
match self.srv_mut(sim).poll()? {
|
||||
// Poll just told us to immediately call poll again
|
||||
P::Sleep(0.0) => continue,
|
||||
|
||||
// No event to handle immediately. We can now check to see if there are some
|
||||
// messages to be handled
|
||||
P::Sleep(wait_time) => {
|
||||
return TE::Wait(wait_time).ok();
|
||||
}
|
||||
|
||||
// Not deleting any keys in practice here, since we just push events to the
|
||||
// transcript
|
||||
P::DeleteKey(_) => {
|
||||
return SE::DeleteKey.into_transcript_event(self).ok();
|
||||
}
|
||||
|
||||
P::SendInitiation(_) => {
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::SendInitiationRequested.into_transcript_event(self),
|
||||
);
|
||||
|
||||
let mut buf = MsgBuf::zero();
|
||||
let len = self
|
||||
.srv_mut(sim)
|
||||
.initiate_handshake(other_peer, &mut buf[..])?;
|
||||
self.enqueue_upcoming_poll_transmission(
|
||||
sim,
|
||||
buf[..len].to_vec(),
|
||||
SendMsgReason::Initiation,
|
||||
);
|
||||
|
||||
return self.flush_upcoming_events(sim).ok(); // Just added them
|
||||
}
|
||||
|
||||
P::SendRetransmission(_) => {
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::SendRetransmissionRequested.into_transcript_event(self),
|
||||
);
|
||||
|
||||
let mut buf = MsgBuf::zero();
|
||||
let len = self
|
||||
.srv_mut(sim)
|
||||
.retransmit_handshake(other_peer, &mut buf[..])?;
|
||||
self.enqueue_upcoming_poll_transmission(
|
||||
sim,
|
||||
buf[..len].to_vec(),
|
||||
SendMsgReason::Retransmission,
|
||||
);
|
||||
|
||||
return self.flush_upcoming_events(sim).ok(); // Just added them
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn process_incoming_messages(
|
||||
self,
|
||||
sim: &mut RosenpassSimulator,
|
||||
) -> anyhow::Result<TranscriptEvent> {
|
||||
use ServerEvent as SE;
|
||||
use TranscriptEvent as TE;
|
||||
|
||||
// Check for a message or exit
|
||||
let rx_msg = match self.recv(sim) {
|
||||
None => return TE::hibernate().ok(),
|
||||
// Actually received a message
|
||||
Some(rx_msg) => rx_msg,
|
||||
};
|
||||
|
||||
// Add info that a message was received into the transcript
|
||||
self.enqueue_upcoming_poll_event(sim, TE::receive(self, rx_msg.borrow()));
|
||||
|
||||
// Let the crypto server handle the message now
|
||||
let mut tx_buf = MsgBuf::zero();
|
||||
let handle_msg_result = if self.get(sim).under_load {
|
||||
self.srv_mut(sim)
|
||||
.handle_msg_under_load(rx_msg.borrow(), tx_buf.borrow_mut(), &self)
|
||||
} else {
|
||||
self.srv_mut(sim)
|
||||
.handle_msg(rx_msg.borrow(), tx_buf.borrow_mut())
|
||||
};
|
||||
|
||||
// Handle bad messages
|
||||
let handle_msg_result = match handle_msg_result {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::DiscardInvalidMessage(e).into_transcript_event(self),
|
||||
);
|
||||
return self.flush_upcoming_events(sim).ok(); // Just added them
|
||||
}
|
||||
};
|
||||
|
||||
// Successful key exchange; emit the appropriate event
|
||||
if handle_msg_result.exchanged_with.is_some() {
|
||||
self.enqueue_on_exchanged_events(sim)?;
|
||||
}
|
||||
|
||||
// Handle message responses
|
||||
if let Some(len) = handle_msg_result.resp {
|
||||
let resp = &tx_buf[..len];
|
||||
self.enqueue_upcoming_poll_transmission(sim, resp.to_vec(), SendMsgReason::Response);
|
||||
};
|
||||
|
||||
// Return the first of the events we just enqueued
|
||||
self.flush_upcoming_events(sim).ok()
|
||||
}
|
||||
|
||||
fn enqueue_on_exchanged_events(self, sim: &mut RosenpassSimulator) -> anyhow::Result<()> {
|
||||
use ServerEvent as SE;
|
||||
use TranscriptEvent as TE;
|
||||
|
||||
// Retrieve the key exchanged; this function will panic if the OSK is missing
|
||||
let osk = self.osk(sim).unwrap();
|
||||
|
||||
// Issue the `Exchanged`
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::Exchanged(osk.clone()).into_transcript_event(self),
|
||||
);
|
||||
|
||||
// Retrieve the other osk
|
||||
let other_osk = match self.other().try_osk(sim) {
|
||||
Some(other_osk) => other_osk,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// Issue the successful exchange event if the OSKs are equal;
|
||||
// be careful to use constant time comparison for things like this!
|
||||
if rosenpass_constant_time::memcmp(osk.secret(), other_osk.secret()) {
|
||||
self.enqueue_upcoming_poll_event(sim, TE::CompletedExchange(osk));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enqueue_upcoming_poll_event(self, sim: &mut RosenpassSimulator, ev: TranscriptEvent) {
|
||||
let upcoming = UpcomingPollResult::IssueEvent(ev);
|
||||
self.get_mut(sim).upcoming_poll_results.push_back(upcoming);
|
||||
}
|
||||
|
||||
fn enqueue_upcoming_poll_transmission(
|
||||
self,
|
||||
sim: &mut RosenpassSimulator,
|
||||
msg: Vec<u8>,
|
||||
reason: SendMsgReason,
|
||||
) {
|
||||
let ev = TranscriptEvent::transmit(self, msg.borrow(), reason);
|
||||
let upcoming = UpcomingPollResult::SendMessage(msg, ev);
|
||||
self.get_mut(sim).upcoming_poll_results.push_back(upcoming);
|
||||
}
|
||||
|
||||
fn try_osk(self, sim: &RosenpassSimulator) -> Option<SymKey> {
|
||||
let peer = self.peer(sim);
|
||||
let has_osk = peer.session().get(self.srv(sim)).is_some();
|
||||
|
||||
has_osk.then(|| {
|
||||
// We already checked whether the OSK is present; there should be no other errors
|
||||
self.osk(sim).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn osk(self, sim: &RosenpassSimulator) -> anyhow::Result<SymKey> {
|
||||
self.srv(sim).osk(self.peer(sim))
|
||||
}
|
||||
|
||||
fn drop_outgoing_packet(self, sim: &mut RosenpassSimulator) -> Option<Vec<u8>> {
|
||||
let pkg = self.tx_queue_mut(sim).pop_front();
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
ServerEvent::DroppedPackage.into_transcript_event(self),
|
||||
);
|
||||
pkg
|
||||
}
|
||||
|
||||
fn other(self) -> Self {
|
||||
match self {
|
||||
Self::A => Self::B,
|
||||
Self::B => Self::A,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(self, sim: &RosenpassSimulator) -> &SimulatorServer {
|
||||
match self {
|
||||
ServerPtr::A => sim.srv_a.borrow(),
|
||||
ServerPtr::B => sim.srv_b.borrow(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(self, sim: &mut RosenpassSimulator) -> &mut SimulatorServer {
|
||||
match self {
|
||||
ServerPtr::A => sim.srv_a.borrow_mut(),
|
||||
ServerPtr::B => sim.srv_b.borrow_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
fn srv(self, sim: &RosenpassSimulator) -> &CryptoServer {
|
||||
self.get(sim).srv.borrow()
|
||||
}
|
||||
|
||||
fn srv_mut(self, sim: &mut RosenpassSimulator) -> &mut CryptoServer {
|
||||
self.get_mut(sim).srv.borrow_mut()
|
||||
}
|
||||
|
||||
fn peer(self, sim: &RosenpassSimulator) -> PeerPtr {
|
||||
self.get(sim).other_peer
|
||||
}
|
||||
|
||||
fn recv(self, sim: &mut RosenpassSimulator) -> Option<Vec<u8>> {
|
||||
self.rx_queue_mut(sim).pop_front()
|
||||
}
|
||||
|
||||
fn transmit(self, sim: &mut RosenpassSimulator, msg: Vec<u8>) {
|
||||
self.tx_queue_mut(sim).push_back(msg);
|
||||
}
|
||||
|
||||
fn rx_queue_mut(self, sim: &mut RosenpassSimulator) -> &mut VecDeque<Vec<u8>> {
|
||||
self.get_mut(sim).rx_queue.borrow_mut()
|
||||
}
|
||||
|
||||
fn tx_queue_mut(self, sim: &mut RosenpassSimulator) -> &mut VecDeque<Vec<u8>> {
|
||||
self.other().rx_queue_mut(sim)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ repository = "https://github.com/rosenpass/rosenpass"
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64ct = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
@@ -20,14 +22,15 @@ rosenpass-ciphers = { workspace = true }
|
||||
rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
rosenpass-wireguard-broker = {workspace = true}
|
||||
rosenpass-wireguard-broker = { workspace = true }
|
||||
|
||||
tokio = {workspace = true}
|
||||
tokio = { workspace = true }
|
||||
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
ctrlc-async = "3.2"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
genetlink = "0.2"
|
||||
rtnetlink = "0.14"
|
||||
netlink-packet-core = "0.7"
|
||||
@@ -35,8 +38,8 @@ netlink-packet-generic = "0.3"
|
||||
netlink-packet-wireguard = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = {workspace = true}
|
||||
stacker = {workspace = true}
|
||||
tempfile = { workspace = true }
|
||||
stacker = { workspace = true }
|
||||
|
||||
[features]
|
||||
experiment_memfd_secret = []
|
||||
|
||||
@@ -3,6 +3,11 @@ use std::{iter::Peekable, net::SocketAddr};
|
||||
|
||||
use crate::exchange::{ExchangeOptions, ExchangePeer};
|
||||
|
||||
/// The different commands supported by the `rp` binary.
|
||||
/// [GenKey](crate::cli::Command::GenKey), [PubKey](crate::cli::Command::PubKey),
|
||||
/// [Exchange](crate::cli::Command::Exchange) and
|
||||
/// [ExchangeConfig](crate::cli::Command::ExchangeConfig)
|
||||
/// contain information specific to the respective command.
|
||||
pub enum Command {
|
||||
GenKey {
|
||||
private_keys_dir: PathBuf,
|
||||
@@ -12,33 +17,57 @@ pub enum Command {
|
||||
public_keys_dir: PathBuf,
|
||||
},
|
||||
Exchange(ExchangeOptions),
|
||||
ExchangeConfig {
|
||||
config_file: PathBuf,
|
||||
},
|
||||
Help,
|
||||
}
|
||||
|
||||
/// The different command types supported by the `rp` binary.
|
||||
/// This enum is exclusively used in [fatal] and when calling [fatal] and is therefore
|
||||
/// limited to the command types that can fail. E.g., the help command can not fail and is therefore
|
||||
/// not part of the [CommandType]-enum.
|
||||
enum CommandType {
|
||||
GenKey,
|
||||
PubKey,
|
||||
Exchange,
|
||||
ExchangeConfig,
|
||||
}
|
||||
|
||||
/// This structure captures the result of parsing the arguments to the `rp` binary.
|
||||
/// A new [Cli] is created by calling [Cli::parse] with the appropriate arguments.
|
||||
#[derive(Default)]
|
||||
pub struct Cli {
|
||||
/// Whether the output should be verbose.
|
||||
pub verbose: bool,
|
||||
/// The command specified by the given arguments.
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
/// Processes a fatal error when parsing cli arguments.
|
||||
/// It *always* returns an [Err(String)], where such that the contained [String] explains
|
||||
/// the parsing error, including the provided `note`.
|
||||
///
|
||||
/// # Generic Parameters
|
||||
/// the generic parameter `T` is given to make the [Result]-type compatible with the respective
|
||||
/// return type of the calling function.
|
||||
///
|
||||
fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
||||
match command {
|
||||
Some(command) => match command {
|
||||
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::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)),
|
||||
}
|
||||
}
|
||||
|
||||
impl ExchangePeer {
|
||||
/// Parses peer parameters given to the `rp` binary in the context of an `exchange` operation.
|
||||
/// It returns a result with either [ExchangePeer] that contains the parameters of the peer
|
||||
/// or an error describing why the arguments could not be parsed.
|
||||
pub fn parse(args: &mut &mut Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut peer = ExchangePeer::default();
|
||||
|
||||
@@ -121,6 +150,9 @@ impl ExchangePeer {
|
||||
}
|
||||
|
||||
impl ExchangeOptions {
|
||||
/// Parses the arguments given to the `rp` binary *if the `exchange` operation is given*.
|
||||
/// It returns a result with either [ExchangeOptions] that contains the result of parsing the
|
||||
/// arguments or an error describing why the arguments could not be parsed.
|
||||
pub fn parse(mut args: &mut Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut options = ExchangeOptions::default();
|
||||
|
||||
@@ -144,6 +176,13 @@ impl ExchangeOptions {
|
||||
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
||||
}
|
||||
}
|
||||
"ip" => {
|
||||
if let Some(ip) = args.next() {
|
||||
options.ip = Some(ip);
|
||||
} else {
|
||||
return fatal("ip option requires parameter", Some(CommandType::Exchange));
|
||||
}
|
||||
}
|
||||
"listen" => {
|
||||
if let Some(addr) = args.next() {
|
||||
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
||||
@@ -179,6 +218,9 @@ impl ExchangeOptions {
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
/// Parses the arguments given to the `rp` binary. It returns a result with either
|
||||
/// a [Cli] that contains the result of parsing the arguments or an error describing
|
||||
/// why the arguments could not be parsed.
|
||||
pub fn parse(mut args: Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut cli = Cli::default();
|
||||
|
||||
@@ -246,6 +288,21 @@ impl Cli {
|
||||
let options = ExchangeOptions::parse(&mut args)?;
|
||||
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" => {
|
||||
cli.command = Some(Command::Help);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,47 @@
|
||||
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;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use crate::key::WG_B64_LEN;
|
||||
|
||||
#[derive(Default)]
|
||||
/// Used to define a peer for the rosenpass connection that consists of
|
||||
/// a directory for storing public keys and optionally an IP address and port of the endpoint,
|
||||
/// for how long the connection should be kept alive and a list of allowed IPs for the peer.
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ExchangePeer {
|
||||
/// Directory where public keys are stored
|
||||
pub public_keys_dir: PathBuf,
|
||||
/// The IP address of the endpoint
|
||||
pub endpoint: Option<SocketAddr>,
|
||||
/// For how long to keep the connection alive
|
||||
pub persistent_keepalive: Option<u32>,
|
||||
/// The IPs that are allowed for this peer.
|
||||
pub allowed_ips: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
/// Options for the exchange operation of the `rp` binary.
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ExchangeOptions {
|
||||
/// Whether the cli output should be verbose.
|
||||
pub verbose: bool,
|
||||
/// path to the directory where private keys are stored.
|
||||
pub private_keys_dir: PathBuf,
|
||||
/// The link rosenpass should run as. If None is given [exchange] will use `"rosenpass0"`
|
||||
/// instead.
|
||||
pub dev: Option<String>,
|
||||
/// The IP-address rosenpass should run under.
|
||||
pub ip: Option<String>,
|
||||
/// The IP-address and port that the rosenpass [AppServer](rosenpass::app_server::AppServer)
|
||||
/// should use.
|
||||
pub listen: Option<SocketAddr>,
|
||||
/// Other peers a connection should be initialized to
|
||||
pub peers: Vec<ExchangePeer>,
|
||||
}
|
||||
|
||||
@@ -41,8 +64,11 @@ mod netlink {
|
||||
use netlink_packet_wireguard::nlas::WgDeviceAttrs;
|
||||
use rtnetlink::Handle;
|
||||
|
||||
/// Creates a netlink named `link_name` and changes the state to up. It returns the index
|
||||
/// of the interface in the list of interfaces as the result or an error if any of the
|
||||
/// operations of creating the link or changing its state to up fails.
|
||||
pub async fn link_create_and_up(rtnetlink: &Handle, link_name: String) -> Result<u32> {
|
||||
// add the link
|
||||
// Add the link, equivalent to `ip link add <link_name> type wireguard`.
|
||||
rtnetlink
|
||||
.link()
|
||||
.add()
|
||||
@@ -50,7 +76,8 @@ mod netlink {
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
// retrieve the link to be able to up it
|
||||
// Retrieve the link to be able to up it, equivalent to `ip link show` and then
|
||||
// using the link shown that is identified by `link_name`.
|
||||
let link = rtnetlink
|
||||
.link()
|
||||
.get()
|
||||
@@ -62,7 +89,7 @@ mod netlink {
|
||||
.0
|
||||
.unwrap()?;
|
||||
|
||||
// up the link
|
||||
// Up the link, equivalent to `ip link set dev <DEV> up`.
|
||||
rtnetlink
|
||||
.link()
|
||||
.set(link.header.index)
|
||||
@@ -73,12 +100,16 @@ mod netlink {
|
||||
Ok(link.header.index)
|
||||
}
|
||||
|
||||
/// Deletes a link using rtnetlink. The link is specified using its index in the list of links.
|
||||
pub async fn link_cleanup(rtnetlink: &Handle, index: u32) -> Result<()> {
|
||||
rtnetlink.link().del(index).execute().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes a link using rtnetlink. The link is specified using its index in the list of links.
|
||||
/// In contrast to [link_cleanup], this function create a new socket connection to netlink and
|
||||
/// *ignores errors* that occur during deletion.
|
||||
pub async fn link_cleanup_standalone(index: u32) -> Result<()> {
|
||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
@@ -104,7 +135,7 @@ mod netlink {
|
||||
use netlink_packet_generic::GenlMessage;
|
||||
use netlink_packet_wireguard::{Wireguard, WireguardCmd};
|
||||
|
||||
// Scope our `set` command to only the device of the specified index
|
||||
// Scope our `set` command to only the device of the specified index.
|
||||
attr.insert(0, WgDeviceAttrs::IfIndex(index));
|
||||
|
||||
// Construct the WireGuard-specific netlink packet
|
||||
@@ -113,12 +144,12 @@ mod netlink {
|
||||
nlas: attr,
|
||||
};
|
||||
|
||||
// Construct final message
|
||||
// Construct final message.
|
||||
let genl = GenlMessage::from_payload(wgc);
|
||||
let mut nlmsg = NetlinkMessage::from(genl);
|
||||
nlmsg.header.flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||
|
||||
// Send and wait for the ACK or error
|
||||
// Send and wait for the ACK or error.
|
||||
let (res, _) = genetlink.request(nlmsg).await?.into_future().await;
|
||||
if let Some(res) = res {
|
||||
let res = res?;
|
||||
@@ -131,6 +162,38 @@ mod netlink {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for a list of cleanup handlers that can be used in an asynchronous context
|
||||
/// to clean up after the usage of rosenpass or if the `rp` binary is interrupted with ctrl+c
|
||||
/// or a `SIGINT` signal in general.
|
||||
#[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 {
|
||||
/// Creates a new list of [CleanupHandlers].
|
||||
fn new() -> Self {
|
||||
CleanupHandlers(Arc::new(::futures::lock::Mutex::new(vec![])))
|
||||
}
|
||||
|
||||
/// Enqueues a new cleanup handler in the form of a [Future].
|
||||
async fn enqueue(&self, handler: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>) {
|
||||
self.0.lock().await.push(Box::pin(handler))
|
||||
}
|
||||
|
||||
/// Runs all cleanup handlers. Following the documentation of [futures::future::try_join_all]:
|
||||
/// If any cleanup handler returns an error then all other cleanup handlers will be canceled and
|
||||
/// an error will be returned immediately. If all cleanup handlers complete successfully,
|
||||
/// however, then the returned future will succeed with a Vec of all the successful results.
|
||||
async fn run(self) -> Result<Vec<()>, Error> {
|
||||
futures::future::try_join_all(self.0.lock().await.deref_mut()).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the rosenpass link and wireguard and configures both with the configuration specified by
|
||||
/// `options`.
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
use std::fs;
|
||||
@@ -151,16 +214,54 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
let (connection, rtnetlink, _) = rtnetlink::new_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?;
|
||||
|
||||
// Set up a list of (initiallc empty) cleanup handlers that are to be run if
|
||||
// ctrl-c is hit or generally a `SIGINT` signal is received and always in the end.
|
||||
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 {
|
||||
netlink::link_cleanup_standalone(link_index)
|
||||
final_cleanup_handlers
|
||||
.run()
|
||||
.await
|
||||
.expect("Failed to clean up");
|
||||
})?;
|
||||
|
||||
// Deploy the classic wireguard private key
|
||||
// Run `ip address add <ip> dev <dev>` and enqueue `ip address del <ip> dev <dev>` as a cleanup.
|
||||
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.
|
||||
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
@@ -181,6 +282,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
|
||||
netlink::wg_set(&mut genetlink, link_index, attr).await?;
|
||||
|
||||
// set up the rosenpass AppServer
|
||||
let pqsk = options.private_keys_dir.join("pqsk");
|
||||
let pqpk = options.private_keys_dir.join("pqpk");
|
||||
|
||||
@@ -188,8 +290,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
let pk = SPk::load(&pqpk)?;
|
||||
|
||||
let mut srv = Box::new(AppServer::new(
|
||||
sk,
|
||||
pk,
|
||||
Some((sk, pk)),
|
||||
if let Some(listen) = options.listen {
|
||||
vec![listen]
|
||||
} else {
|
||||
@@ -209,6 +310,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
||||
}
|
||||
|
||||
// Configure everything per peer.
|
||||
for peer in options.peers {
|
||||
let wgpk = peer.public_keys_dir.join("wgpk");
|
||||
let pqpk = peer.public_keys_dir.join("pqpk");
|
||||
@@ -255,6 +357,30 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
broker_peer,
|
||||
peer.endpoint.map(|x| x.to_string()),
|
||||
)?;
|
||||
|
||||
// Configure routes, equivalent to `ip route replace <allowed_ips> dev <dev>` and set up
|
||||
// the cleanup as `ip route del <allowed_ips>`.
|
||||
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();
|
||||
@@ -264,7 +390,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
match out {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
// Check if the returned error is actually EINTR, in which case, the run actually succeeded.
|
||||
// Check if the returned error is actually EINTR, in which case, the run actually
|
||||
// succeeded.
|
||||
let is_ok = if let Some(e) = e.root_cause().downcast_ref::<std::io::Error>() {
|
||||
matches!(e.kind(), std::io::ErrorKind::Interrupted)
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret};
|
||||
|
||||
/// The length of wireguard keys as a length in base 64 encoding.
|
||||
pub const WG_B64_LEN: usize = 32 * 5 / 3;
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
@@ -24,6 +25,14 @@ pub fn genkey(_: &Path) -> Result<()> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Generates a new symmetric keys for wireguard and asymmetric keys for rosenpass
|
||||
/// in the provided `private_keys_dir`.
|
||||
///
|
||||
/// It checks whether the directory `private_keys_dir` points to exists and creates it otherwise.
|
||||
/// If it exists, it ensures that the permission is set to 0700 and aborts otherwise. If the
|
||||
/// directory is newly created, the appropriate permissions are set.
|
||||
///
|
||||
/// Already existing keys are not overwritten.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
||||
if private_keys_dir.exists() {
|
||||
@@ -70,6 +79,11 @@ pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new directory under `public_keys_dir` and stores the public keys for rosenpass and for
|
||||
/// wireguard that correspond to the private keys in `private_keys_dir` in `public_keys_dir`.
|
||||
///
|
||||
/// If `public_keys_dir` already exists, the wireguard private key or the rosenpass public key
|
||||
/// are not present in `private_keys_dir`, an error is returned.
|
||||
pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
||||
if public_keys_dir.exists() {
|
||||
return Err(anyhow!("Directory {:?} already exists", public_keys_dir));
|
||||
@@ -90,9 +104,11 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
||||
Public::from_slice(public.as_bytes())
|
||||
};
|
||||
|
||||
// Store the wireguard public key.
|
||||
wgpk.store_b64::<WG_B64_LEN, _>(public_wgpk)?;
|
||||
wgpk.zeroize();
|
||||
|
||||
// Copy the pq-public key to the public directory.
|
||||
fs::copy(private_pqpk, public_pqpk)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::process::exit;
|
||||
use std::{fs, process::exit};
|
||||
|
||||
use cli::{Cli, Command};
|
||||
use exchange::exchange;
|
||||
@@ -36,6 +36,13 @@ async fn main() {
|
||||
options.verbose = cli.verbose;
|
||||
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 => {
|
||||
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
||||
Ok(())
|
||||
|
||||
50
rp/tests/smoketest.rs
Normal file
50
rp/tests/smoketest.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn smoketest() -> anyhow::Result<()> {
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let secret = tmpdir.path().join("server.secret");
|
||||
let public = tmpdir.path().join("server.public");
|
||||
let invalid = tmpdir.path().join("invalid.secret");
|
||||
let toml = tmpdir.path().join("config.toml");
|
||||
|
||||
let invalid_config = r#"
|
||||
verbose = false
|
||||
private_keys_dir = "invliad"
|
||||
|
||||
[[peers]]
|
||||
public_keys_dir = "invliad"
|
||||
"#;
|
||||
|
||||
// Generate keys
|
||||
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args(["genkey", secret.to_str().unwrap()])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
assert!(status.success());
|
||||
|
||||
// Derive Public keys
|
||||
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args(["pubkey", secret.to_str().unwrap(), public.to_str().unwrap()])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
assert!(status.success());
|
||||
|
||||
// Can not exchange keys using exchange with invalid keys
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args(["exchange", invalid.to_str().unwrap()])
|
||||
.output()?;
|
||||
assert!(!out.status.success());
|
||||
|
||||
std::fs::write(toml, invalid_config)?;
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args([
|
||||
"exchange-config",
|
||||
tmpdir.path().join("invalid_config").to_str().unwrap(),
|
||||
])
|
||||
.output()?;
|
||||
assert!(!out.status.success());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -21,6 +21,6 @@ log = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
allocator-api2-tests = { workspace = true }
|
||||
tempfile = {workspace = true}
|
||||
base64ct = {workspace = true}
|
||||
procspawn = {workspace = true}
|
||||
tempfile = { workspace = true }
|
||||
base64ct = { workspace = true }
|
||||
procspawn = { workspace = true }
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module provides a wrapper [MallocAllocator] around the memsec allocator in
|
||||
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memsec allocator
|
||||
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
|
||||
//!
|
||||
//! The module also provides the [MallocVec] and [MallocBox] types.
|
||||
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
@@ -6,31 +12,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MallocAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
|
||||
/// the [allocator_api2] crate.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MallocAllocator {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
|
||||
|
||||
/// Try to allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box_try, MallocBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let malloc_box: MallocBox<u8> = malloc_box_try(data)?;
|
||||
/// # assert_eq!(*malloc_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
|
||||
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box, MallocBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let malloc_box: MallocBox<u8> = malloc_box(data);
|
||||
/// # assert_eq!(*malloc_box, 42u8);
|
||||
/// ```
|
||||
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
|
||||
MallocBox::<T>::new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MallocVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_vec, MallocVec};
|
||||
/// let mut malloc_vec: MallocVec<u8> = malloc_vec();
|
||||
/// malloc_vec.push(0u8);
|
||||
/// malloc_vec.push(1u8);
|
||||
/// malloc_vec.push(2u8);
|
||||
/// # let mut element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn malloc_vec<T>() -> MallocVec<T> {
|
||||
MallocVec::<T>::new_in(MallocAllocator::new())
|
||||
}
|
||||
|
||||
impl MallocAllocator {
|
||||
/// Creates a new [MallocAllocator].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
@@ -94,6 +147,9 @@ mod test {
|
||||
malloc_allocation_impl::<8>(&alloc);
|
||||
malloc_allocation_impl::<64>(&alloc);
|
||||
malloc_allocation_impl::<999>(&alloc);
|
||||
|
||||
// Also test the debug-print for good measure
|
||||
let _ = format!("{:?}", alloc);
|
||||
}
|
||||
|
||||
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module provides a wrapper [MemfdSecAllocator] around the memfdsec allocator in
|
||||
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memfdsec allocator
|
||||
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
|
||||
//!
|
||||
//! The module also provides the [MemfdSecVec] and [MemfdSecBox] types.
|
||||
|
||||
#![cfg(target_os = "linux")]
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
@@ -7,31 +13,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemfdSecAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
|
||||
/// the [allocator_api2] crate.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MemfdSecAllocator {
|
||||
_dummy_private_data: MemfdSecAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MemfdSecVec<T> = allocator_api2::vec::Vec<T, MemfdSecAllocator>;
|
||||
|
||||
/// Try to allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box_try, MemfdSecBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box_try(data)?;
|
||||
/// # assert_eq!(*memfdsec_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
|
||||
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box, MemfdSecBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box(data);
|
||||
/// # assert_eq!(*memfdsec_box, 42u8);
|
||||
/// ```
|
||||
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
|
||||
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MemfdSecVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_vec, MemfdSecVec};
|
||||
/// let mut memfdsec_vec: MemfdSecVec<u8> = memfdsec_vec();
|
||||
/// memfdsec_vec.push(0u8);
|
||||
/// memfdsec_vec.push(1u8);
|
||||
/// memfdsec_vec.push(2u8);
|
||||
/// # let mut element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
|
||||
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
impl MemfdSecAllocator {
|
||||
/// Create a new [MemfdSecAllocator].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MemfdSecAllocatorContents,
|
||||
@@ -95,6 +148,9 @@ mod test {
|
||||
memfdsec_allocation_impl::<8>(&alloc);
|
||||
memfdsec_allocation_impl::<64>(&alloc);
|
||||
memfdsec_allocation_impl::<999>(&alloc);
|
||||
|
||||
// Also test the debug-print for good measure
|
||||
let _ = format!("{:?}", alloc);
|
||||
}
|
||||
|
||||
fn memfdsec_allocation_impl<const N: usize>(alloc: &MemfdSecAllocator) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user