mirror of
https://github.com/rosenpass/rosenpass.git
synced 2026-02-27 22:13:12 -08:00
Compare commits
381 Commits
dev/update
...
dev/karo/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e9519e26 | ||
|
|
c3def9744f | ||
|
|
e3d3584adb | ||
|
|
a1982e0245 | ||
|
|
4896cd6130 | ||
|
|
9aab9d2d2a | ||
|
|
108ca440fe | ||
|
|
03e408b7c2 | ||
|
|
67f387a190 | ||
|
|
745c3962bb | ||
|
|
f6971aa5ad | ||
|
|
b46cd636d2 | ||
|
|
f22f4aad7d | ||
|
|
a83589d76a | ||
|
|
508d46f2bc | ||
|
|
3fc3083a54 | ||
|
|
faa45a8540 | ||
|
|
77632d0725 | ||
|
|
7218b0a3f4 | ||
|
|
4266cbfb72 | ||
|
|
070d299329 | ||
|
|
15699710a0 | ||
|
|
ae418ffba7 | ||
|
|
e3f7773bac | ||
|
|
9ab754eb0b | ||
|
|
b055457d01 | ||
|
|
b3403e7120 | ||
|
|
abd5210ae4 | ||
|
|
03464e1be7 | ||
|
|
54fc904c15 | ||
|
|
ceff8b711a | ||
|
|
c84bbed3bd | ||
|
|
d453002230 | ||
|
|
e81612d2e3 | ||
|
|
d558bdb633 | ||
|
|
e8fb7206fc | ||
|
|
b47d3a9deb | ||
|
|
f7fb09bc44 | ||
|
|
db6530ef77 | ||
|
|
8f519b042d | ||
|
|
954162b61f | ||
|
|
c65abe7bd9 | ||
|
|
80885d81d7 | ||
|
|
d023108d3b | ||
|
|
417df7aa7f | ||
|
|
9dd00e04c1 | ||
|
|
1a8e220aa8 | ||
|
|
de0022f092 | ||
|
|
dbb891a2ed | ||
|
|
531ae0ef70 | ||
|
|
8bb54b9cca | ||
|
|
7566eadef8 | ||
|
|
ebf6403ea7 | ||
|
|
62d408eade | ||
|
|
d1cf6af531 | ||
|
|
5e6c85d73d | ||
|
|
3205f8c572 | ||
|
|
b21a95dbbd | ||
|
|
006946442a | ||
|
|
33901d598a | ||
|
|
944be10bd2 | ||
|
|
23cf60c7ec | ||
|
|
6f71767529 | ||
|
|
38f371e3d7 | ||
|
|
2dba9205e7 | ||
|
|
30c3de3f87 | ||
|
|
b16619b1d3 | ||
|
|
576ad5f6d0 | ||
|
|
6494518460 | ||
|
|
185e92108e | ||
|
|
253243a8c8 | ||
|
|
075d9ffff3 | ||
|
|
01a1408044 | ||
|
|
b84e0beae8 | ||
|
|
949a3e4d23 | ||
|
|
d61b137761 | ||
|
|
a1f41953b7 | ||
|
|
46ebb6f46c | ||
|
|
32ae8f7051 | ||
|
|
b94ddd980d | ||
|
|
44e46895aa | ||
|
|
2ddd1488b3 | ||
|
|
c9aad280b2 | ||
|
|
d7398d9bcf | ||
|
|
6d25c13fd1 | ||
|
|
2d2d109246 | ||
|
|
30e158f594 | ||
|
|
cf74584f51 | ||
|
|
793cfd227f | ||
|
|
54c8e91db4 | ||
|
|
1b0179e751 | ||
|
|
760ecdc457 | ||
|
|
6a9bbddde3 | ||
|
|
530f81b9d5 | ||
|
|
b96df1588c | ||
|
|
5a2555a327 | ||
|
|
ac3f21c4bd | ||
|
|
b36d30d89d | ||
|
|
62fe529d36 | ||
|
|
76d01ffaf9 | ||
|
|
576b17cd9c | ||
|
|
cbc1bb4be2 | ||
|
|
c8a084157e | ||
|
|
09f1353dcc | ||
|
|
43225c1fe8 | ||
|
|
8e41cfc0b4 | ||
|
|
69538622b4 | ||
|
|
45a7c17cdd | ||
|
|
b8ecdab8dc | ||
|
|
af9d83b472 | ||
|
|
f81e329a11 | ||
|
|
5e2c72ef99 | ||
|
|
88e7d1d1cb | ||
|
|
43a930d3f7 | ||
|
|
b5f6d07650 | ||
|
|
be3c3d3d61 | ||
|
|
fe60cea959 | ||
|
|
441988cf43 | ||
|
|
b40b7f4f2f | ||
|
|
da76d88170 | ||
|
|
e35955f99c | ||
|
|
87587399ed | ||
|
|
9fdba31b32 | ||
|
|
0bfe47e5b8 | ||
|
|
771dce3ac7 | ||
|
|
436c6e6f87 | ||
|
|
f093406c34 | ||
|
|
eadf70ee38 | ||
|
|
7ac0883970 | ||
|
|
b1658b83a0 | ||
|
|
27650e95a7 | ||
|
|
6ab4e1152c | ||
|
|
2c64da23f1 | ||
|
|
03cc609a1e | ||
|
|
3effcb313e | ||
|
|
fded3b2e79 | ||
|
|
1471bb6a9f | ||
|
|
7edf84bd4a | ||
|
|
5187e50bb7 | ||
|
|
fd5806ba55 | ||
|
|
8e50d38b38 | ||
|
|
377f2f40d2 | ||
|
|
9bae080c4d | ||
|
|
3392da5163 | ||
|
|
3109cf1ffc | ||
|
|
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 |
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
|
||||||
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
.gitignore
|
||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|||||||
4
.github/workflows/doc-upload.yml
vendored
4
.github/workflows/doc-upload.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Clone rosenpass-website repository
|
- name: Clone rosenpass-website repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: rosenpass/rosenpass-website
|
repository: rosenpass/rosenpass-website
|
||||||
ref: main
|
ref: main
|
||||||
|
|||||||
288
.github/workflows/docker.yaml
vendored
Normal file
288
.github/workflows/docker.yaml
vendored
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
name: Build Docker Images
|
||||||
|
|
||||||
|
# Run this job on all non-pull-request events,
|
||||||
|
# or if Docker-related files are changed in a pull request.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "docker/Dockerfile"
|
||||||
|
- ".github/workflows/docker.yaml"
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# --------------------------------
|
||||||
|
# 1. BUILD & TEST
|
||||||
|
# --------------------------------
|
||||||
|
build-and-test-rp:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build (no push) and Load
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
# no pushing here, so we can test locally
|
||||||
|
push: false
|
||||||
|
# load the built image into the local Docker daemon on the runner
|
||||||
|
load: true
|
||||||
|
target: rosenpass
|
||||||
|
tags: rosenpass:test
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
- name: Integration Test - Standalone Key Exchange
|
||||||
|
run: |
|
||||||
|
# Create separate workdirs
|
||||||
|
mkdir -p workdir-server workdir-client
|
||||||
|
|
||||||
|
# Create a Docker network
|
||||||
|
docker network create -d bridge rp
|
||||||
|
|
||||||
|
echo "=== GENERATE SERVER KEYS ==="
|
||||||
|
docker run --rm \
|
||||||
|
-v $PWD/workdir-server:/workdir \
|
||||||
|
rosenpass:test gen-keys \
|
||||||
|
--public-key=workdir/server-public \
|
||||||
|
--secret-key=workdir/server-secret
|
||||||
|
|
||||||
|
echo "=== GENERATE CLIENT KEYS ==="
|
||||||
|
docker run --rm \
|
||||||
|
-v $PWD/workdir-client:/workdir \
|
||||||
|
rosenpass:test gen-keys \
|
||||||
|
--public-key=workdir/client-public \
|
||||||
|
--secret-key=workdir/client-secret
|
||||||
|
|
||||||
|
echo "=== SHARE PUBLIC KEYS ==="
|
||||||
|
cp workdir-client/client-public workdir-server/client-public
|
||||||
|
cp workdir-server/server-public workdir-client/server-public
|
||||||
|
|
||||||
|
echo "=== START SERVER CONTAINER ==="
|
||||||
|
docker run -d --rm \
|
||||||
|
--name rpserver \
|
||||||
|
--network rp \
|
||||||
|
-v $PWD/workdir-server:/workdir \
|
||||||
|
rosenpass:test exchange \
|
||||||
|
private-key workdir/server-secret \
|
||||||
|
public-key workdir/server-public \
|
||||||
|
listen 0.0.0.0:9999 \
|
||||||
|
peer public-key workdir/client-public \
|
||||||
|
outfile workdir/server-sharedkey
|
||||||
|
|
||||||
|
# Get the container IP of the server
|
||||||
|
SERVER_IP=$(docker inspect --format='{{.NetworkSettings.Networks.rp.IPAddress}}' rpserver)
|
||||||
|
echo "SERVER_IP=$SERVER_IP"
|
||||||
|
|
||||||
|
echo "=== START CLIENT CONTAINER ==="
|
||||||
|
docker run -d --rm \
|
||||||
|
--name rpclient \
|
||||||
|
--network rp \
|
||||||
|
-v $PWD/workdir-client:/workdir \
|
||||||
|
rosenpass:test exchange \
|
||||||
|
private-key workdir/client-secret \
|
||||||
|
public-key workdir/client-public \
|
||||||
|
peer public-key workdir/server-public \
|
||||||
|
endpoint ${SERVER_IP}:9999 \
|
||||||
|
outfile workdir/client-sharedkey
|
||||||
|
|
||||||
|
echo "=== COMPARE SHARED KEYS ==="
|
||||||
|
echo "Waiting up to 30 seconds for the server to generate 'server-sharedkey'..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if [ -f "workdir-server/server-sharedkey" ]; then
|
||||||
|
echo "server-sharedkey found!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
sudo cmp workdir-server/server-sharedkey workdir-client/client-sharedkey
|
||||||
|
|
||||||
|
echo "Standalone Key Exchange test OK."
|
||||||
|
# --------------------------------
|
||||||
|
# 2. PUSH (only if tests pass)
|
||||||
|
# --------------------------------
|
||||||
|
docker-image-rp:
|
||||||
|
needs:
|
||||||
|
- build-and-test-rp
|
||||||
|
# Skip if this is not a PR. Then we want to push this image.
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
# Use a matrix to build for both AMD64 and ARM64
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
# Switch the runner based on the architecture
|
||||||
|
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/rp
|
||||||
|
labels: |
|
||||||
|
maintainer=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.authors=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.title=Rosenpass
|
||||||
|
org.opencontainers.image.description=The rp command-line integrates Rosenpass and WireGuard to help you create a VPN
|
||||||
|
org.opencontainers.image.vendor=Rosenpass e.V.
|
||||||
|
org.opencontainers.image.licenses=MIT OR Apache-2.0
|
||||||
|
org.opencontainers.image.url=https://rosenpass.eu
|
||||||
|
org.opencontainers.image.documentation=https://rosenpass.eu/docs/
|
||||||
|
org.opencontainers.image.source=https://github.com/rosenpass/rosenpass
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/${{ github.repository_owner }}/rp
|
||||||
|
target: rp
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-rp-${{ matrix.arch }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-image-rosenpass:
|
||||||
|
needs:
|
||||||
|
- build-and-test-rp
|
||||||
|
# Skip if this is not a PR. Then we want to push this image.
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
# Use a matrix to build for both AMD64 and ARM64
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
# Switch the runner based on the architecture
|
||||||
|
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/rosenpass
|
||||||
|
labels: |
|
||||||
|
maintainer=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.authors=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.title=Rosenpass
|
||||||
|
org.opencontainers.image.description=Reference implementation of the protocol rosenpass protocol
|
||||||
|
org.opencontainers.image.vendor=Rosenpass e.V.
|
||||||
|
org.opencontainers.image.licenses=MIT OR Apache-2.0
|
||||||
|
org.opencontainers.image.url=https://rosenpass.eu
|
||||||
|
org.opencontainers.image.documentation=https://rosenpass.eu/docs/
|
||||||
|
org.opencontainers.image.source=https://github.com/rosenpass/rosenpass
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/${{ github.repository_owner }}/rosenpass
|
||||||
|
target: rosenpass
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-rosenpass-${{ matrix.arch }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
merge-digests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- docker-image-rosenpass
|
||||||
|
- docker-image-rp
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [rp, rosenpass]
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-${{ matrix.target }}-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.target }}
|
||||||
|
tags: |
|
||||||
|
type=edge,branch=main
|
||||||
|
type=sha,branch=main
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf 'ghcr.io/${{ github.repository_owner }}/${{ matrix.target }}@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/${{ matrix.target }}:${{ steps.meta.outputs.version }}
|
||||||
19
.github/workflows/manual-mac-pr.yaml
vendored
Normal file
19
.github/workflows/manual-mac-pr.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: PR Validation on Mac
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: write
|
||||||
|
concurrency:
|
||||||
|
group: manual-mac-${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
qc:
|
||||||
|
uses: ./.github/workflows/qc-mac.yaml
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
nix:
|
||||||
|
uses: ./.github/workflows/nix-mac.yaml
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
114
.github/workflows/nix-mac.yaml
vendored
Normal file
114
.github/workflows/nix-mac.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
name: Nix on Mac
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
aarch64-darwin---default:
|
||||||
|
name: Build aarch64-darwin.default
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs:
|
||||||
|
- aarch64-darwin---rosenpass
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.default --print-build-logs
|
||||||
|
aarch64-darwin---release-package:
|
||||||
|
name: Build aarch64-darwin.release-package
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs:
|
||||||
|
- aarch64-darwin---rosenpass
|
||||||
|
- aarch64-darwin---rp
|
||||||
|
- aarch64-darwin---rosenpass-oci-image
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.release-package --print-build-logs
|
||||||
|
aarch64-darwin---rosenpass:
|
||||||
|
name: Build aarch64-darwin.rosenpass
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs: []
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.rosenpass --print-build-logs
|
||||||
|
aarch64-darwin---rp:
|
||||||
|
name: Build aarch64-darwin.rp
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs: []
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.rp --print-build-logs
|
||||||
|
aarch64-darwin---rosenpass-oci-image:
|
||||||
|
name: Build aarch64-darwin.rosenpass-oci-image
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs:
|
||||||
|
- aarch64-darwin---rosenpass
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.rosenpass-oci-image --print-build-logs
|
||||||
|
aarch64-darwin---check:
|
||||||
|
name: Run Nix checks on aarch64-darwin
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Check
|
||||||
|
run: nix flake check . --print-build-logs
|
||||||
268
.github/workflows/nix.yaml
vendored
268
.github/workflows/nix.yaml
vendored
@@ -15,15 +15,15 @@ jobs:
|
|||||||
i686-linux---default:
|
i686-linux---default:
|
||||||
name: Build i686-linux.default
|
name: Build i686-linux.default
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -32,14 +32,14 @@ jobs:
|
|||||||
i686-linux---rosenpass:
|
i686-linux---rosenpass:
|
||||||
name: Build i686-linux.rosenpass
|
name: Build i686-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -48,15 +48,15 @@ jobs:
|
|||||||
i686-linux---rosenpass-oci-image:
|
i686-linux---rosenpass-oci-image:
|
||||||
name: Build i686-linux.rosenpass-oci-image
|
name: Build i686-linux.rosenpass-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -65,113 +65,13 @@ jobs:
|
|||||||
i686-linux---check:
|
i686-linux---check:
|
||||||
name: Run Nix checks on i686-linux
|
name: Run Nix checks on i686-linux
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Check
|
|
||||||
run: nix flake check . --print-build-logs
|
|
||||||
x86_64-darwin---default:
|
|
||||||
name: Build x86_64-darwin.default
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs:
|
|
||||||
- x86_64-darwin---rosenpass
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.default --print-build-logs
|
|
||||||
x86_64-darwin---release-package:
|
|
||||||
name: Build x86_64-darwin.release-package
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs:
|
|
||||||
- x86_64-darwin---rosenpass
|
|
||||||
- x86_64-darwin---rp
|
|
||||||
- x86_64-darwin---rosenpass-oci-image
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.release-package --print-build-logs
|
|
||||||
x86_64-darwin---rosenpass:
|
|
||||||
name: Build x86_64-darwin.rosenpass
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs: []
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.rosenpass --print-build-logs
|
|
||||||
x86_64-darwin---rp:
|
|
||||||
name: Build x86_64-darwin.rp
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs: []
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.rp --print-build-logs
|
|
||||||
x86_64-darwin---rosenpass-oci-image:
|
|
||||||
name: Build x86_64-darwin.rosenpass-oci-image
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs:
|
|
||||||
- x86_64-darwin---rosenpass
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.rosenpass-oci-image --print-build-logs
|
|
||||||
x86_64-darwin---check:
|
|
||||||
name: Run Nix checks on x86_64-darwin
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -180,15 +80,15 @@ jobs:
|
|||||||
x86_64-linux---default:
|
x86_64-linux---default:
|
||||||
name: Build x86_64-linux.default
|
name: Build x86_64-linux.default
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -197,15 +97,15 @@ jobs:
|
|||||||
x86_64-linux---proof-proverif:
|
x86_64-linux---proof-proverif:
|
||||||
name: Build x86_64-linux.proof-proverif
|
name: Build x86_64-linux.proof-proverif
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---proverif-patched
|
- x86_64-linux---proverif-patched
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -214,14 +114,14 @@ jobs:
|
|||||||
x86_64-linux---proverif-patched:
|
x86_64-linux---proverif-patched:
|
||||||
name: Build x86_64-linux.proverif-patched
|
name: Build x86_64-linux.proverif-patched
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -230,17 +130,17 @@ jobs:
|
|||||||
x86_64-linux---release-package:
|
x86_64-linux---release-package:
|
||||||
name: Build x86_64-linux.release-package
|
name: Build x86_64-linux.release-package
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass-static
|
- x86_64-linux---rosenpass-static
|
||||||
- x86_64-linux---rosenpass-static-oci-image
|
- x86_64-linux---rosenpass-static-oci-image
|
||||||
- x86_64-linux---rp-static
|
- x86_64-linux---rp-static
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -249,7 +149,7 @@ jobs:
|
|||||||
# aarch64-linux---release-package:
|
# aarch64-linux---release-package:
|
||||||
# name: Build aarch64-linux.release-package
|
# name: Build aarch64-linux.release-package
|
||||||
# runs-on:
|
# runs-on:
|
||||||
# - ubuntu-latest
|
# - ubicloud-standard-2-arm-ubuntu-2204
|
||||||
# needs:
|
# needs:
|
||||||
# - aarch64-linux---rosenpass-oci-image
|
# - aarch64-linux---rosenpass-oci-image
|
||||||
# - aarch64-linux---rosenpass
|
# - aarch64-linux---rosenpass
|
||||||
@@ -258,13 +158,13 @@ jobs:
|
|||||||
# - run: |
|
# - run: |
|
||||||
# DEBIAN_FRONTEND=noninteractive
|
# DEBIAN_FRONTEND=noninteractive
|
||||||
# sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
# sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||||
# - uses: actions/checkout@v3
|
# - uses: actions/checkout@v4
|
||||||
# - uses: cachix/install-nix-action@v22
|
# - uses: cachix/install-nix-action@v30
|
||||||
# with:
|
# with:
|
||||||
# nix_path: nixpkgs=channel:nixos-unstable
|
# nix_path: nixpkgs=channel:nixos-unstable
|
||||||
# extra_nix_config: |
|
# extra_nix_config: |
|
||||||
# system = aarch64-linux
|
# system = aarch64-linux
|
||||||
# - uses: cachix/cachix-action@v12
|
# - uses: cachix/cachix-action@v15
|
||||||
# with:
|
# with:
|
||||||
# name: rosenpass
|
# name: rosenpass
|
||||||
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -273,14 +173,14 @@ jobs:
|
|||||||
x86_64-linux---rosenpass:
|
x86_64-linux---rosenpass:
|
||||||
name: Build x86_64-linux.rosenpass
|
name: Build x86_64-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -289,19 +189,19 @@ jobs:
|
|||||||
aarch64-linux---rosenpass:
|
aarch64-linux---rosenpass:
|
||||||
name: Build aarch64-linux.rosenpass
|
name: Build aarch64-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-arm-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi-aarch64 binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -310,19 +210,19 @@ jobs:
|
|||||||
aarch64-linux---rp:
|
aarch64-linux---rp:
|
||||||
name: Build aarch64-linux.rp
|
name: Build aarch64-linux.rp
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-arm-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi-aarch64 binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -331,15 +231,15 @@ jobs:
|
|||||||
x86_64-linux---rosenpass-oci-image:
|
x86_64-linux---rosenpass-oci-image:
|
||||||
name: Build x86_64-linux.rosenpass-oci-image
|
name: Build x86_64-linux.rosenpass-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -348,20 +248,20 @@ jobs:
|
|||||||
aarch64-linux---rosenpass-oci-image:
|
aarch64-linux---rosenpass-oci-image:
|
||||||
name: Build aarch64-linux.rosenpass-oci-image
|
name: Build aarch64-linux.rosenpass-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-arm-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- aarch64-linux---rosenpass
|
- aarch64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi-aarch64 binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -370,14 +270,14 @@ jobs:
|
|||||||
x86_64-linux---rosenpass-static:
|
x86_64-linux---rosenpass-static:
|
||||||
name: Build x86_64-linux.rosenpass-static
|
name: Build x86_64-linux.rosenpass-static
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -386,14 +286,14 @@ jobs:
|
|||||||
x86_64-linux---rp-static:
|
x86_64-linux---rp-static:
|
||||||
name: Build x86_64-linux.rp-static
|
name: Build x86_64-linux.rp-static
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -402,15 +302,15 @@ jobs:
|
|||||||
x86_64-linux---rosenpass-static-oci-image:
|
x86_64-linux---rosenpass-static-oci-image:
|
||||||
name: Build x86_64-linux.rosenpass-static-oci-image
|
name: Build x86_64-linux.rosenpass-static-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass-static
|
- x86_64-linux---rosenpass-static
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -419,14 +319,14 @@ jobs:
|
|||||||
x86_64-linux---whitepaper:
|
x86_64-linux---whitepaper:
|
||||||
name: Build x86_64-linux.whitepaper
|
name: Build x86_64-linux.whitepaper
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -435,13 +335,13 @@ jobs:
|
|||||||
x86_64-linux---check:
|
x86_64-linux---check:
|
||||||
name: Run Nix checks on x86_64-linux
|
name: Run Nix checks on x86_64-linux
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -449,14 +349,14 @@ jobs:
|
|||||||
run: nix flake check . --print-build-logs
|
run: nix flake check . --print-build-logs
|
||||||
x86_64-linux---whitepaper-upload:
|
x86_64-linux---whitepaper-upload:
|
||||||
name: Upload whitepaper x86_64-linux
|
name: Upload whitepaper x86_64-linux
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
if: ${{ github.ref == 'refs/heads/main' }}
|
if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -465,7 +365,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
||||||
- name: Deploy PDF artifacts
|
- name: Deploy PDF artifacts
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: result/
|
publish_dir: result/
|
||||||
|
|||||||
32
.github/workflows/qc-mac.yaml
vendored
Normal file
32
.github/workflows/qc-mac.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: QC Mac
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo-test-mac:
|
||||||
|
runs-on: warp-macos-13-arm64-6x
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
# liboqs requires quite a lot of stack memory, thus we adjust
|
||||||
|
# the default stack size picked for new threads (which is used
|
||||||
|
# by `cargo test`) to be _big enough_. Setting it to 8 MiB
|
||||||
|
- run: RUST_MIN_STACK=8388608 cargo test --workspace --all-features
|
||||||
86
.github/workflows/qc.yaml
vendored
86
.github/workflows/qc.yaml
vendored
@@ -14,34 +14,34 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prettier:
|
prettier:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actionsx/prettier@v2
|
- uses: actionsx/prettier@v3
|
||||||
with:
|
with:
|
||||||
args: --check .
|
args: --check .
|
||||||
|
|
||||||
shellcheck:
|
shellcheck:
|
||||||
name: Shellcheck
|
name: Shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: ludeeus/action-shellcheck@master
|
||||||
|
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: Rust Format
|
name: Rust Format
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run Rust Formatting Script
|
- name: Run Rust Formatting Script
|
||||||
run: bash format_rust_code.sh --mode check
|
run: bash format_rust_code.sh --mode check
|
||||||
|
|
||||||
cargo-bench:
|
cargo-bench:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -57,29 +57,27 @@ jobs:
|
|||||||
|
|
||||||
mandoc:
|
mandoc:
|
||||||
name: mandoc
|
name: mandoc
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install mandoc
|
- name: Install mandoc
|
||||||
run: sudo apt-get install -y mandoc
|
run: sudo apt-get install -y mandoc
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Check rosenpass.1
|
|
||||||
run: doc/check.sh doc/rosenpass.1
|
|
||||||
- name: Check rp.1
|
- name: Check rp.1
|
||||||
run: doc/check.sh doc/rp.1
|
run: doc/check.sh doc/rp.1
|
||||||
|
|
||||||
cargo-audit:
|
cargo-audit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rs/audit-check@v1
|
- uses: actions-rs/audit-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
cargo-clippy:
|
cargo-clippy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -95,10 +93,10 @@ jobs:
|
|||||||
args: --all-features
|
args: --all-features
|
||||||
|
|
||||||
cargo-doc:
|
cargo-doc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -117,12 +115,12 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-13]
|
os: [ubicloud-standard-2-ubuntu-2204, warp-macos-13-arm64-6x]
|
||||||
# - ubuntu is x86-64
|
# - ubuntu is x86-64
|
||||||
# - macos-13 is also x86-64 architecture
|
# - macos-13 is also x86-64 architecture
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -138,10 +136,10 @@ jobs:
|
|||||||
|
|
||||||
cargo-test-nix-devshell-x86_64-linux:
|
cargo-test-nix-devshell-x86_64-linux:
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -150,20 +148,21 @@ jobs:
|
|||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
- uses: cachix/install-nix-action@v21
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- run: nix develop --command cargo test --workspace --all-features
|
- run: nix develop --command cargo test --workspace --all-features
|
||||||
|
|
||||||
cargo-fuzz:
|
cargo-fuzz:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
|
env:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -175,7 +174,7 @@ jobs:
|
|||||||
- name: Install nightly toolchain
|
- name: Install nightly toolchain
|
||||||
run: |
|
run: |
|
||||||
rustup toolchain install nightly
|
rustup toolchain install nightly
|
||||||
rustup default nightly
|
rustup override nightly
|
||||||
- name: Install cargo-fuzz
|
- name: Install cargo-fuzz
|
||||||
run: cargo install cargo-fuzz
|
run: cargo install cargo-fuzz
|
||||||
- name: Run fuzzing
|
- name: Run fuzzing
|
||||||
@@ -193,22 +192,23 @@ jobs:
|
|||||||
cargo fuzz run fuzz_vec_secret_alloc_memfdsec_mallocfb -- -max_total_time=5
|
cargo fuzz run fuzz_vec_secret_alloc_memfdsec_mallocfb -- -max_total_time=5
|
||||||
|
|
||||||
codecov:
|
codecov:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
- run: rustup default nightly
|
||||||
- run: rustup component add llvm-tools-preview
|
- run: rustup component add llvm-tools-preview
|
||||||
- run: |
|
- run: |
|
||||||
cargo install cargo-llvm-cov || true
|
cargo install cargo-llvm-cov || true
|
||||||
cargo llvm-cov \
|
cargo install grcov || true
|
||||||
--workspace\
|
./coverage_report.sh
|
||||||
--all-features \
|
|
||||||
--lcov \
|
|
||||||
--output-path coverage.lcov
|
|
||||||
# If using tarapulin
|
# If using tarapulin
|
||||||
#- run: cargo install cargo-tarpaulin
|
#- run: cargo install cargo-tarpaulin
|
||||||
#- run: cargo tarpaulin --out Xml
|
#- run: cargo tarpaulin --out Xml
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
files: ./coverage.lcov
|
files: ./target/grcov/lcov
|
||||||
verbose: true
|
verbose: true
|
||||||
|
env:
|
||||||
|
RUSTUP_TOOLCHAIN: 1.81
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
16
.github/workflows/regressions.yml
vendored
16
.github/workflows/regressions.yml
vendored
@@ -14,12 +14,24 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
multi-peer:
|
multi-peer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: cargo build --bin rosenpass --release
|
- run: cargo build --bin rosenpass --release
|
||||||
- run: python misc/generate_configs.py
|
- run: python misc/generate_configs.py
|
||||||
- run: chmod +x .ci/run-regression.sh
|
- run: chmod +x .ci/run-regression.sh
|
||||||
- run: .ci/run-regression.sh 100 20
|
- run: .ci/run-regression.sh 100 20
|
||||||
- run: |
|
- run: |
|
||||||
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
||||||
|
|
||||||
|
boot-race:
|
||||||
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
|
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
|
||||||
|
|||||||
49
.github/workflows/release.yaml
vendored
49
.github/workflows/release.yaml
vendored
@@ -11,18 +11,16 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
- uses: cachix/cachix-action@v15
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
@@ -32,18 +30,16 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- macos-13
|
- macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
- uses: cachix/cachix-action@v15
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
@@ -53,19 +49,40 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
files: result/*
|
files: result/*
|
||||||
|
linux-packages:
|
||||||
|
name: Build and upload DEB and RPM packages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build DEB & RPM package
|
||||||
|
run: |
|
||||||
|
mkdir packages
|
||||||
|
for f in $(nix build .#package-deb .#package-rpm --print-out-paths); do cp "$f" "packages/${f#*-}"; done
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
|
files: |
|
||||||
|
packages/*
|
||||||
|
|||||||
91
.github/workflows/supply-chain.yml
vendored
Normal file
91
.github/workflows/supply-chain.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
name: Supply-Chain
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo-deny:
|
||||||
|
name: Deny dependencies with vulnerabilities or incompatible licenses
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
|
cargo-supply-chain:
|
||||||
|
name: Supply Chain Report
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cache/cargo-supply-chain/
|
||||||
|
key: cargo-supply-chain-cache
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.tool_cache }}/cargo-supply-chain
|
||||||
|
key: cargo-supply-chain-bin
|
||||||
|
- name: Add the tool cache directory to the search path
|
||||||
|
run: echo "${{ runner.tool_cache }}/cargo-supply-chain/bin" >> $GITHUB_PATH
|
||||||
|
- name: Ensure that the tool cache is populated with the cargo-supply-chain binary
|
||||||
|
run: cargo install --root ${{ runner.tool_cache }}/cargo-supply-chain cargo-supply-chain
|
||||||
|
- name: Update data for cargo-supply-chain
|
||||||
|
run: cargo supply-chain update
|
||||||
|
- name: Generate cargo-supply-chain report about publishers
|
||||||
|
run: cargo supply-chain publishers
|
||||||
|
- name: Generate cargo-supply-chain report about crates
|
||||||
|
run: cargo supply-chain crates
|
||||||
|
# The setup for cargo-vet follows the recommendations in the cargo-vet documentation: https://mozilla.github.io/cargo-vet/configuring-ci.html
|
||||||
|
cargo-vet:
|
||||||
|
name: Vet Dependencies
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
key: cargo-vet-cache
|
||||||
|
- name: Install stable toolchain # Since we are running/compiling cargo-vet, we should rely on the stable toolchain.
|
||||||
|
run: |
|
||||||
|
rustup toolchain install stable
|
||||||
|
rustup default stable
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.tool_cache }}/cargo-vet
|
||||||
|
key: cargo-vet-bin
|
||||||
|
- name: Add the tool cache directory to the search path
|
||||||
|
run: echo "${{ runner.tool_cache }}/cargo-vet/bin" >> $GITHUB_PATH
|
||||||
|
- name: Ensure that the tool cache is populated with the cargo-vet binary
|
||||||
|
run: cargo install --root ${{ runner.tool_cache }}/cargo-vet cargo-vet
|
||||||
|
- name: Regenerate vet exemptions for dependabot PRs
|
||||||
|
if: github.actor == 'dependabot[bot]' # Run only for Dependabot PRs
|
||||||
|
run: cargo vet regenerate exemptions
|
||||||
|
- name: Check for changes in case of dependabot PR
|
||||||
|
if: github.actor == 'dependabot[bot]' # Run only for Dependabot PRs
|
||||||
|
run: git diff --exit-code || echo "Changes detected, committing..."
|
||||||
|
- name: Commit and push changes for dependabot PRs
|
||||||
|
if: success() && github.actor == 'dependabot[bot]'
|
||||||
|
run: |
|
||||||
|
git fetch origin ${{ github.head_ref }}
|
||||||
|
git switch ${{ github.head_ref }}
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions@github.com"
|
||||||
|
git add supply-chain/*
|
||||||
|
git commit -m "Regenerate cargo vet exemptions"
|
||||||
|
git push origin ${{ github.head_ref }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Invoke cargo-vet
|
||||||
|
run: cargo vet --locked
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ _markdown_*
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
/output
|
/output
|
||||||
|
.nixos-test-history
|
||||||
|
|||||||
@@ -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**.
|
## Common operations
|
||||||
If any other issue occurs
|
|
||||||
|
|
||||||
0. Make sure you are in the root directory of the project
|
### Apply code formatting
|
||||||
- `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!
|
|
||||||
|
|
||||||
**Frequently Asked Questions (FAQ)**
|
Format rust code:
|
||||||
|
|
||||||
- You have untracked files, which `cargo release` complains about?
|
```bash
|
||||||
- `git stash --include-untracked`
|
cargo fmt
|
||||||
- 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?
|
Format rust code in markdown files:
|
||||||
- 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)
|
```bash
|
||||||
- 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.
|
./format_rust_code.sh --mode fix
|
||||||
- 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?
|
### Spawn a development environment with nix
|
||||||
- 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
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|||||||
934
Cargo.lock
generated
934
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
@@ -35,7 +35,7 @@ doc-comment = "0.3.3"
|
|||||||
base64ct = { version = "1.6.0", default-features = false }
|
base64ct = { version = "1.6.0", default-features = false }
|
||||||
zeroize = "1.8.1"
|
zeroize = "1.8.1"
|
||||||
memoffset = "0.9.1"
|
memoffset = "0.9.1"
|
||||||
thiserror = "1.0.64"
|
thiserror = "1.0.69"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
env_logger = "0.10.2"
|
env_logger = "0.10.2"
|
||||||
toml = "0.7.8"
|
toml = "0.7.8"
|
||||||
@@ -47,39 +47,46 @@ memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec
|
|||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
typenum = "1.17.0"
|
typenum = "1.17.0"
|
||||||
log = { version = "0.4.22" }
|
log = { version = "0.4.22" }
|
||||||
clap = { version = "4.5.20", features = ["derive"] }
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
clap_mangen = "0.2.24"
|
||||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
clap_complete = "4.5.40"
|
||||||
anyhow = { version = "1.0.89", features = ["backtrace", "std"] }
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
mio = { version = "1.0.2", features = ["net", "os-poll"] }
|
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 = [
|
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||||
'classic_mceliece',
|
'classic_mceliece',
|
||||||
'kyber',
|
'kyber',
|
||||||
] }
|
] }
|
||||||
blake2 = "0.10.6"
|
blake2 = "0.10.6"
|
||||||
|
sha3 = "0.10.8"
|
||||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"heapless",
|
"heapless",
|
||||||
] }
|
] }
|
||||||
zerocopy = { version = "0.7.35", features = ["derive"] }
|
zerocopy = { version = "0.7.35", features = ["derive"] }
|
||||||
home = "0.5.9"
|
home = "=0.5.9" # 5.11 requires rustc 1.81
|
||||||
derive_builder = "0.20.1"
|
derive_builder = "0.20.1"
|
||||||
tokio = { version = "1.40", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] }
|
||||||
postcard = { version = "1.0.10", features = ["alloc"] }
|
postcard = { version = "1.1.1", features = ["alloc"] }
|
||||||
libcrux = { version = "0.0.2-pre.2" }
|
libcrux = { version = "0.0.2-pre.2" }
|
||||||
|
libcrux-chacha20poly1305 = { version = "0.0.2-beta.3" }
|
||||||
|
libcrux-ml-kem = { version = "0.0.2-beta.3" }
|
||||||
|
libcrux-blake2 = { git = "https://github.com/cryspen/libcrux.git", rev = "10ce653e9476"}
|
||||||
hex-literal = { version = "0.4.1" }
|
hex-literal = { version = "0.4.1" }
|
||||||
hex = { version = "0.4.3" }
|
hex = { version = "0.4.3" }
|
||||||
heck = { version = "0.5.0" }
|
heck = { version = "0.5.0" }
|
||||||
libc = { version = "0.2" }
|
libc = { version = "0.2" }
|
||||||
uds = { git = "https://github.com/rosenpass/uds" }
|
uds = { git = "https://github.com/rosenpass/uds" }
|
||||||
|
signal-hook = "0.3.17"
|
||||||
|
|
||||||
#Dev dependencies
|
#Dev dependencies
|
||||||
serial_test = "3.1.1"
|
serial_test = "3.2.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
stacker = "0.1.17"
|
stacker = "0.1.17"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
test_bin = "0.4.0"
|
test_bin = "0.4.0"
|
||||||
criterion = "0.4.0"
|
criterion = "0.5.1"
|
||||||
allocator-api2-tests = "0.2.15"
|
allocator-api2-tests = "0.2.15"
|
||||||
procspawn = { version = "1.0.1", features = ["test-support"] }
|
procspawn = { version = "1.0.1", features = ["test-support"] }
|
||||||
|
|
||||||
@@ -87,4 +94,7 @@ procspawn = { version = "1.0.1", features = ["test-support"] }
|
|||||||
#Broker dependencies (might need cleanup or changes)
|
#Broker dependencies (might need cleanup or changes)
|
||||||
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||||
command-fds = "0.2.3"
|
command-fds = "0.2.3"
|
||||||
rustix = { version = "0.38.37", features = ["net", "fs"] }
|
rustix = { version = "0.38.42", features = ["net", "fs", "process"] }
|
||||||
|
futures = "0.3"
|
||||||
|
futures-util = "0.3"
|
||||||
|
x25519-dalek = "2"
|
||||||
|
|||||||
@@ -8,5 +8,13 @@ description = "Rosenpass internal traits for cryptographic primitives"
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
rosenpass-to = { workspace = true }
|
||||||
|
|
||||||
|
[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.
|
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.
|
||||||
|
|||||||
137
cipher-traits/src/algorithms.rs
Normal file
137
cipher-traits/src/algorithms.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
//! This module contains the traits for all the cryptographic algorithms used throughout Rosenpass.
|
||||||
|
//! These traits are marker traits that signal intent. They can also be used for trait objects.
|
||||||
|
|
||||||
|
/// Constants and trait for the Incorrect HMAC over Blake2b, with 256 key and hash length.
|
||||||
|
pub mod keyed_hash_incorrect_hmac_blake2b {
|
||||||
|
use crate::primitives::keyed_hash::*;
|
||||||
|
|
||||||
|
// These constants describe how they are used here, not what the algorithm defines.
|
||||||
|
|
||||||
|
/// The key length used in [`KeyedHashIncorrectHmacBlake2b`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The hash length used in [`KeyedHashIncorrectHmacBlake2b`].
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`KeyedHash`] that is an incorrect HMAC over Blake2 (a custom Rosenpass construction)
|
||||||
|
pub trait KeyedHashIncorrectHmacBlake2b: KeyedHash<KEY_LEN, HASH_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for Blake2b, with 256 key and hash length.
|
||||||
|
pub mod keyed_hash_blake2b {
|
||||||
|
use crate::primitives::keyed_hash::*;
|
||||||
|
|
||||||
|
// These constants describe how they are used here, not what the algorithm defines.
|
||||||
|
|
||||||
|
/// The key length used in [`KeyedHashBlake2b`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The hash length used in [`KeyedHashBlake2b`].
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`KeyedHash`] that is Blake2b
|
||||||
|
pub trait KeyedHashBlake2b: KeyedHash<KEY_LEN, HASH_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for SHAKE256, with 256 key and hash length.
|
||||||
|
pub mod keyed_hash_shake256 {
|
||||||
|
use crate::primitives::keyed_hash::*;
|
||||||
|
|
||||||
|
// These constants describe how they are used here, not what the algorithm defines.
|
||||||
|
|
||||||
|
/// The key length used in [`KeyedHashShake256`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The hash length used in [`KeyedHashShake256`].
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`KeyedHash`] that is SHAKE256.
|
||||||
|
pub trait KeyedHashShake256: KeyedHash<KEY_LEN, HASH_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the ChaCha20Poly1305 AEAD
|
||||||
|
pub mod aead_chacha20poly1305 {
|
||||||
|
use crate::primitives::aead::*;
|
||||||
|
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc7539#section-2.8
|
||||||
|
|
||||||
|
/// The key length used in [`AeadChaCha20Poly1305`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The nonce length used in [`AeadChaCha20Poly1305`].
|
||||||
|
pub const NONCE_LEN: usize = 12;
|
||||||
|
/// The tag length used in [`AeadChaCha20Poly1305`].
|
||||||
|
pub const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
|
/// An [`Aead`] that is ChaCha20Poly1305.
|
||||||
|
pub trait AeadChaCha20Poly1305: Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the XChaCha20Poly1305 AEAD (i.e. ChaCha20Poly1305 with extended nonce
|
||||||
|
/// lengths)
|
||||||
|
pub mod aead_xchacha20poly1305 {
|
||||||
|
use crate::primitives::aead::*;
|
||||||
|
|
||||||
|
// See https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03
|
||||||
|
|
||||||
|
/// The key length used in [`AeadXChaCha20Poly1305`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The nonce length used in [`AeadXChaCha20Poly1305`].
|
||||||
|
pub const NONCE_LEN: usize = 24;
|
||||||
|
/// The tag length used in [`AeadXChaCha20Poly1305`].
|
||||||
|
pub const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
|
/// An [`Aead`] that is XChaCha20Poly1305.
|
||||||
|
pub trait AeadXChaCha20Poly1305: Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the Kyber512 KEM
|
||||||
|
pub mod kem_kyber512 {
|
||||||
|
use crate::primitives::kem::*;
|
||||||
|
|
||||||
|
// page 39 of https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf
|
||||||
|
// (which is ml-kem instead of kyber, but it's the same)
|
||||||
|
|
||||||
|
/// The secret key length used in [`KemKyber512`].
|
||||||
|
pub const SK_LEN: usize = 1632;
|
||||||
|
|
||||||
|
/// The public key length used in [`KemKyber512`].
|
||||||
|
pub const PK_LEN: usize = 800;
|
||||||
|
|
||||||
|
/// The ciphertext length used in [`KemKyber512`].
|
||||||
|
pub const CT_LEN: usize = 768;
|
||||||
|
|
||||||
|
/// The shared key length used in [`KemKyber512`].
|
||||||
|
pub const SHK_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`Kem`] that is Kyber512.
|
||||||
|
pub trait KemKyber512: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the Classic McEliece 460896 KEM
|
||||||
|
pub mod kem_classic_mceliece460896 {
|
||||||
|
use crate::primitives::kem::*;
|
||||||
|
|
||||||
|
// page 6 of https://classic.mceliece.org/mceliece-impl-20221023.pdf
|
||||||
|
|
||||||
|
/// The secret key length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const SK_LEN: usize = 13608;
|
||||||
|
|
||||||
|
/// The public key length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const PK_LEN: usize = 524160;
|
||||||
|
|
||||||
|
/// The ciphertext length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const CT_LEN: usize = 156;
|
||||||
|
|
||||||
|
/// The shared key length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const SHK_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`Kem`] that is ClassicMceliece460896.
|
||||||
|
pub trait KemClassicMceliece460896: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use aead_chacha20poly1305::AeadChaCha20Poly1305;
|
||||||
|
pub use aead_xchacha20poly1305::AeadXChaCha20Poly1305;
|
||||||
|
|
||||||
|
pub use kem_classic_mceliece460896::KemClassicMceliece460896;
|
||||||
|
pub use kem_kyber512::KemKyber512;
|
||||||
|
|
||||||
|
pub use keyed_hash_blake2b::KeyedHashBlake2b;
|
||||||
|
pub use keyed_hash_incorrect_hmac_blake2b::KeyedHashIncorrectHmacBlake2b;
|
||||||
|
pub use keyed_hash_shake256::KeyedHashShake256;
|
||||||
@@ -5,10 +5,128 @@
|
|||||||
//!
|
//!
|
||||||
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
|
//! 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
|
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
|
||||||
//!
|
|
||||||
//! encapsulation.
|
//! 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
|
/// Key Encapsulation Mechanism
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
mod kem;
|
//! This trait contains traits, constants and wrappers that provid= the interface between Rosenpass
|
||||||
pub use kem::Kem;
|
//! as a consumer of cryptographic libraries and the implementations of cryptographic algorithms.
|
||||||
|
|
||||||
|
pub mod algorithms;
|
||||||
|
pub mod primitives;
|
||||||
|
|||||||
10
cipher-traits/src/primitives.rs
Normal file
10
cipher-traits/src/primitives.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//! Traits for cryptographic primitives used in Rosenpass, specifically KEM, AEAD and keyed
|
||||||
|
//! hashing.
|
||||||
|
|
||||||
|
pub(crate) mod aead;
|
||||||
|
pub(crate) mod kem;
|
||||||
|
pub(crate) mod keyed_hash;
|
||||||
|
|
||||||
|
pub use aead::{Aead, AeadWithNonceInCiphertext, Error as AeadError};
|
||||||
|
pub use kem::{Error as KemError, Kem};
|
||||||
|
pub use keyed_hash::*;
|
||||||
175
cipher-traits/src/primitives/aead.rs
Normal file
175
cipher-traits/src/primitives/aead.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use rosenpass_to::{ops::copy_slice, To as _};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Models authenticated encryption with assiciated data (AEAD) functionality.
|
||||||
|
///
|
||||||
|
/// The methods of this trait take a `&self` argument as a receiver. This has two reasons:
|
||||||
|
/// 1. It makes type inference a lot smoother
|
||||||
|
/// 2. It allows to use the functionality through a trait object or having an enum that has
|
||||||
|
/// variants for multiple options (like e.g. the `KeyedHash` enum in `rosenpass-ciphers`).
|
||||||
|
///
|
||||||
|
/// Since the caller needs an instance of the type to use the functionality, implementors are
|
||||||
|
/// adviced to implement the [`Default`] trait where possible.
|
||||||
|
///
|
||||||
|
/// Example for encrypting a message with a specific [`Aead`] instance:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Aead;
|
||||||
|
///
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const NONCE_LEN: usize = 12;
|
||||||
|
/// const TAG_LEN: usize = 16;
|
||||||
|
///
|
||||||
|
/// fn encrypt_message_given_an_aead<AeadImpl>(
|
||||||
|
/// aead: &AeadImpl,
|
||||||
|
/// msg: &str,
|
||||||
|
/// nonce: &[u8; NONCE_LEN],
|
||||||
|
/// encrypted: &mut [u8]
|
||||||
|
/// ) where AeadImpl: Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {
|
||||||
|
/// let key = [0u8; KEY_LEN]; // This is not a secure key!
|
||||||
|
/// let ad = b""; // we don't need associated data here
|
||||||
|
/// aead.encrypt(encrypted, &key, nonce, ad, msg.as_bytes()).unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If only the type (but no instance) is available, then we can still encrypt, as long as the type
|
||||||
|
/// also is [`Default`]:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Aead;
|
||||||
|
///
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const NONCE_LEN: usize = 12;
|
||||||
|
/// const TAG_LEN: usize = 16;
|
||||||
|
///
|
||||||
|
/// fn encrypt_message_without_aead<AeadImpl>(
|
||||||
|
/// msg: &str,
|
||||||
|
/// nonce: &[u8; NONCE_LEN],
|
||||||
|
/// encrypted: &mut [u8]
|
||||||
|
/// ) where AeadImpl: Default + Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {
|
||||||
|
/// let key = [0u8; KEY_LEN]; // This is not a secure key!
|
||||||
|
/// let ad = b""; // we don't need associated data here
|
||||||
|
/// AeadImpl::default().encrypt(encrypted, &key, nonce, ad, msg.as_bytes()).unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait Aead<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> {
|
||||||
|
const KEY_LEN: usize = KEY_LEN;
|
||||||
|
const NONCE_LEN: usize = NONCE_LEN;
|
||||||
|
const TAG_LEN: usize = TAG_LEN;
|
||||||
|
|
||||||
|
/// Encrypts `plaintext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `ciphertext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` longer than `plaintext`.
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Decrypts `ciphertexttext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `plaintext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` longer than `plaintext`.
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides an AEAD API where the nonce is part of the ciphertext.
|
||||||
|
///
|
||||||
|
/// The old xaead API had the ciphertext begin with the `nonce`. In order to not having to change
|
||||||
|
/// the calling code too much, we add a wrapper trait that provides this API and implement it for
|
||||||
|
/// all AEAD.
|
||||||
|
pub trait AeadWithNonceInCiphertext<
|
||||||
|
const KEY_LEN: usize,
|
||||||
|
const NONCE_LEN: usize,
|
||||||
|
const TAG_LEN: usize,
|
||||||
|
>: Aead<KEY_LEN, NONCE_LEN, TAG_LEN>
|
||||||
|
{
|
||||||
|
/// Encrypts `plaintext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `ciphertext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` + `NONCE_LEN` longer than `plaintext`.
|
||||||
|
fn encrypt_with_nonce_in_ctxt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < NONCE_LEN + TAG_LEN
|
||||||
|
|| ciphertext.len() - TAG_LEN - NONCE_LEN < plaintext.len()
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (n, rest) = ciphertext.split_at_mut(NONCE_LEN);
|
||||||
|
copy_slice(nonce).to(n);
|
||||||
|
|
||||||
|
self.encrypt(rest, key, nonce, ad, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts `ciphertexttext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `plaintext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` + `NONCE_LEN` longer than `plaintext`.
|
||||||
|
fn decrypt_with_nonce_in_ctxt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < NONCE_LEN + TAG_LEN
|
||||||
|
|| ciphertext.len() - TAG_LEN - NONCE_LEN < plaintext.len()
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (nonce, rest) = ciphertext.split_at(NONCE_LEN);
|
||||||
|
// We know this should be the right length (we just split it), and everything else would be
|
||||||
|
// very unexpected.
|
||||||
|
let nonce = nonce.try_into().map_err(|_| Error::InternalError)?;
|
||||||
|
|
||||||
|
self.decrypt(plaintext, key, nonce, ad, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const KEY_LEN: usize,
|
||||||
|
const NONCE_LEN: usize,
|
||||||
|
const TAG_LEN: usize,
|
||||||
|
T: Aead<KEY_LEN, NONCE_LEN, TAG_LEN>,
|
||||||
|
> AeadWithNonceInCiphertext<KEY_LEN, NONCE_LEN, TAG_LEN> for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error returned by AEAD operations
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An internal error occurred. This should never be happen and indicates an error in the
|
||||||
|
/// AEAD implementation.
|
||||||
|
#[error("internal error")]
|
||||||
|
InternalError,
|
||||||
|
|
||||||
|
/// Could not decrypt a message because the message is not a valid ciphertext for the given
|
||||||
|
/// key.
|
||||||
|
#[error("decryption error")]
|
||||||
|
DecryptError,
|
||||||
|
|
||||||
|
/// The provided buffers have the wrong lengths.
|
||||||
|
#[error("buffers have invalid length")]
|
||||||
|
InvalidLengths,
|
||||||
|
}
|
||||||
212
cipher-traits/src/primitives/kem.rs
Normal file
212
cipher-traits/src/primitives/kem.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
|
||||||
|
//!
|
||||||
|
//! KEMs are the interface provided by almost all post-quantum
|
||||||
|
//! secure key exchange mechanisms.
|
||||||
|
//!
|
||||||
|
//! 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:
|
||||||
|
//! [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::primitives::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::default().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::default().encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||||
|
//!
|
||||||
|
//! let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||||
|
//! MyKem::default().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::primitives::{Kem, KemError as Error};
|
||||||
|
//!
|
||||||
|
//! struct DummyKem {}
|
||||||
|
//! impl Kem<1,1,1,1> for DummyKem {
|
||||||
|
//!
|
||||||
|
//! // 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(&self, sk: &mut [u8;1], pk: &mut [u8;1]) -> Result<(), Error> {
|
||||||
|
//! sk[0] = 42;
|
||||||
|
//! pk[0] = 21;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn encaps(&self, shk: &mut [u8;1], ct: &mut [u8;1], pk: &[u8;1]) -> Result<(), Error> {
|
||||||
|
//! if pk[0] != 21 {
|
||||||
|
//! return Err(Error::InvalidArgument);
|
||||||
|
//! }
|
||||||
|
//! ct[0] = 7;
|
||||||
|
//! shk[0] = 17;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn decaps(&self, shk: &mut [u8;1 ], sk: &[u8;1], ct: &[u8;1]) -> Result<(), Error> {
|
||||||
|
//! if sk[0] != 42 {
|
||||||
|
//! return Err(Error::InvalidArgument);
|
||||||
|
//! }
|
||||||
|
//! if ct[0] != 7 {
|
||||||
|
//! return Err(Error::InvalidArgument);
|
||||||
|
//! }
|
||||||
|
//! shk[0] = 17;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Default for DummyKem {
|
||||||
|
//! fn default() -> Self {
|
||||||
|
//! Self{}
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # 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::default().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::default().encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||||
|
//! #
|
||||||
|
//! # let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||||
|
//! # MyKem::default().decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||||
|
//! #
|
||||||
|
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||||
|
//! #
|
||||||
|
//! # Ok::<(), Error>(())
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Key Encapsulation Mechanism
|
||||||
|
///
|
||||||
|
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
||||||
|
/// decapsulation. The parameters are made available as associated constants for convenience.
|
||||||
|
///
|
||||||
|
/// The methods of this trait take a `&self` argument as a receiver. This has two reasons:
|
||||||
|
/// 1. It makes type inference a lot smoother
|
||||||
|
/// 2. It allows to use the functionality through a trait object or having an enum that has
|
||||||
|
/// variants for multiple options (like e.g. the `KeyedHash` enum in `rosenpass-ciphers`).
|
||||||
|
///
|
||||||
|
/// Since the caller needs an instance of the type to use the functionality, implementors are
|
||||||
|
/// adviced to implement the [`Default`] trait where possible.
|
||||||
|
///
|
||||||
|
/// Example for encrypting a message with a specific [`Kem`] instance:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Kem;
|
||||||
|
///
|
||||||
|
/// const SK_LEN: usize = 1632;
|
||||||
|
/// const PK_LEN: usize = 800;
|
||||||
|
/// const CT_LEN: usize = 768;
|
||||||
|
/// const SHK_LEN: usize = 32;
|
||||||
|
///
|
||||||
|
/// fn encaps_given_a_kem<KemImpl>(
|
||||||
|
/// kem: &KemImpl,
|
||||||
|
/// pk: &[u8; PK_LEN],
|
||||||
|
/// ct: &mut [u8; CT_LEN]
|
||||||
|
/// ) -> [u8; SHK_LEN] where KemImpl: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN>{
|
||||||
|
/// let mut shk = [0u8; SHK_LEN];
|
||||||
|
/// kem.encaps(&mut shk, ct, pk).unwrap();
|
||||||
|
/// shk
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If only the type (but no instance) is available, then we can still use the trait, as long as
|
||||||
|
/// the type also is [`Default`]:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Kem;
|
||||||
|
///
|
||||||
|
/// const SK_LEN: usize = 1632;
|
||||||
|
/// const PK_LEN: usize = 800;
|
||||||
|
/// const CT_LEN: usize = 768;
|
||||||
|
/// const SHK_LEN: usize = 32;
|
||||||
|
///
|
||||||
|
/// fn encaps_without_kem<KemImpl>(
|
||||||
|
/// pk: &[u8; PK_LEN],
|
||||||
|
/// ct: &mut [u8; CT_LEN]
|
||||||
|
/// ) -> [u8; SHK_LEN]
|
||||||
|
/// where KemImpl: Default + Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> {
|
||||||
|
/// let mut shk = [0u8; SHK_LEN];
|
||||||
|
/// KemImpl::default().encaps(&mut shk, ct, pk).unwrap();
|
||||||
|
/// shk
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait Kem<const SK_LEN: usize, const PK_LEN: usize, const CT_LEN: usize, const SHK_LEN: usize> {
|
||||||
|
/// The length of the secret (decapsulation) key.
|
||||||
|
const SK_LEN: usize = SK_LEN;
|
||||||
|
|
||||||
|
/// The length of the public (encapsulation) key.
|
||||||
|
const PK_LEN: usize = PK_LEN;
|
||||||
|
|
||||||
|
/// The length of the ciphertext.
|
||||||
|
const CT_LEN: usize = CT_LEN;
|
||||||
|
|
||||||
|
/// The legnth of the resulting shared key.
|
||||||
|
const SHK_LEN: usize = SHK_LEN;
|
||||||
|
|
||||||
|
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
|
||||||
|
///
|
||||||
|
/// `keygen() -> sk, pk`
|
||||||
|
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
|
||||||
|
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
|
||||||
|
///
|
||||||
|
/// `encaps(pk) -> shk, ct`
|
||||||
|
fn encaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
ct: &mut [u8; CT_LEN],
|
||||||
|
pk: &[u8; PK_LEN],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
|
||||||
|
/// (`shk`)
|
||||||
|
///
|
||||||
|
/// `decaps(sk, ct) -> shk`
|
||||||
|
fn decaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
sk: &[u8; SK_LEN],
|
||||||
|
ct: &[u8; CT_LEN],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("invalid argument")]
|
||||||
|
InvalidArgument,
|
||||||
|
#[error("internal error")]
|
||||||
|
InternalError,
|
||||||
|
}
|
||||||
159
cipher-traits/src/primitives/keyed_hash.rs
Normal file
159
cipher-traits/src/primitives/keyed_hash.rs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// Models a keyed hash function using an associated function (i.e. without `&self` receiver).
|
||||||
|
pub trait KeyedHash<const KEY_LEN: usize, const HASH_LEN: usize> {
|
||||||
|
/// The error type used to signal what went wrong.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Performs a keyed hash using `key` and `data` and writes the output to `out`
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Models a keyed hash function using a method (i.e. with a `&self` receiver).
|
||||||
|
///
|
||||||
|
/// This makes type inference easier, but also requires having a [`KeyedHashInstance`] value,
|
||||||
|
/// instead of just the [`KeyedHash`] type.
|
||||||
|
pub trait KeyedHashInstance<const KEY_LEN: usize, const HASH_LEN: usize> {
|
||||||
|
/// The error type used to signal what went wrong.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Performs a keyed hash using `key` and `data` and writes the output to `out`
|
||||||
|
fn keyed_hash(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a helper to allow for type parameter inference when calling functions
|
||||||
|
/// that need a [KeyedHash].
|
||||||
|
///
|
||||||
|
/// Really just binds the [KeyedHash] trait to a dummy variable, so the type of this dummy variable
|
||||||
|
/// can be used for type inference. Less typing work.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct InferKeyedHash<Static, const KEY_LEN: usize, const HASH_LEN: usize>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, HASH_LEN>,
|
||||||
|
{
|
||||||
|
pub _phantom_keyed_hasher: PhantomData<*const Static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const HASH_LEN: usize> InferKeyedHash<Static, KEY_LEN, HASH_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, HASH_LEN>,
|
||||||
|
{
|
||||||
|
pub const KEY_LEN: usize = KEY_LEN;
|
||||||
|
pub const HASH_LEN: usize = HASH_LEN;
|
||||||
|
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_phantom_keyed_hasher: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This just forwards to [KeyedHash::keyed_hash] of the type parameter `Static`
|
||||||
|
fn keyed_hash_internal<'a>(
|
||||||
|
&self,
|
||||||
|
key: &'a [u8; KEY_LEN],
|
||||||
|
data: &'a [u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Static::Error> {
|
||||||
|
Static::keyed_hash(key, data, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the key length of the keyed hash function.
|
||||||
|
pub const fn key_len(self) -> usize {
|
||||||
|
Self::KEY_LEN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the hash length of the keyed hash function.
|
||||||
|
pub const fn hash_len(self) -> usize {
|
||||||
|
Self::HASH_LEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize, Static: KeyedHash<KEY_LEN, HASH_LEN>>
|
||||||
|
KeyedHashInstance<KEY_LEN, HASH_LEN> for InferKeyedHash<Static, KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
type Error = Static::Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Static::Error> {
|
||||||
|
self.keyed_hash_internal(key, data, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper traits /////////////////////////////////////////////
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const OUT_LEN: usize> Default
|
||||||
|
for InferKeyedHash<Static, KEY_LEN, OUT_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, OUT_LEN>,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const OUT_LEN: usize> Clone
|
||||||
|
for InferKeyedHash<Static, KEY_LEN, OUT_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, OUT_LEN>,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const OUT_LEN: usize> Copy
|
||||||
|
for InferKeyedHash<Static, KEY_LEN, OUT_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, OUT_LEN>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
use rosenpass_to::{with_destination, To};
|
||||||
|
|
||||||
|
/// Extends the [`KeyedHash`] trait with a [`To`]-flavoured function.
|
||||||
|
pub trait KeyedHashTo<const KEY_LEN: usize, const HASH_LEN: usize>:
|
||||||
|
KeyedHash<KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
fn keyed_hash_to(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
) -> impl To<[u8; HASH_LEN], Result<(), Self::Error>> {
|
||||||
|
with_destination(|out| Self::keyed_hash(key, data, out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize, T: KeyedHash<KEY_LEN, HASH_LEN>>
|
||||||
|
KeyedHashTo<KEY_LEN, HASH_LEN> for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extends the [`KeyedHashInstance`] trait with a [`To`]-flavoured function.
|
||||||
|
pub trait KeyedHashInstanceTo<const KEY_LEN: usize, const HASH_LEN: usize>:
|
||||||
|
KeyedHashInstance<KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
fn keyed_hash_to(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
) -> impl To<[u8; HASH_LEN], Result<(), Self::Error>> {
|
||||||
|
with_destination(|out| self.keyed_hash(key, data, out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize, T: KeyedHashInstance<KEY_LEN, HASH_LEN>>
|
||||||
|
KeyedHashInstanceTo<KEY_LEN, HASH_LEN> for T
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -8,9 +8,22 @@ description = "Rosenpass internal ciphers and other cryptographic primitives use
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_libcrux = ["dep:libcrux"]
|
experiment_libcrux_all = [
|
||||||
|
"experiment_libcrux_blake2",
|
||||||
|
"experiment_libcrux_chachapoly",
|
||||||
|
"experiment_libcrux_chachapoly_test",
|
||||||
|
"experiment_libcrux_kyber",
|
||||||
|
]
|
||||||
|
experiment_libcrux_blake2 = ["dep:libcrux-blake2", "dep:thiserror"]
|
||||||
|
experiment_libcrux_chachapoly = ["dep:libcrux-chacha20poly1305"]
|
||||||
|
experiment_libcrux_chachapoly_test = [
|
||||||
|
"experiment_libcrux_chachapoly",
|
||||||
|
"dep:libcrux",
|
||||||
|
]
|
||||||
|
experiment_libcrux_kyber = ["dep:libcrux-ml-kem", "dep:rand"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
@@ -19,8 +32,21 @@ rosenpass-constant-time = { workspace = true }
|
|||||||
rosenpass-secret-memory = { workspace = true }
|
rosenpass-secret-memory = { workspace = true }
|
||||||
rosenpass-oqs = { workspace = true }
|
rosenpass-oqs = { workspace = true }
|
||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
|
rosenpass-cipher-traits = { workspace = true }
|
||||||
static_assertions = { workspace = true }
|
static_assertions = { workspace = true }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
chacha20poly1305 = { workspace = true }
|
chacha20poly1305 = { workspace = true }
|
||||||
blake2 = { workspace = true }
|
blake2 = { workspace = true }
|
||||||
|
sha3 = { workspace = true }
|
||||||
|
rand = { workspace = true, optional = true }
|
||||||
|
thiserror = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
libcrux-chacha20poly1305 = { workspace = true, optional = true }
|
||||||
|
libcrux-blake2 = { workspace = true, optional = true }
|
||||||
|
libcrux-ml-kem = { workspace = true, optional = true, features = ["kyber"] }
|
||||||
|
|
||||||
|
# this one is only used in testing, so it requires the `experiment_libcrux_chachapoly_test` feature.
|
||||||
libcrux = { workspace = true, optional = true }
|
libcrux = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = { workspace = true }
|
||||||
|
|||||||
@@ -1,109 +1,209 @@
|
|||||||
|
//!
|
||||||
|
//!```rust
|
||||||
|
//! # use rosenpass_ciphers::hash_domain::{HashDomain, HashDomainNamespace, SecretHashDomain, SecretHashDomainNamespace};
|
||||||
|
//! use rosenpass_ciphers::KeyedHash;
|
||||||
|
//! 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(KeyedHash::keyed_shake256());
|
||||||
|
//! 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>(())
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass_secret_memory::Secret;
|
use rosenpass_secret_memory::Secret;
|
||||||
use rosenpass_to::To;
|
use rosenpass_to::To as _;
|
||||||
|
|
||||||
use crate::subtle::incorrect_hmac_blake2b as hash;
|
pub use crate::{KeyedHash, KEY_LEN};
|
||||||
|
|
||||||
pub use hash::KEY_LEN;
|
use rosenpass_cipher_traits::primitives::KeyedHashInstanceTo;
|
||||||
|
|
||||||
// TODO Use a proper Dec interface
|
// 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HashDomain([u8; KEY_LEN]);
|
pub struct HashDomain([u8; KEY_LEN], KeyedHash);
|
||||||
|
/// 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HashDomainNamespace([u8; KEY_LEN]);
|
pub struct HashDomainNamespace([u8; KEY_LEN], KeyedHash);
|
||||||
|
/// 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SecretHashDomain(Secret<KEY_LEN>);
|
pub struct SecretHashDomain(Secret<KEY_LEN>, KeyedHash);
|
||||||
|
/// 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
|
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>, KeyedHash);
|
||||||
|
|
||||||
impl HashDomain {
|
impl HashDomain {
|
||||||
pub fn zero() -> Self {
|
/// Creates a nw [HashDomain] initialized with a all-zeros key.
|
||||||
Self([0u8; KEY_LEN])
|
pub fn zero(choice: KeyedHash) -> Self {
|
||||||
|
Self([0u8; KEY_LEN], choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns this [HashDomain] into a [HashDomainNamespace], keeping the key.
|
||||||
pub fn dup(self) -> HashDomainNamespace {
|
pub fn dup(self) -> HashDomainNamespace {
|
||||||
HashDomainNamespace(self.0)
|
HashDomainNamespace(self.0, self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub fn turn_secret(self) -> SecretHashDomain {
|
||||||
SecretHashDomain(Secret::from_slice(&self.0))
|
SecretHashDomain(Secret::from_slice(&self.0), self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Protocol! Use domain separation to ensure that
|
// 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> {
|
pub fn mix(self, v: &[u8]) -> Result<Self> {
|
||||||
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
|
let mut new_key: [u8; KEY_LEN] = [0u8; KEY_LEN];
|
||||||
|
self.1.keyed_hash_to(&self.0, v).to(&mut new_key)?;
|
||||||
|
Ok(Self(new_key, self.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
SecretHashDomain::invoke_primitive(&self.0, v.secret(), self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the key of this [HashDomain].
|
||||||
pub fn into_value(self) -> [u8; KEY_LEN] {
|
pub fn into_value(self) -> [u8; KEY_LEN] {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HashDomainNamespace {
|
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> {
|
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
|
||||||
Ok(HashDomain(
|
let mut new_key: [u8; KEY_LEN] = [0u8; KEY_LEN];
|
||||||
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
|
self.1.keyed_hash_to(&self.0, v).to(&mut new_key)?;
|
||||||
))
|
Ok(HashDomain(new_key, self.1.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
SecretHashDomain::invoke_primitive(&self.0, v.secret(), self.1.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SecretHashDomain {
|
impl SecretHashDomain {
|
||||||
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
|
/// Create a new [SecretHashDomain] with the given key `k` and data `d` by calling
|
||||||
let mut r = SecretHashDomain(Secret::zero());
|
/// [hash::hash] with `k` as the `key` and `d` s the `data`, and using the result
|
||||||
hash::hash(k, d).to(r.0.secret_mut())?;
|
/// as the content for the new [SecretHashDomain].
|
||||||
|
/// Both `k` and `d` have to be exactly [KEY_LEN] bytes in length.
|
||||||
|
/// TODO: docu
|
||||||
|
pub fn invoke_primitive(
|
||||||
|
k: &[u8],
|
||||||
|
d: &[u8],
|
||||||
|
hash_choice: KeyedHash,
|
||||||
|
) -> Result<SecretHashDomain> {
|
||||||
|
let mut new_secret_key = Secret::zero();
|
||||||
|
hash_choice
|
||||||
|
.keyed_hash_to(k.try_into()?, d)
|
||||||
|
.to(new_secret_key.secret_mut())?;
|
||||||
|
let r = SecretHashDomain(new_secret_key, hash_choice);
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zero() -> Self {
|
/// Creates a new [SecretHashDomain] that is initialized with an all zeros key.
|
||||||
Self(Secret::zero())
|
pub fn zero(hash_choice: KeyedHash) -> Self {
|
||||||
|
Self(Secret::zero(), hash_choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns this [SecretHashDomain] into a [SecretHashDomainNamespace].
|
||||||
pub fn dup(self) -> SecretHashDomainNamespace {
|
pub fn dup(self) -> SecretHashDomainNamespace {
|
||||||
SecretHashDomainNamespace(self.0)
|
SecretHashDomainNamespace(self.0, self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
|
/// Creates a new [SecretHashDomain] from a [Secret] `k`.
|
||||||
Self(k)
|
///
|
||||||
|
/// It requires that `k` consist of exactly [KEY_LEN] bytes.
|
||||||
|
pub fn danger_from_secret(k: Secret<KEY_LEN>, hash_choice: KeyedHash) -> Self {
|
||||||
|
Self(k, hash_choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||||
Self::invoke_primitive(self.0.secret(), v)
|
Self::invoke_primitive(self.0.secret(), v, self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
Self::invoke_primitive(self.0.secret(), v.secret())
|
Self::invoke_primitive(self.0.secret(), v.secret(), self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the secret key data from this [SecretHashDomain].
|
||||||
pub fn into_secret(self) -> Secret<KEY_LEN> {
|
pub fn into_secret(self) -> Secret<KEY_LEN> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
|
|
||||||
hash::hash(v, dst).to(self.0.secret_mut())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SecretHashDomainNamespace {
|
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> {
|
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(self.0.secret(), v)
|
SecretHashDomain::invoke_primitive(self.0.secret(), v, self.1.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
|
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret(), self.1.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This entire API is not very nice; we need this for biscuits, but
|
// TODO: This entire API is not very nice; we need this for biscuits, but
|
||||||
// it might be better to extract a special "biscuit"
|
// it might be better to extract a special "biscuit"
|
||||||
// labeled subkey and reinitialize the chain with this
|
// 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> {
|
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn keyed_hash(&self) -> &KeyedHash {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,46 @@
|
|||||||
|
use rosenpass_cipher_traits::primitives::Aead as AeadTrait;
|
||||||
use static_assertions::const_assert;
|
use static_assertions::const_assert;
|
||||||
|
|
||||||
pub mod subtle;
|
pub mod subtle;
|
||||||
|
|
||||||
|
/// All keyed primitives in this crate use 32 byte keys
|
||||||
pub const KEY_LEN: usize = 32;
|
pub const KEY_LEN: usize = 32;
|
||||||
const_assert!(KEY_LEN == aead::KEY_LEN);
|
const_assert!(KEY_LEN == Aead::KEY_LEN);
|
||||||
const_assert!(KEY_LEN == xaead::KEY_LEN);
|
const_assert!(KEY_LEN == XAead::KEY_LEN);
|
||||||
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
||||||
|
|
||||||
/// Authenticated encryption with associated data
|
/// Keyed hashing
|
||||||
pub mod aead {
|
///
|
||||||
#[cfg(not(feature = "experiment_libcrux"))]
|
/// This should only be used for implementation details; anything with relevance
|
||||||
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
|
/// to the cryptographic protocol should use the facilities in [hash_domain], (though
|
||||||
#[cfg(feature = "experiment_libcrux")]
|
/// hash domain uses this module internally)
|
||||||
pub use crate::subtle::chacha20poly1305_ietf_libcrux::{
|
pub use crate::subtle::keyed_hash::KeyedHash;
|
||||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Authenticated encryption with associated data with a constant nonce
|
/// Authenticated encryption with associated data (AEAD)
|
||||||
pub mod xaead {
|
/// Chacha20poly1305 is used.
|
||||||
pub use crate::subtle::xchacha20poly1305_ietf::{
|
#[cfg(feature = "experiment_libcrux_chachapoly")]
|
||||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
pub use subtle::libcrux::chacha20poly1305_ietf::ChaCha20Poly1305 as Aead;
|
||||||
};
|
|
||||||
}
|
/// Authenticated encryption with associated data (AEAD)
|
||||||
|
/// Chacha20poly1305 is used.
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_chachapoly"))]
|
||||||
|
pub use crate::subtle::rust_crypto::chacha20poly1305_ietf::ChaCha20Poly1305 as Aead;
|
||||||
|
|
||||||
|
/// Authenticated encryption with associated data with a extended-length nonce (XAEAD)
|
||||||
|
/// XChacha20poly1305 is used.
|
||||||
|
pub use crate::subtle::rust_crypto::xchacha20poly1305_ietf::XChaCha20Poly1305 as XAead;
|
||||||
|
|
||||||
|
/// Use Classic-McEcliece-460986 as the Static KEM.
|
||||||
|
///
|
||||||
|
/// See [rosenpass_oqs::ClassicMceliece460896] for more details.
|
||||||
|
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
||||||
|
|
||||||
|
/// Use Kyber-512 as the Static KEM
|
||||||
|
///
|
||||||
|
/// See [rosenpass_oqs::Kyber512] for more details.
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_kyber"))]
|
||||||
|
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
||||||
|
#[cfg(feature = "experiment_libcrux_kyber")]
|
||||||
|
pub use subtle::libcrux::kyber512::Kyber512 as EphemeralKem;
|
||||||
|
|
||||||
pub mod hash_domain;
|
pub mod hash_domain;
|
||||||
|
|
||||||
pub mod kem {
|
|
||||||
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
|
||||||
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
use blake2::digest::crypto_common::generic_array::GenericArray;
|
|
||||||
use blake2::digest::crypto_common::typenum::U32;
|
|
||||||
use blake2::digest::crypto_common::KeySizeUser;
|
|
||||||
use blake2::digest::{FixedOutput, Mac, OutputSizeUser};
|
|
||||||
use blake2::Blake2bMac;
|
|
||||||
|
|
||||||
use rosenpass_to::{ops::copy_slice, with_destination, To};
|
|
||||||
use rosenpass_util::typenum2const;
|
|
||||||
|
|
||||||
type Impl = Blake2bMac<U32>;
|
|
||||||
|
|
||||||
type KeyLen = <Impl as KeySizeUser>::KeySize;
|
|
||||||
type OutLen = <Impl as OutputSizeUser>::OutputSize;
|
|
||||||
|
|
||||||
const KEY_LEN: usize = typenum2const! { KeyLen };
|
|
||||||
const OUT_LEN: usize = typenum2const! { OutLen };
|
|
||||||
|
|
||||||
pub const KEY_MIN: usize = KEY_LEN;
|
|
||||||
pub const KEY_MAX: usize = KEY_LEN;
|
|
||||||
pub const OUT_MIN: usize = OUT_LEN;
|
|
||||||
pub const OUT_MAX: usize = OUT_LEN;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
|
||||||
with_destination(|out: &mut [u8]| {
|
|
||||||
let mut h = Impl::new_from_slice(key)?;
|
|
||||||
h.update(data);
|
|
||||||
|
|
||||||
// Jesus christ, blake2 crate, your usage of GenericArray might be nice and fancy
|
|
||||||
// but it introduces a ton of complexity. This cost me half an hour just to figure
|
|
||||||
// out the right way to use the imports while allowing for zeroization.
|
|
||||||
// An API based on slices might actually be simpler.
|
|
||||||
let mut tmp = Zeroizing::new([0u8; OUT_LEN]);
|
|
||||||
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
|
||||||
h.finalize_into(tmp);
|
|
||||||
copy_slice(tmp.as_ref()).to(out);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
use rosenpass_to::ops::copy_slice;
|
|
||||||
use rosenpass_to::To;
|
|
||||||
use rosenpass_util::typenum2const;
|
|
||||||
|
|
||||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
|
||||||
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
|
|
||||||
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
|
|
||||||
|
|
||||||
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
|
|
||||||
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
|
|
||||||
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn encrypt(
|
|
||||||
ciphertext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
plaintext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let nonce = GenericArray::from_slice(nonce);
|
|
||||||
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
|
||||||
copy_slice(plaintext).to(ct);
|
|
||||||
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
|
|
||||||
copy_slice(&mac_value[..]).to(mac);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn decrypt(
|
|
||||||
plaintext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
ciphertext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let nonce = GenericArray::from_slice(nonce);
|
|
||||||
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
|
||||||
let tag = GenericArray::from_slice(mac);
|
|
||||||
copy_slice(ct).to(plaintext);
|
|
||||||
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
use rosenpass_to::ops::copy_slice;
|
|
||||||
use rosenpass_to::To;
|
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
|
|
||||||
pub const TAG_LEN: usize = 16;
|
|
||||||
pub const NONCE_LEN: usize = 12;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn encrypt(
|
|
||||||
ciphertext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
plaintext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
|
||||||
|
|
||||||
use libcrux::aead as C;
|
|
||||||
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
|
||||||
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
|
||||||
|
|
||||||
copy_slice(plaintext).to(ciphertext);
|
|
||||||
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
|
|
||||||
copy_slice(crux_tag.as_ref()).to(mac);
|
|
||||||
|
|
||||||
match crux_key {
|
|
||||||
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn decrypt(
|
|
||||||
plaintext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
ciphertext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
|
||||||
|
|
||||||
use libcrux::aead as C;
|
|
||||||
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
|
||||||
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
|
||||||
let crux_tag = C::Tag::from_slice(mac).unwrap();
|
|
||||||
|
|
||||||
copy_slice(ciphertext).to(plaintext);
|
|
||||||
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
|
|
||||||
|
|
||||||
match crux_key {
|
|
||||||
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
79
ciphers/src/subtle/custom/incorrect_hmac_blake2b.rs
Normal file
79
ciphers/src/subtle/custom/incorrect_hmac_blake2b.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use rosenpass_cipher_traits::{
|
||||||
|
algorithms::KeyedHashIncorrectHmacBlake2b,
|
||||||
|
primitives::{InferKeyedHash, KeyedHash, KeyedHashTo},
|
||||||
|
};
|
||||||
|
use rosenpass_constant_time::xor;
|
||||||
|
use rosenpass_to::{ops::copy_slice, To};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_blake2"))]
|
||||||
|
use crate::subtle::rust_crypto::blake2b::Blake2b;
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_blake2"))]
|
||||||
|
use anyhow::Error;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_blake2")]
|
||||||
|
use crate::subtle::libcrux::blake2b::{Blake2b, Error};
|
||||||
|
|
||||||
|
/// The key length, 32 bytes or 256 bits.
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// The hash length, 32 bytes or 256 bits.
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// This is a woefully incorrect implementation of hmac_blake2b.
|
||||||
|
/// See <https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563612222>
|
||||||
|
///
|
||||||
|
/// It accepts 32 byte keys, exclusively.
|
||||||
|
///
|
||||||
|
/// This will be replaced, likely by Kekkac at some point soon.
|
||||||
|
/// <https://github.com/rosenpass/rosenpass/pull/145>
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::custom::incorrect_hmac_blake2b::IncorrectHmacBlake2bCore;
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHashTo;
|
||||||
|
/// 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!(IncorrectHmacBlake2bCore::keyed_hash_to(&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);
|
||||||
|
///```
|
||||||
|
///
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct IncorrectHmacBlake2bCore;
|
||||||
|
|
||||||
|
impl KeyedHash<KEY_LEN, HASH_LEN> for IncorrectHmacBlake2bCore {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];
|
||||||
|
const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN];
|
||||||
|
|
||||||
|
type Key = Zeroizing<[u8; KEY_LEN]>;
|
||||||
|
let mut tmp_key = Key::default();
|
||||||
|
|
||||||
|
copy_slice(key).to(tmp_key.as_mut());
|
||||||
|
xor(&IPAD).to(tmp_key.as_mut());
|
||||||
|
let mut outer_data = Key::default();
|
||||||
|
Blake2b::keyed_hash_to(&tmp_key, data).to(&mut outer_data)?;
|
||||||
|
|
||||||
|
copy_slice(key).to(tmp_key.as_mut());
|
||||||
|
xor(&OPAD).to(tmp_key.as_mut());
|
||||||
|
Blake2b::keyed_hash_to(&tmp_key, outer_data.as_ref()).to(out)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type IncorrectHmacBlake2b = InferKeyedHash<IncorrectHmacBlake2bCore, KEY_LEN, HASH_LEN>;
|
||||||
|
|
||||||
|
impl KeyedHashIncorrectHmacBlake2b for IncorrectHmacBlake2bCore {}
|
||||||
3
ciphers/src/subtle/custom/mod.rs
Normal file
3
ciphers/src/subtle/custom/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//! Own implementations of custom algorithms
|
||||||
|
|
||||||
|
pub mod incorrect_hmac_blake2b;
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
use anyhow::ensure;
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
use rosenpass_constant_time::xor;
|
|
||||||
use rosenpass_to::{ops::copy_slice, with_destination, To};
|
|
||||||
|
|
||||||
use crate::subtle::blake2b;
|
|
||||||
|
|
||||||
pub const KEY_LEN: usize = 32;
|
|
||||||
pub const KEY_MIN: usize = KEY_LEN;
|
|
||||||
pub const KEY_MAX: usize = KEY_LEN;
|
|
||||||
pub const OUT_MIN: usize = blake2b::OUT_MIN;
|
|
||||||
pub const OUT_MAX: usize = blake2b::OUT_MAX;
|
|
||||||
|
|
||||||
/// This is a woefully incorrect implementation of hmac_blake2b.
|
|
||||||
/// See <https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563612222>
|
|
||||||
///
|
|
||||||
/// It accepts 32 byte keys, exclusively.
|
|
||||||
///
|
|
||||||
/// This will be replaced, likely by Kekkac at some point soon.
|
|
||||||
/// <https://github.com/rosenpass/rosenpass/pull/145>
|
|
||||||
#[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];
|
|
||||||
const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN];
|
|
||||||
|
|
||||||
with_destination(|out: &mut [u8]| {
|
|
||||||
// Not bothering with padding; the implementation
|
|
||||||
// uses appropriately sized keys.
|
|
||||||
ensure!(key.len() == KEY_LEN);
|
|
||||||
|
|
||||||
type Key = Zeroizing<[u8; KEY_LEN]>;
|
|
||||||
let mut tmp_key = Key::default();
|
|
||||||
|
|
||||||
copy_slice(key).to(tmp_key.as_mut());
|
|
||||||
xor(&IPAD).to(tmp_key.as_mut());
|
|
||||||
let mut outer_data = Key::default();
|
|
||||||
blake2b::hash(tmp_key.as_ref(), data).to(outer_data.as_mut())?;
|
|
||||||
|
|
||||||
copy_slice(key).to(tmp_key.as_mut());
|
|
||||||
xor(&OPAD).to(tmp_key.as_mut());
|
|
||||||
blake2b::hash(tmp_key.as_ref(), outer_data.as_ref()).to(out)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
65
ciphers/src/subtle/keyed_hash.rs
Normal file
65
ciphers/src/subtle/keyed_hash.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//! This module provides types that enabling choosing the keyed hash building block to be used at
|
||||||
|
//! runtime (using enums) instead of at compile time (using generics).
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHashInstance;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::subtle::{
|
||||||
|
custom::incorrect_hmac_blake2b::IncorrectHmacBlake2b, rust_crypto::keyed_shake256::SHAKE256_32,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Length of symmetric key throughout Rosenpass.
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// The hash is used as a symmetric key and should have the same length.
|
||||||
|
pub const HASH_LEN: usize = KEY_LEN;
|
||||||
|
|
||||||
|
/// Provides a way to pick which keyed hash to use at runtime.
|
||||||
|
/// Implements [`KeyedHashInstance`] to allow hashing using the respective algorithm.
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum KeyedHash {
|
||||||
|
/// A hasher backed by [`SHAKE256_32`].
|
||||||
|
KeyedShake256(SHAKE256_32),
|
||||||
|
/// A hasher backed by [`IncorrectHmacBlake2b`].
|
||||||
|
IncorrectHmacBlake2b(IncorrectHmacBlake2b),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyedHash {
|
||||||
|
/// Creates an [`KeyedHash`] backed by SHAKE256.
|
||||||
|
pub fn keyed_shake256() -> Self {
|
||||||
|
Self::KeyedShake256(Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an [`KeyedHash`] backed by Blake2B.
|
||||||
|
pub fn incorrect_hmac_blake2b() -> Self {
|
||||||
|
Self::IncorrectHmacBlake2b(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyedHashInstance<KEY_LEN, HASH_LEN> for KeyedHash {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
Self::KeyedShake256(h) => h.keyed_hash(key, data, out)?,
|
||||||
|
Self::IncorrectHmacBlake2b(h) => h.keyed_hash(key, data, out)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for KeyedHash {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::KeyedShake256(_) => write!(f, "KeyedShake256_32"),
|
||||||
|
Self::IncorrectHmacBlake2b(_) => write!(f, "IncorrectHmacBlake2b"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
ciphers/src/subtle/libcrux/blake2b.rs
Normal file
88
ciphers/src/subtle/libcrux/blake2b.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//! Implementation of the [`KeyedHashBlake2b`] trait based on the [`libcrux_blake2`] crate.
|
||||||
|
|
||||||
|
use libcrux_blake2::Blake2bBuilder;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::KeyedHashBlake2b;
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHash;
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_blake2b::HASH_LEN;
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_blake2b::KEY_LEN;
|
||||||
|
|
||||||
|
/// Describles which error occurred
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An unexpected internal error occurred. Should never be returned and points to a bug in the
|
||||||
|
/// implementation.
|
||||||
|
#[error("internal error")]
|
||||||
|
InternalError,
|
||||||
|
|
||||||
|
/// Indicates that the provided data was too long.
|
||||||
|
#[error("data is too long")]
|
||||||
|
DataTooLong,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hasher for the given `data` with the Blake2b hash function.
|
||||||
|
pub struct Blake2b;
|
||||||
|
|
||||||
|
impl KeyedHash<KEY_LEN, HASH_LEN> for Blake2b {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut h = Blake2bBuilder::new_keyed_const(key)
|
||||||
|
// this may fail if the key length is invalid, but 32 is fine
|
||||||
|
.map_err(|_| Error::InternalError)?
|
||||||
|
.build_const_digest_len()
|
||||||
|
.map_err(|_|
|
||||||
|
// this can only fail if the output length is invalid, but 32 is fine.
|
||||||
|
Error::InternalError)?;
|
||||||
|
|
||||||
|
h.update(data).map_err(|_| Error::DataTooLong)?;
|
||||||
|
h.finalize(out);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyedHashBlake2b for Blake2b {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod equivalence_tests {
|
||||||
|
use super::*;
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzz_equivalence_libcrux_old_new() {
|
||||||
|
let datas: [&[u8]; 3] = [
|
||||||
|
b"".as_slice(),
|
||||||
|
b"test".as_slice(),
|
||||||
|
b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut key = [0; KEY_LEN];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut hash_left = [0; 32];
|
||||||
|
let mut hash_right = [0; 32];
|
||||||
|
|
||||||
|
for data in datas {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
rng.fill_bytes(&mut key);
|
||||||
|
|
||||||
|
crate::subtle::rust_crypto::blake2b::Blake2b::keyed_hash(
|
||||||
|
&key,
|
||||||
|
data,
|
||||||
|
&mut hash_left,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
crate::subtle::libcrux::blake2b::Blake2b::keyed_hash(&key, data, &mut hash_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(hash_left, hash_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
274
ciphers/src/subtle/libcrux/chacha20poly1305_ietf.rs
Normal file
274
ciphers/src/subtle/libcrux/chacha20poly1305_ietf.rs
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
//! Implementation of the [`AeadChaCha20Poly1305`] trait based on the [`libcrux_chacha20poly1305`] crate.
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::AeadChaCha20Poly1305;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Aead, AeadError};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::aead_chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||||
|
|
||||||
|
/// An implementation of the ChaCha20Poly1305 AEAD based on libcrux
|
||||||
|
pub struct ChaCha20Poly1305;
|
||||||
|
|
||||||
|
impl Aead<KEY_LEN, NONCE_LEN, TAG_LEN> for ChaCha20Poly1305 {
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
let (ctxt, tag) = libcrux_chacha20poly1305::encrypt(key, plaintext, ciphertext, ad, nonce)
|
||||||
|
.map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
// return an error of the destination buffer is longer than expected
|
||||||
|
// because the caller wouldn't know where the end is
|
||||||
|
if ctxt.len() + tag.len() != ciphertext.len() {
|
||||||
|
return Err(AeadError::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
let ptxt = libcrux_chacha20poly1305::decrypt(key, plaintext, ciphertext, ad, nonce)
|
||||||
|
.map_err(|_| AeadError::DecryptError)?;
|
||||||
|
|
||||||
|
// return an error of the destination buffer is longer than expected
|
||||||
|
// because the caller wouldn't know where the end is
|
||||||
|
if ptxt.len() != plaintext.len() {
|
||||||
|
return Err(AeadError::DecryptError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadChaCha20Poly1305 for ChaCha20Poly1305 {}
|
||||||
|
|
||||||
|
/// The idea of these tests is to check that the above implemenatation behaves, by and large, the
|
||||||
|
/// same as the one from the old libcrux and the one from RustCrypto. You can consider them janky,
|
||||||
|
/// self-rolled property-based tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
mod equivalence_tests {
|
||||||
|
use super::*;
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proptest_equivalence_libcrux_rustcrypto() {
|
||||||
|
use crate::subtle::rust_crypto::chacha20poly1305_ietf::ChaCha20Poly1305 as RustCryptoChaCha20Poly1305;
|
||||||
|
let ptxts: [&[u8]; 3] = [
|
||||||
|
b"".as_slice(),
|
||||||
|
b"test".as_slice(),
|
||||||
|
b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
];
|
||||||
|
let mut key = [0; KEY_LEN];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut ctxt_left = [0; 64 + TAG_LEN];
|
||||||
|
let mut ctxt_right = [0; 64 + TAG_LEN];
|
||||||
|
|
||||||
|
let mut ptxt_left = [0; 64];
|
||||||
|
let mut ptxt_right = [0; 64];
|
||||||
|
|
||||||
|
let nonce = [0; NONCE_LEN];
|
||||||
|
let ad = b"";
|
||||||
|
|
||||||
|
for ptxt in ptxts {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
rng.fill_bytes(&mut key);
|
||||||
|
let ctxt_left = &mut ctxt_left[..ptxt.len() + TAG_LEN];
|
||||||
|
let ctxt_right = &mut ctxt_right[..ptxt.len() + TAG_LEN];
|
||||||
|
|
||||||
|
let ptxt_left = &mut ptxt_left[..ptxt.len()];
|
||||||
|
let ptxt_right = &mut ptxt_right[..ptxt.len()];
|
||||||
|
|
||||||
|
RustCryptoChaCha20Poly1305
|
||||||
|
.encrypt(ctxt_left, &key, &nonce, ad, ptxt)
|
||||||
|
.unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.encrypt(ctxt_right, &key, &nonce, ad, ptxt)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ctxt_left, ctxt_right);
|
||||||
|
|
||||||
|
RustCryptoChaCha20Poly1305
|
||||||
|
.decrypt(ptxt_left, &key, &nonce, ad, ctxt_left)
|
||||||
|
.unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.decrypt(ptxt_right, &key, &nonce, ad, ctxt_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ptxt_left, ptxt);
|
||||||
|
assert_eq!(ptxt_right, ptxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "experiment_libcrux_chachapoly_test")]
|
||||||
|
fn proptest_equivalence_libcrux_old_new() {
|
||||||
|
let ptxts: [&[u8]; 3] = [
|
||||||
|
b"".as_slice(),
|
||||||
|
b"test".as_slice(),
|
||||||
|
b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
];
|
||||||
|
let mut key = [0; KEY_LEN];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut ctxt_left = [0; 64 + TAG_LEN];
|
||||||
|
let mut ctxt_right = [0; 64 + TAG_LEN];
|
||||||
|
|
||||||
|
let mut ptxt_left = [0; 64];
|
||||||
|
let mut ptxt_right = [0; 64];
|
||||||
|
|
||||||
|
let nonce = [0; NONCE_LEN];
|
||||||
|
let ad = b"";
|
||||||
|
|
||||||
|
for ptxt in ptxts {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
rng.fill_bytes(&mut key);
|
||||||
|
let ctxt_left = &mut ctxt_left[..ptxt.len() + TAG_LEN];
|
||||||
|
let ctxt_right = &mut ctxt_right[..ptxt.len() + TAG_LEN];
|
||||||
|
|
||||||
|
let ptxt_left = &mut ptxt_left[..ptxt.len()];
|
||||||
|
let ptxt_right = &mut ptxt_right[..ptxt.len()];
|
||||||
|
|
||||||
|
encrypt(ctxt_left, &key, &nonce, ad, ptxt).unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.encrypt(ctxt_right, &key, &nonce, ad, ptxt)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ctxt_left, ctxt_right);
|
||||||
|
|
||||||
|
decrypt(ptxt_left, &key, &nonce, ad, ctxt_left).unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.decrypt(ptxt_right, &key, &nonce, ad, ctxt_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ptxt_left, ptxt);
|
||||||
|
assert_eq!(ptxt_right, ptxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The old libcrux functions:
|
||||||
|
|
||||||
|
// The functions below are from the old libcrux backend. I am keeping them around so we can
|
||||||
|
// check if they behave the same.
|
||||||
|
use rosenpass_to::ops::copy_slice;
|
||||||
|
use rosenpass_to::To;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
/// 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],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: &[u8],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
|
||||||
|
use libcrux::aead as C;
|
||||||
|
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||||
|
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||||
|
|
||||||
|
copy_slice(plaintext).to(ciphertext);
|
||||||
|
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
|
||||||
|
copy_slice(crux_tag.as_ref()).to(mac);
|
||||||
|
|
||||||
|
match crux_key {
|
||||||
|
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: &[u8],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
|
||||||
|
use libcrux::aead as C;
|
||||||
|
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||||
|
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||||
|
let crux_tag = C::Tag::from_slice(mac).unwrap();
|
||||||
|
|
||||||
|
copy_slice(ciphertext).to(plaintext);
|
||||||
|
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
|
||||||
|
|
||||||
|
match crux_key {
|
||||||
|
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
ciphers/src/subtle/libcrux/kyber512.rs
Normal file
133
ciphers/src/subtle/libcrux/kyber512.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//! Implementation of the [`KemKyber512`] trait based on the [`libcrux_ml_kem`] crate.
|
||||||
|
|
||||||
|
use libcrux_ml_kem::kyber512;
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::KemKyber512;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Kem, KemError};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::kem_kyber512::{CT_LEN, PK_LEN, SHK_LEN, SK_LEN};
|
||||||
|
|
||||||
|
/// An implementation of the Kyber512 KEM based on libcrux
|
||||||
|
pub struct Kyber512;
|
||||||
|
|
||||||
|
impl Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> for Kyber512 {
|
||||||
|
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), KemError> {
|
||||||
|
let mut randomness = [0u8; libcrux_ml_kem::KEY_GENERATION_SEED_SIZE];
|
||||||
|
rand::thread_rng().fill_bytes(&mut randomness);
|
||||||
|
|
||||||
|
let key_pair = kyber512::generate_key_pair(randomness);
|
||||||
|
|
||||||
|
let new_sk: &[u8; SK_LEN] = key_pair.sk();
|
||||||
|
let new_pk: &[u8; PK_LEN] = key_pair.pk();
|
||||||
|
|
||||||
|
sk.clone_from_slice(new_sk);
|
||||||
|
pk.clone_from_slice(new_pk);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
ct: &mut [u8; CT_LEN],
|
||||||
|
pk: &[u8; PK_LEN],
|
||||||
|
) -> Result<(), KemError> {
|
||||||
|
let mut randomness = [0u8; libcrux_ml_kem::SHARED_SECRET_SIZE];
|
||||||
|
rand::thread_rng().fill_bytes(&mut randomness);
|
||||||
|
|
||||||
|
let (new_ct, new_shk) = kyber512::encapsulate(&pk.into(), randomness);
|
||||||
|
let new_ct: &[u8; CT_LEN] = new_ct.as_slice();
|
||||||
|
|
||||||
|
shk.clone_from_slice(&new_shk);
|
||||||
|
ct.clone_from_slice(new_ct);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
sk: &[u8; SK_LEN],
|
||||||
|
ct: &[u8; CT_LEN],
|
||||||
|
) -> Result<(), KemError> {
|
||||||
|
let new_shk: [u8; SHK_LEN] = kyber512::decapsulate(&sk.into(), &ct.into());
|
||||||
|
shk.clone_from(&new_shk);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Kyber512 {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KemKyber512 for Kyber512 {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod equivalence_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Test that libcrux and OQS produce the same results
|
||||||
|
#[test]
|
||||||
|
fn proptest_equivalence_libcrux_oqs() {
|
||||||
|
use rosenpass_oqs::Kyber512 as OqsKyber512;
|
||||||
|
|
||||||
|
let (mut sk1, mut pk1) = ([0; SK_LEN], [0; PK_LEN]);
|
||||||
|
let (mut sk2, mut pk2) = ([0; SK_LEN], [0; PK_LEN]);
|
||||||
|
|
||||||
|
let mut ct_left = [0; CT_LEN];
|
||||||
|
let mut ct_right = [0; CT_LEN];
|
||||||
|
|
||||||
|
let mut shk_enc_left = [0; SHK_LEN];
|
||||||
|
let mut shk_enc_right = [0; SHK_LEN];
|
||||||
|
|
||||||
|
// naming schema: shk_dec_{encapsing lib}_{decapsing lib}
|
||||||
|
// should be the same if the encapsing lib was the same.
|
||||||
|
let mut shk_dec_left_left = [0; SHK_LEN];
|
||||||
|
let mut shk_dec_left_right = [0; SHK_LEN];
|
||||||
|
let mut shk_dec_right_left = [0; SHK_LEN];
|
||||||
|
let mut shk_dec_right_right = [0; SHK_LEN];
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let sk1 = &mut sk1;
|
||||||
|
let pk1 = &mut pk1;
|
||||||
|
let sk2 = &mut sk2;
|
||||||
|
let pk2 = &mut pk2;
|
||||||
|
|
||||||
|
let ct_left = &mut ct_left;
|
||||||
|
let ct_right = &mut ct_right;
|
||||||
|
|
||||||
|
let shk_enc_left = &mut shk_enc_left;
|
||||||
|
let shk_enc_right = &mut shk_enc_right;
|
||||||
|
|
||||||
|
let shk_dec_left_left = &mut shk_dec_left_left;
|
||||||
|
let shk_dec_left_right = &mut shk_dec_left_right;
|
||||||
|
let shk_dec_right_left = &mut shk_dec_right_left;
|
||||||
|
let shk_dec_right_right = &mut shk_dec_right_right;
|
||||||
|
|
||||||
|
Kyber512.keygen(sk1, pk1).unwrap();
|
||||||
|
Kyber512.keygen(sk2, pk2).unwrap();
|
||||||
|
|
||||||
|
Kyber512.encaps(shk_enc_left, ct_left, pk2).unwrap();
|
||||||
|
OqsKyber512.encaps(shk_enc_right, ct_right, pk2).unwrap();
|
||||||
|
|
||||||
|
Kyber512.decaps(shk_dec_left_left, sk2, ct_left).unwrap();
|
||||||
|
Kyber512.decaps(shk_dec_right_left, sk2, ct_right).unwrap();
|
||||||
|
|
||||||
|
OqsKyber512
|
||||||
|
.decaps(shk_dec_left_right, sk2, ct_left)
|
||||||
|
.unwrap();
|
||||||
|
OqsKyber512
|
||||||
|
.decaps(shk_dec_right_right, sk2, ct_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(shk_enc_left, shk_dec_left_left);
|
||||||
|
assert_eq!(shk_enc_left, shk_dec_left_right);
|
||||||
|
|
||||||
|
assert_eq!(shk_enc_right, shk_dec_right_left);
|
||||||
|
assert_eq!(shk_enc_right, shk_dec_right_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
ciphers/src/subtle/libcrux/mod.rs
Normal file
14
ciphers/src/subtle/libcrux/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//! Implementations backed by libcrux, a verified crypto library.
|
||||||
|
//!
|
||||||
|
//! [Website](https://cryspen.com/libcrux/)
|
||||||
|
//!
|
||||||
|
//! [Github](https://github.com/cryspen/libcrux)
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_blake2")]
|
||||||
|
pub mod blake2b;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_chachapoly")]
|
||||||
|
pub mod chacha20poly1305_ietf;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_kyber")]
|
||||||
|
pub mod kyber512;
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
pub mod blake2b;
|
//! Contains the implementations of the crypto algorithms used throughout Rosenpass.
|
||||||
#[cfg(not(feature = "experiment_libcrux"))]
|
|
||||||
pub mod chacha20poly1305_ietf;
|
pub mod keyed_hash;
|
||||||
#[cfg(feature = "experiment_libcrux")]
|
|
||||||
pub mod chacha20poly1305_ietf_libcrux;
|
pub use custom::incorrect_hmac_blake2b;
|
||||||
pub mod incorrect_hmac_blake2b;
|
pub use rust_crypto::{blake2b, keyed_shake256};
|
||||||
pub mod xchacha20poly1305_ietf;
|
|
||||||
|
pub mod custom;
|
||||||
|
pub mod rust_crypto;
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "experiment_libcrux_blake2",
|
||||||
|
feature = "experiment_libcrux_chachapoly",
|
||||||
|
feature = "experiment_libcrux_kyber"
|
||||||
|
))]
|
||||||
|
pub mod libcrux;
|
||||||
|
|||||||
44
ciphers/src/subtle/rust_crypto/blake2b.rs
Normal file
44
ciphers/src/subtle/rust_crypto/blake2b.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
use blake2::digest::crypto_common::generic_array::GenericArray;
|
||||||
|
use blake2::digest::crypto_common::typenum::U32;
|
||||||
|
use blake2::digest::{FixedOutput, Mac};
|
||||||
|
use blake2::Blake2bMac;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHash;
|
||||||
|
use rosenpass_to::{ops::copy_slice, To};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_blake2b::{HASH_LEN, KEY_LEN};
|
||||||
|
|
||||||
|
/// 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>;
|
||||||
|
|
||||||
|
/// 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).
|
||||||
|
pub struct Blake2b;
|
||||||
|
|
||||||
|
impl KeyedHash<KEY_LEN, HASH_LEN> for Blake2b {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut h = Impl::new_from_slice(key)?;
|
||||||
|
h.update(data);
|
||||||
|
|
||||||
|
// Jesus christ, blake2 crate, your usage of GenericArray might be nice and fancy,
|
||||||
|
// but it introduces a ton of complexity. This cost me half an hour just to figure
|
||||||
|
// out the right way to use the imports while allowing for zeroization.
|
||||||
|
// An API based on slices might actually be simpler.
|
||||||
|
let mut tmp = Zeroizing::new([0u8; HASH_LEN]);
|
||||||
|
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
||||||
|
h.finalize_into(tmp);
|
||||||
|
copy_slice(tmp.as_ref()).to(out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rosenpass_cipher_traits::algorithms::KeyedHashBlake2b for Blake2b {}
|
||||||
79
ciphers/src/subtle/rust_crypto/chacha20poly1305_ietf.rs
Normal file
79
ciphers/src/subtle/rust_crypto/chacha20poly1305_ietf.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use rosenpass_to::ops::copy_slice;
|
||||||
|
use rosenpass_to::To;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::AeadChaCha20Poly1305;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Aead, AeadError};
|
||||||
|
|
||||||
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
|
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
|
||||||
|
use chacha20poly1305::{AeadInPlace, KeyInit};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::aead_chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||||
|
|
||||||
|
/// Implements the [`Aead`] and [`AeadChaCha20Poly1305`] traits backed by the RustCrypto
|
||||||
|
/// implementation.
|
||||||
|
pub struct ChaCha20Poly1305;
|
||||||
|
|
||||||
|
impl Aead<KEY_LEN, NONCE_LEN, TAG_LEN> for ChaCha20Poly1305 {
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
copy_slice(plaintext).to(ct);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let encrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
let mac_value = encrypter
|
||||||
|
.encrypt_in_place_detached(nonce, ad, ct)
|
||||||
|
.map_err(|_| AeadError::InternalError)?;
|
||||||
|
copy_slice(&mac_value[..]).to(mac);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
let tag = GenericArray::from_slice(mac);
|
||||||
|
copy_slice(ct).to(plaintext);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let decrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
decrypter
|
||||||
|
.decrypt_in_place_detached(nonce, ad, plaintext, tag)
|
||||||
|
.map_err(|_| AeadError::DecryptError)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadChaCha20Poly1305 for ChaCha20Poly1305 {}
|
||||||
117
ciphers/src/subtle/rust_crypto/keyed_shake256.rs
Normal file
117
ciphers/src/subtle/rust_crypto/keyed_shake256.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use anyhow::ensure;
|
||||||
|
use rosenpass_cipher_traits::primitives::{InferKeyedHash, KeyedHash};
|
||||||
|
use sha3::digest::{ExtendableOutput, Update, XofReader};
|
||||||
|
use sha3::Shake256;
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_shake256::{HASH_LEN, KEY_LEN};
|
||||||
|
|
||||||
|
/// An implementation of the [`KeyedHash`] trait backed by the RustCrypto implementation of SHAKE256.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SHAKE256Core<const KEY_LEN: usize, const HASH_LEN: usize>;
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize> KeyedHash<KEY_LEN, HASH_LEN>
|
||||||
|
for SHAKE256Core<KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
/// Provides a keyed hash function based on SHAKE256. To work for the protocol, the output length
|
||||||
|
/// and key length are fixed to 32 bytes (also see [KEY_LEN] and [HASH_LEN]).
|
||||||
|
///
|
||||||
|
/// Note that the SHAKE256 is designed for 64 bytes output length, which we truncate to 32 bytes
|
||||||
|
/// to work well with the overall protocol. Referring to Table 4 of FIPS 202, this offers the
|
||||||
|
/// same collision resistance as SHAKE128, but 256 bits of preimage resistance. We therefore
|
||||||
|
/// prefer a truncated SHAKE256 over SHAKE128.
|
||||||
|
///
|
||||||
|
/// #Examples
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::rust_crypto::keyed_shake256::SHAKE256Core;
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHash;
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const HASH_LEN: usize = 32;
|
||||||
|
/// let key: [u8; 32] = [0; KEY_LEN];
|
||||||
|
/// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; HASH_LEN];
|
||||||
|
///
|
||||||
|
/// assert!(SHAKE256Core::<32, 32>::keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[174, 4, 47, 188, 1, 228, 179, 246, 67, 43, 255, 94, 155, 11,
|
||||||
|
/// 187, 161, 38, 110, 217, 23, 4, 62, 172, 30, 218, 187, 249, 80, 171, 21, 145, 238];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
/// ```
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
// Since SHAKE256 is a XOF, we fix the output length manually to what is required for the
|
||||||
|
// protocol.
|
||||||
|
ensure!(out.len() == HASH_LEN);
|
||||||
|
// Not bothering with padding; the implementation
|
||||||
|
// uses appropriately sized keys.
|
||||||
|
ensure!(key.len() == KEY_LEN);
|
||||||
|
let mut shake256 = Shake256::default();
|
||||||
|
shake256.update(key);
|
||||||
|
shake256.update(data);
|
||||||
|
|
||||||
|
// Since we use domain separation extensively, related outputs of the truncated XOF
|
||||||
|
// are not a concern. This follows the NIST recommendations in Section A.2 of the FIPS 202
|
||||||
|
// standard, (pages 24/25, i.e., 32/33 in the PDF).
|
||||||
|
shake256.finalize_xof().read(out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize> SHAKE256Core<KEY_LEN, HASH_LEN> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize> Default for SHAKE256Core<KEY_LEN, HASH_LEN> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This type provides the same functionality as [SHAKE256Core], but bound to an instance.
|
||||||
|
/// In contrast to [SHAKE256Core], this allows for type interference and thus allows the user of the
|
||||||
|
/// type to omit explicit type parameters when instantiating the type or using it.
|
||||||
|
///
|
||||||
|
/// The instantiation is based on the [InferKeyedHash] trait.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::rust_crypto::keyed_shake256::{SHAKE256};
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHashInstance;
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const HASH_LEN: usize = 32;
|
||||||
|
/// let key: [u8; KEY_LEN] = [0; KEY_LEN];
|
||||||
|
/// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; HASH_LEN];
|
||||||
|
/// assert!(SHAKE256::new().keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[174, 4, 47, 188, 1, 228, 179, 246, 67, 43, 255, 94, 155, 11, 187,
|
||||||
|
/// 161, 38, 110, 217, 23, 4, 62, 172, 30, 218, 187, 249, 80, 171, 21, 145, 238];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
/// ```
|
||||||
|
pub type SHAKE256<const KEY_LEN: usize, const HASH_LEN: usize> =
|
||||||
|
InferKeyedHash<SHAKE256Core<KEY_LEN, HASH_LEN>, KEY_LEN, HASH_LEN>;
|
||||||
|
|
||||||
|
/// The SHAKE256_32 type is a specific instance of the [SHAKE256] type with the key length and hash
|
||||||
|
/// length fixed to 32 bytes.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::keyed_shake256::{SHAKE256_32};
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHashInstance;
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const HASH_LEN: usize = 32;
|
||||||
|
/// let key: [u8; 32] = [0; KEY_LEN];
|
||||||
|
/// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; HASH_LEN];
|
||||||
|
///
|
||||||
|
/// assert!(SHAKE256_32::new().keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[174, 4, 47, 188, 1, 228, 179, 246, 67, 43, 255, 94, 155, 11, 187,
|
||||||
|
/// 161, 38, 110, 217, 23, 4, 62, 172, 30, 218, 187, 249, 80, 171, 21, 145, 238];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
/// ```
|
||||||
|
pub type SHAKE256_32 = SHAKE256<32, 32>;
|
||||||
7
ciphers/src/subtle/rust_crypto/mod.rs
Normal file
7
ciphers/src/subtle/rust_crypto/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! Implementations backed by RustCrypto
|
||||||
|
|
||||||
|
pub mod blake2b;
|
||||||
|
pub mod keyed_shake256;
|
||||||
|
|
||||||
|
pub mod chacha20poly1305_ietf;
|
||||||
|
pub mod xchacha20poly1305_ietf;
|
||||||
164
ciphers/src/subtle/rust_crypto/xchacha20poly1305_ietf.rs
Normal file
164
ciphers/src/subtle/rust_crypto/xchacha20poly1305_ietf.rs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
use rosenpass_to::ops::copy_slice;
|
||||||
|
use rosenpass_to::To;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::aead_xchacha20poly1305::AeadXChaCha20Poly1305;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Aead, AeadError, AeadWithNonceInCiphertext};
|
||||||
|
|
||||||
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
|
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
|
||||||
|
use chacha20poly1305::{AeadInPlace, KeyInit};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::aead_xchacha20poly1305::{
|
||||||
|
KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||||
|
};
|
||||||
|
/// Implements the [`Aead`] and [`AeadXChaCha20Poly1305`] traits backed by the RustCrypto
|
||||||
|
/// implementation.
|
||||||
|
pub struct XChaCha20Poly1305;
|
||||||
|
|
||||||
|
impl Aead<KEY_LEN, NONCE_LEN, TAG_LEN> for XChaCha20Poly1305 {
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
copy_slice(plaintext).to(ct);
|
||||||
|
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let encrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
let mac_value = encrypter
|
||||||
|
.encrypt_in_place_detached(nonce, ad, ct)
|
||||||
|
.map_err(|_| AeadError::InternalError)?;
|
||||||
|
copy_slice(&mac_value[..]).to(mac);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
let tag = GenericArray::from_slice(mac);
|
||||||
|
copy_slice(ct).to(plaintext);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let decrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
decrypter
|
||||||
|
.decrypt_in_place_detached(nonce, ad, plaintext, tag)
|
||||||
|
.map_err(|_| AeadError::DecryptError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadXChaCha20Poly1305 for XChaCha20Poly1305 {}
|
||||||
|
|
||||||
|
/// 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::rust_crypto::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; KEY_LEN] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||||
|
/// let nonce: &[u8; NONCE_LEN] = &[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],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
XChaCha20Poly1305
|
||||||
|
.encrypt_with_nonce_in_ctxt(ciphertext, key, nonce, ad, plaintext)
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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::rust_crypto::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; KEY_LEN] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||||
|
/// let nonce: &[u8; NONCE_LEN] = &[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],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
XChaCha20Poly1305
|
||||||
|
.decrypt_with_nonce_in_ctxt(plaintext, key, ad, ciphertext)
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
use rosenpass_to::ops::copy_slice;
|
|
||||||
use rosenpass_to::To;
|
|
||||||
use rosenpass_util::typenum2const;
|
|
||||||
|
|
||||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
|
||||||
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
|
|
||||||
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
|
|
||||||
|
|
||||||
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
|
|
||||||
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
|
|
||||||
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn encrypt(
|
|
||||||
ciphertext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
plaintext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let nonce = GenericArray::from_slice(nonce);
|
|
||||||
let (n, ct_mac) = ciphertext.split_at_mut(NONCE_LEN);
|
|
||||||
let (ct, mac) = ct_mac.split_at_mut(ct_mac.len() - TAG_LEN);
|
|
||||||
copy_slice(nonce).to(n);
|
|
||||||
copy_slice(plaintext).to(ct);
|
|
||||||
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
|
|
||||||
copy_slice(&mac_value[..]).to(mac);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn decrypt(
|
|
||||||
plaintext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
ciphertext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (n, ct_mac) = ciphertext.split_at(NONCE_LEN);
|
|
||||||
let (ct, mac) = ct_mac.split_at(ct_mac.len() - TAG_LEN);
|
|
||||||
let nonce = GenericArray::from_slice(n);
|
|
||||||
let tag = GenericArray::from_slice(mac);
|
|
||||||
copy_slice(ct).to(plaintext);
|
|
||||||
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ description = "Rosenpass internal utilities for constant time crypto implementat
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -19,4 +20,7 @@ rosenpass-to = { workspace = true }
|
|||||||
memsec = { workspace = true }
|
memsec = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8.5"
|
rand = { workspace = true }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||||
|
|||||||
@@ -2,14 +2,29 @@
|
|||||||
|
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
|
||||||
/// Little endian memcmp version of quinier/memsec
|
/// Little endian memcmp version of [quinier/memsec](https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30)
|
||||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
|
||||||
///
|
///
|
||||||
/// # Panic & Safety
|
/// # Panic & Safety
|
||||||
///
|
///
|
||||||
/// Both input arrays must be at least of the indicated length.
|
/// Both input arrays must be at least of the indicated length.
|
||||||
///
|
///
|
||||||
/// See [std::ptr::read_volatile] on safety.
|
/// 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)]
|
#[inline(never)]
|
||||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||||
let mut res = 0;
|
let mut res = 0;
|
||||||
@@ -77,3 +92,23 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
|||||||
assert!(a.len() == b.len());
|
assert!(a.len() == b.len());
|
||||||
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,16 @@ use core::hint::black_box;
|
|||||||
/// and increment that integer.
|
/// and increment that integer.
|
||||||
///
|
///
|
||||||
/// # Leaks
|
/// # Leaks
|
||||||
/// TODO: mention here if this function leaks any information, see
|
/// This function may leak timing information in the following ways:
|
||||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
///
|
||||||
|
/// - 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
|
/// ## Tests
|
||||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||||
|
|||||||
@@ -7,6 +7,32 @@
|
|||||||
//! ## TODO
|
//! ## TODO
|
||||||
//! Figure out methodology to ensure that code is actually constant time, see
|
//! Figure out methodology to ensure that code is actually constant time, see
|
||||||
//! <https://github.com/rosenpass/rosenpass/issues/232>
|
//! <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 compare;
|
||||||
mod increment;
|
mod increment;
|
||||||
@@ -14,6 +40,7 @@ mod memcmp;
|
|||||||
mod xor;
|
mod xor;
|
||||||
|
|
||||||
pub use compare::compare;
|
pub use compare::compare;
|
||||||
|
pub use compare::memcmp_le;
|
||||||
pub use increment::increment;
|
pub use increment::increment;
|
||||||
pub use memcmp::memcmp;
|
pub use memcmp::memcmp;
|
||||||
pub use xor::xor;
|
pub use xor::xor;
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
|||||||
/// For discussion on how to (further) ensure the constant-time execution of this function,
|
/// For discussion on how to (further) ensure the constant-time execution of this function,
|
||||||
/// see <https://github.com/rosenpass/rosenpass/issues/232>
|
/// see <https://github.com/rosenpass/rosenpass/issues/232>
|
||||||
#[cfg(all(test, feature = "constant_time_tests"))]
|
#[cfg(all(test, feature = "constant_time_tests"))]
|
||||||
|
// Stopgap measure against https://github.com/rosenpass/rosenpass/issues/634
|
||||||
|
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use core::hint::black_box;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -50,14 +53,12 @@ mod tests {
|
|||||||
fn memcmp_runs_in_constant_time() {
|
fn memcmp_runs_in_constant_time() {
|
||||||
// prepare data to compare
|
// prepare data to compare
|
||||||
let n: usize = 1E6 as usize; // number of comparisons to run
|
let n: usize = 1E6 as usize; // number of comparisons to run
|
||||||
let len = 1024; // length of each slice passed as parameters to the tested comparison function
|
const LEN: usize = 1024; // length of each slice passed as parameters to the tested comparison function
|
||||||
let a1 = "a".repeat(len);
|
|
||||||
let a2 = a1.clone();
|
|
||||||
let b = "b".repeat(len);
|
|
||||||
|
|
||||||
let a1 = a1.as_bytes();
|
let a = [b'a'; LEN];
|
||||||
let a2 = a2.as_bytes();
|
let b = [b'b'; LEN];
|
||||||
let b = b.as_bytes();
|
|
||||||
|
let mut tmp = [0u8; LEN];
|
||||||
|
|
||||||
// vector representing all timing tests
|
// vector representing all timing tests
|
||||||
//
|
//
|
||||||
@@ -71,12 +72,14 @@ mod tests {
|
|||||||
|
|
||||||
// run comparisons / call function to test
|
// run comparisons / call function to test
|
||||||
for test in tests.iter_mut() {
|
for test in tests.iter_mut() {
|
||||||
|
let src = match test.0 {
|
||||||
|
true => a,
|
||||||
|
false => b,
|
||||||
|
};
|
||||||
|
tmp.copy_from_slice(&src);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if test.0 {
|
memcmp(black_box(&a), black_box(&tmp));
|
||||||
memcmp(a1, a2);
|
|
||||||
} else {
|
|
||||||
memcmp(a1, b);
|
|
||||||
}
|
|
||||||
test.1 = now.elapsed();
|
test.1 = now.elapsed();
|
||||||
// println!("eq: {}, elapsed: {:.2?}", test.0, test.1);
|
// println!("eq: {}, elapsed: {:.2?}", test.0, test.1);
|
||||||
}
|
}
|
||||||
@@ -113,6 +116,7 @@ mod tests {
|
|||||||
// Pearson correlation
|
// Pearson correlation
|
||||||
let correlation = cv / (sd_x * sd_y);
|
let correlation = cv / (sd_x * sd_y);
|
||||||
println!("correlation: {:.6?}", correlation);
|
println!("correlation: {:.6?}", correlation);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
correlation.abs() < 0.01,
|
correlation.abs() < 0.01,
|
||||||
"execution time correlates with result"
|
"execution time correlates with result"
|
||||||
|
|||||||
@@ -5,12 +5,23 @@ use rosenpass_to::{with_destination, To};
|
|||||||
|
|
||||||
/// Xors the source into the destination
|
/// 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
|
/// # Panics
|
||||||
/// If source and destination are of different sizes.
|
/// If source and destination are of different sizes.
|
||||||
///
|
///
|
||||||
/// # Leaks
|
/// # Leaks
|
||||||
/// TODO: mention here if this function leaks any information, see
|
/// This function may leak timing information in the following ways:
|
||||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
///
|
||||||
|
/// - 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
|
/// ## Tests
|
||||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||||
|
|||||||
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 "$@"
|
||||||
129
deny.toml
Normal file
129
deny.toml
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# The graph table configures how the dependency graph is constructed and thus
|
||||||
|
# which crates the checks are performed against
|
||||||
|
[graph]
|
||||||
|
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||||
|
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||||
|
# is recommended to pass `--all-features` on the cmd line instead
|
||||||
|
all-features = true
|
||||||
|
# If true, metadata will be collected with `--no-default-features`. The same
|
||||||
|
# caveat with `all-features` applies
|
||||||
|
no-default-features = false
|
||||||
|
|
||||||
|
# The output table provides options for how/if diagnostics are outputted
|
||||||
|
[output]
|
||||||
|
# When outputting inclusion graphs in diagnostics that include features, this
|
||||||
|
# option can be used to specify the depth at which feature edges will be added.
|
||||||
|
# This option is included since the graphs can be quite large and the addition
|
||||||
|
# of features from the crate(s) to all of the graph roots can be far too verbose.
|
||||||
|
# This option can be overridden via `--feature-depth` on the cmd line
|
||||||
|
feature-depth = 1
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check advisories`
|
||||||
|
# More documentation for the advisories section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||||
|
[advisories]
|
||||||
|
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||||
|
# output a note when they are encountered.
|
||||||
|
ignore = [
|
||||||
|
"RUSTSEC-2024-0370",
|
||||||
|
"RUSTSEC-2024-0436",
|
||||||
|
"RUSTSEC-2023-0089",
|
||||||
|
]
|
||||||
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
|
# If this is false, then it uses a built-in git library.
|
||||||
|
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||||
|
# See Git Authentication for more information about setting up git authentication.
|
||||||
|
#git-fetch-with-cli = true
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check #licenses`
|
||||||
|
# More documentation for the licenses section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||||
|
[licenses]
|
||||||
|
# List of explicitly allowed licenses
|
||||||
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
|
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||||
|
allow = [
|
||||||
|
"MIT",
|
||||||
|
"Apache-2.0",
|
||||||
|
"Apache-2.0 WITH LLVM-exception",
|
||||||
|
"BSD-3-Clause",
|
||||||
|
"ISC",
|
||||||
|
]
|
||||||
|
# The confidence threshold for detecting a license from license text.
|
||||||
|
# The higher the value, the more closely the license text must be to the
|
||||||
|
# canonical license text of a valid SPDX license file.
|
||||||
|
# [possible values: any between 0.0 and 1.0].
|
||||||
|
confidence-threshold = 0.8
|
||||||
|
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||||
|
# aren't accepted for every possible crate as with the normal allow list
|
||||||
|
exceptions = [
|
||||||
|
# Each entry is the crate and version constraint, and its specific allow
|
||||||
|
# list
|
||||||
|
{ allow = ["Unicode-DFS-2016", "Unicode-3.0"], crate = "unicode-ident" },
|
||||||
|
{ allow = ["NCSA"], crate = "libfuzzer-sys" },
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
[licenses.private]
|
||||||
|
# If true, ignores workspace crates that aren't published, or are only
|
||||||
|
# published to private registries.
|
||||||
|
# To see how to mark a crate as unpublished (to the official registry),
|
||||||
|
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||||
|
ignore = true
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check bans`.
|
||||||
|
# More documentation about the 'bans' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||||
|
[bans]
|
||||||
|
# Lint level for when multiple versions of the same crate are detected
|
||||||
|
multiple-versions = "warn"
|
||||||
|
# Lint level for when a crate version requirement is `*`
|
||||||
|
wildcards = "allow"
|
||||||
|
# The graph highlighting used when creating dotgraphs for crates
|
||||||
|
# with multiple versions
|
||||||
|
# * lowest-version - The path to the lowest versioned duplicate is highlighted
|
||||||
|
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||||
|
# * all - Both lowest-version and simplest-path are used
|
||||||
|
highlight = "all"
|
||||||
|
# The default lint level for `default` features for crates that are members of
|
||||||
|
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||||
|
# `default` on a crate-by-crate basis if desired.
|
||||||
|
workspace-default-features = "allow"
|
||||||
|
# The default lint level for `default` features for external crates that are not
|
||||||
|
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||||
|
# on a crate-by-crate basis if desired.
|
||||||
|
external-default-features = "allow"
|
||||||
|
# List of crates that are allowed. Use with care!
|
||||||
|
allow = [
|
||||||
|
]
|
||||||
|
# List of crates to deny
|
||||||
|
deny = [
|
||||||
|
]
|
||||||
|
|
||||||
|
skip-tree = [
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check sources`.
|
||||||
|
# More documentation about the 'sources' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||||
|
[sources]
|
||||||
|
# Lint level for what to happen when a crate from a crate registry that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-registry = "warn"
|
||||||
|
# Lint level for what to happen when a crate from a git repository that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-git = "warn"
|
||||||
|
# List of URLs for allowed crate registries. Defaults to the crates.io index
|
||||||
|
# if not specified. If it is specified but empty, no registries are allowed.
|
||||||
|
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||||
|
# List of URLs for allowed Git repositories
|
||||||
|
allow-git = ["git+https://github.com/rosenpass/memsec.git?branch=master"]
|
||||||
|
|
||||||
|
[sources.allow-org]
|
||||||
|
# github.com organizations to allow git sources for
|
||||||
|
github = []
|
||||||
|
# gitlab.com organizations to allow git sources for
|
||||||
|
gitlab = []
|
||||||
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
bitbucket = []
|
||||||
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 .
|
|
||||||
45
docker/Dockerfile
Normal file
45
docker/Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
ARG BASE_IMAGE=debian:bookworm-slim
|
||||||
|
|
||||||
|
# Stage 1: Base image with cargo-chef installed
|
||||||
|
FROM rust:latest AS chef
|
||||||
|
RUN cargo install cargo-chef
|
||||||
|
# install software required for liboqs-rust
|
||||||
|
RUN apt-get update && apt-get install -y clang cmake && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Stage 2: Prepare the cargo-chef recipe
|
||||||
|
FROM chef AS planner
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
|
# Stage 3: Cache dependencies using the recipe
|
||||||
|
FROM chef AS cacher
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=planner /app/recipe.json recipe.json
|
||||||
|
RUN cargo chef cook --release --recipe-path recipe.json
|
||||||
|
|
||||||
|
# Stage 4: Build the application
|
||||||
|
FROM cacher AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# Stage 5: Install runtime-dependencies in the base image
|
||||||
|
FROM ${BASE_IMAGE} AS base_image_with_dependencies
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y iproute2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Final Stage (rosenpass): Copy the rosenpass binary
|
||||||
|
FROM base_image_with_dependencies AS rosenpass
|
||||||
|
COPY --from=builder /app/target/release/rosenpass /usr/local/bin/rosenpass
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/rosenpass" ]
|
||||||
|
|
||||||
|
# Final Stage (rp): Copy the rp binary
|
||||||
|
FROM base_image_with_dependencies AS rp
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y wireguard && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /app/target/release/rp /usr/local/bin/rp
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/rp" ]
|
||||||
203
docker/USAGE.md
Normal file
203
docker/USAGE.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# Rosenpass in Docker
|
||||||
|
|
||||||
|
Rosenpass provides post-quantum-secure key exchange for VPNs. It generates symmetric keys used by [WireGuard](https://www.wireguard.com/papers/wireguard.pdf) or other applications. The protocol enhances "Post-Quantum WireGuard" ([PQWG](https://eprint.iacr.org/2020/379)) with a cookie mechanism for better security against state disruption attacks.
|
||||||
|
|
||||||
|
Prebuilt Docker images are available for easy deployment:
|
||||||
|
|
||||||
|
- [`ghcr.io/rosenpass/rosenpass`](https://github.com/rosenpass/rosenpass/pkgs/container/rosenpass) – the core key exchange tool
|
||||||
|
- [`ghcr.io/rosenpass/rp`](https://github.com/rosenpass/rosenpass/pkgs/container/rp) – a frontend for setting up WireGuard VPNs
|
||||||
|
|
||||||
|
The entrypoint of the `rosenpass` image is the `rosenpass` executable, whose documentation can be found [here](https://rosenpass.eu/docs/rosenpass-tool/manuals/rp_manual/).
|
||||||
|
Similarly, the entrypoint of the `rp` image is the `rp` executable, with its documentation available [here](https://rosenpass.eu/docs/rosenpass-tool/manuals/rp1/).
|
||||||
|
|
||||||
|
## Usage - Standalone Key Exchange
|
||||||
|
|
||||||
|
The `ghcr.io/rosenpass/rosenpass` image can be used in a server-client setup to exchange quantum-secure shared keys.
|
||||||
|
This setup uses rosenpass as a standalone application, without using any other component such as wireguard.
|
||||||
|
What follows, is a simple setup for illustrative purposes.
|
||||||
|
|
||||||
|
Create a docker network that is used to connect the containers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create -d bridge rp
|
||||||
|
export NET=rp
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the server and client key pairs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir ./workdir-client ./workdir-server
|
||||||
|
docker run -it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
gen-keys --public-key=workdir/server-public --secret-key=workdir/server-secret
|
||||||
|
docker run -it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
gen-keys --public-key=workdir/client-public --secret-key=workdir/client-secret
|
||||||
|
# share the public keys between client and server
|
||||||
|
cp workdir-client/client-public workdir-server/client-public
|
||||||
|
cp workdir-server/server-public workdir-client/server-public
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name "rpserver" --network ${NET} \
|
||||||
|
-it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
exchange \
|
||||||
|
private-key workdir/server-secret \
|
||||||
|
public-key workdir/server-public \
|
||||||
|
listen 0.0.0.0:9999 \
|
||||||
|
peer public-key workdir/client-public \
|
||||||
|
outfile workdir/server-sharedkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Find out the ip address of the server container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EP="rpserver"
|
||||||
|
EP=$(docker inspect --format '{{ .NetworkSettings.Networks.rp.IPAddress }}' $EP)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the client container and perform the key exchange:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name "rpclient" --network ${NET} \
|
||||||
|
-it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
exchange \
|
||||||
|
private-key workdir/client-secret \
|
||||||
|
public-key workdir/client-public \
|
||||||
|
peer public-key workdir/server-public endpoint ${EP}:9999 \
|
||||||
|
outfile workdir/client-sharedkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the containers will exchange shared keys and each put them into their respective outfile.
|
||||||
|
|
||||||
|
Comparing the outfiles shows that these shared keys equal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmp workdir-server/server-sharedkey workdir-client/client-sharedkey
|
||||||
|
```
|
||||||
|
|
||||||
|
It is now possible to set add these keys as pre-shared keys within a wireguard interface.
|
||||||
|
For example as the server,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PREKEY=$(cat workdir-server/server-sharedkey)
|
||||||
|
wg set <server-interface> peer <client-peer-public-key> preshared-key <(echo "$PREKEY")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage - Combined with wireguard
|
||||||
|
|
||||||
|
The `ghcr.io/rosenpass/rp` image can be used to build a VPN with WireGuard and Rosenpass.
|
||||||
|
In this example, we run two containers on the same system and connect them with a bridge network within the docker overlay network.
|
||||||
|
|
||||||
|
Create the named docker network, to be able to connect the containers.
|
||||||
|
|
||||||
|
Create a docker network that is used to connect the containers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create -d bridge rp
|
||||||
|
export NET=rp
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the server and client secret keys and extract public keys.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ./workdir-server ./workdir-client
|
||||||
|
|
||||||
|
# server
|
||||||
|
docker run -it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
genkey workdir/server.rosenpass-secret
|
||||||
|
docker run -it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
pubkey workdir/server.rosenpass-secret workdir/server.rosenpass-public
|
||||||
|
|
||||||
|
# client
|
||||||
|
docker run -it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
genkey workdir/client.rosenpass-secret
|
||||||
|
docker run -it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
pubkey workdir/client.rosenpass-secret workdir/client.rosenpass-public
|
||||||
|
|
||||||
|
# share the public keys between client and server
|
||||||
|
cp -r workdir-client/client.rosenpass-public workdir-server/client.rosenpass-public
|
||||||
|
cp -r workdir-server/server.rosenpass-public workdir-client/server.rosenpass-public
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server container.
|
||||||
|
Note that the `NET_ADMIN` capability is neccessary, the rp command will create and manage wireguard interfaces.
|
||||||
|
Also make sure the `wireguard` kernel module is loaded by the host. (`lsmod | grep wireguard`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name "rpserver" --network ${NET} -it -d --rm -v ./workdir-server:/workdir \
|
||||||
|
--cap-add=NET_ADMIN \
|
||||||
|
ghcr.io/rosenpass/rp \
|
||||||
|
exchange workdir/server.rosenpass-secret dev rosenpass0 \
|
||||||
|
listen 0.0.0.0:9999 peer workdir/client.rosenpass-public allowed-ips 10.0.0.0/8
|
||||||
|
```
|
||||||
|
|
||||||
|
Now find out the ip-address of the server container and then start the client container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EP="rpserver"
|
||||||
|
EP=$(docker inspect --format '{{ .NetworkSettings.Networks.rp.IPAddress }}' $EP)
|
||||||
|
docker run --name "rpclient" --network ${NET} -it -d --rm -v ./workdir-client:/workdir \
|
||||||
|
--cap-add=NET_ADMIN \
|
||||||
|
ghcr.io/rosenpass/rp \
|
||||||
|
exchange workdir/client.rosenpass-secret dev rosenpass1 \
|
||||||
|
peer workdir/server.rosenpass-public endpoint ${EP}:9999 allowed-ips 10.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the docker containers assign the IP addresses:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# server
|
||||||
|
docker exec -it rpserver ip a add 10.0.0.1/24 dev rosenpass0
|
||||||
|
|
||||||
|
# client
|
||||||
|
docker exec -it rpclient ip a add 10.0.0.2/24 dev rosenpass1
|
||||||
|
```
|
||||||
|
|
||||||
|
Done! The two containers should now be connected through a wireguard VPN (Port 1000) with pre-shared keys exchanged by rosenpass (Port 9999).
|
||||||
|
|
||||||
|
Now, test the connection by starting a shell inside the client container, and ping the server through the VPN:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# client
|
||||||
|
docker exec -it rpclient bash
|
||||||
|
apt update; apt install iputils-ping
|
||||||
|
ping 10.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
The ping command should continuously show ping-logs:
|
||||||
|
|
||||||
|
```
|
||||||
|
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
|
||||||
|
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.119 ms
|
||||||
|
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.132 ms
|
||||||
|
64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.394 ms
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
While the ping is running, you may stop the server container, and verify that the ping-log halts. In another terminal do:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker stop -t 1 rpserver
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building the Docker Images Locally
|
||||||
|
|
||||||
|
Clone the Rosenpass repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/rosenpass/rosenpass
|
||||||
|
cd rosenpass
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the rp image from the root of the repository as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -f docker/Dockerfile -t ghcr.io/rosenpass/rp --target rp .
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the rosenpass image from the root of the repostiry with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -f docker/Dockerfile -t ghcr.io/rosenpass/rosenpass --target rosenpass .
|
||||||
|
```
|
||||||
21
flake.lock
generated
21
flake.lock
generated
@@ -39,6 +39,26 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix-vm-test": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1734355073,
|
||||||
|
"narHash": "sha256-FfdPOGy1zElTwKzjgIMp5K2D3gfPn6VWjVa4MJ9L1Tc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-vm-test",
|
||||||
|
"rev": "5948de39a616f2261dbbf4b6f25cbe1cbefd788c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-vm-test",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728193676,
|
"lastModified": 1728193676,
|
||||||
@@ -59,6 +79,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
|
"nix-vm-test": "nix-vm-test",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
44
flake.nix
44
flake.nix
@@ -6,9 +6,13 @@
|
|||||||
# for rust nightly with llvm-tools-preview
|
# for rust nightly with llvm-tools-preview
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
nix-vm-test.url = "github:numtide/nix-vm-test";
|
||||||
|
nix-vm-test.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
nix-vm-test.inputs.flake-utils.follows = "flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
outputs = { self, nixpkgs, flake-utils, nix-vm-test, ... }@inputs:
|
||||||
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
||||||
|
|
||||||
|
|
||||||
@@ -77,10 +81,19 @@
|
|||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||||
overlays = [ self.overlays.default ];
|
overlays = [
|
||||||
|
self.overlays.default
|
||||||
|
nix-vm-test.overlays.default
|
||||||
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
packages.package-deb = pkgs.callPackage ./pkgs/package-deb.nix {
|
||||||
|
rosenpass = pkgs.pkgsStatic.rosenpass;
|
||||||
|
};
|
||||||
|
packages.package-rpm = pkgs.callPackage ./pkgs/package-rpm.nix {
|
||||||
|
rosenpass = pkgs.pkgsStatic.rosenpass;
|
||||||
|
};
|
||||||
|
|
||||||
#
|
#
|
||||||
### Reading materials ###
|
### Reading materials ###
|
||||||
@@ -109,16 +122,37 @@
|
|||||||
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-audit
|
||||||
|
cargo-release
|
||||||
|
cargo-msrv
|
||||||
|
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 {
|
devShells.coverage = pkgs.mkShell {
|
||||||
inputsFrom = [ pkgs.rosenpass ];
|
inputsFrom = [ pkgs.rosenpass ];
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
inputs.fenix.packages.${system}.complete.toolchain
|
inputs.fenix.packages.${system}.complete.toolchain
|
||||||
pkgs.cargo-llvm-cov
|
pkgs.cargo-llvm-cov
|
||||||
|
pkgs.grcov
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
|
systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix;
|
||||||
|
systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix;
|
||||||
|
|
||||||
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
||||||
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
||||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
||||||
@@ -131,7 +165,11 @@
|
|||||||
{ nativeBuildInputs = [ pkgs.nodePackages.prettier ]; } ''
|
{ nativeBuildInputs = [ pkgs.nodePackages.prettier ]; } ''
|
||||||
cd ${./.} && prettier --check . && touch $out
|
cd ${./.} && prettier --check . && touch $out
|
||||||
'';
|
'';
|
||||||
};
|
} // pkgs.lib.optionalAttrs (system == "x86_64-linux") (import ./tests/legacy-distro-packaging.nix {
|
||||||
|
inherit pkgs;
|
||||||
|
rosenpass-deb = self.packages.${system}.package-deb;
|
||||||
|
rosenpass-rpm = self.packages.${system}.package-rpm;
|
||||||
|
});
|
||||||
|
|
||||||
formatter = pkgs.nixpkgs-fmt;
|
formatter = pkgs.nixpkgs-fmt;
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ name = "rosenpass-fuzzing"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
publish = false
|
publish = false
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux_all"]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass_ciphers::aead;
|
use rosenpass_cipher_traits::primitives::Aead as _;
|
||||||
|
use rosenpass_ciphers::Aead;
|
||||||
|
|
||||||
#[derive(arbitrary::Arbitrary, Debug)]
|
#[derive(arbitrary::Arbitrary, Debug)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
@@ -17,7 +18,7 @@ pub struct Input {
|
|||||||
fuzz_target!(|input: Input| {
|
fuzz_target!(|input: Input| {
|
||||||
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
|
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
|
||||||
|
|
||||||
aead::encrypt(
|
Aead.encrypt(
|
||||||
ciphertext.as_mut_slice(),
|
ciphertext.as_mut_slice(),
|
||||||
&input.key,
|
&input.key,
|
||||||
&input.nonce,
|
&input.nonce,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHashTo;
|
||||||
use rosenpass_ciphers::subtle::blake2b;
|
use rosenpass_ciphers::subtle::blake2b;
|
||||||
use rosenpass_to::To;
|
use rosenpass_to::To;
|
||||||
|
|
||||||
@@ -16,5 +17,7 @@ pub struct Blake2b {
|
|||||||
fuzz_target!(|input: Blake2b| {
|
fuzz_target!(|input: Blake2b| {
|
||||||
let mut out = [0u8; 32];
|
let mut out = [0u8; 32];
|
||||||
|
|
||||||
blake2b::hash(&input.key, &input.data).to(&mut out).unwrap();
|
blake2b::Blake2b::keyed_hash_to(&input.key, &input.data)
|
||||||
|
.to(&mut out)
|
||||||
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ extern crate rosenpass;
|
|||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass::protocol::CryptoServer;
|
use rosenpass::protocol::CryptoServer;
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
use rosenpass_secret_memory::policy::*;
|
use rosenpass_secret_memory::policy::*;
|
||||||
use rosenpass_secret_memory::{PublicBox, Secret};
|
use rosenpass_secret_memory::{PublicBox, Secret};
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::EphemeralKem;
|
use rosenpass_ciphers::EphemeralKem;
|
||||||
|
|
||||||
#[derive(arbitrary::Arbitrary, Debug)]
|
#[derive(arbitrary::Arbitrary, Debug)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
@@ -16,5 +16,7 @@ fuzz_target!(|input: Input| {
|
|||||||
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
||||||
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
|
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
|
||||||
|
|
||||||
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
EphemeralKem
|
||||||
|
.encaps(&mut shared_secret, &mut ciphertext, &input.pk)
|
||||||
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
|
|
||||||
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
|
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
|
||||||
let mut ciphertext = [0u8; StaticKem::CT_LEN];
|
let mut ciphertext = [0u8; StaticKem::CT_LEN];
|
||||||
let mut shared_secret = [0u8; StaticKem::SHK_LEN];
|
let mut shared_secret = [0u8; StaticKem::SHK_LEN];
|
||||||
|
|
||||||
// We expect errors while fuzzing therefore we do not check the result.
|
// We expect errors while fuzzing therefore we do not check the result.
|
||||||
let _ = StaticKem::encaps(&mut shared_secret, &mut ciphertext, &input);
|
let _ = StaticKem.encaps(&mut shared_secret, &mut ciphertext, &input);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ description = "Rosenpass internal bindings to liboqs"
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rosenpass-cipher-traits = { workspace = true }
|
rosenpass-cipher-traits = { workspace = true }
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
/// Generate bindings to a liboqs-provided KEM
|
/// Generate bindings to a liboqs-provided KEM
|
||||||
macro_rules! oqs_kem {
|
macro_rules! oqs_kem {
|
||||||
($name:ident) => { ::paste::paste!{
|
($name:ident, $algo_trait:path) => { ::paste::paste!{
|
||||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
mod [< $name:snake >] {
|
mod [< $name:snake >] {
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::{Kem, KemError};
|
||||||
use rosenpass_util::result::Guaranteed;
|
|
||||||
|
|
||||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
@@ -14,7 +13,7 @@ macro_rules! oqs_kem {
|
|||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "```rust"]
|
#[doc = "```rust"]
|
||||||
#[doc = "use std::borrow::{Borrow, BorrowMut};"]
|
#[doc = "use std::borrow::{Borrow, BorrowMut};"]
|
||||||
#[doc = "use rosenpass_cipher_traits::Kem;"]
|
#[doc = "use rosenpass_cipher_traits::primitives::Kem;"]
|
||||||
#[doc = "use rosenpass_oqs::" $name:camel " as MyKem;"]
|
#[doc = "use rosenpass_oqs::" $name:camel " as MyKem;"]
|
||||||
#[doc = "use rosenpass_secret_memory::{Secret, Public};"]
|
#[doc = "use rosenpass_secret_memory::{Secret, Public};"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
@@ -23,21 +22,26 @@ macro_rules! oqs_kem {
|
|||||||
#[doc = "// Recipient generates secret key, transfers pk to sender"]
|
#[doc = "// Recipient generates secret key, transfers pk to sender"]
|
||||||
#[doc = "let mut sk = Secret::<{ MyKem::SK_LEN }>::zero();"]
|
#[doc = "let mut sk = Secret::<{ MyKem::SK_LEN }>::zero();"]
|
||||||
#[doc = "let mut pk = Public::<{ MyKem::PK_LEN }>::zero();"]
|
#[doc = "let mut pk = Public::<{ MyKem::PK_LEN }>::zero();"]
|
||||||
#[doc = "MyKem::keygen(sk.secret_mut(), pk.borrow_mut());"]
|
#[doc = "MyKem.keygen(sk.secret_mut(), &mut pk);"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "// Sender generates ciphertext and local shared key, sends ciphertext to recipient"]
|
#[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 shk_enc = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||||
#[doc = "let mut ct = Public::<{ MyKem::CT_LEN }>::zero();"]
|
#[doc = "let mut ct = Public::<{ MyKem::CT_LEN }>::zero();"]
|
||||||
#[doc = "MyKem::encaps(shk_enc.secret_mut(), ct.borrow_mut(), pk.borrow());"]
|
#[doc = "MyKem.encaps(shk_enc.secret_mut(), &mut ct, &pk);"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "// Recipient decapsulates ciphertext"]
|
#[doc = "// Recipient decapsulates ciphertext"]
|
||||||
#[doc = "let mut shk_dec = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
#[doc = "let mut shk_dec = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||||
#[doc = "MyKem::decaps(shk_dec.secret_mut(), sk.secret(), ct.borrow());"]
|
#[doc = "MyKem.decaps(shk_dec.secret_mut(), sk.secret_mut(), &ct);"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "// Both parties end up with the same shared key"]
|
#[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 = "assert!(rosenpass_constant_time::compare(shk_enc.secret(), shk_dec.secret()) == 0);"]
|
||||||
#[doc = "```"]
|
#[doc = "```"]
|
||||||
pub enum [< $name:camel >] {}
|
pub struct [< $name:camel >];
|
||||||
|
|
||||||
|
pub const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
|
||||||
|
pub const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
|
||||||
|
pub const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
|
||||||
|
pub const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
|
||||||
|
|
||||||
/// # Panic & Safety
|
/// # Panic & Safety
|
||||||
///
|
///
|
||||||
@@ -51,17 +55,8 @@ macro_rules! oqs_kem {
|
|||||||
/// to only check that the buffers are big enough, allowing them to be even
|
/// to only check that the buffers are big enough, allowing them to be even
|
||||||
/// bigger. However, from a correctness point of view it does not make sense to
|
/// bigger. However, from a correctness point of view it does not make sense to
|
||||||
/// allow bigger buffers.
|
/// allow bigger buffers.
|
||||||
impl Kem for [< $name:camel >] {
|
impl Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> for [< $name:camel >] {
|
||||||
type Error = ::std::convert::Infallible;
|
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), KemError> {
|
||||||
|
|
||||||
const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
|
|
||||||
const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
|
|
||||||
const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
|
|
||||||
const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
|
|
||||||
|
|
||||||
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Guaranteed<()> {
|
|
||||||
assert_eq!(sk.len(), Self::SK_LEN);
|
|
||||||
assert_eq!(pk.len(), Self::PK_LEN);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
oqs_call!(
|
oqs_call!(
|
||||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
|
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
|
||||||
@@ -73,10 +68,7 @@ macro_rules! oqs_kem {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
|
fn encaps(&self, shk: &mut [u8; SHK_LEN], ct: &mut [u8; CT_LEN], pk: &[u8; PK_LEN]) -> Result<(), KemError> {
|
||||||
assert_eq!(shk.len(), Self::SHK_LEN);
|
|
||||||
assert_eq!(ct.len(), Self::CT_LEN);
|
|
||||||
assert_eq!(pk.len(), Self::PK_LEN);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
oqs_call!(
|
oqs_call!(
|
||||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
|
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
|
||||||
@@ -89,10 +81,7 @@ macro_rules! oqs_kem {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
|
fn decaps(&self, shk: &mut [u8; SHK_LEN], sk: &[u8; SK_LEN], ct: &[u8; CT_LEN]) -> Result<(), KemError> {
|
||||||
assert_eq!(shk.len(), Self::SHK_LEN);
|
|
||||||
assert_eq!(sk.len(), Self::SK_LEN);
|
|
||||||
assert_eq!(ct.len(), Self::CT_LEN);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
oqs_call!(
|
oqs_call!(
|
||||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
|
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
|
||||||
@@ -105,9 +94,16 @@ macro_rules! oqs_kem {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for [< $name:camel >] {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $algo_trait for [< $name:camel >] {}
|
||||||
|
|
||||||
pub use [< $name:snake >] :: [< $name:camel >];
|
pub use [< $name:snake >] :: [< $name:camel >];
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,5 +22,8 @@ macro_rules! oqs_call {
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod kem_macro;
|
mod kem_macro;
|
||||||
oqs_kem!(kyber_512);
|
oqs_kem!(kyber_512, rosenpass_cipher_traits::algorithms::KemKyber512);
|
||||||
oqs_kem!(classic_mceliece_460896);
|
oqs_kem!(
|
||||||
|
classic_mceliece_460896,
|
||||||
|
rosenpass_cipher_traits::algorithms::KemClassicMceliece460896
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
template: rosenpass
|
template: rosenpass
|
||||||
title: Rosenpass
|
title: Rosenpass
|
||||||
author:
|
author:
|
||||||
- Karolin Varner = Independent Researcher
|
- Karolin Varner = Rosenpass e.V., Max Planck Institute for Security and Privacy (MPI-SP)
|
||||||
- Benjamin Lipp = 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
|
- Wanja Zaeske
|
||||||
- Lisa Schmidt = {Scientific Illustrator – \\url{mullana.de}}
|
- Lisa Schmidt = {Scientific Illustrator – \\url{mullana.de}}
|
||||||
- Prabhpreet Dua
|
- Prabhpreet Dua
|
||||||
@@ -383,9 +383,18 @@ fn load_biscuit(nct) {
|
|||||||
"biscuit additional data",
|
"biscuit additional data",
|
||||||
spkr, sidi, sidr);
|
spkr, sidi, sidr);
|
||||||
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
|
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
|
||||||
|
|
||||||
// Find the peer and apply retransmission protection
|
// Find the peer and apply retransmission protection
|
||||||
lookup_peer(pt.peerid);
|
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
|
// Restore the chaining key
|
||||||
ck ← pt.ck;
|
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 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
|
### Interaction with cookie reply system
|
||||||
|
|
||||||
@@ -515,6 +524,76 @@ When the responder is under load and it recieves an InitConf message, the messag
|
|||||||
|
|
||||||
# Changelog
|
# 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
|
- Added section "Denial of Service Mitigation and Cookies", and modify "Dealing with Packet Loss" for DoS cookie mechanism
|
||||||
|
|
||||||
\printbibliography
|
\printbibliography
|
||||||
|
|||||||
9
pkgs/example.toml
Normal file
9
pkgs/example.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
dev = "rp-example"
|
||||||
|
ip = "fc00::1/64"
|
||||||
|
listen = "[::]:51821"
|
||||||
|
private_keys_dir = "/run/credentials/rp@example.service"
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_keys_dir = "/etc/rosenpass/example/peers/client"
|
||||||
|
allowed_ips = "fc00::2"
|
||||||
30
pkgs/package-deb.nix
Normal file
30
pkgs/package-deb.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{ runCommand, dpkg, rosenpass }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (rosenpass) version;
|
||||||
|
in
|
||||||
|
|
||||||
|
runCommand "rosenpass-${version}.deb" { } ''
|
||||||
|
mkdir -p packageroot/DEBIAN
|
||||||
|
|
||||||
|
cat << EOF > packageroot/DEBIAN/control
|
||||||
|
Package: rosenpass
|
||||||
|
Version: ${version}
|
||||||
|
Architecture: all
|
||||||
|
Maintainer: Jacek Galowicz <jacek@galowicz.de>
|
||||||
|
Depends:
|
||||||
|
Description: Post-quantum-secure VPN tool Rosenpass
|
||||||
|
Rosenpass is a post-quantum-secure VPN
|
||||||
|
that uses WireGuard to transport the actual data.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
mkdir -p packageroot/usr/bin
|
||||||
|
install -m755 -t packageroot/usr/bin ${rosenpass}/bin/*
|
||||||
|
|
||||||
|
mkdir -p packageroot/etc/rosenpass
|
||||||
|
cp -r ${rosenpass}/lib/systemd packageroot/etc/
|
||||||
|
cp ${./example.toml} packageroot/etc/rosenpass/example.toml
|
||||||
|
|
||||||
|
${dpkg}/bin/dpkg --build packageroot $out
|
||||||
|
''
|
||||||
57
pkgs/package-rpm.nix
Normal file
57
pkgs/package-rpm.nix
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{ lib, system, runCommand, rosenpass, rpm }:
|
||||||
|
|
||||||
|
let
|
||||||
|
splitVersion = lib.strings.splitString "-" rosenpass.version;
|
||||||
|
version = builtins.head splitVersion;
|
||||||
|
release =
|
||||||
|
if builtins.length splitVersion != 2
|
||||||
|
then "release"
|
||||||
|
else builtins.elemAt splitVersion 1;
|
||||||
|
arch = builtins.head (builtins.split "-" system);
|
||||||
|
in
|
||||||
|
|
||||||
|
runCommand "rosenpass-${version}.rpm" { } ''
|
||||||
|
mkdir -p rpmbuild/SPECS
|
||||||
|
|
||||||
|
cat << EOF > rpmbuild/SPECS/rosenpass.spec
|
||||||
|
Name: rosenpass
|
||||||
|
Release: ${release}
|
||||||
|
Version: ${version}
|
||||||
|
Summary: Post-quantum-secure VPN key exchange
|
||||||
|
License: Apache-2.0
|
||||||
|
|
||||||
|
%description
|
||||||
|
Post-quantum-secure VPN tool Rosenpass
|
||||||
|
Rosenpass is a post-quantum-secure VPN
|
||||||
|
that uses WireGuard to transport the actual data.
|
||||||
|
|
||||||
|
%files
|
||||||
|
/usr/bin/rosenpass
|
||||||
|
/usr/bin/rp
|
||||||
|
/etc/systemd/system/rosenpass.target
|
||||||
|
/etc/systemd/system/rosenpass@.service
|
||||||
|
/etc/systemd/system/rp@.service
|
||||||
|
/etc/rosenpass/example.toml
|
||||||
|
EOF
|
||||||
|
|
||||||
|
buildroot=rpmbuild/BUILDROOT/rosenpass-${version}-${release}.${arch}
|
||||||
|
mkdir -p $buildroot/usr/bin
|
||||||
|
install -m755 -t $buildroot/usr/bin ${rosenpass}/bin/*
|
||||||
|
|
||||||
|
mkdir -p $buildroot/etc/rosenpass
|
||||||
|
cp -r ${rosenpass}/lib/systemd $buildroot/etc/
|
||||||
|
chmod -R 744 $buildroot/etc/systemd
|
||||||
|
cp ${./example.toml} $buildroot/etc/rosenpass/example.toml
|
||||||
|
|
||||||
|
export HOME=/build
|
||||||
|
mkdir -p /build/tmp
|
||||||
|
ls -R rpmbuild
|
||||||
|
|
||||||
|
${rpm}/bin/rpmbuild \
|
||||||
|
-bb \
|
||||||
|
--dbpath=$HOME \
|
||||||
|
--define "_tmppath /build/tmp" \
|
||||||
|
rpmbuild/SPECS/rosenpass.spec
|
||||||
|
|
||||||
|
cp rpmbuild/RPMS/${arch}/rosenpass*.rpm $out
|
||||||
|
''
|
||||||
@@ -20,7 +20,7 @@ in
|
|||||||
runCommandNoCC "lace-result" { } ''
|
runCommandNoCC "lace-result" { } ''
|
||||||
mkdir {bin,$out}
|
mkdir {bin,$out}
|
||||||
tar -cvf $out/rosenpass-${stdenvNoCC.hostPlatform.system}-${version}.tar \
|
tar -cvf $out/rosenpass-${stdenvNoCC.hostPlatform.system}-${version}.tar \
|
||||||
-C ${package} bin/rosenpass \
|
-C ${package} bin/rosenpass lib/systemd \
|
||||||
-C ${rp} bin/rp
|
-C ${rp} bin/rp
|
||||||
cp ${oci-image} \
|
cp ${oci-image} \
|
||||||
$out/rosenpass-oci-image-${stdenvNoCC.hostPlatform.system}-${version}.tar.gz
|
$out/rosenpass-oci-image-${stdenvNoCC.hostPlatform.system}-${version}.tar.gz
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ let
|
|||||||
extensions = [
|
extensions = [
|
||||||
"lock"
|
"lock"
|
||||||
"rs"
|
"rs"
|
||||||
|
"service"
|
||||||
|
"target"
|
||||||
"toml"
|
"toml"
|
||||||
];
|
];
|
||||||
# Files to explicitly include
|
# Files to explicitly include
|
||||||
@@ -55,6 +57,7 @@ rustPlatform.buildRustPackage {
|
|||||||
outputHashes = {
|
outputHashes = {
|
||||||
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
||||||
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
||||||
|
"libcrux-blake2-0.0.3-pre" = "sha256-0CLjuzwJqGooiODOHf5D8Hc8ClcG/XcGvVGyOVnLmJY=";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,6 +72,13 @@ rustPlatform.buildRustPackage {
|
|||||||
|
|
||||||
hardeningDisable = lib.optional isStatic "fortify";
|
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 = {
|
meta = {
|
||||||
inherit (cargoToml.package) description homepage;
|
inherit (cargoToml.package) description homepage;
|
||||||
license = with lib.licenses; [ mit asl20 ];
|
license = with lib.licenses; [ mit asl20 ];
|
||||||
|
|||||||
15
readme.md
15
readme.md
@@ -23,6 +23,12 @@ rosenpass help
|
|||||||
|
|
||||||
Follow [quick start instructions](https://rosenpass.eu/#start) to get a VPN up and running.
|
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
|
## 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.
|
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.
|
||||||
@@ -72,6 +78,15 @@ Rosenpass is packaged for more and more distributions, maybe also for the distri
|
|||||||
|
|
||||||
[](https://repology.org/project/rosenpass/versions)
|
[](https://repology.org/project/rosenpass/versions)
|
||||||
|
|
||||||
|
## Docker Images
|
||||||
|
|
||||||
|
Rosenpass is also available as prebuilt Docker images:
|
||||||
|
|
||||||
|
- [`ghcr.io/rosenpass/rosenpass`](https://github.com/rosenpass/rosenpass/pkgs/container/rosenpass)
|
||||||
|
- [`ghcr.io/rosenpass/rp`](https://github.com/rosenpass/rosenpass/pkgs/container/rp)
|
||||||
|
|
||||||
|
For details on how to use these images, refer to the [Docker usage guide](docker/USAGE.md).
|
||||||
|
|
||||||
# Mirrors
|
# Mirrors
|
||||||
|
|
||||||
Don't want to use GitHub or only have an IPv6 connection? Rosenpass has set up two mirrors for this:
|
Don't want to use GitHub or only have an IPv6 connection? Rosenpass has set up two mirrors for this:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ description = "Build post-quantum-secure VPNs with WireGuard!"
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rosenpass"
|
name = "rosenpass"
|
||||||
@@ -26,6 +27,14 @@ required-features = ["experiment_api", "internal_testing"]
|
|||||||
name = "api-integration-tests-api-setup"
|
name = "api-integration-tests-api-setup"
|
||||||
required-features = ["experiment_api", "internal_testing"]
|
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]]
|
[[bench]]
|
||||||
name = "handshake"
|
name = "handshake"
|
||||||
harness = false
|
harness = false
|
||||||
@@ -47,6 +56,8 @@ env_logger = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
clap_complete = { workspace = true }
|
||||||
|
clap_mangen = { workspace = true }
|
||||||
mio = { workspace = true }
|
mio = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
zerocopy = { workspace = true }
|
zerocopy = { workspace = true }
|
||||||
@@ -58,8 +69,9 @@ hex-literal = { workspace = true, optional = true }
|
|||||||
hex = { workspace = true, optional = true }
|
hex = { workspace = true, optional = true }
|
||||||
heck = { workspace = true, optional = true }
|
heck = { workspace = true, optional = true }
|
||||||
command-fds = { workspace = true, optional = true }
|
command-fds = { workspace = true, optional = true }
|
||||||
rustix = { workspace = true }
|
rustix = { workspace = true, optional = true }
|
||||||
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
||||||
|
signal-hook = { workspace = true, optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
@@ -74,15 +86,26 @@ tempfile = { workspace = true }
|
|||||||
rustix = { workspace = true }
|
rustix = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["experiment_api"]
|
#default = ["experiment_libcrux_all"]
|
||||||
|
experiment_cookie_dos_mitigation = []
|
||||||
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
||||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
experiment_libcrux_all = ["rosenpass-ciphers/experiment_libcrux_all"]
|
||||||
|
experiment_libcrux_blake2 = ["rosenpass-ciphers/experiment_libcrux_blake2"]
|
||||||
|
experiment_libcrux_chachapoly = [
|
||||||
|
"rosenpass-ciphers/experiment_libcrux_chachapoly",
|
||||||
|
]
|
||||||
|
experiment_libcrux_kyber = ["rosenpass-ciphers/experiment_libcrux_kyber"]
|
||||||
experiment_api = [
|
experiment_api = [
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"uds",
|
"uds",
|
||||||
"command-fds",
|
"command-fds",
|
||||||
|
"rustix",
|
||||||
"rosenpass-util/experiment_file_descriptor_passing",
|
"rosenpass-util/experiment_file_descriptor_passing",
|
||||||
"rosenpass-wireguard-broker/experiment_api",
|
"rosenpass-wireguard-broker/experiment_api",
|
||||||
]
|
]
|
||||||
|
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
||||||
internal_testing = []
|
internal_testing = []
|
||||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
|
use rosenpass::protocol::{
|
||||||
|
CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, ProtocolVersion, SPk, SSk, SymKey,
|
||||||
|
};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
|
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
|
||||||
@@ -41,25 +43,33 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
|
|||||||
|
|
||||||
fn keygen() -> Result<(SSk, SPk)> {
|
fn keygen() -> Result<(SSk, SPk)> {
|
||||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
StaticKem.keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||||
Ok((sk, pk))
|
Ok((sk, pk))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
|
fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> {
|
||||||
let psk = SymKey::random();
|
let psk = SymKey::random();
|
||||||
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
|
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
|
||||||
let (mut a, mut b) = (
|
let (mut a, mut b) = (
|
||||||
CryptoServer::new(ska, pka.clone()),
|
CryptoServer::new(ska, pka.clone()),
|
||||||
CryptoServer::new(skb, pkb.clone()),
|
CryptoServer::new(skb, pkb.clone()),
|
||||||
);
|
);
|
||||||
a.add_peer(Some(psk.clone()), pkb)?;
|
a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?;
|
||||||
b.add_peer(Some(psk), pka)?;
|
b.add_peer(Some(psk), pka, protocol_version)?;
|
||||||
Ok((a, b))
|
Ok((a, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
fn criterion_benchmark_v02(c: &mut Criterion) {
|
||||||
|
criterion_benchmark(c, ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn criterion_benchmark_v03(c: &mut Criterion) {
|
||||||
|
criterion_benchmark(c, ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion, protocol_version: ProtocolVersion) {
|
||||||
secret_policy_try_use_memfd_secrets();
|
secret_policy_try_use_memfd_secrets();
|
||||||
let (mut a, mut b) = make_server_pair().unwrap();
|
let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
|
||||||
c.bench_function("cca_secret_alloc", |bench| {
|
c.bench_function("cca_secret_alloc", |bench| {
|
||||||
bench.iter(|| {
|
bench.iter(|| {
|
||||||
SSk::zero();
|
SSk::zero();
|
||||||
@@ -82,5 +92,6 @@ fn criterion_benchmark(c: &mut Criterion) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, criterion_benchmark);
|
criterion_group!(benches_v02, criterion_benchmark_v02);
|
||||||
criterion_main!(benches);
|
criterion_group!(benches_v03, criterion_benchmark_v03);
|
||||||
|
criterion_main!(benches_v02, benches_v03);
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Note: This is business logic; tested through the integration tests in
|
||||||
|
// rosenpass/tests/
|
||||||
|
|
||||||
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
|
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -20,37 +23,80 @@ use crate::{
|
|||||||
|
|
||||||
use super::{supply_keypair_response_status, Server as ApiServer};
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct ApiHandler {
|
pub struct ApiHandler {
|
||||||
_dummy: (),
|
_dummy: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiHandler {
|
impl ApiHandler {
|
||||||
|
/// Construct an [Self]
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { _dummy: () }
|
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 {
|
pub trait ApiHandlerContext {
|
||||||
|
/// Retrieve the [ApiHandler]
|
||||||
fn api_handler(&self) -> &ApiHandler;
|
fn api_handler(&self) -> &ApiHandler;
|
||||||
|
/// Retrieve the [AppServer]
|
||||||
fn app_server(&self) -> &AppServer;
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Retrieve the [ApiHandler]
|
||||||
fn api_handler_mut(&mut self) -> &mut ApiHandler;
|
fn api_handler_mut(&mut self) -> &mut ApiHandler;
|
||||||
|
/// Retrieve the [AppServer]
|
||||||
fn app_server_mut(&mut self) -> &mut 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)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[error("Error in SupplyKeypair")]
|
#[error("Error in SupplyKeypair")]
|
||||||
struct SupplyKeypairError {
|
struct SupplyKeypairError {
|
||||||
|
/// The status code communicated via the Rosenpass API
|
||||||
status: u128,
|
status: u128,
|
||||||
|
/// The underlying error that caused the Rosenpass API level Error
|
||||||
#[source]
|
#[source]
|
||||||
cause: anyhow::Error,
|
cause: anyhow::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SupplyKeypairErrorExt<T> {
|
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>;
|
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>;
|
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>;
|
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>;
|
fn einvalid_req(self) -> Result<T, SupplyKeypairError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,210 +8,250 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub trait ByteSliceRefExt: ByteSlice {
|
pub trait ByteSliceRefExt: ByteSlice {
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and
|
||||||
self.zk_parse()
|
/// [RefMakerRawMsgTypeExt::parse_request_msg_type]
|
||||||
}
|
|
||||||
|
|
||||||
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
|
||||||
self.zk_parse_prefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
|
||||||
self.zk_parse_suffix()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||||
self.msg_type_maker().parse_request_msg_type()
|
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> {
|
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_prefix()?
|
.from_prefix()?
|
||||||
.parse_request_msg_type()
|
.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> {
|
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_suffix()?
|
.from_suffix()?
|
||||||
.parse_request_msg_type()
|
.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> {
|
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
self.msg_type_maker().parse_response_msg_type()
|
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> {
|
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_prefix()?
|
.from_prefix()?
|
||||||
.parse_response_msg_type()
|
.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> {
|
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_suffix()?
|
.from_suffix()?
|
||||||
.parse_response_msg_type()
|
.parse_response_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [RequestRef::parse] in chaining.
|
||||||
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
RequestRef::parse(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>> {
|
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
RequestRef::parse_from_prefix(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>> {
|
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
RequestRef::parse_from_suffix(self)
|
RequestRef::parse_from_suffix(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [ResponseRef::parse] in chaining.
|
||||||
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
ResponseRef::parse(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>> {
|
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
ResponseRef::parse_from_prefix(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>> {
|
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
ResponseRef::parse_from_suffix(self)
|
ResponseRef::parse_from_suffix(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
self.zk_parse_prefix()
|
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>> {
|
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
self.zk_parse_prefix()
|
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>> {
|
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
self.zk_parse()
|
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>> {
|
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
self.zk_parse_prefix()
|
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>> {
|
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn supply_keypair_response_from_prefix(
|
fn supply_keypair_response_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn supply_keypair_response_from_suffix(
|
fn supply_keypair_response_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
self.zk_parse_suffix()
|
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>> {
|
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_listen_socket_request_from_prefix(
|
fn add_listen_socket_request_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_listen_socket_request_from_suffix(
|
fn add_listen_socket_request_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
self.zk_parse_suffix()
|
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> {
|
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn add_listen_socket_response(
|
fn add_listen_socket_response(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_listen_socket_response_from_prefix(
|
fn add_listen_socket_response_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_listen_socket_response_from_suffix(
|
fn add_listen_socket_response_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
self.zk_parse_suffix()
|
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>> {
|
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_psk_broker_request_from_prefix(
|
fn add_psk_broker_request_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_psk_broker_request_from_suffix(
|
fn add_psk_broker_request_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
self.zk_parse_suffix()
|
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> {
|
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
||||||
self.zk_ref_maker()
|
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>> {
|
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_psk_broker_response_from_prefix(
|
fn add_psk_broker_response_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_psk_broker_response_from_suffix(
|
fn add_psk_broker_response_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
|
|||||||
@@ -4,16 +4,41 @@ use rosenpass_util::zerocopy::RefMaker;
|
|||||||
|
|
||||||
use super::RawMsgType;
|
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 {
|
pub trait Message {
|
||||||
|
/// The payload this API message contains. E.g. this is [crate::api::PingRequestPayload] for [[crate::api::PingRequest].
|
||||||
type Payload;
|
type Payload;
|
||||||
|
/// Either [crate::api::RequestMsgType] or [crate::api::ResponseMsgType]
|
||||||
type MessageClass: Into<RawMsgType>;
|
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;
|
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;
|
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);
|
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>>;
|
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> {
|
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
||||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
||||||
}
|
}
|
||||||
@@ -23,6 +48,27 @@ where
|
|||||||
B: ByteSliceMut,
|
B: ByteSliceMut,
|
||||||
T: Message,
|
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>> {
|
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
||||||
T::setup(self.into_buf())
|
T::setup(self.into_buf())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,15 @@ const ADD_PSK_BROKER_REQUEST: RawMsgType =
|
|||||||
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
||||||
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
||||||
|
|
||||||
|
/// Message properties global to the message type
|
||||||
pub trait MessageAttributes {
|
pub trait MessageAttributes {
|
||||||
|
/// Get the size of the message
|
||||||
|
///
|
||||||
|
/// # Exampleds
|
||||||
fn message_size(&self) -> usize;
|
fn message_size(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API request message types as an enum
|
||||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
pub enum RequestMsgType {
|
pub enum RequestMsgType {
|
||||||
Ping,
|
Ping,
|
||||||
@@ -47,6 +52,7 @@ pub enum RequestMsgType {
|
|||||||
AddPskBroker,
|
AddPskBroker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API response messages types as an enum
|
||||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
pub enum ResponseMsgType {
|
pub enum ResponseMsgType {
|
||||||
Ping,
|
Ping,
|
||||||
@@ -131,8 +137,17 @@ impl From<ResponseMsgType> for RawMsgType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
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>;
|
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>;
|
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +161,11 @@ impl RawMsgTypeExt for RawMsgType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
|
||||||
pub trait RefMakerRawMsgTypeExt {
|
pub trait RefMakerRawMsgTypeExt {
|
||||||
|
/// Parse a request message type from bytes
|
||||||
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
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>;
|
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 byte_slice_ext;
|
||||||
mod message_trait;
|
mod message_trait;
|
||||||
mod message_type;
|
mod message_type;
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
|
|||||||
|
|
||||||
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
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
|
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
|
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;
|
pub const MAX_REQUEST_FDS: usize = 2;
|
||||||
|
|
||||||
|
/// Message envelope for API messages
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||||
@@ -17,9 +20,12 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
|||||||
pub payload: M,
|
pub payload: M,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Message envelope for API requests
|
||||||
pub type RequestEnvelope<M> = Envelope<M>;
|
pub type RequestEnvelope<M> = Envelope<M>;
|
||||||
|
/// Message envelope for API responses
|
||||||
pub type ResponseEnvelope<M> = Envelope<M>;
|
pub type ResponseEnvelope<M> = Envelope<M>;
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct PingRequestPayload {
|
pub struct PingRequestPayload {
|
||||||
@@ -27,9 +33,11 @@ pub struct PingRequestPayload {
|
|||||||
pub echo: [u8; 256],
|
pub echo: [u8; 256],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
||||||
|
|
||||||
impl PingRequest {
|
impl PingRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(echo: [u8; 256]) -> Self {
|
pub fn new(echo: [u8; 256]) -> Self {
|
||||||
Self::from_payload(PingRequestPayload { echo })
|
Self::from_payload(PingRequestPayload { echo })
|
||||||
}
|
}
|
||||||
@@ -58,6 +66,7 @@ impl Message for PingRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct PingResponsePayload {
|
pub struct PingResponsePayload {
|
||||||
@@ -65,9 +74,11 @@ pub struct PingResponsePayload {
|
|||||||
pub echo: [u8; 256],
|
pub echo: [u8; 256],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
||||||
|
|
||||||
impl PingResponse {
|
impl PingResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(echo: [u8; 256]) -> Self {
|
pub fn new(echo: [u8; 256]) -> Self {
|
||||||
Self::from_payload(PingResponsePayload { echo })
|
Self::from_payload(PingResponsePayload { echo })
|
||||||
}
|
}
|
||||||
@@ -96,10 +107,12 @@ impl Message for PingResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct SupplyKeypairRequestPayload {}
|
pub struct SupplyKeypairRequestPayload {}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
||||||
|
|
||||||
impl Default for SupplyKeypairRequest {
|
impl Default for SupplyKeypairRequest {
|
||||||
@@ -109,6 +122,7 @@ impl Default for SupplyKeypairRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SupplyKeypairRequest {
|
impl SupplyKeypairRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_payload(SupplyKeypairRequestPayload {})
|
Self::from_payload(SupplyKeypairRequestPayload {})
|
||||||
}
|
}
|
||||||
@@ -137,23 +151,35 @@ impl Message for SupplyKeypairRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub mod supply_keypair_response_status {
|
pub mod supply_keypair_response_status {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
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;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INVALID_REQUEST: u128 = 3;
|
pub const INVALID_REQUEST: u128 = 3;
|
||||||
|
/// TODO: Deprectaed, remove
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const IO_ERROR: u128 = 4;
|
pub const IO_ERROR: u128 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct SupplyKeypairResponsePayload {
|
pub struct SupplyKeypairResponsePayload {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub status: u128,
|
pub status: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
||||||
|
|
||||||
impl SupplyKeypairResponse {
|
impl SupplyKeypairResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(status: u128) -> Self {
|
pub fn new(status: u128) -> Self {
|
||||||
Self::from_payload(SupplyKeypairResponsePayload { status })
|
Self::from_payload(SupplyKeypairResponsePayload { status })
|
||||||
}
|
}
|
||||||
@@ -182,10 +208,12 @@ impl Message for SupplyKeypairResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddListenSocketRequestPayload {}
|
pub struct AddListenSocketRequestPayload {}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
||||||
|
|
||||||
impl Default for AddListenSocketRequest {
|
impl Default for AddListenSocketRequest {
|
||||||
@@ -195,6 +223,7 @@ impl Default for AddListenSocketRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AddListenSocketRequest {
|
impl AddListenSocketRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_payload(AddListenSocketRequestPayload {})
|
Self::from_payload(AddListenSocketRequestPayload {})
|
||||||
}
|
}
|
||||||
@@ -223,21 +252,28 @@ impl Message for AddListenSocketRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub mod add_listen_socket_response_status {
|
pub mod add_listen_socket_response_status {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INVALID_REQUEST: u128 = 1;
|
pub const INVALID_REQUEST: u128 = 1;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INTERNAL_ERROR: u128 = 2;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddListenSocketResponsePayload {
|
pub struct AddListenSocketResponsePayload {
|
||||||
pub status: u128,
|
pub status: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
||||||
|
|
||||||
impl AddListenSocketResponse {
|
impl AddListenSocketResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(status: u128) -> Self {
|
pub fn new(status: u128) -> Self {
|
||||||
Self::from_payload(AddListenSocketResponsePayload { status })
|
Self::from_payload(AddListenSocketResponsePayload { status })
|
||||||
}
|
}
|
||||||
@@ -266,19 +302,23 @@ impl Message for AddListenSocketResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddPskBrokerRequestPayload {}
|
pub struct AddPskBrokerRequestPayload {}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
||||||
|
|
||||||
impl Default for AddPskBrokerRequest {
|
impl Default for AddPskBrokerRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddPskBrokerRequest {
|
impl AddPskBrokerRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_payload(AddPskBrokerRequestPayload {})
|
Self::from_payload(AddPskBrokerRequestPayload {})
|
||||||
}
|
}
|
||||||
@@ -307,21 +347,28 @@ impl Message for AddPskBrokerRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub mod add_psk_broker_response_status {
|
pub mod add_psk_broker_response_status {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INVALID_REQUEST: u128 = 1;
|
pub const INVALID_REQUEST: u128 = 1;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INTERNAL_ERROR: u128 = 2;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddPskBrokerResponsePayload {
|
pub struct AddPskBrokerResponsePayload {
|
||||||
pub status: u128,
|
pub status: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
||||||
|
|
||||||
impl AddPskBrokerResponse {
|
impl AddPskBrokerResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(status: u128) -> Self {
|
pub fn new(status: u128) -> Self {
|
||||||
Self::from_payload(AddPskBrokerResponsePayload { status })
|
Self::from_payload(AddPskBrokerResponsePayload { status })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,63 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
|
|
||||||
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
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> {
|
struct RequestRefMaker<B> {
|
||||||
buf: B,
|
buf: B,
|
||||||
msg_type: RequestMsgType,
|
msg_type: RequestMsgType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: ByteSlice> RequestRef<B> {
|
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> {
|
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||||
RequestRefMaker::new(buf)?.parse()
|
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> {
|
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||||
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
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> {
|
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||||
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the message type [Self] contains
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse]
|
||||||
pub fn message_type(&self) -> RequestMsgType {
|
pub fn message_type(&self) -> RequestMsgType {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(_) => RequestMsgType::Ping,
|
Self::Ping(_) => RequestMsgType::Ping,
|
||||||
@@ -110,6 +149,7 @@ impl<B: ByteSlice> RequestRefMaker<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a API message response, typed as an enum.
|
||||||
pub enum RequestRef<B> {
|
pub enum RequestRef<B> {
|
||||||
Ping(Ref<B, PingRequest>),
|
Ping(Ref<B, PingRequest>),
|
||||||
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
||||||
@@ -121,6 +161,11 @@ impl<B> RequestRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSlice,
|
B: ByteSlice,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse].
|
||||||
pub fn bytes(&self) -> &[u8] {
|
pub fn bytes(&self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes(),
|
Self::Ping(r) => r.bytes(),
|
||||||
@@ -135,6 +180,7 @@ impl<B> RequestRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSliceMut,
|
B: ByteSliceMut,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference; mutably
|
||||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes_mut(),
|
Self::Ping(r) => r.bytes_mut(),
|
||||||
|
|||||||
@@ -6,23 +6,29 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
use super::{Message, PingRequest, PingResponse};
|
use super::{Message, PingRequest, PingResponse};
|
||||||
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
||||||
|
|
||||||
|
/// Extension trait for [Message]s that are requests messages
|
||||||
pub trait RequestMsg: Sized + Message {
|
pub trait RequestMsg: Sized + Message {
|
||||||
|
/// The response message belonging to this request message
|
||||||
type ResponseMsg: ResponseMsg;
|
type ResponseMsg: ResponseMsg;
|
||||||
|
|
||||||
|
/// Construct a response make for this particular message
|
||||||
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
||||||
buf.zk_ref_maker()
|
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>> {
|
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
Self::zk_response_maker(buf).setup_msg()
|
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>(
|
fn setup_response_from_prefix<B: ByteSliceMut>(
|
||||||
buf: B,
|
buf: B,
|
||||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
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>(
|
fn setup_response_from_suffix<B: ByteSliceMut>(
|
||||||
buf: B,
|
buf: B,
|
||||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
) -> 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 {
|
pub trait ResponseMsg: Message {
|
||||||
type RequestMsg: RequestMsg;
|
type RequestMsg: RequestMsg;
|
||||||
}
|
}
|
||||||
@@ -66,20 +73,25 @@ impl ResponseMsg for super::AddPskBrokerResponse {
|
|||||||
type RequestMsg = super::AddPskBrokerRequest;
|
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>);
|
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> = (
|
pub type SupplyKeypairPair<B1, B2> = (
|
||||||
Ref<B1, super::SupplyKeypairRequest>,
|
Ref<B1, super::SupplyKeypairRequest>,
|
||||||
Ref<B2, super::SupplyKeypairResponse>,
|
Ref<B2, super::SupplyKeypairResponse>,
|
||||||
);
|
);
|
||||||
|
/// Request and response for the [crate::api::RequestMsgType::AddListenSocket] message type
|
||||||
pub type AddListenSocketPair<B1, B2> = (
|
pub type AddListenSocketPair<B1, B2> = (
|
||||||
Ref<B1, super::AddListenSocketRequest>,
|
Ref<B1, super::AddListenSocketRequest>,
|
||||||
Ref<B2, super::AddListenSocketResponse>,
|
Ref<B2, super::AddListenSocketResponse>,
|
||||||
);
|
);
|
||||||
|
/// Request and response for the [crate::api::RequestMsgType::AddPskBroker] message type
|
||||||
pub type AddPskBrokerPair<B1, B2> = (
|
pub type AddPskBrokerPair<B1, B2> = (
|
||||||
Ref<B1, super::AddPskBrokerRequest>,
|
Ref<B1, super::AddPskBrokerRequest>,
|
||||||
Ref<B2, super::AddPskBrokerResponse>,
|
Ref<B2, super::AddPskBrokerResponse>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// A pair of references to messages; request and response each.
|
||||||
pub enum RequestResponsePair<B1, B2> {
|
pub enum RequestResponsePair<B1, B2> {
|
||||||
Ping(PingPair<B1, B2>),
|
Ping(PingPair<B1, B2>),
|
||||||
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
||||||
@@ -116,6 +128,7 @@ where
|
|||||||
B1: ByteSlice,
|
B1: ByteSlice,
|
||||||
B2: ByteSlice,
|
B2: ByteSlice,
|
||||||
{
|
{
|
||||||
|
/// Returns a tuple to both the request and the response message
|
||||||
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping((req, res)) => {
|
Self::Ping((req, res)) => {
|
||||||
@@ -141,10 +154,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the request message
|
||||||
pub fn request(&self) -> RequestRef<&[u8]> {
|
pub fn request(&self) -> RequestRef<&[u8]> {
|
||||||
self.both().0
|
self.both().0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the response message
|
||||||
pub fn response(&self) -> ResponseRef<&[u8]> {
|
pub fn response(&self) -> ResponseRef<&[u8]> {
|
||||||
self.both().1
|
self.both().1
|
||||||
}
|
}
|
||||||
@@ -155,6 +170,7 @@ where
|
|||||||
B1: ByteSliceMut,
|
B1: ByteSliceMut,
|
||||||
B2: 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]>) {
|
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping((req, res)) => {
|
Self::Ping((req, res)) => {
|
||||||
@@ -180,10 +196,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the request message, mutably
|
||||||
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
||||||
self.both_mut().0
|
self.both_mut().0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the response message, mutably
|
||||||
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
||||||
self.both_mut().1
|
self.both_mut().1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,66 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
|
|
||||||
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
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> {
|
struct ResponseRefMaker<B> {
|
||||||
|
/// Buffer we are referencing
|
||||||
buf: B,
|
buf: B,
|
||||||
|
/// Message type we are producing
|
||||||
msg_type: ResponseMsgType,
|
msg_type: ResponseMsgType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: ByteSlice> ResponseRef<B> {
|
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> {
|
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||||
ResponseRefMaker::new(buf)?.parse()
|
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> {
|
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||||
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
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> {
|
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||||
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the message type [Self] contains
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse]
|
||||||
pub fn message_type(&self) -> ResponseMsgType {
|
pub fn message_type(&self) -> ResponseMsgType {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(_) => ResponseMsgType::Ping,
|
Self::Ping(_) => ResponseMsgType::Ping,
|
||||||
@@ -111,6 +153,7 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a API message response, typed.
|
||||||
pub enum ResponseRef<B> {
|
pub enum ResponseRef<B> {
|
||||||
Ping(Ref<B, PingResponse>),
|
Ping(Ref<B, PingResponse>),
|
||||||
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
||||||
@@ -122,6 +165,11 @@ impl<B> ResponseRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSlice,
|
B: ByteSlice,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse].
|
||||||
pub fn bytes(&self) -> &[u8] {
|
pub fn bytes(&self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes(),
|
Self::Ping(r) => r.bytes(),
|
||||||
@@ -136,6 +184,7 @@ impl<B> ResponseRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSliceMut,
|
B: ByteSliceMut,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference; mutably
|
||||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes_mut(),
|
Self::Ping(r) => r.bytes_mut(),
|
||||||
|
|||||||
@@ -2,7 +2,21 @@ use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, Req
|
|||||||
use std::{collections::VecDeque, os::fd::OwnedFd};
|
use std::{collections::VecDeque, os::fd::OwnedFd};
|
||||||
use zerocopy::{ByteSlice, ByteSliceMut};
|
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||||
|
|
||||||
|
/// 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 {
|
pub trait Server {
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
||||||
|
///
|
||||||
|
/// It merely takes a buffer and returns that same buffer.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the example of how to use the API in [crate::api].
|
||||||
fn ping(
|
fn ping(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &PingRequest,
|
req: &PingRequest,
|
||||||
@@ -10,6 +24,51 @@ pub trait Server {
|
|||||||
res: &mut PingResponse,
|
res: &mut PingResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> 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(
|
fn supply_keypair(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::SupplyKeypairRequest,
|
req: &super::SupplyKeypairRequest,
|
||||||
@@ -17,6 +76,32 @@ pub trait Server {
|
|||||||
res: &mut super::SupplyKeypairResponse,
|
res: &mut super::SupplyKeypairResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> 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(
|
fn add_listen_socket(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::AddListenSocketRequest,
|
req: &super::AddListenSocketRequest,
|
||||||
@@ -24,6 +109,31 @@ pub trait Server {
|
|||||||
res: &mut super::AddListenSocketResponse,
|
res: &mut super::AddListenSocketResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> 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(
|
fn add_psk_broker(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::AddPskBrokerRequest,
|
req: &super::AddPskBrokerRequest,
|
||||||
@@ -31,6 +141,11 @@ pub trait Server {
|
|||||||
res: &mut super::AddPskBrokerResponse,
|
res: &mut super::AddPskBrokerResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> 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>(
|
fn dispatch<ReqBuf, ResBuf>(
|
||||||
&mut self,
|
&mut self,
|
||||||
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
||||||
@@ -52,6 +167,14 @@ pub trait Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>(
|
fn handle_message<ReqBuf, ResBuf>(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: ReqBuf,
|
req: ReqBuf,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::config::Rosenpass as RosenpassConfig;
|
|||||||
|
|
||||||
use super::config::ApiConfig;
|
use super::config::ApiConfig;
|
||||||
|
|
||||||
|
/// Additional command line arguments for the API
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct ApiCli {
|
pub struct ApiCli {
|
||||||
@@ -27,10 +28,14 @@ pub struct ApiCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<()> {
|
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
||||||
self.apply_to_api_config(&mut cfg.api)
|
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<()> {
|
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
|
||||||
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
||||||
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::app_server::AppServer;
|
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 {
|
pub struct ApiConfig {
|
||||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||||
/// connections on
|
/// connections on
|
||||||
@@ -23,6 +24,10 @@ pub struct ApiConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<()> {
|
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
for path in self.listen_path.iter() {
|
for path in self.listen_path.iter() {
|
||||||
srv.add_api_listener(UnixListener::bind(path)?)?;
|
srv.add_api_listener(UnixListener::bind(path)?)?;
|
||||||
@@ -39,10 +44,12 @@ impl ApiConfig {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sum of all the API sources configured in here
|
||||||
pub fn count_api_sources(&self) -> usize {
|
pub fn count_api_sources(&self) -> usize {
|
||||||
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
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 {
|
pub fn has_api_sources(&self) -> bool {
|
||||||
self.count_api_sources() > 0
|
self.count_api_sources() > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ struct MioConnectionBuffers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[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 {
|
pub struct MioConnection {
|
||||||
io: UnixStream,
|
io: UnixStream,
|
||||||
mio_token: mio::Token,
|
mio_token: mio::Token,
|
||||||
@@ -62,6 +65,8 @@ pub struct MioConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MioConnection {
|
impl MioConnection {
|
||||||
|
/// 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> {
|
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
|
||||||
let mio_token = app_server.mio_token_dispenser.dispense();
|
let mio_token = app_server.mio_token_dispenser.dispense();
|
||||||
app_server
|
app_server
|
||||||
@@ -88,7 +93,9 @@ impl MioConnection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shoud_close(&self) -> bool {
|
/// Checks if this unix stream should be closed by the enclosing
|
||||||
|
/// structure
|
||||||
|
pub fn should_close(&self) -> bool {
|
||||||
let exhausted = self
|
let exhausted = self
|
||||||
.buffers
|
.buffers
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -97,22 +104,30 @@ impl MioConnection {
|
|||||||
self.invalid_read && exhausted
|
self.invalid_read && exhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close and deregister this particular API connection
|
||||||
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
||||||
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the mio token
|
||||||
pub fn mio_token(&self) -> mio::Token {
|
pub fn mio_token(&self) -> mio::Token {
|
||||||
self.mio_token
|
self.mio_token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We require references to both [MioConnection] and to the [AppServer] that contains it.
|
||||||
pub trait MioConnectionContext {
|
pub trait MioConnectionContext {
|
||||||
|
/// Reference to the [MioConnection] we are focusing on
|
||||||
fn mio_connection(&self) -> &MioConnection;
|
fn mio_connection(&self) -> &MioConnection;
|
||||||
|
/// Reference to the [AppServer] that contains the [Self::mio_connection]
|
||||||
fn app_server(&self) -> &AppServer;
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Mutable reference to the [MioConnection] we are focusing on
|
||||||
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
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;
|
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<()> {
|
fn poll(&mut self) -> anyhow::Result<()> {
|
||||||
macro_rules! short {
|
macro_rules! short {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
@@ -133,6 +148,7 @@ pub trait MioConnectionContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [Self::poll] to process incoming messages
|
||||||
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
self.with_buffers_stolen(|this, bufs| {
|
self.with_buffers_stolen(|this, bufs| {
|
||||||
// Acquire request & response. Caller is responsible to make sure
|
// Acquire request & response. Caller is responsible to make sure
|
||||||
@@ -156,6 +172,7 @@ pub trait MioConnectionContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [Self::poll] to write data in the send buffer to the unix stream
|
||||||
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
if self.write_buf_mut().exhausted() {
|
if self.write_buf_mut().exhausted() {
|
||||||
return Ok(Some(()));
|
return Ok(Some(()));
|
||||||
@@ -194,6 +211,7 @@ pub trait MioConnectionContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [Self::poll] to check for messages to receive
|
||||||
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -257,12 +275,14 @@ pub trait MioConnectionContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forwards to [MioConnection::mio_token]
|
||||||
fn mio_token(&self) -> mio::Token {
|
fn mio_token(&self) -> mio::Token {
|
||||||
self.mio_connection().mio_token()
|
self.mio_connection().mio_token()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forwards to [MioConnection::should_close]
|
||||||
fn should_close(&self) -> bool {
|
fn should_close(&self) -> bool {
|
||||||
self.mio_connection().shoud_close()
|
self.mio_connection().should_close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +319,7 @@ trait MioConnectionContextPrivate: MioConnectionContext {
|
|||||||
|
|
||||||
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
||||||
|
|
||||||
|
/// Every [MioConnectionContext] is also a [ApiHandlerContext]
|
||||||
impl<T> ApiHandlerContext for T
|
impl<T> ApiHandlerContext for T
|
||||||
where
|
where
|
||||||
T: ?Sized + MioConnectionContext,
|
T: ?Sized + MioConnectionContext,
|
||||||
|
|||||||
@@ -10,41 +10,59 @@ use crate::app_server::{AppServer, AppServerIoSource};
|
|||||||
|
|
||||||
use super::{MioConnection, MioConnectionContext};
|
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)]
|
#[derive(Default, Debug)]
|
||||||
pub struct MioManager {
|
pub struct MioManager {
|
||||||
listeners: Vec<UnixListener>,
|
listeners: Vec<UnixListener>,
|
||||||
connections: Vec<Option<MioConnection>>,
|
connections: Vec<Option<MioConnection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Points at a particular source of IO events inside [MioManager]
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum MioManagerIoSource {
|
pub enum MioManagerIoSource {
|
||||||
|
// Source of IO events is the Nth unix socket listener (see [MioManager::listeners])
|
||||||
Listener(usize),
|
Listener(usize),
|
||||||
|
// Source of IO events is the Nth unix socket listener (see [MioManager::connections])
|
||||||
Connection(usize),
|
Connection(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MioManager {
|
impl MioManager {
|
||||||
|
/// Construct an empty [Self]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Focus in on a particular [MioConnection] inside a [MioManager]
|
||||||
|
///
|
||||||
|
/// This is mainly used to implement [MioConnectionContext].
|
||||||
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
||||||
|
/// [MioConnectionContext] to access the [MioManager] instance and [AppServer]
|
||||||
ctx: &'a mut T,
|
ctx: &'a mut T,
|
||||||
|
/// Index of the connection referenced to by [Self]
|
||||||
conn_idx: usize,
|
conn_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
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 {
|
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
||||||
Self { ctx, conn_idx }
|
Self { ctx, conn_idx }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MioManagerContext {
|
pub trait MioManagerContext {
|
||||||
|
/// Reference to the [MioManager]
|
||||||
fn mio_manager(&self) -> &MioManager;
|
fn mio_manager(&self) -> &MioManager;
|
||||||
|
/// Reference to the [MioManager], mutably
|
||||||
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
||||||
|
/// Reference to the [AppServer] this [MioManager] is associated with
|
||||||
fn app_server(&self) -> &AppServer;
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Mutable reference to the [AppServer] this [MioManager] is associated with
|
||||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
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<()> {
|
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
|
||||||
let srv = self.app_server_mut();
|
let srv = self.app_server_mut();
|
||||||
let mio_token = srv.mio_token_dispenser.dispense();
|
let mio_token = srv.mio_token_dispenser.dispense();
|
||||||
@@ -64,6 +82,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new connection to an API client
|
||||||
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
||||||
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
||||||
let mio_token = connection.mio_token();
|
let mio_token = connection.mio_token();
|
||||||
@@ -84,6 +103,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Poll a particular [MioManagerIoSource] in this [MioManager]
|
||||||
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
||||||
use MioManagerIoSource as S;
|
use MioManagerIoSource as S;
|
||||||
match io_source {
|
match io_source {
|
||||||
@@ -93,12 +113,14 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check for new connections and poll all the [MioConnectionContext]s managed by [Self]
|
||||||
fn poll(&mut self) -> anyhow::Result<()> {
|
fn poll(&mut self) -> anyhow::Result<()> {
|
||||||
self.accept_connections()?;
|
self.accept_connections()?;
|
||||||
self.poll_connections()?;
|
self.poll_connections()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check all the [UnixListener]s managed by this [MioManager] for new connections
|
||||||
fn accept_connections(&mut self) -> io::Result<()> {
|
fn accept_connections(&mut self) -> io::Result<()> {
|
||||||
for idx in 0..self.mio_manager_mut().listeners.len() {
|
for idx in 0..self.mio_manager_mut().listeners.len() {
|
||||||
self.accept_from(idx)?;
|
self.accept_from(idx)?;
|
||||||
@@ -106,6 +128,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check a particular [UnixListener] managed by this for new connections.
|
||||||
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
||||||
// Accept connection until the socket would block or returns another error
|
// Accept connection until the socket would block or returns another error
|
||||||
// TODO: This currently only adds connections--we eventually need the ability to remove
|
// TODO: This currently only adds connections--we eventually need the ability to remove
|
||||||
@@ -122,6 +145,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call [MioConnectionContext::poll] on all the [MioConnection]s in This
|
||||||
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
||||||
for idx in 0..self.mio_manager().connections.len() {
|
for idx in 0..self.mio_manager().connections.len() {
|
||||||
self.poll_particular_connection(idx)?;
|
self.poll_particular_connection(idx)?;
|
||||||
@@ -129,6 +153,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call [MioConnectionContext::poll] on a particular connection
|
||||||
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
||||||
if self.mio_manager().connections[idx].is_none() {
|
if self.mio_manager().connections[idx].is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
//! 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 api_handler;
|
||||||
mod boilerplate;
|
mod boilerplate;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/// This contains the bulk of the rosenpass server IO handling code whereas
|
||||||
|
/// the actual cryptographic code lives in the [crate::protocol] module
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -40,6 +42,7 @@ use std::slice;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use crate::config::ProtocolVersion;
|
||||||
use crate::protocol::BuildCryptoServer;
|
use crate::protocol::BuildCryptoServer;
|
||||||
use crate::protocol::HostIdentification;
|
use crate::protocol::HostIdentification;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -49,33 +52,78 @@ use crate::{
|
|||||||
use rosenpass_util::attempt;
|
use rosenpass_util::attempt;
|
||||||
use rosenpass_util::b64::B64Display;
|
use rosenpass_util::b64::B64Display;
|
||||||
|
|
||||||
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
/// The maximum size of a base64 encoded symmetric key (estimate)
|
||||||
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
||||||
|
/// The maximum size of a base64 peer ID (estimate)
|
||||||
|
pub const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
||||||
|
|
||||||
|
/// The zero IPv4 address; this is generally used to tell network servers to choose any interface
|
||||||
|
/// when listening
|
||||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||||
|
/// The zero IPv6 address; this is generally used to tell network servers to choose any interface
|
||||||
|
/// when listening
|
||||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
/// Ratio of blocking epoll(7) polls to non-blocking polls at which the rosenpass server
|
||||||
|
/// assumes it is under load (i.e. a DOS attack may be happening)
|
||||||
const UNDER_LOAD_RATIO: f64 = 0.5;
|
const UNDER_LOAD_RATIO: f64 = 0.5;
|
||||||
|
/// Period at which the DOS detection code updates whether there is an "under load" status
|
||||||
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
|
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
const BROKER_ID_BYTES: usize = 8;
|
pub const BROKER_ID_BYTES: usize = 8;
|
||||||
|
|
||||||
fn ipv4_any_binding() -> SocketAddr {
|
/// IPv4 address that tells the network layer to listen on any interface
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [AppServer::new].
|
||||||
|
pub fn ipv4_any_binding() -> SocketAddr {
|
||||||
// addr, port
|
// addr, port
|
||||||
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ipv6_any_binding() -> SocketAddr {
|
/// IPv6 address that tells the network layer to listen on any interface
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [AppServer::new].
|
||||||
|
pub fn ipv6_any_binding() -> SocketAddr {
|
||||||
// addr, port, flowinfo, scope_id
|
// addr, port, flowinfo, scope_id
|
||||||
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is used to assign indices to MIO (epoll) event sources
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MioTokenDispenser {
|
pub struct MioTokenDispenser {
|
||||||
counter: usize,
|
pub counter: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MioTokenDispenser {
|
impl MioTokenDispenser {
|
||||||
|
/// Produces a single IO event source ID
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Use is quite straightforward:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::app_server::MioTokenDispenser;
|
||||||
|
/// use mio::Token;
|
||||||
|
///
|
||||||
|
/// let mut dispenser = MioTokenDispenser {
|
||||||
|
/// counter: 0
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let t1 = dispenser.dispense();
|
||||||
|
/// let t2 = dispenser.dispense();
|
||||||
|
///
|
||||||
|
/// assert_ne!(t1, t2);
|
||||||
|
///
|
||||||
|
/// // If you inspected the output, you would find that the dispenser is really just a counter.
|
||||||
|
/// // Though this is an implementation detail
|
||||||
|
/// assert_eq!(t1, Token(0));
|
||||||
|
/// assert_eq!(t2, Token(1));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
pub fn dispense(&mut self) -> Token {
|
pub fn dispense(&mut self) -> Token {
|
||||||
let r = self.counter;
|
let r = self.counter;
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
@@ -83,42 +131,132 @@ impl MioTokenDispenser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of WireGuard brokers
|
||||||
|
///
|
||||||
|
/// Each WireGuard peer ([AppPeer]) is assigned a broker ([AppPeer::broker_peer]).
|
||||||
|
///
|
||||||
|
/// When a new has been exchanged, then its associated broker is called to transmit the key
|
||||||
|
/// to WireGuard (or to do something else).
|
||||||
|
///
|
||||||
|
/// Brokers live in [AppServer::brokers]. They are added/removed from the AppServer via
|
||||||
|
/// [AppServer::register_broker] and [AppServer::unregister_broker]. PSKs are distributed
|
||||||
|
/// to their respective brokers via [AppPeerPtr::set_psk].
|
||||||
|
///
|
||||||
|
/// Note that the entire broker system is an experimental feature; it is not up to the
|
||||||
|
/// same quality standards as the rest of the code. In particular, the use of [BROKER_ID_BYTES]
|
||||||
|
/// and a hash map is simultaneously overengineered and not really that useful for what it is
|
||||||
|
/// doing.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct BrokerStore {
|
pub struct BrokerStore {
|
||||||
|
/// The collection of WireGuard brokers. See [Self].
|
||||||
pub store: HashMap<
|
pub store: HashMap<
|
||||||
Public<BROKER_ID_BYTES>,
|
Public<BROKER_ID_BYTES>,
|
||||||
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a broker imbued with utility methods
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
|
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
|
||||||
|
|
||||||
|
/// This is the broker configuration for a particular broker peer
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BrokerPeer {
|
pub struct BrokerPeer {
|
||||||
|
/// Reference to the broker used for this particular peer
|
||||||
ptr: BrokerStorePtr,
|
ptr: BrokerStorePtr,
|
||||||
|
/// Configuration for a WireGuard broker.
|
||||||
|
///
|
||||||
|
/// This is woefully overengineered and there is very little reason why the broker
|
||||||
|
/// configuration should not live in the particular WireGuard broker.
|
||||||
peer_cfg: Box<dyn WireguardBrokerCfg>,
|
peer_cfg: Box<dyn WireguardBrokerCfg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrokerPeer {
|
impl BrokerPeer {
|
||||||
|
/// Create a broker peer
|
||||||
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
|
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
|
||||||
Self { ptr, peer_cfg }
|
Self { ptr, peer_cfg }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the pointer to WireGuard PSK broker used with this peer
|
||||||
pub fn ptr(&self) -> &BrokerStorePtr {
|
pub fn ptr(&self) -> &BrokerStorePtr {
|
||||||
&self.ptr
|
&self.ptr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// IO/BusinessLogic information for a particular protocol peer.
|
||||||
|
///
|
||||||
|
/// There is a one-to-one correspondence between this struct and [crate::protocol::Peer];
|
||||||
|
/// whereas the struct in the protocol module stores information specific to the cryptographic layer,
|
||||||
|
/// this struct stores information IO information.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct AppPeer {
|
pub struct AppPeer {
|
||||||
|
/// If set, then [AppServer::output_key] will write generated output keys
|
||||||
|
/// to a file configured here and produce information on standard out to
|
||||||
|
/// notify the calling process that
|
||||||
pub outfile: Option<PathBuf>,
|
pub outfile: Option<PathBuf>,
|
||||||
|
/// If this option is set, then [AppServer::output_key] will send generated output
|
||||||
|
/// keys to the broker configured here
|
||||||
pub broker_peer: Option<BrokerPeer>,
|
pub broker_peer: Option<BrokerPeer>,
|
||||||
|
/// This is the network address configured for a particular peer at program start.
|
||||||
|
///
|
||||||
|
/// I.e. this is the address the rosenpass program will send [crate::msgs::InitHello]
|
||||||
|
/// packets to, trying to exchange a key.
|
||||||
|
///
|
||||||
|
/// Note that the remote peer may connect with another address. See [Self::current_endpoint].
|
||||||
pub initial_endpoint: Option<Endpoint>,
|
pub initial_endpoint: Option<Endpoint>,
|
||||||
|
/// The network address currently used for a particular peer.
|
||||||
|
///
|
||||||
|
/// This is not necessarily the address that was configured at program start (see [Self::initial_endpoint]),
|
||||||
|
/// because the remote peer can initiate handshakes from an arbitrary network address.
|
||||||
|
///
|
||||||
|
/// If another peer successfully connects to this one from any address, then this field will
|
||||||
|
/// be updated to reflect which address this was.
|
||||||
pub current_endpoint: Option<Endpoint>,
|
pub current_endpoint: Option<Endpoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppPeer {
|
impl AppPeer {
|
||||||
|
/// Retrieve the [Endpoint] associated with this peer.
|
||||||
|
///
|
||||||
|
/// I.e. the [Self::current_endpoint] if set and [Self::initial_endpoint] otherwise.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::app_server::{Endpoint, AppPeer};
|
||||||
|
/// use rosenpass_util::functional::run;
|
||||||
|
///
|
||||||
|
/// let mut peer = AppPeer {
|
||||||
|
/// outfile: None,
|
||||||
|
/// broker_peer: None,
|
||||||
|
/// initial_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:0".to_string())?),
|
||||||
|
/// current_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:1".to_string())?),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// fn same(a: Option<&Endpoint>, b: Option<&Endpoint>) -> bool {
|
||||||
|
/// if a.is_none() && b.is_none() {
|
||||||
|
/// return true;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// run(|| Some(std::ptr::eq(a?, b?)) )
|
||||||
|
/// .unwrap_or(a.is_some() == b.is_some())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
|
||||||
|
///
|
||||||
|
/// let mut tmp = None;
|
||||||
|
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
|
||||||
|
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
|
||||||
|
///
|
||||||
|
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
|
||||||
|
/// std::mem::swap(&mut tmp, &mut peer.current_endpoint);
|
||||||
|
/// assert!(same(peer.endpoint(), peer.initial_endpoint.as_ref()));
|
||||||
|
///
|
||||||
|
/// peer.initial_endpoint = None;
|
||||||
|
/// peer.current_endpoint = None;
|
||||||
|
/// assert!(peer.endpoint().is_none());
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn endpoint(&self) -> Option<&Endpoint> {
|
pub fn endpoint(&self) -> Option<&Endpoint> {
|
||||||
self.current_endpoint
|
self.current_endpoint
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -126,6 +264,8 @@ impl AppPeer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// No longer in use since we have the broker system (see [BrokerPeer])
|
||||||
|
/// TODO: Remove
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct WireguardOut {
|
pub struct WireguardOut {
|
||||||
// impl KeyOutput
|
// impl KeyOutput
|
||||||
@@ -134,12 +274,20 @@ pub struct WireguardOut {
|
|||||||
pub extra_params: Vec<String>,
|
pub extra_params: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to indicate whether the rosenpass server is in normal operating
|
||||||
|
/// conditions or under load (i.e. a DOS attack is happening)
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum DoSOperation {
|
pub enum DoSOperation {
|
||||||
UnderLoad,
|
UnderLoad,
|
||||||
Normal,
|
Normal,
|
||||||
}
|
}
|
||||||
/// Integration test helpers for AppServer
|
/// Integration test helpers for AppServer
|
||||||
|
///
|
||||||
|
/// TODO: Remove; this is no way to write integration tests
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [AppServer]
|
||||||
#[derive(Debug, Builder)]
|
#[derive(Debug, Builder)]
|
||||||
#[builder(pattern = "owned")]
|
#[builder(pattern = "owned")]
|
||||||
pub struct AppServerTest {
|
pub struct AppServerTest {
|
||||||
@@ -151,41 +299,100 @@ pub struct AppServerTest {
|
|||||||
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This represents a some source of IO operations in the context of the Rosenpass server
|
||||||
|
///
|
||||||
|
/// I.e. this identifies some structure that could be marked as "ready for IO" by [mio]
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum AppServerIoSource {
|
pub enum AppServerIoSource {
|
||||||
|
/// IO source refers to a socket in [AppServer::sockets]
|
||||||
Socket(usize),
|
Socket(usize),
|
||||||
|
/// IO source refers to a PSK broker in [AppServer::brokers]
|
||||||
PskBroker(Public<BROKER_ID_BYTES>),
|
PskBroker(Public<BROKER_ID_BYTES>),
|
||||||
|
/// IO source refers to some IO sources used in the API;
|
||||||
|
/// see [AppServer::api_manager]
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
MioManager(crate::api::mio::MioManagerIoSource),
|
MioManager(crate::api::mio::MioManagerIoSource),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of epoll(7) events Rosenpass can receive at a time
|
||||||
const EVENT_CAPACITY: usize = 20;
|
const EVENT_CAPACITY: usize = 20;
|
||||||
|
|
||||||
/// Holds the state of the application, namely the external IO
|
/// This holds pretty much all of the state of the Rosenpass application
|
||||||
|
/// including the cryptographic state in [Self::crypto_site]
|
||||||
///
|
///
|
||||||
/// Responsible for file IO, network IO
|
/// Responsible for file IO, network IO, etc…
|
||||||
// TODO add user control via unix domain socket and stdin/stdout
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/app_server_example.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppServer {
|
pub struct AppServer {
|
||||||
|
/// Contains the actual cryptographic implementation.
|
||||||
|
///
|
||||||
|
/// Because the API supports initializing the server with a keypair
|
||||||
|
/// and CryptoServer needs to be initialized with a keypair, the struct
|
||||||
|
/// is wrapped in a ConstructionSite
|
||||||
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
|
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
|
||||||
|
/// The UDP sockets used to send and receive protocol messages
|
||||||
pub sockets: Vec<mio::net::UdpSocket>,
|
pub sockets: Vec<mio::net::UdpSocket>,
|
||||||
|
/// Buffer for [mio] (epoll(7), async IO handling) IO events
|
||||||
pub events: mio::Events,
|
pub events: mio::Events,
|
||||||
|
/// Supplemental buffer for [mio] events. See the inline documentation of [AppServer::try_recv]
|
||||||
|
/// for details.
|
||||||
pub short_poll_queue: VecDeque<mio::event::Event>,
|
pub short_poll_queue: VecDeque<mio::event::Event>,
|
||||||
|
/// We have two different polling modes; long polling (legacy) and short polling (new and
|
||||||
|
/// somewhat experimental). See [AppServer::try_recv] for details
|
||||||
pub performed_long_poll: bool,
|
pub performed_long_poll: bool,
|
||||||
|
/// Events produced by [mio] refer to IO sources by a numeric token assigned to them
|
||||||
|
/// (see [MioTokenDispenser]). This index associateds mio token with the specific IO
|
||||||
|
/// source stored in this object
|
||||||
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
|
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
|
||||||
|
/// Asynchronous IO source
|
||||||
pub mio_poll: mio::Poll,
|
pub mio_poll: mio::Poll,
|
||||||
|
/// MIO associates IO sources with numeric tokens. This struct takes care of generating these
|
||||||
|
/// tokens
|
||||||
pub mio_token_dispenser: MioTokenDispenser,
|
pub mio_token_dispenser: MioTokenDispenser,
|
||||||
|
/// Helpers handling communication with WireGuard; these take a generated key and forward it to
|
||||||
|
/// WireGuard
|
||||||
pub brokers: BrokerStore,
|
pub brokers: BrokerStore,
|
||||||
|
/// This is our view of the peers; generally every peer in here is associated with one peer in
|
||||||
|
/// CryptoServer
|
||||||
pub peers: Vec<AppPeer>,
|
pub peers: Vec<AppPeer>,
|
||||||
|
/// If set to [Verbosity::Verbose], then some extra information will be printed
|
||||||
|
/// at the info log level
|
||||||
pub verbosity: Verbosity,
|
pub verbosity: Verbosity,
|
||||||
|
/// Used by [AppServer::try_recv] to ensure that all packages have been read
|
||||||
|
/// from the UDP sockets
|
||||||
pub all_sockets_drained: bool,
|
pub all_sockets_drained: bool,
|
||||||
|
/// Whether network message handling determined that a Denial of Service attack is happening
|
||||||
pub under_load: DoSOperation,
|
pub under_load: DoSOperation,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
pub blocking_polls_count: usize,
|
pub blocking_polls_count: usize,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
pub non_blocking_polls_count: usize,
|
pub non_blocking_polls_count: usize,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
pub unpolled_count: usize,
|
pub unpolled_count: usize,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
pub last_update_time: Instant,
|
pub last_update_time: Instant,
|
||||||
|
/// Used by integration tests to force [Self] into DoS condition
|
||||||
|
/// and to terminate the AppServer after the test is complete
|
||||||
pub test_helpers: Option<AppServerTest>,
|
pub test_helpers: Option<AppServerTest>,
|
||||||
|
/// Helper for integration tests running rosenpass as a subprocess
|
||||||
|
/// to terminate properly upon receiving an appropriate system signal.
|
||||||
|
///
|
||||||
|
/// This is primarily needed for coverage testing, since llvm-cov does not
|
||||||
|
/// write coverage reports to disk when a process is stopped by the default
|
||||||
|
/// signal handler.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rosenpass/rosenpass/issues/385>
|
||||||
|
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||||
|
pub term_signal: terminate::TerminateRequested,
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
|
/// The Rosenpass unix socket API handler; this is an experimental
|
||||||
|
/// feature that can be used to embed Rosenpass in external applications
|
||||||
|
/// via communication by unix socket
|
||||||
pub api_manager: crate::api::mio::MioManager,
|
pub api_manager: crate::api::mio::MioManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,14 +406,19 @@ pub struct AppServer {
|
|||||||
pub struct SocketPtr(pub usize);
|
pub struct SocketPtr(pub usize);
|
||||||
|
|
||||||
impl SocketPtr {
|
impl SocketPtr {
|
||||||
|
/// Retrieve the concrete udp socket associated with the pointer
|
||||||
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
|
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
|
||||||
&srv.sockets[self.0]
|
&srv.sockets[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the concrete udp socket associated with the pointer, mutably
|
||||||
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
|
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
|
||||||
&mut srv.sockets[self.0]
|
&mut srv.sockets[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a UDP packet to another address.
|
||||||
|
///
|
||||||
|
/// Merely forwards to [mio::net::UdpSocket::send_to]
|
||||||
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
|
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
|
||||||
self.get(srv).send_to(buf, addr)?;
|
self.get(srv).send_to(buf, addr)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -214,28 +426,41 @@ impl SocketPtr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Index based pointer to a Peer
|
/// Index based pointer to a Peer
|
||||||
|
///
|
||||||
|
/// This allows retrieving both the io-oriented and the cryptographic information
|
||||||
|
/// about a peer.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct AppPeerPtr(pub usize);
|
pub struct AppPeerPtr(pub usize);
|
||||||
|
|
||||||
impl AppPeerPtr {
|
impl AppPeerPtr {
|
||||||
/// Takes an index based handle and returns the actual peer
|
/// Takes an pointer from the cryptography subsystem
|
||||||
|
/// in [AppServer::crypto_site] and derives the associated AppPeerPtr
|
||||||
|
/// in [AppServer]
|
||||||
pub fn lift(p: PeerPtr) -> Self {
|
pub fn lift(p: PeerPtr) -> Self {
|
||||||
Self(p.0)
|
Self(p.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an index based handle to one Peer
|
/// Turns this pointer into a cryptographic peer pointer for [CryptoServer]
|
||||||
|
/// in [AppServer::crypto_site]
|
||||||
pub fn lower(&self) -> PeerPtr {
|
pub fn lower(&self) -> PeerPtr {
|
||||||
PeerPtr(self.0)
|
PeerPtr(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the [AppPeer] pointed to by [Self]
|
||||||
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
||||||
&srv.peers[self.0]
|
&srv.peers[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the [AppPeer] pointed to by [Self], mutably
|
||||||
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
||||||
&mut srv.peers[self.0]
|
&mut srv.peers[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the associated WireGuard PSK broker via [BrokerStorePtr]
|
||||||
|
/// to upload a new PSK.
|
||||||
|
///
|
||||||
|
/// If no PSK broker is set and [AppPeer::outfile] is none, then
|
||||||
|
/// this prints a warning
|
||||||
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
|
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
|
||||||
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
|
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
|
||||||
let config = broker.peer_cfg.create_config(psk);
|
let config = broker.peer_cfg.create_config(psk);
|
||||||
@@ -248,17 +473,34 @@ impl AppPeerPtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of [AppServer::poll].
|
||||||
|
///
|
||||||
|
/// Instructs [AppServer::event_loop_without_error_handling] on how to proceed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AppPollResult {
|
pub enum AppPollResult {
|
||||||
|
/// Erase the key for a given peer. Corresponds to [crate::protocol::PollResult::DeleteKey]
|
||||||
DeleteKey(AppPeerPtr),
|
DeleteKey(AppPeerPtr),
|
||||||
|
/// Send an initiation to the given peer. Corresponds to [crate::protocol::PollResult::SendInitiation]
|
||||||
SendInitiation(AppPeerPtr),
|
SendInitiation(AppPeerPtr),
|
||||||
|
/// Send a retransmission to the given peer. Corresponds to
|
||||||
|
/// [crate::protocol::PollResult::SendRetransmission]
|
||||||
SendRetransmission(AppPeerPtr),
|
SendRetransmission(AppPeerPtr),
|
||||||
|
/// Received a network message.
|
||||||
|
///
|
||||||
|
/// This is the only case without a correspondence in [crate::protocol::PollResult]
|
||||||
ReceivedMessage(usize, Endpoint),
|
ReceivedMessage(usize, Endpoint),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The reason why we are outputting a key
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum KeyOutputReason {
|
pub enum KeyOutputReason {
|
||||||
|
/// The reason is that a new key for the given peer was successfully exchanged
|
||||||
Exchanged,
|
Exchanged,
|
||||||
|
/// The reason is, that no key could be exchanged with the peer before the output
|
||||||
|
/// key lifetime was reached; a [AppPollResult::DeleteKey] event was issued.
|
||||||
|
///
|
||||||
|
/// The key we output in this case is chosen randomly and serves to securely
|
||||||
|
/// erase whatever key is currently stored.
|
||||||
Stale,
|
Stale,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,14 +539,21 @@ impl std::fmt::Display for Endpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A network address bound to a particular socket.
|
||||||
|
///
|
||||||
|
/// We need the information which socket is used because different listen sockets
|
||||||
|
/// might be on different networks.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SocketBoundEndpoint {
|
pub struct SocketBoundEndpoint {
|
||||||
/// The socket the address can be reached under; this is generally
|
/// The socket the address can be reached under; this is generally
|
||||||
/// determined when we actually receive an RespHello message
|
/// determined when we actually receive an RespHello message
|
||||||
socket: SocketPtr,
|
socket: SocketPtr,
|
||||||
/// Just the address
|
/// The network address
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
/// identifier
|
/// Byte representation of this socket bound network address.
|
||||||
|
/// Generated through [SocketBoundEndpoint::to_bytes].
|
||||||
|
///
|
||||||
|
/// Read through [HostIdentification::encode]
|
||||||
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
|
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,16 +564,22 @@ impl std::fmt::Display for SocketBoundEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SocketBoundEndpoint {
|
impl SocketBoundEndpoint {
|
||||||
|
/// Length in bytes of the serialized socket index
|
||||||
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
|
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
|
||||||
|
/// Length in bytes of the serialized ipv6 address
|
||||||
const IPV6_SIZE: usize = 16;
|
const IPV6_SIZE: usize = 16;
|
||||||
|
/// Length in bytes of the serialized port
|
||||||
const PORT_SIZE: usize = 2;
|
const PORT_SIZE: usize = 2;
|
||||||
|
/// Length in bytes of the serialized ipv6 address scope (see [SocketAddrV6::scope_id])
|
||||||
const SCOPE_ID_SIZE: usize = 4;
|
const SCOPE_ID_SIZE: usize = 4;
|
||||||
|
|
||||||
|
/// Length in size of
|
||||||
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
|
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
|
||||||
+ SocketBoundEndpoint::IPV6_SIZE
|
+ SocketBoundEndpoint::IPV6_SIZE
|
||||||
+ SocketBoundEndpoint::PORT_SIZE
|
+ SocketBoundEndpoint::PORT_SIZE
|
||||||
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
|
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
|
||||||
|
|
||||||
|
/// Produce a new [Self]
|
||||||
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
|
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
|
||||||
let bytes = Self::to_bytes(&socket, &addr);
|
let bytes = Self::to_bytes(&socket, &addr);
|
||||||
Self {
|
Self {
|
||||||
@@ -334,6 +589,7 @@ impl SocketBoundEndpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes [HostIdentification::encode] for [Self]. Value cached in [Self::bytes].
|
||||||
fn to_bytes(
|
fn to_bytes(
|
||||||
socket: &SocketPtr,
|
socket: &SocketPtr,
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
@@ -368,12 +624,18 @@ impl HostIdentification for SocketBoundEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
/// Start discovery from some addresses
|
/// Given a list of potential network addresses, start peer discovery.
|
||||||
|
///
|
||||||
|
/// Will send initiations to different addresses given here on each [crate::msgs::InitHello]
|
||||||
|
/// retransmission during the peer discovery phase.
|
||||||
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||||
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
|
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start endpoint discovery from a hostname
|
/// Given a hostname, start peer discovery.
|
||||||
|
///
|
||||||
|
/// Will send initiations to different addresses assigned to the host name
|
||||||
|
/// on each [crate::msgs::InitHello] retransmission during the peer discovery phase.
|
||||||
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
|
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
|
||||||
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
|
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
|
||||||
Ok(Endpoint::Discovery(host))
|
Ok(Endpoint::Discovery(host))
|
||||||
@@ -404,6 +666,8 @@ impl Endpoint {
|
|||||||
Some(Self::discovery_from_addresses(addrs))
|
Some(Self::discovery_from_addresses(addrs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a message to the address referenced by this endpoint or to one of
|
||||||
|
/// the endpoints if we are in the peer discovery phase for this endpoint
|
||||||
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
|
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
|
||||||
use Endpoint::*;
|
use Endpoint::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -412,6 +676,9 @@ impl Endpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of addresses this endpoint may be associated with.
|
||||||
|
///
|
||||||
|
/// During peer discovery, this can be multiple addresses.
|
||||||
fn addresses(&self) -> &[SocketAddr] {
|
fn addresses(&self) -> &[SocketAddr] {
|
||||||
use Endpoint::*;
|
use Endpoint::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -449,7 +716,14 @@ impl Endpoint {
|
|||||||
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
|
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HostPathDiscoveryEndpoint {
|
pub struct HostPathDiscoveryEndpoint {
|
||||||
scouting_state: Cell<(usize, usize)>, // addr_off, sock_off
|
/// Round robin index the next [Self::send_scouting] call should send packets to
|
||||||
|
///
|
||||||
|
/// (address offset, socket offset)
|
||||||
|
///
|
||||||
|
/// Including the socket here accounts for the fact that some network addresses may be
|
||||||
|
/// reachable only through particular UDP sockets
|
||||||
|
scouting_state: Cell<(usize, usize)>,
|
||||||
|
/// List of addresses fir oeer discovery
|
||||||
addresses: Vec<SocketAddr>,
|
addresses: Vec<SocketAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +734,7 @@ impl std::fmt::Display for HostPathDiscoveryEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HostPathDiscoveryEndpoint {
|
impl HostPathDiscoveryEndpoint {
|
||||||
|
/// Initiate a peer discovery process through a list of potential addresses
|
||||||
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||||
let scouting_state = Cell::new((0, 0));
|
let scouting_state = Cell::new((0, 0));
|
||||||
Self {
|
Self {
|
||||||
@@ -468,7 +743,7 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup a hostname
|
/// Initiate a peer discovery process through hostname lookup
|
||||||
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
|
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
|
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
|
||||||
@@ -476,10 +751,14 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of address candidates for the peer
|
||||||
pub fn addresses(&self) -> &Vec<SocketAddr> {
|
pub fn addresses(&self) -> &Vec<SocketAddr> {
|
||||||
&self.addresses
|
&self.addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates and stores the next value for [Self::scouting_state]
|
||||||
|
/// given the address and socket we just sent a scouting [crate::msgs::InitHello] message
|
||||||
|
/// to
|
||||||
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
|
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
|
||||||
self.scouting_state.set((
|
self.scouting_state.set((
|
||||||
(addr_no + 1) % self.addresses.len(),
|
(addr_no + 1) % self.addresses.len(),
|
||||||
@@ -534,6 +813,11 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppServer {
|
impl AppServer {
|
||||||
|
/// Construct a new AppServer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
keypair: Option<(SSk, SPk)>,
|
keypair: Option<(SSk, SPk)>,
|
||||||
addrs: Vec<SocketAddr>,
|
addrs: Vec<SocketAddr>,
|
||||||
@@ -633,6 +917,8 @@ impl AppServer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||||
|
term_signal: terminate::TerminateRequested::new()?,
|
||||||
crypto_site,
|
crypto_site,
|
||||||
peers: Vec::new(),
|
peers: Vec::new(),
|
||||||
verbosity,
|
verbosity,
|
||||||
@@ -656,22 +942,37 @@ impl AppServer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the cryptographic protocol server
|
||||||
|
///
|
||||||
|
/// This may return an error if [Self] was initialized without a keypair
|
||||||
|
/// and no keypair has been supplied since then.
|
||||||
|
///
|
||||||
|
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
|
||||||
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
||||||
self.crypto_site
|
self.crypto_site
|
||||||
.product_ref()
|
.product_ref()
|
||||||
.context("Cryptography handler not initialized")
|
.context("Cryptography handler not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the cryptographic protocol server, mutably
|
||||||
|
///
|
||||||
|
/// This may return an error if [Self] was initialized without a keypair
|
||||||
|
/// and no keypair has been supplied since then.
|
||||||
|
///
|
||||||
|
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
|
||||||
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
||||||
self.crypto_site
|
self.crypto_site
|
||||||
.product_mut()
|
.product_mut()
|
||||||
.context("Cryptography handler not initialized")
|
.context("Cryptography handler not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If set to [Verbosity::Verbose], then some extra information will be printed
|
||||||
|
/// at the info log level
|
||||||
pub fn verbose(&self) -> bool {
|
pub fn verbose(&self) -> bool {
|
||||||
matches!(self.verbosity, Verbosity::Verbose)
|
matches!(self.verbosity, Verbosity::Verbose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by [Self::new] to register a new udp listen source
|
||||||
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
||||||
let mio_token = self.mio_token_dispenser.dispense();
|
let mio_token = self.mio_token_dispenser.dispense();
|
||||||
self.mio_poll
|
self.mio_poll
|
||||||
@@ -683,16 +984,19 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to register a source of IO such as a listen socket with [Self::io_source_index]
|
||||||
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
||||||
let prev = self.io_source_index.insert(token, io_source);
|
let prev = self.io_source_index.insert(token, io_source);
|
||||||
assert!(prev.is_none());
|
assert!(prev.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unregister an IO source registered with [Self::register_io_source]
|
||||||
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
||||||
let value = self.io_source_index.remove(&token);
|
let value = self.io_source_index.remove(&token);
|
||||||
assert!(value.is_some(), "Removed IO source that does not exist");
|
assert!(value.is_some(), "Removed IO source that does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new WireGuard PSK broker
|
||||||
pub fn register_broker(
|
pub fn register_broker(
|
||||||
&mut self,
|
&mut self,
|
||||||
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||||
@@ -715,6 +1019,7 @@ impl AppServer {
|
|||||||
Ok(BrokerStorePtr(ptr))
|
Ok(BrokerStorePtr(ptr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unregister a WireGuard PSK broker registered with [Self::register_broker]
|
||||||
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
||||||
let mut broker = self
|
let mut broker = self
|
||||||
.brokers
|
.brokers
|
||||||
@@ -726,6 +1031,11 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new protocol peer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::new].
|
||||||
pub fn add_peer(
|
pub fn add_peer(
|
||||||
&mut self,
|
&mut self,
|
||||||
psk: Option<SymKey>,
|
psk: Option<SymKey>,
|
||||||
@@ -733,11 +1043,12 @@ impl AppServer {
|
|||||||
outfile: Option<PathBuf>,
|
outfile: Option<PathBuf>,
|
||||||
broker_peer: Option<BrokerPeer>,
|
broker_peer: Option<BrokerPeer>,
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
|
protocol_version: ProtocolVersion,
|
||||||
) -> anyhow::Result<AppPeerPtr> {
|
) -> anyhow::Result<AppPeerPtr> {
|
||||||
let PeerPtr(pn) = match &mut self.crypto_site {
|
let PeerPtr(pn) = match &mut self.crypto_site {
|
||||||
ConstructionSite::Void => bail!("Crypto server construction site is void"),
|
ConstructionSite::Void => bail!("Crypto server construction site is void"),
|
||||||
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk),
|
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk, protocol_version),
|
||||||
ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?,
|
ConstructionSite::Product(srv) => srv.add_peer(psk, pk, protocol_version.into())?,
|
||||||
};
|
};
|
||||||
assert!(pn == self.peers.len());
|
assert!(pn == self.peers.len());
|
||||||
|
|
||||||
@@ -754,18 +1065,40 @@ impl AppServer {
|
|||||||
Ok(AppPeerPtr(pn))
|
Ok(AppPeerPtr(pn))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen_loop(&mut self) -> anyhow::Result<()> {
|
/// Main IO handler; this generally does not terminate
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::new].
|
||||||
|
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||||
const INIT_SLEEP: f64 = 0.01;
|
const INIT_SLEEP: f64 = 0.01;
|
||||||
const MAX_FAILURES: i32 = 10;
|
const MAX_FAILURES: i32 = 10;
|
||||||
let mut failure_cnt = 0;
|
let mut failure_cnt = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let msgs_processed = 0usize;
|
let msgs_processed = 0usize;
|
||||||
let err = match self.event_loop() {
|
let err = match self.event_loop_without_error_handling() {
|
||||||
Ok(()) => return Ok(()),
|
Ok(()) => return Ok(()),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||||
|
{
|
||||||
|
let terminated_by_signal = err
|
||||||
|
.downcast_ref::<std::io::Error>()
|
||||||
|
.filter(|e| e.kind() == std::io::ErrorKind::Interrupted)
|
||||||
|
.filter(|_| self.term_signal.value())
|
||||||
|
.is_some();
|
||||||
|
if terminated_by_signal {
|
||||||
|
log::warn!(
|
||||||
|
"\
|
||||||
|
Terminated by signal; this signal handler is correct during coverage testing \
|
||||||
|
but should be otherwise disabled"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This should not happen…
|
// This should not happen…
|
||||||
failure_cnt = if msgs_processed > 0 {
|
failure_cnt = if msgs_processed > 0 {
|
||||||
0
|
0
|
||||||
@@ -790,7 +1123,10 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
/// IO handler without proactive restarts after errors.
|
||||||
|
///
|
||||||
|
/// This is used internally in [Self::event_loop].
|
||||||
|
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
|
||||||
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
||||||
|
|
||||||
/// if socket address for peer is known, call closure
|
/// if socket address for peer is known, call closure
|
||||||
@@ -909,6 +1245,8 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for [Self::event_loop_without_error_handling] to handle network messages
|
||||||
|
/// under DoS condition
|
||||||
fn handle_msg_under_load(
|
fn handle_msg_under_load(
|
||||||
&mut self,
|
&mut self,
|
||||||
endpoint: &Endpoint,
|
endpoint: &Endpoint,
|
||||||
@@ -925,6 +1263,8 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used as a helper by [Self::event_loop_without_error_handling] when
|
||||||
|
/// a new output key has been exchanged
|
||||||
pub fn output_key(
|
pub fn output_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
peer: AppPeerPtr,
|
peer: AppPeerPtr,
|
||||||
@@ -974,6 +1314,10 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Poll for events from the cryptographic server ([Self::crypto_server()])
|
||||||
|
/// and for IO events through [Self::poll].
|
||||||
|
///
|
||||||
|
/// Used internally in [Self::event_loop_without_error_handling]
|
||||||
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
||||||
use crate::protocol::PollResult as C;
|
use crate::protocol::PollResult as C;
|
||||||
use AppPollResult as A;
|
use AppPollResult as A;
|
||||||
@@ -1005,7 +1349,9 @@ impl AppServer {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to receive a new message
|
/// Tries to receive a new message from the network sockets.
|
||||||
|
///
|
||||||
|
/// Used internally in [Self::poll]
|
||||||
///
|
///
|
||||||
/// - might wait for an duration up to `timeout`
|
/// - might wait for an duration up to `timeout`
|
||||||
/// - returns immediately if an error occurs
|
/// - returns immediately if an error occurs
|
||||||
@@ -1177,6 +1523,7 @@ impl AppServer {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
||||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||||
// Fill the short poll buffer with the acquired events
|
// Fill the short poll buffer with the acquired events
|
||||||
@@ -1187,6 +1534,7 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn try_recv_from_mio_token(
|
fn try_recv_from_mio_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -1203,6 +1551,7 @@ impl AppServer {
|
|||||||
self.try_recv_from_io_source(buf, io_source)
|
self.try_recv_from_io_source(buf, io_source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn try_recv_from_io_source(
|
fn try_recv_from_io_source(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -1233,6 +1582,7 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn try_recv_from_listen_socket(
|
fn try_recv_from_listen_socket(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -1268,6 +1618,20 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
|
/// This is a wrapper around a reference to [AppServer] that
|
||||||
|
/// dishes out references to [AppServer::api_manager] and
|
||||||
|
/// to [AppServer] itself.
|
||||||
|
///
|
||||||
|
/// It really just implements [crate::api::mio::MioManagerContext] which
|
||||||
|
/// provides the methods operating on [crate::api::mio::MioManager] provided
|
||||||
|
/// through a trait.
|
||||||
|
///
|
||||||
|
/// This is a rather complicated way of providing just a few functions. The entire
|
||||||
|
/// point of this exercise is to decouple the code in the API from [AppServer] and
|
||||||
|
/// this file a bit, despite those functions all needing access to [AppServer].
|
||||||
|
///
|
||||||
|
/// We want the code to live in its own module instead of expanding and expanding the source
|
||||||
|
/// file with [AppServer] more and more.
|
||||||
struct MioManagerFocus<'a>(&'a mut AppServer);
|
struct MioManagerFocus<'a>(&'a mut AppServer);
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
@@ -1288,3 +1652,48 @@ impl crate::api::mio::MioManagerContext for MioManagerFocus<'_> {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// These signal handlers are used exclusively used during coverage testing
|
||||||
|
/// to ensure that the llvm-cov can produce reports during integration tests
|
||||||
|
/// with multiple processes where subprocesses are terminated via kill(2).
|
||||||
|
///
|
||||||
|
/// llvm-cov does not support producing coverage reports when the process exits
|
||||||
|
/// through a signal, so this is necessary.
|
||||||
|
///
|
||||||
|
/// The functionality of exiting gracefully upon reception of a terminating signal
|
||||||
|
/// is desired for the production variant of Rosenpass, but we should make sure
|
||||||
|
/// to use a higher quality implementation; in particular, we should use signalfd(2).
|
||||||
|
///
|
||||||
|
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||||
|
mod terminate {
|
||||||
|
use signal_hook::flag::register as sig_register;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Automatically register a signal handler for common termination signals;
|
||||||
|
/// whether one of these signals was issued can be polled using [Self::value].
|
||||||
|
///
|
||||||
|
/// The signal handler is not removed when this struct goes out of scope.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TerminateRequested {
|
||||||
|
value: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerminateRequested {
|
||||||
|
/// Register signal handlers watching for common termination signals
|
||||||
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
|
let value = Arc::new(AtomicBool::new(false));
|
||||||
|
for sig in signal_hook::consts::TERM_SIGNALS.iter().copied() {
|
||||||
|
sig_register(sig, Arc::clone(&value))?;
|
||||||
|
}
|
||||||
|
Ok(Self { value })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a termination signal has been set
|
||||||
|
pub fn value(&self) -> bool {
|
||||||
|
self.value.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use heck::ToShoutySnakeCase;
|
use heck::ToShoutySnakeCase;
|
||||||
|
|
||||||
|
use rosenpass_ciphers::subtle::keyed_hash::KeyedHash;
|
||||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
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]> {
|
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
|
||||||
match values.split_first() {
|
match values.split_first() {
|
||||||
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
|
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
|
||||||
@@ -10,12 +12,13 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_literal(path: &[&str]) -> Result<()> {
|
/// Print a hash literal for pasting into the Rosenpass source code
|
||||||
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
fn print_literal(path: &[&str], shake_or_blake: KeyedHash) -> Result<()> {
|
||||||
|
let val = calculate_hash_value(HashDomain::zero(shake_or_blake.clone()), path)?;
|
||||||
let (last, prefix) = path.split_last().context("developer error!")?;
|
let (last, prefix) = path.split_last().context("developer error!")?;
|
||||||
let var_name = last.to_shouty_snake_case();
|
let var_name = last.to_shouty_snake_case();
|
||||||
|
|
||||||
print!("// hash domain hash of: ");
|
print!("// hash domain hash with hash {} of: ", shake_or_blake);
|
||||||
for n in prefix.iter() {
|
for n in prefix.iter() {
|
||||||
print!("{n} -> ");
|
print!("{n} -> ");
|
||||||
}
|
}
|
||||||
@@ -33,6 +36,8 @@ fn print_literal(path: &[&str]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tree of domain separators where each leaf represents
|
||||||
|
/// an API message ID
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Tree {
|
enum Tree {
|
||||||
Branch(String, Vec<Tree>),
|
Branch(String, Vec<Tree>),
|
||||||
@@ -47,27 +52,28 @@ impl Tree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> {
|
fn gen_code_inner(&self, prefix: &[&str], shake_or_blake: KeyedHash) -> Result<()> {
|
||||||
let mut path = prefix.to_owned();
|
let mut path = prefix.to_owned();
|
||||||
path.push(self.name());
|
path.push(self.name());
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Branch(_, ref children) => {
|
Self::Branch(_, ref children) => {
|
||||||
for c in children.iter() {
|
for c in children.iter() {
|
||||||
c.gen_code_inner(&path)?
|
c.gen_code_inner(&path, shake_or_blake.clone())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Leaf(_) => print_literal(&path)?,
|
Self::Leaf(_) => print_literal(&path, shake_or_blake)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_code(&self) -> Result<()> {
|
fn gen_code(&self, shake_or_blake: KeyedHash) -> Result<()> {
|
||||||
self.gen_code_inner(&[])
|
self.gen_code_inner(&[], shake_or_blake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for generating hash-based message IDs for the IPC API
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let tree = Tree::Branch(
|
let tree = Tree::Branch(
|
||||||
"Rosenpass IPC API".to_owned(),
|
"Rosenpass IPC API".to_owned(),
|
||||||
@@ -88,5 +94,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
println!("type RawMsgType = u128;");
|
println!("type RawMsgType = u128;");
|
||||||
println!();
|
println!();
|
||||||
tree.gen_code()
|
tree.gen_code(KeyedHash::keyed_shake256())?;
|
||||||
|
println!();
|
||||||
|
tree.gen_code(KeyedHash::incorrect_hmac_blake2b())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
//! 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 anyhow::{bail, ensure, Context};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
use rosenpass_secret_memory::file::StoreSecret;
|
use rosenpass_secret_memory::file::StoreSecret;
|
||||||
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
|
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
|
||||||
use rosenpass_wireguard_broker::brokers::native_unix::{
|
use rosenpass_wireguard_broker::brokers::native_unix::{
|
||||||
@@ -24,34 +29,44 @@ use {
|
|||||||
rosenpass_util::fd::claim_fd,
|
rosenpass_util::fd::claim_fd,
|
||||||
rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient,
|
rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient,
|
||||||
rosenpass_wireguard_broker::WireguardBrokerMio,
|
rosenpass_wireguard_broker::WireguardBrokerMio,
|
||||||
rustix::fd::AsRawFd,
|
|
||||||
rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType},
|
rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType},
|
||||||
|
std::os::fd::AsRawFd,
|
||||||
std::os::unix::net,
|
std::os::unix::net,
|
||||||
std::process::Command,
|
std::process::Command,
|
||||||
std::thread,
|
std::thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// enum representing a choice of interface to a WireGuard broker
|
/// How to reach a WireGuard PSK Broker
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BrokerInterface {
|
pub enum BrokerInterface {
|
||||||
|
/// The PSK Broker is listening on a unix socket at the given path
|
||||||
Socket(PathBuf),
|
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),
|
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,
|
SocketPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// struct holding all CLI arguments for `clap` crate to parse
|
/// Command line arguments to the Rosenpass binary.
|
||||||
|
///
|
||||||
|
/// Used for parsing with [clap].
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about)]
|
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
/// lowest log level to show – log messages at higher levels will be omitted
|
/// Lowest log level to show
|
||||||
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
|
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
|
||||||
log_level: Option<log::LevelFilter>,
|
log_level: Option<log::LevelFilter>,
|
||||||
|
|
||||||
/// show verbose log output – sets log level to "debug"
|
/// Show verbose log output – sets log level to "debug"
|
||||||
#[arg(short, long, group = "log-level")]
|
#[arg(short, long, group = "log-level")]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
|
||||||
/// show no log output – sets log level to "error"
|
/// Show no log output – sets log level to "error"
|
||||||
#[arg(short, long, group = "log-level")]
|
#[arg(short, long, group = "log-level")]
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
|
||||||
@@ -59,31 +74,50 @@ pub struct CliArgs {
|
|||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
api: crate::api::cli::ApiCli,
|
api: crate::api::cli::ApiCli,
|
||||||
|
|
||||||
/// path of the wireguard_psk broker socket to connect to
|
/// Path of the `wireguard_psk` broker socket to connect to
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[arg(long, group = "psk-broker-specs")]
|
#[arg(long, group = "psk-broker-specs")]
|
||||||
psk_broker_path: Option<PathBuf>,
|
psk_broker_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// fd of the wireguard_spk broker socket to connect to
|
/// 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
|
/// When this command is called from another process, the other process can
|
||||||
/// Unix socket for the psk broker connection to use themselves, passing it to this process --
|
/// open and bind the Unix socket for the PSK broker connection to use
|
||||||
/// in Rust this can be achieved using the
|
/// themselves, passing it to this process - in Rust this can be achieved
|
||||||
/// [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate
|
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/)
|
||||||
|
/// crate
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[arg(long, group = "psk-broker-specs")]
|
#[arg(long, group = "psk-broker-specs")]
|
||||||
psk_broker_fd: Option<i32>,
|
psk_broker_fd: Option<i32>,
|
||||||
|
|
||||||
/// spawn a psk broker locally using a socket pair
|
/// Spawn a PSK broker locally using a socket pair
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[arg(short, long, group = "psk-broker-specs")]
|
#[arg(short, long, group = "psk-broker-specs")]
|
||||||
psk_broker_spawn: bool,
|
psk_broker_spawn: bool,
|
||||||
|
|
||||||
|
/// The subcommand to be invoked
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: CliCommand,
|
pub command: Option<CliCommand>,
|
||||||
|
|
||||||
|
/// Generate man pages for the CLI
|
||||||
|
///
|
||||||
|
/// This option is used to generate man pages for Rosenpass in the specified
|
||||||
|
/// directory and exit.
|
||||||
|
#[clap(long, value_name = "out_dir")]
|
||||||
|
pub generate_manpage: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Generate completion file for a shell
|
||||||
|
///
|
||||||
|
/// This option is used to generate completion files for the specified shell
|
||||||
|
#[clap(long, value_name = "shell")]
|
||||||
|
pub print_completions: Option<clap_complete::Shell>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
|
/// 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<()> {
|
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
self.api.apply_to_config(_cfg)?;
|
self.api.apply_to_config(_cfg)?;
|
||||||
@@ -109,9 +143,11 @@ impl CliArgs {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the WireGuard PSK broker interface configured.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
/// returns the broker interface set by CLI args
|
|
||||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
|
||||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||||
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
||||||
@@ -124,9 +160,10 @@ impl CliArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the WireGuard PSK broker interface configured.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||||
#[cfg(not(feature = "experiment_api"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
/// returns the broker interface set by CLI args
|
|
||||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
|
||||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -135,20 +172,20 @@ impl CliArgs {
|
|||||||
/// represents a command specified via CLI
|
/// represents a command specified via CLI
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum CliCommand {
|
pub enum CliCommand {
|
||||||
/// Start Rosenpass in server mode and carry on with the key exchange
|
/// Start Rosenpass key exchanges based on a configuration file
|
||||||
///
|
///
|
||||||
/// This will parse the configuration file and perform the key exchange
|
/// This will parse the configuration file and perform key exchanges with
|
||||||
/// with the specified peers. If a peer's endpoint is specified, this
|
/// the specified peers. If a peer's endpoint is specified, this Rosenpass
|
||||||
/// Rosenpass instance will try to initiate a key exchange with the peer,
|
/// instance will try to initiate a key exchange with the peer; otherwise,
|
||||||
/// otherwise only initiation attempts from the peer will be responded to.
|
/// only initiation attempts from other peers will be responded to.
|
||||||
ExchangeConfig { config_file: PathBuf },
|
ExchangeConfig { config_file: PathBuf },
|
||||||
|
|
||||||
/// Start in daemon mode, performing key exchanges
|
/// Start Rosenpass key exchanges based on command line arguments
|
||||||
///
|
///
|
||||||
/// The configuration is read from the command line. The `peer` token
|
/// The configuration is read from the command line. The `peer` token always
|
||||||
/// always separates multiple peers, e. g. if the token `peer` appears
|
/// separates multiple peers, e.g., if the token `peer` appears in the
|
||||||
/// in the WIREGUARD_EXTRA_ARGS it is not put into the WireGuard arguments
|
/// WIREGUARD_EXTRA_ARGS, it is not put into the WireGuard arguments but
|
||||||
/// but instead a new peer is created.
|
/// instead a new peer is created.
|
||||||
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
||||||
* `Vec<String>`. They are only used to trick clap into displaying some
|
* `Vec<String>`. They are only used to trick clap into displaying some
|
||||||
* guidance on the CLI usage.
|
* guidance on the CLI usage.
|
||||||
@@ -177,7 +214,10 @@ pub enum CliCommand {
|
|||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Generate a demo config file
|
/// Generate a demo config file for Rosenpass
|
||||||
|
///
|
||||||
|
/// The generated config file will contain a single peer and all common
|
||||||
|
/// options.
|
||||||
GenConfig {
|
GenConfig {
|
||||||
config_file: PathBuf,
|
config_file: PathBuf,
|
||||||
|
|
||||||
@@ -186,19 +226,19 @@ pub enum CliCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Generate the keys mentioned in a configFile
|
/// Generate secret & public key for Rosenpass
|
||||||
///
|
///
|
||||||
/// Generates secret- & public-key to their destination. If a config file
|
/// Generates secret & public key to their destination. If a config file is
|
||||||
/// is provided then the key file destination is taken from there.
|
/// provided then the key file destination is taken from there, otherwise
|
||||||
/// Otherwise the
|
/// the destination is taken from the CLI arguments.
|
||||||
GenKeys {
|
GenKeys {
|
||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// where to write public-key to
|
/// Where to write public key to
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
public_key: Option<PathBuf>,
|
public_key: Option<PathBuf>,
|
||||||
|
|
||||||
/// where to write secret-key to
|
/// Where to write secret key to
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
secret_key: Option<PathBuf>,
|
secret_key: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -207,55 +247,50 @@ pub enum CliCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Deprecated - use gen-keys instead
|
/// Validate a configuration file
|
||||||
|
///
|
||||||
|
/// This command will validate the configuration file and print any errors
|
||||||
|
/// it finds. If the configuration file is valid, it will print a success.
|
||||||
|
/// Defined secret & public keys are checked for existence and validity.
|
||||||
|
Validate { config_files: Vec<PathBuf> },
|
||||||
|
|
||||||
|
/// DEPRECATED - use the gen-keys command instead
|
||||||
#[allow(rustdoc::broken_intra_doc_links)]
|
#[allow(rustdoc::broken_intra_doc_links)]
|
||||||
#[allow(rustdoc::invalid_html_tags)]
|
#[allow(rustdoc::invalid_html_tags)]
|
||||||
|
#[command(hide = true)]
|
||||||
Keygen {
|
Keygen {
|
||||||
// NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"!
|
// NOTE yes, the legacy keygen argument initially really accepted
|
||||||
|
// "private-key", not "secret-key"!
|
||||||
/// public-key <PATH> private-key <PATH>
|
/// public-key <PATH> private-key <PATH>
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Validate a configuration
|
|
||||||
Validate { config_files: Vec<PathBuf> },
|
|
||||||
|
|
||||||
/// Show the rosenpass manpage
|
|
||||||
// TODO make this the default, but only after the manpage has been adjusted once the CLI stabilizes
|
|
||||||
Man,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
/// runs the command specified via CLI
|
/// Run Rosenpass with the given command line parameters
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// This contains the bulk of our startup logic with
|
||||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
/// the main function just setting up the basic environment
|
||||||
|
/// and then calling this function.
|
||||||
pub fn run(
|
pub fn run(
|
||||||
self,
|
self,
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
test_helpers: Option<AppServerTest>,
|
test_helpers: Option<AppServerTest>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||||
use CliCommand::*;
|
use CliCommand::*;
|
||||||
match &self.command {
|
match &self.command {
|
||||||
Man => {
|
Some(GenConfig { config_file, force }) => {
|
||||||
let man_cmd = std::process::Command::new("man")
|
|
||||||
.args(["1", "rosenpass"])
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if !(man_cmd.is_ok() && man_cmd.unwrap().success()) {
|
|
||||||
println!(include_str!(env!("ROSENPASS_MAN")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GenConfig { config_file, force } => {
|
|
||||||
ensure!(
|
ensure!(
|
||||||
*force || !config_file.exists(),
|
*force || !config_file.exists(),
|
||||||
"config file {config_file:?} already exists"
|
"config file {config_file:?} already exists"
|
||||||
);
|
);
|
||||||
|
|
||||||
config::Rosenpass::example_config().store(config_file)?;
|
std::fs::write(config_file, config::EXAMPLE_CONFIG)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated - use gen-keys instead
|
// Deprecated - use gen-keys instead
|
||||||
Keygen { args } => {
|
Some(Keygen { args }) => {
|
||||||
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
||||||
|
|
||||||
let mut public_key: Option<PathBuf> = None;
|
let mut public_key: Option<PathBuf> = None;
|
||||||
@@ -288,12 +323,12 @@ impl CliArgs {
|
|||||||
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
GenKeys {
|
Some(GenKeys {
|
||||||
config_file,
|
config_file,
|
||||||
public_key,
|
public_key,
|
||||||
secret_key,
|
secret_key,
|
||||||
force,
|
force,
|
||||||
} => {
|
}) => {
|
||||||
// figure out where the key file is specified, in the config file or directly as flag?
|
// figure out where the key file is specified, in the config file or directly as flag?
|
||||||
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
||||||
(Some(config_file), _, _) => {
|
(Some(config_file), _, _) => {
|
||||||
@@ -337,7 +372,7 @@ impl CliArgs {
|
|||||||
generate_and_save_keypair(skf, pkf)?;
|
generate_and_save_keypair(skf, pkf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExchangeConfig { config_file } => {
|
Some(ExchangeConfig { config_file }) => {
|
||||||
ensure!(
|
ensure!(
|
||||||
config_file.exists(),
|
config_file.exists(),
|
||||||
"config file '{config_file:?}' does not exist"
|
"config file '{config_file:?}' does not exist"
|
||||||
@@ -351,11 +386,11 @@ impl CliArgs {
|
|||||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Exchange {
|
Some(Exchange {
|
||||||
first_arg,
|
first_arg,
|
||||||
rest_of_args,
|
rest_of_args,
|
||||||
config_file,
|
config_file,
|
||||||
} => {
|
}) => {
|
||||||
let mut rest_of_args = rest_of_args.clone();
|
let mut rest_of_args = rest_of_args.clone();
|
||||||
rest_of_args.insert(0, first_arg.clone());
|
rest_of_args.insert(0, first_arg.clone());
|
||||||
let args = rest_of_args;
|
let args = rest_of_args;
|
||||||
@@ -372,25 +407,28 @@ impl CliArgs {
|
|||||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate { config_files } => {
|
Some(Validate { config_files }) => {
|
||||||
for file in config_files {
|
for file in config_files {
|
||||||
match config::Rosenpass::load(file) {
|
match config::Rosenpass::load(file) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
||||||
match config.validate() {
|
match config.validate() {
|
||||||
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
|
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
|
||||||
Err(_) => eprintln!("{file:?} contains logical errors"),
|
Err(err) => eprintln!("{file:?} contains logical errors: '{err}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&None => {} // calp print help if no command is given
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by [Self::run] to start the Rosenpass key exchange server
|
||||||
fn event_loop(
|
fn event_loop(
|
||||||
config: config::Rosenpass,
|
config: config::Rosenpass,
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
@@ -452,12 +490,26 @@ impl CliArgs {
|
|||||||
cfg_peer.key_out,
|
cfg_peer.key_out,
|
||||||
broker_peer,
|
broker_peer,
|
||||||
cfg_peer.endpoint.clone(),
|
cfg_peer.endpoint.clone(),
|
||||||
|
cfg_peer.protocol_version.into(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.event_loop()
|
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")]
|
#[cfg(feature = "experiment_api")]
|
||||||
fn create_broker(
|
fn create_broker(
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
@@ -473,6 +525,19 @@ impl CliArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
fn create_broker(
|
fn create_broker(
|
||||||
_broker_interface: Option<BrokerInterface>,
|
_broker_interface: Option<BrokerInterface>,
|
||||||
@@ -480,6 +545,10 @@ impl CliArgs {
|
|||||||
Ok(Box::new(NativeUnixBroker::new()))
|
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")]
|
#[cfg(feature = "experiment_api")]
|
||||||
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||||
// Connect to the psk broker unix socket if one was specified
|
// Connect to the psk broker unix socket if one was specified
|
||||||
@@ -537,10 +606,10 @@ impl CliArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||||
let mut ssk = crate::protocol::SSk::random();
|
let mut ssk = crate::protocol::SSk::random();
|
||||||
let mut spk = crate::protocol::SPk::random();
|
let mut spk = crate::protocol::SPk::random();
|
||||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
StaticKem.keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||||
ssk.store_secret(secret_key)?;
|
ssk.store_secret(secret_key)?;
|
||||||
spk.store(public_key)
|
spk.store(public_key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
//! [`Rosenpass`] which holds such a configuration.
|
//! [`Rosenpass`] which holds such a configuration.
|
||||||
//!
|
//!
|
||||||
//! ## TODO
|
//! ## TODO
|
||||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
//! - 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: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||||
|
|
||||||
|
use crate::protocol::{SPk, SSk};
|
||||||
|
use rosenpass_util::file::LoadValue;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
@@ -30,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// 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 {
|
pub struct Rosenpass {
|
||||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||||
@@ -45,7 +50,10 @@ pub struct Rosenpass {
|
|||||||
/// list of [`SocketAddr`] to listen on
|
/// list of [`SocketAddr`] to listen on
|
||||||
///
|
///
|
||||||
/// Examples:
|
/// 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>,
|
pub listen: Vec<SocketAddr>,
|
||||||
|
|
||||||
/// log verbosity
|
/// log verbosity
|
||||||
@@ -67,6 +75,7 @@ pub struct Rosenpass {
|
|||||||
pub config_file_path: PathBuf,
|
pub config_file_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public key and secret key locations.
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||||
pub struct Keypair {
|
pub struct Keypair {
|
||||||
/// path to the public key file
|
/// path to the public key file
|
||||||
@@ -77,6 +86,7 @@ pub struct Keypair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Keypair {
|
impl Keypair {
|
||||||
|
/// Construct a keypair from its fields
|
||||||
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
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 public_key = public_key.as_ref().to_path_buf();
|
||||||
let secret_key = secret_key.as_ref().to_path_buf();
|
let secret_key = secret_key.as_ref().to_path_buf();
|
||||||
@@ -87,62 +97,84 @@ impl Keypair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Level of verbosity for [crate::app_server::AppServer]
|
||||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
///
|
||||||
|
/// 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)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
Quiet,
|
Quiet,
|
||||||
Verbose,
|
Verbose,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// The protocol version to be used by a peer.
|
||||||
/// - examples
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone, Default)]
|
||||||
/// - documentation
|
pub enum ProtocolVersion {
|
||||||
|
#[default]
|
||||||
|
V02,
|
||||||
|
V03,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration data for a single Rosenpass peer
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RosenpassPeer {
|
pub struct RosenpassPeer {
|
||||||
/// path to the public key of the peer
|
/// path to the public key of the peer
|
||||||
pub public_key: PathBuf,
|
pub public_key: PathBuf,
|
||||||
|
|
||||||
/// ## TODO
|
/// The hostname and port to connect to
|
||||||
/// - documentation
|
///
|
||||||
|
/// 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>,
|
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
|
/// 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>,
|
pub pre_shared_key: Option<PathBuf>,
|
||||||
|
|
||||||
/// ## TODO
|
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
|
||||||
/// - documentation
|
/// 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)]
|
#[serde(default)]
|
||||||
pub key_out: Option<PathBuf>,
|
pub key_out: Option<PathBuf>,
|
||||||
|
|
||||||
/// ## TODO
|
/// Information for supplying exchanged keys directly to WireGuard
|
||||||
/// - documentation
|
|
||||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub wg: Option<WireGuard>,
|
pub wg: Option<WireGuard>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
/// The protocol version to use for the exchange
|
||||||
|
pub protocol_version: ProtocolVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Information for supplying exchanged keys directly to WireGuard
|
||||||
/// - documentation
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WireGuard {
|
pub struct WireGuard {
|
||||||
/// ## TODO
|
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
|
||||||
/// - documentation
|
/// key exchange
|
||||||
pub device: String,
|
pub device: String,
|
||||||
|
|
||||||
/// ## TODO
|
/// WireGuard public key of the peer to supply with pre-shared keys
|
||||||
/// - documentation
|
|
||||||
pub peer: String,
|
pub peer: String,
|
||||||
|
|
||||||
/// ## TODO
|
/// Extra parameters passed to the `wg` command
|
||||||
/// - documentation
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub extra_params: Vec<String>,
|
pub extra_params: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rosenpass {
|
impl Default for Rosenpass {
|
||||||
|
/// Generate an empty configuration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::empty()
|
Self::empty()
|
||||||
}
|
}
|
||||||
@@ -155,8 +187,15 @@ impl Rosenpass {
|
|||||||
/// checked whether they even exist.
|
/// checked whether they even exist.
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// ## TODO
|
||||||
|
///
|
||||||
/// - consider using a different algorithm to determine home directory – the below one may
|
/// - consider using a different algorithm to determine home directory – the below one may
|
||||||
/// behave unexpectedly on Windows
|
/// 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> {
|
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||||
// read file and deserialize
|
// read file and deserialize
|
||||||
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||||
@@ -184,7 +223,13 @@ impl Rosenpass {
|
|||||||
Ok(config)
|
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<()> {
|
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||||
let serialized_config =
|
let serialized_config =
|
||||||
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
||||||
@@ -193,6 +238,12 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Commit the configuration to where it came from, overwriting the original file
|
/// 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<()> {
|
pub fn commit(&self) -> anyhow::Result<()> {
|
||||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||||
@@ -200,30 +251,48 @@ impl Rosenpass {
|
|||||||
self.store(&self.config_file_path)
|
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<()> {
|
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
self.api.apply_to_app_server(_srv)?;
|
self.api.apply_to_app_server(_srv)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate a configuration
|
/// Check that the configuration is sound, ensuring
|
||||||
|
/// for instance that the referenced files exist
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// # Examples
|
||||||
/// - 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.)
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn validate(&self) -> anyhow::Result<()> {
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
if let Some(ref keypair) = self.keypair {
|
if let Some(ref keypair) = self.keypair {
|
||||||
// check the public key file exists
|
// check the public key file exists
|
||||||
ensure!(
|
ensure!(
|
||||||
keypair.public_key.is_file(),
|
keypair.public_key.is_file(),
|
||||||
"could not find public-key file {:?}: no such file",
|
"could not find public-key file {:?}: no such file. Consider running `rosenpass gen-keys` to generate a new keypair.",
|
||||||
|
keypair.public_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the public-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SPk::load(&keypair.public_key).is_ok(),
|
||||||
|
"could not load public-key file {:?}: invalid key",
|
||||||
keypair.public_key
|
keypair.public_key
|
||||||
);
|
);
|
||||||
|
|
||||||
// check the secret-key file exists
|
// check the secret-key file exists
|
||||||
ensure!(
|
ensure!(
|
||||||
keypair.secret_key.is_file(),
|
keypair.secret_key.is_file(),
|
||||||
"could not find secret-key file {:?}: no such 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
|
keypair.secret_key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -236,6 +305,13 @@ impl Rosenpass {
|
|||||||
peer.public_key
|
peer.public_key
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// check peer's public-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SPk::load(&peer.public_key).is_ok(),
|
||||||
|
"peer {i} public-key file {:?} is invalid",
|
||||||
|
peer.public_key
|
||||||
|
);
|
||||||
|
|
||||||
// check endpoint is usable
|
// check endpoint is usable
|
||||||
if let Some(addr) = peer.endpoint.as_ref() {
|
if let Some(addr) = peer.endpoint.as_ref() {
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -245,12 +321,42 @@ impl Rosenpass {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO warn if neither out_key nor exchange_command is defined
|
// check if `key_out` or `device` and `peer` are defined
|
||||||
|
if peer.key_out.is_none() {
|
||||||
|
if let Some(wg) = &peer.wg {
|
||||||
|
if wg.device.is_empty() || wg.peer.is_empty() {
|
||||||
|
ensure!(
|
||||||
|
false,
|
||||||
|
"peer {i} has neither `key_out` nor valid wireguard config defined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ensure!(
|
||||||
|
false,
|
||||||
|
"peer {i} has neither `key_out` nor valid wireguard config defined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<()> {
|
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||||
#[cfg(not(feature = "experiment_api"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||||
@@ -266,15 +372,38 @@ impl Rosenpass {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce an empty confuguration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::new(None)
|
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 {
|
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||||
Self::new(Some(Keypair::new(pk, sk)))
|
Self::new(Some(Keypair::new(pk, sk)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new configuration
|
/// 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 {
|
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
keypair,
|
keypair,
|
||||||
@@ -288,6 +417,14 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
/// 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) {
|
pub fn add_if_any(&mut self, port: u16) {
|
||||||
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||||
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||||
@@ -300,8 +437,20 @@ impl Rosenpass {
|
|||||||
self.listen.push(ipv6_any);
|
self.listen.push(ipv6_any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// from chaotic args
|
/// Parser for the old, IP style grammar.
|
||||||
/// Quest: the grammar is undecideable, what do we do here?
|
///
|
||||||
|
/// 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> {
|
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||||
let mut config = Self::new(Some(Keypair::new("", "")));
|
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||||
|
|
||||||
@@ -491,43 +640,38 @@ 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 {
|
|
||||||
keypair: Some(Keypair {
|
|
||||||
public_key: "/path/to/rp-public-key".into(),
|
|
||||||
secret_key: "/path/to/rp-secret-key".into(),
|
|
||||||
}),
|
|
||||||
peers: vec![peer],
|
|
||||||
..Self::new(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Verbosity {
|
impl Default for Verbosity {
|
||||||
|
/// Self::Quiet
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Quiet
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::{borrow::Borrow, net::IpAddr};
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||||
toml::from_str(s.borrow())
|
toml::from_str(s.borrow())
|
||||||
@@ -639,34 +783,50 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_cli_parse() {
|
fn test_protocol_version() {
|
||||||
let args = split_str(
|
let mut rosenpass = Rosenpass::empty();
|
||||||
"public-key /my/public-key secret-key /my/secret-key verbose \
|
let mut peer_v_02 = RosenpassPeer::default();
|
||||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
peer_v_02.protocol_version = ProtocolVersion::V02;
|
||||||
peer.test:9999 outfile /peer/rp-out",
|
rosenpass.peers.push(peer_v_02);
|
||||||
);
|
let mut peer_v_03 = RosenpassPeer::default();
|
||||||
|
peer_v_03.protocol_version = ProtocolVersion::V03;
|
||||||
|
rosenpass.peers.push(peer_v_03);
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
{
|
||||||
|
rosenpass.api.listen_fd = vec![];
|
||||||
|
rosenpass.api.listen_path = vec![];
|
||||||
|
rosenpass.api.stream_fd = vec![];
|
||||||
|
}
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
let expected_toml = r#"listen = []
|
||||||
|
verbosity = "Quiet"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_fd = []
|
||||||
|
listen_path = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
let config = Rosenpass::parse_args(args).unwrap();
|
[[peers]]
|
||||||
|
protocol_version = "V02"
|
||||||
|
public_key = ""
|
||||||
|
|
||||||
assert_eq!(
|
[[peers]]
|
||||||
config.keypair,
|
protocol_version = "V03"
|
||||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
public_key = ""
|
||||||
);
|
"#;
|
||||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
assert_eq!(
|
let expected_toml = r#"listen = []
|
||||||
&config.listen,
|
verbosity = "Quiet"
|
||||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
|
||||||
);
|
[[peers]]
|
||||||
assert_eq!(
|
protocol_version = "V02"
|
||||||
config.peers,
|
public_key = ""
|
||||||
vec![RosenpassPeer {
|
|
||||||
public_key: PathBuf::from("/peer/public-key"),
|
[[peers]]
|
||||||
endpoint: Some("peer.test:9999".into()),
|
protocol_version = "V03"
|
||||||
pre_shared_key: None,
|
public_key = ""
|
||||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
"#;
|
||||||
..Default::default()
|
assert_toml_round(rosenpass, expected_toml).unwrap()
|
||||||
}]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user