mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 21:34:37 +03:00
Compare commits
310 Commits
bench
...
dev/flake-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00b1020f0a | ||
|
|
a5208795f6 | ||
|
|
0959148305 | ||
|
|
f2bc3a8b64 | ||
|
|
06529df2c0 | ||
|
|
128c77f77a | ||
|
|
501cc9bb05 | ||
|
|
9ad5277a90 | ||
|
|
0cbcaeaf98 | ||
|
|
687ef3f6f8 | ||
|
|
b0706354d3 | ||
|
|
c1e86daec8 | ||
|
|
18a286e688 | ||
|
|
cb92313391 | ||
|
|
5cd30b4c13 | ||
|
|
76d8d38744 | ||
|
|
f63f0bbc2e | ||
|
|
4a449e6502 | ||
|
|
1e6d2df004 | ||
|
|
3fa9aadda2 | ||
|
|
0c79a4ce95 | ||
|
|
036960b5b1 | ||
|
|
e7258849cb | ||
|
|
8c88f68990 | ||
|
|
cf20536576 | ||
|
|
72e18e3ec2 | ||
|
|
6040156a0e | ||
|
|
d3b318b413 | ||
|
|
3a49345138 | ||
|
|
4ec7813259 | ||
|
|
db31da14d3 | ||
|
|
4c20efc8a8 | ||
|
|
c81d484294 | ||
|
|
cc578169d6 | ||
|
|
91527702f1 | ||
|
|
0179f1c673 | ||
|
|
2238919657 | ||
|
|
d913e19883 | ||
|
|
1555d0897b | ||
|
|
abdbf8f3da | ||
|
|
9f78531979 | ||
|
|
624d8d2f44 | ||
|
|
9bbf9433e6 | ||
|
|
77760d71df | ||
|
|
53e560191f | ||
|
|
93cd266c68 | ||
|
|
594f894206 | ||
|
|
a831e01a5c | ||
|
|
0884641d64 | ||
|
|
ae85d0ed2b | ||
|
|
163f66f20e | ||
|
|
3caff91515 | ||
|
|
24eebe29a1 | ||
|
|
1d2fa7d038 | ||
|
|
edf1e774c1 | ||
|
|
7a31b57227 | ||
|
|
d5a8c85abe | ||
|
|
48f7ff93e3 | ||
|
|
5f6c36e773 | ||
|
|
7b3b7612cf | ||
|
|
c1704b1464 | ||
|
|
2785aaf783 | ||
|
|
15002a74cc | ||
|
|
0fe2d9825b | ||
|
|
ab805dae75 | ||
|
|
08653c3338 | ||
|
|
520c8c6eaa | ||
|
|
258efe408c | ||
|
|
fd0f35b279 | ||
|
|
8808ed5dbc | ||
|
|
6fc45cab53 | ||
|
|
1f7196e473 | ||
|
|
c359b87d0c | ||
|
|
355b48169b | ||
|
|
274d245bed | ||
|
|
065b0fcc8a | ||
|
|
191fb10663 | ||
|
|
3faa84117f | ||
|
|
fda75a0184 | ||
|
|
96b1f6c0d3 | ||
|
|
fb73c68626 | ||
|
|
42b0e23695 | ||
|
|
c58f832727 | ||
|
|
7b6a9eebc1 | ||
|
|
4554dc4bb3 | ||
|
|
465c6beaab | ||
|
|
1853e0a3c0 | ||
|
|
245d4d1a0f | ||
|
|
d5d15cd9bc | ||
|
|
9fd3df67ed | ||
|
|
6d47169a5c | ||
|
|
4bcd38a4ea | ||
|
|
730a03957a | ||
|
|
ea071f5363 | ||
|
|
3063d3e4c2 | ||
|
|
1bf0eed90a | ||
|
|
138e6b6553 | ||
|
|
2dde0a2b47 | ||
|
|
3cc3b6009f | ||
|
|
1ab457ed37 | ||
|
|
c9c266fe7c | ||
|
|
8d3c8790fe | ||
|
|
648a94ead8 | ||
|
|
54ac5eecdb | ||
|
|
40c5bbd167 | ||
|
|
a4b8fc2226 | ||
|
|
37f7b3e4e9 | ||
|
|
deafc1c1af | ||
|
|
6bbe85a57b | ||
|
|
e70c5b33a8 | ||
|
|
25fdfef4d0 | ||
|
|
6ab8fafe59 | ||
|
|
c1aacf76b8 | ||
|
|
1bcaf5781f | ||
|
|
de60e5f8f0 | ||
|
|
b50ddda151 | ||
|
|
7282fba3b3 | ||
|
|
0cca389f10 | ||
|
|
8a08d49215 | ||
|
|
8637bc7884 | ||
|
|
4412c2bdd1 | ||
|
|
ecc815dd8e | ||
|
|
b7d7c03e35 | ||
|
|
f6320c3c35 | ||
|
|
19f7905bc9 | ||
|
|
9b5b7ee620 | ||
|
|
4fdd271de7 | ||
|
|
860e65965a | ||
|
|
87144233da | ||
|
|
d0a6e99a1f | ||
|
|
79b634fadf | ||
|
|
99ac3c0902 | ||
|
|
010c14dadf | ||
|
|
45b6132312 | ||
|
|
77f9fd38f3 | ||
|
|
775ed86adc | ||
|
|
40377dce1f | ||
|
|
19293471e8 | ||
|
|
cc5877dd83 | ||
|
|
ebb591aa6f | ||
|
|
07146d9914 | ||
|
|
cd04dbc4eb | ||
|
|
cc22165dc4 | ||
|
|
8496571765 | ||
|
|
ee3a1f580e | ||
|
|
89584645c3 | ||
|
|
3286e49370 | ||
|
|
100d7b6e1c | ||
|
|
921b2bfc39 | ||
|
|
a18658847c | ||
|
|
bdad414c90 | ||
|
|
7c54a37618 | ||
|
|
7a4f700186 | ||
|
|
f535a31cd7 | ||
|
|
ac2aaa5fbd | ||
|
|
e472fa1fcd | ||
|
|
526c930119 | ||
|
|
5f8b00d045 | ||
|
|
b46fca99cb | ||
|
|
70c5ec2c29 | ||
|
|
0e059af5da | ||
|
|
99754f326e | ||
|
|
fd397b9ea0 | ||
|
|
e92fa552e3 | ||
|
|
c438d5a99d | ||
|
|
d4eef998f5 | ||
|
|
c1abfbfd14 | ||
|
|
ae7577c641 | ||
|
|
f07f598e44 | ||
|
|
988f66cf2b | ||
|
|
06969c406d | ||
|
|
b5215aecba | ||
|
|
3e32bbad7c | ||
|
|
650110a04f | ||
|
|
ee669823de | ||
|
|
40940ca1df | ||
|
|
b77eccffc0 | ||
|
|
e17d8cd559 | ||
|
|
c72e8bcda1 | ||
|
|
2bac991305 | ||
|
|
e6d114c557 | ||
|
|
29efbba97a | ||
|
|
3fb1220262 | ||
|
|
1ccf92c538 | ||
|
|
4bb3153761 | ||
|
|
a8ed0e8c66 | ||
|
|
ad6405f865 | ||
|
|
761d5730af | ||
|
|
b45b7bc7f5 | ||
|
|
77a985dc02 | ||
|
|
21e693a9da | ||
|
|
be91b3049c | ||
|
|
4dc24f745c | ||
|
|
61a1cc3825 | ||
|
|
2e01d1df46 | ||
|
|
2c6411a2b1 | ||
|
|
96b12ac261 | ||
|
|
3e734e0d57 | ||
|
|
c9e296794b | ||
|
|
bc6bff499d | ||
|
|
de905056fc | ||
|
|
4e8344660e | ||
|
|
a581f7dfa7 | ||
|
|
bd6a6e5dce | ||
|
|
e0496c12c6 | ||
|
|
f4116f2c20 | ||
|
|
8099bc4bdd | ||
|
|
39d174c605 | ||
|
|
0257aa9e15 | ||
|
|
3299b2bdb4 | ||
|
|
f43b018511 | ||
|
|
0f884b79fa | ||
|
|
ab83d3fae6 | ||
|
|
cc7e8dc510 | ||
|
|
c2d0d34c57 | ||
|
|
5d46c93b2b | ||
|
|
e6d7a7232f | ||
|
|
6ba1be6eae | ||
|
|
c194c74e55 | ||
|
|
96de84e68f | ||
|
|
6215bc1514 | ||
|
|
b0a93d6884 | ||
|
|
bba0c874f2 | ||
|
|
a32efb61d1 | ||
|
|
10bdb5f371 | ||
|
|
b07859f6ec | ||
|
|
65df24a98b | ||
|
|
9396784c0f | ||
|
|
8420d953eb | ||
|
|
e7de4848fb | ||
|
|
92824bb5b0 | ||
|
|
8d20e77173 | ||
|
|
15aafe7563 | ||
|
|
b56af8b696 | ||
|
|
a3e91a95df | ||
|
|
4ea51ab123 | ||
|
|
4b849a4fe4 | ||
|
|
16e67269ba | ||
|
|
76d5093a20 | ||
|
|
0e8945db78 | ||
|
|
ffd81b6a72 | ||
|
|
d1d218ac0f | ||
|
|
0edfb625e8 | ||
|
|
16c0080cdc | ||
|
|
b05c4bbe24 | ||
|
|
639c65ef93 | ||
|
|
332c549305 | ||
|
|
ef973e9d7f | ||
|
|
199ecb814b | ||
|
|
40d955a156 | ||
|
|
cd23e9a2d0 | ||
|
|
4d482aaab7 | ||
|
|
3175b7b783 | ||
|
|
baa35af558 | ||
|
|
b2de384fcf | ||
|
|
c69fd889fb | ||
|
|
13a853ff42 | ||
|
|
13df700ef5 | ||
|
|
19a0a22b62 | ||
|
|
b51466eaec | ||
|
|
9552d5a46c | ||
|
|
a1d61bb48e | ||
|
|
ec0b5f7fb1 | ||
|
|
0b4699e24a | ||
|
|
d18107b3a9 | ||
|
|
715893e1ac | ||
|
|
92b2f6bc7c | ||
|
|
3498ab2d7b | ||
|
|
efd0ce51cb | ||
|
|
7739020931 | ||
|
|
ecfecbb8f9 | ||
|
|
e8a81102f4 | ||
|
|
591e5226fd | ||
|
|
b336a0d264 | ||
|
|
0b7bec75de | ||
|
|
87bbd1eef7 | ||
|
|
2646dc8398 | ||
|
|
4295ec9d80 | ||
|
|
7cb643b181 | ||
|
|
109d624227 | ||
|
|
b96d195f54 | ||
|
|
775b464496 | ||
|
|
e2cd25c184 | ||
|
|
fdcb488d4b | ||
|
|
a8a596ca7e | ||
|
|
9ced9996d2 | ||
|
|
df683f96b2 | ||
|
|
27a8bdbe7b | ||
|
|
bdabae9c33 | ||
|
|
4d7c030476 | ||
|
|
95f22e98ac | ||
|
|
b0dada7613 | ||
|
|
e54ea1feaa | ||
|
|
0fd09c908b | ||
|
|
36628a46d6 | ||
|
|
2904c90d4b | ||
|
|
f0dbe2bb54 | ||
|
|
e2792272e8 | ||
|
|
1c65e67be2 | ||
|
|
2ae3d6c271 | ||
|
|
96d4f0b545 | ||
|
|
ad947a755c | ||
|
|
35f9c3bf68 | ||
|
|
ff44002b7c | ||
|
|
0ce8304c69 | ||
|
|
5f91feb3a4 | ||
|
|
baebb8632f | ||
|
|
cb97f90581 | ||
|
|
3d13caa37b | ||
|
|
54ecfaddcf |
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
use log *
|
||||
use std log
|
||||
|
||||
# cd to git root
|
||||
cd (git rev-parse --show-toplevel)
|
||||
@@ -116,6 +116,7 @@ for system in ($targets | columns) {
|
||||
} }
|
||||
| filter {|it| $it.needed}
|
||||
| each {|it| job-id $system $it.name}
|
||||
| sort
|
||||
)
|
||||
|
||||
mut new_job = {
|
||||
|
||||
33
.ci/run-regression.sh
Executable file
33
.ci/run-regression.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
iterations="$1"
|
||||
sleep_time="$2"
|
||||
|
||||
PWD="$(pwd)"
|
||||
EXEC="$PWD/target/release/rosenpass"
|
||||
LOGS="$PWD/output/logs"
|
||||
|
||||
mkdir -p "$LOGS"
|
||||
|
||||
run_command() {
|
||||
local file=$1
|
||||
local log_file="$2"
|
||||
("$EXEC" exchange-config "$file" 2>&1 | tee -a "$log_file") &
|
||||
echo $!
|
||||
}
|
||||
|
||||
pids=()
|
||||
|
||||
(cd output/dut && run_command "configs/dut-$iterations.toml" "$LOGS/dut.log")
|
||||
for (( x=0; x<iterations; x++ )); do
|
||||
(cd output/ate && run_command "configs/ate-$x.toml" "$LOGS/ate-$x.log") & pids+=($!)
|
||||
done
|
||||
|
||||
sleep "$sleep_time"
|
||||
|
||||
lsof -i :9999 | awk 'NR!=1 {print $2}' | xargs kill
|
||||
|
||||
for (( x=0; x<iterations; x++ )); do
|
||||
port=$((x + 50000))
|
||||
lsof -i :$port | awk 'NR!=1 {print $2}' | xargs kill
|
||||
done
|
||||
1
.devcontainer/Dockerfile
Normal file
1
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1 @@
|
||||
FROM ghcr.io/xtruder/nix-devcontainer:v1
|
||||
33
.devcontainer/devcontainer.json
Normal file
33
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,33 @@
|
||||
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-existing-dockerfile
|
||||
{
|
||||
"name": "devcontainer-project",
|
||||
"dockerFile": "Dockerfile",
|
||||
"context": "${localWorkspaceFolder}",
|
||||
"build": {
|
||||
"args": {
|
||||
"USER_UID": "${localEnv:USER_UID}",
|
||||
"USER_GID": "${localEnv:USER_GID}"
|
||||
}
|
||||
},
|
||||
|
||||
// run arguments passed to docker
|
||||
"runArgs": ["--security-opt", "label=disable"],
|
||||
|
||||
// disable command overriding and updating remote user ID
|
||||
"overrideCommand": false,
|
||||
"userEnvProbe": "loginShell",
|
||||
"updateRemoteUserUID": false,
|
||||
|
||||
// build development environment on creation, make sure you already have shell.nix
|
||||
"onCreateCommand": "nix develop",
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [],
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
.github/codecov.yml
vendored
Normal file
14
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
codecov:
|
||||
branch: main
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# basic
|
||||
target: auto #default
|
||||
threshold: 5
|
||||
base: auto
|
||||
if_ci_failed: error #success, failure, error, ignore
|
||||
informational: false
|
||||
only_pulls: true
|
||||
patch: off
|
||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
63
.github/workflows/dependent-issues.yml
vendored
Normal file
63
.github/workflows/dependent-issues.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Dependent Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- closed
|
||||
- reopened
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- closed
|
||||
- reopened
|
||||
# Makes sure we always add status check for PRs. Useful only if
|
||||
# this action is required to pass before merging. Otherwise, it
|
||||
# can be removed.
|
||||
- synchronize
|
||||
|
||||
# Schedule a daily check. Useful if you reference cross-repository
|
||||
# issues or pull requests. Otherwise, it can be removed.
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: z0al/dependent-issues@v1
|
||||
env:
|
||||
# (Required) The token to use to make API calls to GitHub.
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# (Optional) The token to use to make API calls to GitHub for remote repos.
|
||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||
|
||||
with:
|
||||
# (Optional) The label to use to mark dependent issues
|
||||
label: dependent
|
||||
|
||||
# (Optional) Enable checking for dependencies in issues.
|
||||
# Enable by setting the value to "on". Default "off"
|
||||
check_issues: off
|
||||
|
||||
# (Optional) Ignore dependabot PRs.
|
||||
# Enable by setting the value to "on". Default "off"
|
||||
ignore_dependabot: off
|
||||
|
||||
# (Optional) A comma-separated list of keywords. Default
|
||||
# "depends on, blocked by"
|
||||
keywords: depends on, blocked by
|
||||
|
||||
# (Optional) A custom comment body. It supports `{{ dependencies }}` token.
|
||||
comment: >
|
||||
This PR/issue depends on:
|
||||
|
||||
{{ dependencies }}
|
||||
|
||||
By **[Dependent Issues](https://github.com/z0al/dependent-issues)** (🤖). Happy coding!
|
||||
109
.github/workflows/nix.yaml
vendored
109
.github/workflows/nix.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
i686-linux---default:
|
||||
name: Build i686-linux.default
|
||||
@@ -95,6 +100,7 @@ jobs:
|
||||
- macos-13
|
||||
needs:
|
||||
- x86_64-darwin---rosenpass
|
||||
- x86_64-darwin---rp
|
||||
- x86_64-darwin---rosenpass-oci-image
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -123,6 +129,22 @@ jobs:
|
||||
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:
|
||||
@@ -210,8 +232,9 @@ jobs:
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
needs:
|
||||
- x86_64-linux---rosenpass-static-oci-image
|
||||
- x86_64-linux---rosenpass-static
|
||||
- x86_64-linux---rosenpass-static-oci-image
|
||||
- x86_64-linux---rp-static
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
@@ -223,29 +246,30 @@ jobs:
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
|
||||
aarch64-linux---release-package:
|
||||
name: Build aarch64-linux.release-package
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
needs:
|
||||
- aarch64-linux---rosenpass-oci-image
|
||||
- aarch64-linux---rosenpass
|
||||
steps:
|
||||
- run: |
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
extra_nix_config: |
|
||||
system = aarch64-linux
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
||||
# aarch64-linux---release-package:
|
||||
# name: Build aarch64-linux.release-package
|
||||
# runs-on:
|
||||
# - ubuntu-latest
|
||||
# needs:
|
||||
# - aarch64-linux---rosenpass-oci-image
|
||||
# - aarch64-linux---rosenpass
|
||||
# - aarch64-linux---rp
|
||||
# steps:
|
||||
# - run: |
|
||||
# DEBIAN_FRONTEND=noninteractive
|
||||
# sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
# - uses: actions/checkout@v3
|
||||
# - uses: cachix/install-nix-action@v22
|
||||
# with:
|
||||
# nix_path: nixpkgs=channel:nixos-unstable
|
||||
# extra_nix_config: |
|
||||
# system = aarch64-linux
|
||||
# - uses: cachix/cachix-action@v12
|
||||
# with:
|
||||
# name: rosenpass
|
||||
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
# - name: Build
|
||||
# run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
||||
x86_64-linux---rosenpass:
|
||||
name: Build x86_64-linux.rosenpass
|
||||
runs-on:
|
||||
@@ -283,6 +307,27 @@ jobs:
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.aarch64-linux.rosenpass --print-build-logs
|
||||
aarch64-linux---rp:
|
||||
name: Build aarch64-linux.rp
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
needs: []
|
||||
steps:
|
||||
- run: |
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
extra_nix_config: |
|
||||
system = aarch64-linux
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.aarch64-linux.rp --print-build-logs
|
||||
x86_64-linux---rosenpass-oci-image:
|
||||
name: Build x86_64-linux.rosenpass-oci-image
|
||||
runs-on:
|
||||
@@ -338,6 +383,22 @@ jobs:
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- name: Build
|
||||
run: nix build .#packages.x86_64-linux.rosenpass-static --print-build-logs
|
||||
x86_64-linux---rp-static:
|
||||
name: Build x86_64-linux.rp-static
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
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-linux.rp-static --print-build-logs
|
||||
x86_64-linux---rosenpass-static-oci-image:
|
||||
name: Build x86_64-linux.rosenpass-static-oci-image
|
||||
runs-on:
|
||||
|
||||
64
.github/workflows/qc.yaml
vendored
64
.github/workflows/qc.yaml
vendored
@@ -4,6 +4,10 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
@@ -46,12 +50,22 @@ jobs:
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install -y libsodium-dev
|
||||
# liboqs requires quite a lot of stack memory, thus we adjust
|
||||
# the default stack size picked for new threads (which is used
|
||||
# by `cargo test`) to be _big enough_. Setting it to 8 MiB
|
||||
- run: RUST_MIN_STACK=8388608 cargo bench --no-run --workspace
|
||||
- run: RUST_MIN_STACK=8388608 cargo bench --workspace --exclude rosenpass-fuzzing
|
||||
|
||||
mandoc:
|
||||
name: mandoc
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install mandoc
|
||||
run: sudo apt-get install -y mandoc
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check rosenpass.1
|
||||
run: doc/check.sh doc/rosenpass.1
|
||||
- name: Check rp.1
|
||||
run: doc/check.sh doc/rp.1
|
||||
|
||||
cargo-audit:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -75,8 +89,6 @@ jobs:
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- run: rustup component add clippy
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install -y libsodium-dev
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -96,15 +108,18 @@ jobs:
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- run: rustup component add clippy
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install -y libsodium-dev
|
||||
# `--no-deps` used as a workaround for a rust compiler bug. See:
|
||||
# - https://github.com/rosenpass/rosenpass/issues/62
|
||||
# - https://github.com/rust-lang/rust/issues/108378
|
||||
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
|
||||
|
||||
cargo-test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-13]
|
||||
# - ubuntu is x86-64
|
||||
# - macos-13 is also x86-64 architecture
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
@@ -116,8 +131,6 @@ jobs:
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install -y libsodium-dev
|
||||
# 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
|
||||
@@ -159,8 +172,6 @@ jobs:
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install -y libsodium-dev
|
||||
- name: Install nightly toolchain
|
||||
run: |
|
||||
rustup toolchain install nightly
|
||||
@@ -174,5 +185,30 @@ jobs:
|
||||
cargo fuzz run fuzz_handle_msg -- -max_total_time=5
|
||||
ulimit -s 8192000 && RUST_MIN_STACK=33554432000 && cargo fuzz run fuzz_kyber_encaps -- -max_total_time=5
|
||||
cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=5
|
||||
cargo fuzz run fuzz_box_secret_alloc -- -max_total_time=5
|
||||
cargo fuzz run fuzz_vec_secret_alloc -- -max_total_time=5
|
||||
cargo fuzz run fuzz_box_secret_alloc_malloc -- -max_total_time=5
|
||||
cargo fuzz run fuzz_box_secret_alloc_memfdsec -- -max_total_time=5
|
||||
cargo fuzz run fuzz_box_secret_alloc_memfdsec_mallocfb -- -max_total_time=5
|
||||
cargo fuzz run fuzz_vec_secret_alloc_malloc -- -max_total_time=5
|
||||
cargo fuzz run fuzz_vec_secret_alloc_memfdsec -- -max_total_time=5
|
||||
cargo fuzz run fuzz_vec_secret_alloc_memfdsec_mallocfb -- -max_total_time=5
|
||||
|
||||
codecov:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: rustup component add llvm-tools-preview
|
||||
- run: |
|
||||
cargo install cargo-llvm-cov || true
|
||||
cargo llvm-cov \
|
||||
--workspace\
|
||||
--all-features \
|
||||
--lcov \
|
||||
--output-path coverage.lcov
|
||||
# If using tarapulin
|
||||
#- run: cargo install cargo-tarpaulin
|
||||
#- run: cargo tarpaulin --out Xml
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage.lcov
|
||||
verbose: true
|
||||
|
||||
25
.github/workflows/regressions.yml
vendored
Normal file
25
.github/workflows/regressions.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Regressions
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
multi-peer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: cargo build --bin rosenpass --release
|
||||
- run: python misc/generate_configs.py
|
||||
- run: chmod +x .ci/run-regression.sh
|
||||
- run: .ci/run-regression.sh 100 20
|
||||
- run: |
|
||||
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -20,3 +20,8 @@ _markdown_*
|
||||
**/result
|
||||
**/result-*
|
||||
.direnv
|
||||
|
||||
# Visual studio code
|
||||
.vscode
|
||||
|
||||
/output
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.direnv/
|
||||
flake.lock
|
||||
papers/whitepaper.md
|
||||
target/
|
||||
src/usage.md
|
||||
target/
|
||||
|
||||
38
CONTRIBUTING.md
Normal file
38
CONTRIBUTING.md
Normal file
@@ -0,0 +1,38 @@
|
||||
**Making a new Release of Rosenpass — Cooking Recipe**
|
||||
|
||||
If you have to change a file, do what it takes to get the change as commit on the main branch, then **start from step 0**.
|
||||
If any other issue occurs
|
||||
|
||||
0. Make sure you are in the root directory of the project
|
||||
- `cd "$(git rev-parse --show-toplevel)"`
|
||||
1. Make sure you locally checked out the head of the main branch
|
||||
- `git stash --include-untracked && git checkout main && git pull`
|
||||
2. Make sure all tests pass
|
||||
- `cargo test`
|
||||
3. Make sure the current version in `rosenpass/Cargo.toml` matches that in the [last release on GitHub](https://github.com/rosenpass/rosenpass/releases)
|
||||
- Only normal releases count, release candidates and draft releases can be ignored
|
||||
4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)
|
||||
- See `cargo release --help` for more information on the available release types
|
||||
- Pick `rc` if in doubt
|
||||
5. Try to release a new version
|
||||
- `cargo release rc --package rosenpass`
|
||||
- An issue was reported? Go fix it, start again with step 0!
|
||||
6. Actually make the release
|
||||
- `cargo release rc --package rosenpass --execute`
|
||||
- Tentatively wait for any interactions, such as entering ssh keys etc.
|
||||
- You may be asked for your ssh key multiple times!
|
||||
|
||||
**Frequently Asked Questions (FAQ)**
|
||||
|
||||
- You have untracked files, which `cargo release` complains about?
|
||||
- `git stash --include-untracked`
|
||||
- You cannot push to crates.io because you are not logged in?
|
||||
- Follow the steps displayed in [`cargo login`](https://doc.rust-lang.org/cargo/commands/cargo-login.html)
|
||||
- How is the release page added to [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) itself?
|
||||
- Our CI Pipeline will create the release, once `cargo release` pushed the new version tag to the repo. The new release should pop up almost immediately in [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) after the [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml) pipeline started.
|
||||
- No new release pops up in the `Release` sidebar element on the [main page](https://github.com/rosenpass/rosenpass)
|
||||
- Did you push a `rc` release? This view only shows non-draft release, but `rc` releases are considered as draft. See [Releases](https://github.com/rosenpass/rosenpass/releases) page to see all (including draft!) releases.
|
||||
- The release page was created on GitHub, but there are no assets/artifacts other than the source code tar ball/zip?
|
||||
- The artifacts are generated and pushed automatically to the release, but this takes some time (a couple of minutes). You can check the respective CI pipeline: [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml), which should start immediately after `cargo release` pushed the new release tag to the repo. The release artifacts only are added later to the release, once all jobs in bespoke pipeline finished.
|
||||
- How are the release artifacts generated, and what are they?
|
||||
- The release artifacts are built using one Nix derivation per platform, `nix build .#release-package`. It contains both statically linked versions of `rosenpass` itself and OCI container images.
|
||||
1531
Cargo.lock
generated
1531
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
75
Cargo.toml
75
Cargo.toml
@@ -11,11 +11,11 @@ members = [
|
||||
"to",
|
||||
"fuzz",
|
||||
"secret-memory",
|
||||
"rp",
|
||||
"wireguard-broker",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
"rosenpass"
|
||||
]
|
||||
default-members = ["rosenpass", "rp", "wireguard-broker"]
|
||||
|
||||
[workspace.metadata.release]
|
||||
# ensure that adding `--package` as argument to `cargo release` still creates version tags in the form of `vx.y.z`
|
||||
@@ -30,32 +30,61 @@ rosenpass-ciphers = { path = "ciphers" }
|
||||
rosenpass-to = { path = "to" }
|
||||
rosenpass-secret-memory = { path = "secret-memory" }
|
||||
rosenpass-oqs = { path = "oqs" }
|
||||
criterion = "0.4.0"
|
||||
test_bin = "0.4.0"
|
||||
libfuzzer-sys = "0.4"
|
||||
stacker = "0.1.15"
|
||||
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
||||
doc-comment = "0.3.3"
|
||||
base64 = "0.21.5"
|
||||
zeroize = "1.7.0"
|
||||
memoffset = "0.9.0"
|
||||
thiserror = "1.0.50"
|
||||
paste = "1.0.14"
|
||||
env_logger = "0.10.1"
|
||||
base64ct = { version = "1.6.0", default-features = false }
|
||||
zeroize = "1.8.1"
|
||||
memoffset = "0.9.1"
|
||||
thiserror = "1.0.64"
|
||||
paste = "1.0.15"
|
||||
env_logger = "0.10.2"
|
||||
toml = "0.7.8"
|
||||
static_assertions = "1.1.0"
|
||||
allocator-api2 = "0.2.14"
|
||||
allocator-api2-tests = "0.2.14"
|
||||
memsec = "0.6.3"
|
||||
memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec6844125bd6612f92e9a281373df", features = [
|
||||
"alloc_ext",
|
||||
] }
|
||||
rand = "0.8.5"
|
||||
typenum = "1.17.0"
|
||||
log = { version = "0.4.20" }
|
||||
clap = { version = "4.4.10", features = ["derive"] }
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
log = { version = "0.4.22" }
|
||||
clap = { version = "4.5.19", features = ["derive"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||
anyhow = { version = "1.0.75", features = ["backtrace", "std"] }
|
||||
mio = { version = "0.8.9", features = ["net", "os-poll"] }
|
||||
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
|
||||
anyhow = { version = "1.0.89", features = ["backtrace", "std"] }
|
||||
mio = { version = "1.0.2", features = ["net", "os-poll"] }
|
||||
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||
'classic_mceliece',
|
||||
'kyber',
|
||||
] }
|
||||
blake2 = "0.10.6"
|
||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ "std", "heapless" ] }
|
||||
zerocopy = { version = "0.7.32", features = ["derive"] }
|
||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
||||
"std",
|
||||
"heapless",
|
||||
] }
|
||||
zerocopy = { version = "0.7.35", features = ["derive"] }
|
||||
home = "0.5.9"
|
||||
derive_builder = "0.20.1"
|
||||
tokio = { version = "1.40", features = ["macros", "rt-multi-thread"] }
|
||||
postcard = { version = "1.0.10", features = ["alloc"] }
|
||||
libcrux = { version = "0.0.2-pre.2" }
|
||||
hex-literal = { version = "0.4.1" }
|
||||
hex = { version = "0.4.3" }
|
||||
heck = { version = "0.5.0" }
|
||||
libc = { version = "0.2" }
|
||||
uds = { git = "https://github.com/rosenpass/uds" }
|
||||
|
||||
#Dev dependencies
|
||||
serial_test = "3.1.1"
|
||||
tempfile = "3"
|
||||
stacker = "0.1.17"
|
||||
libfuzzer-sys = "0.4"
|
||||
test_bin = "0.4.0"
|
||||
criterion = "0.4.0"
|
||||
allocator-api2-tests = "0.2.15"
|
||||
procspawn = { version = "1.0.1", features = ["test-support"] }
|
||||
|
||||
|
||||
#Broker dependencies (might need cleanup or changes)
|
||||
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||
command-fds = "0.2.3"
|
||||
rustix = { version = "0.38.37", features = ["net", "fs"] }
|
||||
|
||||
@@ -3,33 +3,12 @@
|
||||
#define SESSION_START_EVENTS 0
|
||||
#define RANDOMIZED_CALL_IDS 0
|
||||
|
||||
|
||||
#include "config.mpv"
|
||||
#include "prelude/basic.mpv"
|
||||
#include "crypto/key.mpv"
|
||||
#include "crypto/kem.mpv"
|
||||
|
||||
#include "rosenpass/oracles.mpv"
|
||||
|
||||
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
|
||||
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
|
||||
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
|
||||
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
|
||||
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
|
||||
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
|
||||
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
|
||||
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
|
||||
nounif Spk:kem_sk_tmpl;
|
||||
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
|
||||
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
|
||||
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
|
||||
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
|
||||
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
|
||||
nounif rh:RespHello_t;
|
||||
attacker(Cresp_hello( *rh ))/6107[conclusion].
|
||||
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
|
||||
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].
|
||||
|
||||
let main = rosenpass_main.
|
||||
|
||||
@lemma "state coherence, initiator: Initiator accepting a RespHello message implies they also generated the associated InitHello message"
|
||||
|
||||
@@ -10,26 +10,6 @@
|
||||
|
||||
let main = rosenpass_main.
|
||||
|
||||
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
|
||||
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
|
||||
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
|
||||
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
|
||||
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
|
||||
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
|
||||
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
|
||||
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
|
||||
nounif Spk:kem_sk_tmpl;
|
||||
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
|
||||
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
|
||||
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
|
||||
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
|
||||
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
|
||||
nounif rh:RespHello_t;
|
||||
attacker(Cresp_hello( *rh ))/6107[conclusion].
|
||||
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
|
||||
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].
|
||||
|
||||
|
||||
@lemma "non-interruptability: Adv cannot prevent a genuine InitHello message from being accepted"
|
||||
lemma ih:InitHello_t, psk:key, sski:kem_sk, sskr:kem_sk;
|
||||
event(IHRjct(ih, psk, sskr, kem_pub(sski)))
|
||||
|
||||
@@ -88,18 +88,6 @@ set verboseCompleted=VERBOSE.
|
||||
#define SES_EV(...)
|
||||
#endif
|
||||
|
||||
#if COOKIE_EVENTS
|
||||
#define COOKIE_EV(...) __VA_ARGS__
|
||||
#else
|
||||
#define COOKIE_EV(...)
|
||||
#endif
|
||||
|
||||
#if KEM_EVENTS
|
||||
#define KEM_EV(...) __VA_ARGS__
|
||||
#else
|
||||
#define KEM_EV(...)
|
||||
#endif
|
||||
|
||||
|
||||
(* TODO: Authentication timing properties *)
|
||||
(* TODO: Proof that every adversary submitted package is equivalent to one generated by the proper algorithm using different coins. This probably requires introducing an oracle that extracts the coins used and explicitly adding the notion of coins used for Packet->Packet steps and an inductive RNG notion. *)
|
||||
|
||||
@@ -41,32 +41,23 @@ restriction s:seed, p1:Atom, p2:Atom, ad1:Atom, ad2:Atom;
|
||||
event(ConsumeSeed(p1, s, ad1)) && event(ConsumeSeed(p2, s, ad2))
|
||||
==> p1 = p2 && ad1 = ad2.
|
||||
|
||||
letfun create_mac2(k:key, msg:bits) = prf(k,msg).
|
||||
|
||||
#include "rosenpass/responder.macro"
|
||||
fun Cinit_conf(kem_sk_tmpl, key_tmpl, kem_pk_tmpl, InitConf_t) : Atom [data].
|
||||
CK_EV( event OskOinit_conf(key, key). )
|
||||
MTX_EV( event ICRjct(InitConf_t, key, kem_sk, kem_pk). )
|
||||
SES_EV( event ResponderSession(InitConf_t, key). )
|
||||
KEM_EV(event Oinit_conf_KemUse(SessionId, SessionId, Atom).)
|
||||
#ifdef KEM_EVENTS
|
||||
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(Oinit_conf_KemUse(sidi, sidr, ad1)) && event(Oinit_conf_KemUse(sidi, sidr, ad2))
|
||||
==> ad1 = ad2.
|
||||
#endif
|
||||
event ConsumeBiscuit(Atom, kem_sk, kem_pk, Atom).
|
||||
|
||||
fun Ccookie(key, bits) : Atom[data].
|
||||
|
||||
let Oinit_conf_inner(Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t, call:Atom) =
|
||||
|
||||
let Oinit_conf() =
|
||||
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
|
||||
#endif
|
||||
SETUP_HANDSHAKE_STATE()
|
||||
|
||||
eski <- kem_sk0;
|
||||
epki <- kem_pk0;
|
||||
let try_ = (
|
||||
let InitConf(sidi, sidr, biscuit, auth) = ic in
|
||||
KEM_EV(event Oinit_conf_KemUse(sidi, sidr, call);)
|
||||
INITCONF_CONSUME()
|
||||
event ConsumeBiscuit(biscuit_no, sskm, spkt, call);
|
||||
CK_EV( event OskOinit_conf(ck_rh, osk); )
|
||||
@@ -82,20 +73,10 @@ let Oinit_conf_inner(Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:Ini
|
||||
#endif
|
||||
).
|
||||
|
||||
let Oinit_conf() =
|
||||
|
||||
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
|
||||
#endif
|
||||
|
||||
Oinit_conf_inner(Ssskm, Spsk, Sspkt, ic, call).
|
||||
|
||||
restriction biscuit_no:Atom, sskm:kem_sk, spkr:kem_pk, ad1:Atom, ad2:Atom;
|
||||
event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad1)) && event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad2))
|
||||
==> ad1 = ad2.
|
||||
|
||||
// TODO: Restriction biscuit no invalidation
|
||||
|
||||
#include "rosenpass/initiator.macro"
|
||||
@@ -104,29 +85,11 @@ CK_EV( event OskOresp_hello(key, key, key). )
|
||||
MTX_EV( event RHRjct(RespHello_t, key, kem_sk, kem_pk). )
|
||||
MTX_EV( event ICSent(RespHello_t, InitConf_t, key, kem_sk, kem_pk). )
|
||||
SES_EV( event InitiatorSession(RespHello_t, key). )
|
||||
|
||||
KEM_EV(event Oresp_hello_KemUse(SessionId, SessionId, Atom).)
|
||||
#ifdef KEM_EVENTS
|
||||
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(Oresp_hello_KemUse(sidi, sidr, ad1)) && event(Oresp_hello_KemUse(sidi, sidr, ad2))
|
||||
==> ad1 = ad2.
|
||||
#endif
|
||||
|
||||
#ifdef COOKIE_EVENTS
|
||||
COOKIE_EVENTS(Oresp_hello)
|
||||
#endif
|
||||
let Oresp_hello(HS_DECL_ARGS, C_in:channel, call:Atom) =
|
||||
in(C_in, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth)));
|
||||
in(C_in, mac2_key:key);
|
||||
let Oresp_hello(HS_DECL_ARGS) =
|
||||
in(C, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth)));
|
||||
rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth);
|
||||
#ifdef COOKIE_EVENTS
|
||||
msg <- RH2b(rh);
|
||||
|
||||
COOKIE_PROCESS(Oresp_hello,
|
||||
#endif
|
||||
/* try */ let ic = (
|
||||
ck_ini <- ck;
|
||||
KEM_EV(event Oresp_hello_KemUse(sidi, sidr, call);)
|
||||
RESPHELLO_CONSUME()
|
||||
ck_ih <- ck;
|
||||
INITCONF_PRODUCE()
|
||||
@@ -135,25 +98,14 @@ let Oresp_hello(HS_DECL_ARGS, C_in:channel, call:Atom) =
|
||||
SES_EV( event InitiatorSession(rh, osk); )
|
||||
ic
|
||||
/* success */ ) in (
|
||||
icbits <- IC2b(ic);
|
||||
mac <- create_mac(spkt, icbits);
|
||||
mac2 <- create_mac2(mac2_key, mac_envelope2b(mac));
|
||||
out(C_in, ic);
|
||||
out(C_in, mac);
|
||||
out(C_in, mac2)
|
||||
|
||||
out(C, ic)
|
||||
/* fail */ ) else (
|
||||
#if MESSAGE_TRANSMISSION_EVENTS
|
||||
#if MESSAGE_TRANSMISSION_EVENTS
|
||||
event RHRjct(rh, psk, sski, spkr)
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
)
|
||||
#ifdef COOKIE_EVENTS
|
||||
)
|
||||
#else
|
||||
.
|
||||
0
|
||||
#endif
|
||||
).
|
||||
|
||||
// TODO: Restriction: Biscuit no invalidation
|
||||
|
||||
@@ -164,33 +116,24 @@ MTX_EV( event IHRjct(InitHello_t, key, kem_sk, kem_pk). )
|
||||
MTX_EV( event RHSent(InitHello_t, RespHello_t, key, kem_sk, kem_pk). )
|
||||
event ConsumeSidr(SessionId, Atom).
|
||||
event ConsumeBn(Atom, kem_sk, kem_pk, Atom).
|
||||
KEM_EV(event Oinit_hello_KemUse(SessionId, SessionId, Atom).)
|
||||
|
||||
#ifdef KEM_EVENTS
|
||||
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(Oinit_hello_KemUse(sidi, sidr, ad1)) && event(Oinit_hello_KemUse(sidi, sidr, ad2))
|
||||
==> ad1 = ad2.
|
||||
let Oinit_hello() =
|
||||
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih);
|
||||
#endif
|
||||
|
||||
let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt: kem_sk_tmpl, Septi: seed_tmpl, Sspti: seed_tmpl, ih: InitHello_t, mac2_key:key, C_out:channel, call:Atom) =
|
||||
// TODO: This is ugly
|
||||
let InitHello(sidi, epki, sctr, pidiC, auth) = ih in
|
||||
|
||||
SETUP_HANDSHAKE_STATE()
|
||||
|
||||
eski <- kem_sk0;
|
||||
|
||||
event ConsumeBn(biscuit_no, sskm, spkt, call);
|
||||
event ConsumeSidr(sidr, call);
|
||||
|
||||
epti <- rng_key(setup_seed(Septi)); // RHR4
|
||||
spti <- rng_key(setup_seed(Sspti)); // RHR5
|
||||
event ConsumeBn(biscuit_no, sskm, spkt, call);
|
||||
event ConsumeSidr(sidr, call);
|
||||
event ConsumeSeed(Epti, setup_seed(Septi), call);
|
||||
event ConsumeSeed(Spti, setup_seed(Sspti), call);
|
||||
// out(C_out, spkt);
|
||||
|
||||
let rh = (
|
||||
KEM_EV(event Oinit_hello_KemUse(sidi, sidr, call);)
|
||||
INITHELLO_CONSUME()
|
||||
ck_ini <- ck;
|
||||
RESPHELLO_PRODUCE()
|
||||
@@ -198,14 +141,7 @@ let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:k
|
||||
MTX_EV( event RHSent(ih, rh, psk, sskr, spki); )
|
||||
rh
|
||||
/* success */ ) in (
|
||||
rhbits <- RH2b(rh);
|
||||
mac <- create_mac(spkt, rhbits);
|
||||
|
||||
out(C_out, rh);
|
||||
out(C_out, mac);
|
||||
mac2 <- create_mac2(mac2_key, mac_envelope2b(mac));
|
||||
out(C_out, mac2)
|
||||
|
||||
out(C, rh)
|
||||
/* fail */ ) else (
|
||||
#if MESSAGE_TRANSMISSION_EVENTS
|
||||
event IHRjct(ih, psk, sskr, spki)
|
||||
@@ -214,18 +150,6 @@ let Oinit_hello_inner(sidm:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:k
|
||||
#endif
|
||||
).
|
||||
|
||||
let Oinit_hello() =
|
||||
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
|
||||
in(C, mac2_key:key);
|
||||
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih);
|
||||
#endif
|
||||
|
||||
Oinit_hello_inner(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih, mac2_key, C, call).
|
||||
|
||||
restriction sid:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(ConsumeSidr(sid, ad1)) && event(ConsumeSidr(sid, ad2))
|
||||
==> ad1 = ad2.
|
||||
@@ -242,55 +166,27 @@ fun Cinitiator(SessionId, kem_sk_tmpl, key_tmpl, kem_pk_tmpl, seed_tmpl, seed_tm
|
||||
CK_EV( event OskOinitiator_ck(key). )
|
||||
CK_EV( event OskOinitiator(key, key, kem_sk, kem_pk, key). )
|
||||
MTX_EV( event IHSent(InitHello_t, key, kem_sk, kem_pk). )
|
||||
KEM_EV(event Oinitiator_inner_KemUse(SessionId, SessionId, Atom).)
|
||||
|
||||
#ifdef KEM_EVENTS
|
||||
restriction sidi:SessionId, sidr:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(Oinitiator_inner_KemUse(sidi, sidr, ad1)) && event(Oinitiator_inner_KemUse(sidi, sidr, ad2))
|
||||
==> ad1 = ad2.
|
||||
#endif
|
||||
event ConsumeSidi(SessionId, Atom).
|
||||
|
||||
let Oinitiator_inner(sidi: SessionId, Ssskm: kem_sk_tmpl, Spsk: key_tmpl, Sspkt: kem_sk_tmpl, Seski: seed_tmpl, Ssptr: seed_tmpl, last_cookie:key, C_out:channel, call:Atom) =
|
||||
|
||||
let Oinitiator() =
|
||||
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
|
||||
#endif
|
||||
SETUP_HANDSHAKE_STATE()
|
||||
sidr <- sid0;
|
||||
|
||||
KEM_EV(event Oinitiator_inner_KemUse(sidi, sidr, call);)
|
||||
|
||||
RNG_KEM_PAIR(eski, epki, Seski) // IHI3
|
||||
sidr <- sid0;
|
||||
sptr <- rng_key(setup_seed(Ssptr)); // IHI5
|
||||
event ConsumeSidi(sidi, call);
|
||||
event ConsumeSeed(Sptr, setup_seed(Ssptr), call);
|
||||
event ConsumeSeed(Eski, setup_seed(Seski), call);
|
||||
|
||||
INITHELLO_PRODUCE()
|
||||
CK_EV( event OskOinitiator_ck(ck); )
|
||||
CK_EV( event OskOinitiator(ck, psk, sski, spkr, sptr); )
|
||||
MTX_EV( event IHSent(ih, psk, sski, spkr); )
|
||||
|
||||
out(C_out, ih);
|
||||
ihbits <- IH2b(ih);
|
||||
mac <- create_mac(spkt, ihbits);
|
||||
out(C_out, mac);
|
||||
mac2 <- create_mac2(last_cookie, mac_envelope2b(mac));
|
||||
out(C_out, mac2);
|
||||
|
||||
Oresp_hello(HS_PASS_ARGS, C_out, call).
|
||||
|
||||
let Oinitiator() =
|
||||
|
||||
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
|
||||
|
||||
#if RANDOMIZED_CALL_IDS
|
||||
new call:Atom;
|
||||
#else
|
||||
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
|
||||
#endif
|
||||
|
||||
in(C, last_cookie:key);
|
||||
Oinitiator_inner(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr, last_cookie, C, call).
|
||||
|
||||
out(C, ih);
|
||||
Oresp_hello(HS_PASS_ARGS).
|
||||
|
||||
restriction sid:SessionId, ad1:Atom, ad2:Atom;
|
||||
event(ConsumeSidi(sid, ad1)) && event(ConsumeSidi(sid, ad2))
|
||||
@@ -311,3 +207,21 @@ let rosenpass_main() = 0
|
||||
| REP(RESPONDER_BOUND, Oinit_hello)
|
||||
| REP(RESPONDER_BOUND, Oinit_conf).
|
||||
|
||||
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
|
||||
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
|
||||
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
|
||||
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
|
||||
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
|
||||
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
|
||||
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
|
||||
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
|
||||
nounif Spk:kem_sk_tmpl;
|
||||
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
|
||||
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
|
||||
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
|
||||
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
|
||||
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
|
||||
nounif rh:RespHello_t;
|
||||
attacker(Cresp_hello( *rh ))/6107[conclusion].
|
||||
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
|
||||
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].
|
||||
|
||||
@@ -2,26 +2,6 @@
|
||||
#include "crypto/kem.mpv"
|
||||
#include "rosenpass/handshake_state.mpv"
|
||||
|
||||
fun Envelope(
|
||||
key,
|
||||
bits
|
||||
): bits [data].
|
||||
|
||||
type mac_envelope_t.
|
||||
fun mac_envelope(
|
||||
key,
|
||||
bits
|
||||
) : mac_envelope_t.
|
||||
|
||||
fun mac_envelope2b(mac_envelope_t) : bits [typeConverter].
|
||||
|
||||
letfun create_mac(pk:kem_pk, payload:bits) = mac_envelope(lprf2(MAC, kem_pk2b(pk), payload), payload).
|
||||
|
||||
fun mac_envelope_pk_test(mac_envelope_t, kem_pk) : bool
|
||||
reduc forall pk:kem_pk, b:bits;
|
||||
mac_envelope_pk_test(mac_envelope(prf(prf(prf(prf(key0,PROTOCOL),MAC),kem_pk2b(pk)),
|
||||
b), b), pk) = true.
|
||||
|
||||
type InitHello_t.
|
||||
fun InitHello(
|
||||
SessionId, // sidi
|
||||
@@ -31,8 +11,6 @@ fun InitHello(
|
||||
bits // auth
|
||||
) : InitHello_t [data].
|
||||
|
||||
fun IH2b(InitHello_t) : bitstring [typeConverter].
|
||||
|
||||
#define INITHELLO_PRODUCE() \
|
||||
ck <- lprf1(CK_INIT, kem_pk2b(spkr)); /* IHI1 */ \
|
||||
/* not handled here */ /* IHI2 */ \
|
||||
@@ -63,8 +41,6 @@ fun RespHello(
|
||||
bits // auth
|
||||
) : RespHello_t [data].
|
||||
|
||||
fun RH2b(RespHello_t) : bitstring [typeConverter].
|
||||
|
||||
#define RESPHELLO_PRODUCE() \
|
||||
/* not handled here */ /* RHR1 */ \
|
||||
MIX2(sid2b(sidr), sid2b(sidi)) /* RHR3 */ \
|
||||
@@ -91,14 +67,13 @@ fun InitConf(
|
||||
bits // auth
|
||||
) : InitConf_t [data].
|
||||
|
||||
fun IC2b(InitConf_t) : bitstring [typeConverter].
|
||||
|
||||
#define INITCONF_PRODUCE() \
|
||||
MIX2(sid2b(sidi), sid2b(sidr)) /* ICI3 */ \
|
||||
ENCRYPT_AND_MIX(auth, empty) /* ICI4 */ \
|
||||
ic <- InitConf(sidi, sidr, biscuit, auth);
|
||||
|
||||
#define INITCONF_CONSUME() \
|
||||
let InitConf(sidi, sidr, biscuit, auth) = ic in \
|
||||
LOAD_BISCUIT(biscuit_no, biscuit) /* ICR1 */ \
|
||||
ENCRYPT_AND_MIX(rh_auth, empty) /* ICIR */ \
|
||||
ck_rh <- ck; /* ---- */ /* TODO: Move into oracles.mpv */ \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Rosenpass internal libsodium bindings
|
||||
# Rosenpass internal cryptographic traits
|
||||
|
||||
Rosenpass internal library providing traits for cryptographic primitives.
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
|
||||
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
|
||||
|
||||
use std::result::Result;
|
||||
|
||||
/// Key Encapsulation Mechanism
|
||||
///
|
||||
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
||||
|
||||
@@ -9,6 +9,9 @@ homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[features]
|
||||
experiment_libcrux = ["dep:libcrux"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
@@ -20,3 +23,4 @@ static_assertions = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
chacha20poly1305 = { workspace = true }
|
||||
blake2 = { workspace = true }
|
||||
libcrux = { workspace = true, optional = true }
|
||||
|
||||
@@ -9,7 +9,12 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
||||
|
||||
/// Authenticated encryption with associated data
|
||||
pub mod aead {
|
||||
#[cfg(not(feature = "experiment_libcrux"))]
|
||||
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||
#[cfg(feature = "experiment_libcrux")]
|
||||
pub use crate::subtle::chacha20poly1305_ietf_libcrux::{
|
||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||
};
|
||||
}
|
||||
|
||||
/// Authenticated encryption with associated data with a constant nonce
|
||||
|
||||
@@ -33,8 +33,8 @@ pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<(
|
||||
// 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 mut tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
||||
h.finalize_into(&mut tmp);
|
||||
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
||||
h.finalize_into(tmp);
|
||||
copy_slice(tmp.as_ref()).to(out);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn encrypt(
|
||||
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)?;
|
||||
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
|
||||
copy_slice(&mac_value[..]).to(mac);
|
||||
Ok(())
|
||||
}
|
||||
@@ -38,6 +38,6 @@ pub fn decrypt(
|
||||
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)?;
|
||||
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
60
ciphers/src/subtle/chacha20poly1305_ietf_libcrux.rs
Normal file
60
ciphers/src/subtle/chacha20poly1305_ietf_libcrux.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use rosenpass_to::ops::copy_slice;
|
||||
use rosenpass_to::To;
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
|
||||
pub const TAG_LEN: usize = 16;
|
||||
pub const NONCE_LEN: usize = 12;
|
||||
|
||||
#[inline]
|
||||
pub fn encrypt(
|
||||
ciphertext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
plaintext: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||
|
||||
use libcrux::aead as C;
|
||||
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||
|
||||
copy_slice(plaintext).to(ciphertext);
|
||||
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
|
||||
copy_slice(crux_tag.as_ref()).to(mac);
|
||||
|
||||
match crux_key {
|
||||
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decrypt(
|
||||
plaintext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
ciphertext: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||
|
||||
use libcrux::aead as C;
|
||||
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||
let crux_tag = C::Tag::from_slice(mac).unwrap();
|
||||
|
||||
copy_slice(ciphertext).to(plaintext);
|
||||
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
|
||||
|
||||
match crux_key {
|
||||
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
pub mod blake2b;
|
||||
#[cfg(not(feature = "experiment_libcrux"))]
|
||||
pub mod chacha20poly1305_ietf;
|
||||
#[cfg(feature = "experiment_libcrux")]
|
||||
pub mod chacha20poly1305_ietf_libcrux;
|
||||
pub mod incorrect_hmac_blake2b;
|
||||
pub mod xchacha20poly1305_ietf;
|
||||
|
||||
@@ -23,7 +23,7 @@ pub fn encrypt(
|
||||
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)?;
|
||||
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
|
||||
copy_slice(&mac_value[..]).to(mac);
|
||||
Ok(())
|
||||
}
|
||||
@@ -40,6 +40,6 @@ pub fn decrypt(
|
||||
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)?;
|
||||
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
use core::ptr;
|
||||
|
||||
/// Little endian memcmp version of quinier/memsec
|
||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
||||
#[inline(never)]
|
||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
let mut res = 0;
|
||||
for i in 0..len {
|
||||
let diff =
|
||||
i32::from(ptr::read_volatile(b1.add(i))) - i32::from(ptr::read_volatile(b2.add(i)));
|
||||
res = (res & (((diff - 1) & !diff) >> 8)) | diff;
|
||||
}
|
||||
((res - 1) >> 8) + (res >> 8) + 1
|
||||
}
|
||||
|
||||
/// compares two slices of memory content and returns an integer indicating the relationship between
|
||||
/// the slices
|
||||
///
|
||||
@@ -20,5 +35,5 @@
|
||||
#[inline]
|
||||
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
unsafe { memsec::memcmp(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
@@ -7,19 +7,16 @@
|
||||
///
|
||||
/// The execution time of the function grows approx. linear with the length of the input. This is
|
||||
/// considered safe.
|
||||
///
|
||||
/// ## Tests
|
||||
/// [`tests::memcmp_runs_in_constant_time`] runs a stasticial test that the equality of the two
|
||||
#[inline]
|
||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
/// [tests::memcmp_runs_in_constant_time] runs a stasticial test that the equality of the two
|
||||
/// input parameters does not correlate with the run time.
|
||||
///
|
||||
/// For discussion on how to (further) ensure the constant-time execution of this function,
|
||||
/// see <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
#[inline]
|
||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len()
|
||||
&& unsafe { memsec::memeq(a.as_ptr() as *const u8, b.as_ptr() as *const u8, a.len()) }
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "constant_time_tests"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
13
doc/check.sh
Executable file
13
doc/check.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# We have to filter this STYLE error out, because it is very platform specific
|
||||
OUTPUT=$(mandoc -Tlint "$1" | grep --invert-match "STYLE: referenced manual not found")
|
||||
|
||||
if [ -z "$OUTPUT" ]
|
||||
then
|
||||
exit 0
|
||||
else
|
||||
echo "$1 is malformatted, check mandoc -Tlint $1"
|
||||
echo "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
@@ -108,7 +108,7 @@ Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
|
||||
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
||||
.Pp
|
||||
This manual page was written by
|
||||
.An Emil Engler
|
||||
.An Clara Engler
|
||||
.Sh BUGS
|
||||
The bugs are tracked at
|
||||
.Lk https://github.com/rosenpass/rosenpass/issues .
|
||||
|
||||
2
doc/rp.1
2
doc/rp.1
@@ -113,7 +113,7 @@ Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
|
||||
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
||||
.Pp
|
||||
This manual page was written by
|
||||
.An Emil Engler
|
||||
.An Clara Engler
|
||||
.Sh BUGS
|
||||
The bugs are tracked at
|
||||
.Lk https://github.com/rosenpass/rosenpass/issues .
|
||||
|
||||
51
flake.lock
generated
51
flake.lock
generated
@@ -8,11 +8,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1699770036,
|
||||
"narHash": "sha256-bZmI7ytPAYLpyFNgj5xirDkKuAniOkj1xHdv5aIJ5GM=",
|
||||
"lastModified": 1728282832,
|
||||
"narHash": "sha256-I7AbcwGggf+CHqpyd/9PiAjpIBGTGx5woYHqtwxaV7I=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "81ab0b4f7ae9ebb57daa0edf119c4891806e4d3a",
|
||||
"rev": "1ec71be1f4b8f3105c5d38da339cb061fefc43f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -26,11 +26,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -39,56 +39,37 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698420672,
|
||||
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1698846319,
|
||||
"narHash": "sha256-4jyW/dqFBVpWFnhl0nvP6EN4lP7/ZqPxYRjl6var0Oc=",
|
||||
"lastModified": 1728193676,
|
||||
"narHash": "sha256-PbDWAIjKJdlVg+qQRhzdSor04bAPApDqIv2DofTyynk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "34bdaaf1f0b7fb6d9091472edc968ff10a8c2857",
|
||||
"rev": "ecbc1ca8ffd6aea8372ad16be9ebbb39889e55b6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1699715108,
|
||||
"narHash": "sha256-yPozsobJU55gj+szgo4Lpcg1lHvGQYAT6Y4MrC80mWE=",
|
||||
"lastModified": 1728249780,
|
||||
"narHash": "sha256-J269DvCI5dzBmPrXhAAtj566qt0b22TJtF3TIK+tMsI=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "5fcf5289e726785d20d3aa4d13d90a43ed248e83",
|
||||
"rev": "2b750da1a1a2c1d2c70896108d7096089842d877",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
330
flake.nix
330
flake.nix
@@ -1,11 +1,8 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
# for quicker rust builds
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
# for rust nightly with llvm-tools-preview
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -14,6 +11,15 @@
|
||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
||||
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
||||
|
||||
|
||||
#
|
||||
### Export the overlay.nix from this flake ###
|
||||
#
|
||||
{
|
||||
overlays.default = import ./overlay.nix;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
### Actual Rosenpass Package and Docker Container Images ###
|
||||
#
|
||||
@@ -29,232 +35,39 @@
|
||||
]
|
||||
(system:
|
||||
let
|
||||
scoped = (scope: scope.result);
|
||||
lib = nixpkgs.lib;
|
||||
|
||||
# normal nixpkgs
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
# TODO remove overlay once a fix for
|
||||
# https://github.com/NixOS/nixpkgs/issues/216904 got merged
|
||||
overlays = [
|
||||
(
|
||||
final: prev: {
|
||||
iproute2 = prev.iproute2.overrideAttrs (old:
|
||||
let
|
||||
isStatic = prev.stdenv.hostPlatform.isStatic;
|
||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
in
|
||||
{
|
||||
makeFlags = old.makeFlags ++ prev.lib.optional isStatic [
|
||||
"TC_CONFIG_NO_XT=y"
|
||||
];
|
||||
});
|
||||
packages = {
|
||||
default = pkgs.rosenpass;
|
||||
rosenpass = pkgs.rosenpass;
|
||||
rosenpass-oci-image = pkgs.rosenpass-oci-image;
|
||||
rp = pkgs.rp;
|
||||
|
||||
release-package = pkgs.release-package;
|
||||
|
||||
# for good measure, we also offer to cross compile to Linux on Arm
|
||||
aarch64-linux-rosenpass-static =
|
||||
pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass;
|
||||
aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp;
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
# parsed Cargo.toml
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
|
||||
|
||||
# source files relevant for rust
|
||||
src = scoped rec {
|
||||
# File suffices to include
|
||||
extensions = [
|
||||
"lock"
|
||||
"rs"
|
||||
"toml"
|
||||
];
|
||||
# Files to explicitly include
|
||||
files = [
|
||||
"to/README.md"
|
||||
];
|
||||
|
||||
src = ./.;
|
||||
filter = (path: type: scoped rec {
|
||||
inherit (lib) any id removePrefix hasSuffix;
|
||||
anyof = (any id);
|
||||
|
||||
basename = baseNameOf (toString path);
|
||||
relative = removePrefix (toString src + "/") (toString path);
|
||||
|
||||
result = anyof [
|
||||
(type == "directory")
|
||||
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
||||
(any (file: file == relative) files)
|
||||
];
|
||||
//
|
||||
# We only offer static builds for linux, as this is not supported on OS X
|
||||
(nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux {
|
||||
rosenpass-static = pkgs.pkgsStatic.rosenpass;
|
||||
rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image;
|
||||
rp-static = pkgs.pkgsStatic.rp;
|
||||
});
|
||||
|
||||
result = pkgs.lib.sources.cleanSourceWith { inherit src filter; };
|
||||
};
|
||||
|
||||
# builds a bin path for all dependencies for the `rp` shellscript
|
||||
rpBinPath = p: with p; lib.makeBinPath [
|
||||
coreutils
|
||||
findutils
|
||||
gawk
|
||||
wireguard-tools
|
||||
];
|
||||
|
||||
# a function to generate a nix derivation for rosenpass against any
|
||||
# given set of nixpkgs
|
||||
rpDerivation = p:
|
||||
let
|
||||
# whether we want to build a statically linked binary
|
||||
isStatic = p.targetPlatform.isStatic;
|
||||
|
||||
# the rust target of `p`
|
||||
target = p.rust.toRustTargetSpec p.targetPlatform;
|
||||
|
||||
# convert a string to shout case
|
||||
shout = string: builtins.replaceStrings [ "-" ] [ "_" ] (pkgs.lib.toUpper string);
|
||||
|
||||
# suitable Rust toolchain
|
||||
toolchain = with inputs.fenix.packages.${system}; combine [
|
||||
stable.cargo
|
||||
stable.rustc
|
||||
targets.${target}.stable.rust-std
|
||||
];
|
||||
|
||||
# naersk with a custom toolchain
|
||||
naersk = pkgs.callPackage inputs.naersk {
|
||||
cargo = toolchain;
|
||||
rustc = toolchain;
|
||||
};
|
||||
|
||||
# used to trick the build.rs into believing that CMake was ran **again**
|
||||
fakecmake = pkgs.writeScriptBin "cmake" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
true
|
||||
'';
|
||||
in
|
||||
naersk.buildPackage
|
||||
{
|
||||
# metadata and source
|
||||
name = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
inherit src;
|
||||
|
||||
cargoBuildOptions = x: x ++ [ "-p" "rosenpass" ];
|
||||
cargoTestOptions = x: x ++ [ "-p" "rosenpass" ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
p.stdenv.cc
|
||||
cmake # for oqs build in the oqs-sys crate
|
||||
mandoc # for the built-in manual
|
||||
makeWrapper # for the rp shellscript
|
||||
pkg-config # let libsodium-sys-stable find libsodium
|
||||
removeReferencesTo
|
||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
||||
];
|
||||
buildInputs = with p; [ bash libsodium ];
|
||||
|
||||
override = x: {
|
||||
preBuild =
|
||||
# nix defaults to building for aarch64 _without_ the armv8-a crypto
|
||||
# extensions, but liboqs depens on these
|
||||
(lib.optionalString (system == "aarch64-linux") ''
|
||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -march=armv8-a+crypto"
|
||||
''
|
||||
);
|
||||
|
||||
# fortify is only compatible with dynamic linking
|
||||
hardeningDisable = lib.optional isStatic "fortify";
|
||||
};
|
||||
|
||||
overrideMain = x: {
|
||||
# CMake detects that it was served a _foreign_ target dir, and CMake
|
||||
# would be executed again upon the second build step of naersk.
|
||||
# By adding our specially optimized CMake version, we reduce the cost
|
||||
# of recompilation by 99 % while, while avoiding any CMake errors.
|
||||
nativeBuildInputs = [ (lib.hiPrio fakecmake) ] ++ x.nativeBuildInputs;
|
||||
|
||||
# make sure that libc is linked, under musl this is not the case per
|
||||
# default
|
||||
preBuild = (lib.optionalString isStatic ''
|
||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -lc"
|
||||
'');
|
||||
|
||||
preInstall = ''
|
||||
install -D ${./rp} $out/bin/rp
|
||||
wrapProgram $out/bin/rp --prefix PATH : "${ rpBinPath p }"
|
||||
'';
|
||||
};
|
||||
|
||||
# We want to build for a specific target...
|
||||
CARGO_BUILD_TARGET = target;
|
||||
|
||||
# ... which might require a non-default linker:
|
||||
"CARGO_TARGET_${shout target}_LINKER" =
|
||||
let
|
||||
inherit (p.stdenv) cc;
|
||||
in
|
||||
"${cc}/bin/${cc.targetPrefix}cc";
|
||||
|
||||
meta = with pkgs.lib;
|
||||
{
|
||||
inherit (cargoToml.package) description homepage;
|
||||
license = with licenses; [ mit asl20 ];
|
||||
maintainers = [ maintainers.wucke13 ];
|
||||
platforms = platforms.all;
|
||||
};
|
||||
} // (lib.mkIf isStatic {
|
||||
# otherwise pkg-config tries to link non-existent dynamic libs
|
||||
# documented here: https://docs.rs/pkg-config/latest/pkg_config/
|
||||
PKG_CONFIG_ALL_STATIC = true;
|
||||
|
||||
# tell rust to build everything statically linked
|
||||
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
|
||||
});
|
||||
# a function to generate a docker image based of rosenpass
|
||||
rosenpassOCI = name: pkgs.dockerTools.buildImage rec {
|
||||
inherit name;
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = [ self.packages.${system}.${name} ];
|
||||
pathsToLink = [ "/bin" ];
|
||||
};
|
||||
config.Cmd = [ "/bin/rosenpass" ];
|
||||
};
|
||||
in
|
||||
rec {
|
||||
packages = rec {
|
||||
default = rosenpass;
|
||||
rosenpass = rpDerivation pkgs;
|
||||
rosenpass-oci-image = rosenpassOCI "rosenpass";
|
||||
|
||||
# derivation for the release
|
||||
release-package =
|
||||
let
|
||||
version = cargoToml.package.version;
|
||||
package =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rosenpass-static
|
||||
else packages.rosenpass;
|
||||
oci-image =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rosenpass-static-oci-image
|
||||
else packages.rosenpass-oci-image;
|
||||
in
|
||||
pkgs.runCommandNoCC "lace-result" { }
|
||||
''
|
||||
mkdir {bin,$out}
|
||||
cp ${./.}/rp bin/
|
||||
tar -cvf $out/rosenpass-${system}-${version}.tar bin/rp \
|
||||
-C ${package} bin/rosenpass
|
||||
cp ${oci-image} \
|
||||
$out/rosenpass-oci-image-${system}-${version}.tar.gz
|
||||
'';
|
||||
} // (if pkgs.stdenv.isLinux then rec {
|
||||
rosenpass-static = rpDerivation pkgs.pkgsStatic;
|
||||
rosenpass-static-oci-image = rosenpassOCI "rosenpass-static";
|
||||
} else { });
|
||||
}
|
||||
))
|
||||
|
||||
|
||||
#
|
||||
### Linux specifics ###
|
||||
#
|
||||
@@ -262,87 +75,46 @@
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
packages = self.packages.${system};
|
||||
in
|
||||
{
|
||||
#
|
||||
### Whitepaper ###
|
||||
#
|
||||
packages.whitepaper =
|
||||
let
|
||||
tlsetup = (pkgs.texlive.combine {
|
||||
inherit (pkgs.texlive) scheme-basic acmart amsfonts ccicons
|
||||
csquotes csvsimple doclicense fancyvrb fontspec gobble
|
||||
koma-script ifmtarg latexmk lm markdown mathtools minted noto
|
||||
nunito pgf soul unicode-math lualatex-math paralist
|
||||
gitinfo2 eso-pic biblatex biblatex-trad biblatex-software
|
||||
xkeyval xurl xifthen biber;
|
||||
});
|
||||
in
|
||||
pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "whitepaper";
|
||||
src = ./papers;
|
||||
nativeBuildInputs = with pkgs; [
|
||||
ncurses # tput
|
||||
python3Packages.pygments
|
||||
tlsetup # custom tex live scheme
|
||||
which
|
||||
];
|
||||
buildPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
latexmk -r tex/CI.rc
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
mv *.pdf readme.md $out/
|
||||
'';
|
||||
};
|
||||
|
||||
#
|
||||
### Reading materials ###
|
||||
#
|
||||
packages.whitepaper = pkgs.whitepaper;
|
||||
|
||||
#
|
||||
### Proof and Proof Tools ###
|
||||
#
|
||||
packages.proverif-patched = pkgs.proverif.overrideAttrs (old: {
|
||||
postInstall = ''
|
||||
install -D -t $out/lib cryptoverif.pvl
|
||||
'';
|
||||
});
|
||||
packages.proof-proverif = pkgs.stdenv.mkDerivation {
|
||||
name = "rosenpass-proverif-proof";
|
||||
version = "unstable";
|
||||
src = pkgs.lib.sources.sourceByRegex ./. [
|
||||
"analyze.sh"
|
||||
"marzipan(/marzipan.awk)?"
|
||||
"analysis(/.*)?"
|
||||
];
|
||||
nativeBuildInputs = [ pkgs.proverif pkgs.graphviz ];
|
||||
CRYPTOVERIF_LIB = packages.proverif-patched + "/lib/cryptoverif.pvl";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
bash analyze.sh -color -html $out
|
||||
'';
|
||||
};
|
||||
packages.proverif-patched = pkgs.proverif-patched;
|
||||
packages.proof-proverif = pkgs.proof-proverif;
|
||||
|
||||
|
||||
#
|
||||
### Devshells ###
|
||||
#
|
||||
devShells.default = pkgs.mkShell {
|
||||
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ packages.default ];
|
||||
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cmake # override the fakecmake from the main step above
|
||||
cargo-release
|
||||
clippy
|
||||
nodePackages.prettier
|
||||
rustfmt
|
||||
packages.proverif-patched
|
||||
nodePackages.prettier
|
||||
nushell # for the .ci/gen-workflow-files.nu script
|
||||
proverif-patched
|
||||
];
|
||||
};
|
||||
devShells.coverage = pkgs.mkShell {
|
||||
inputsFrom = [ packages.default ];
|
||||
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = [
|
||||
inputs.fenix.packages.${system}.complete.toolchain
|
||||
pkgs.cargo-llvm-cov
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ version = "0.0.1"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
@@ -48,13 +51,37 @@ test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_box_secret_alloc"
|
||||
path = "fuzz_targets/box_secret_alloc.rs"
|
||||
name = "fuzz_box_secret_alloc_malloc"
|
||||
path = "fuzz_targets/box_secret_alloc_malloc.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_vec_secret_alloc"
|
||||
path = "fuzz_targets/vec_secret_alloc.rs"
|
||||
name = "fuzz_vec_secret_alloc_malloc"
|
||||
path = "fuzz_targets/vec_secret_alloc_malloc.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_box_secret_alloc_memfdsec"
|
||||
path = "fuzz_targets/box_secret_alloc_memfdsec.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_vec_secret_alloc_memfdsec"
|
||||
path = "fuzz_targets/vec_secret_alloc_memfdsec.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_box_secret_alloc_memfdsec_mallocfb"
|
||||
path = "fuzz_targets/box_secret_alloc_memfdsec_mallocfb.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_vec_secret_alloc_memfdsec_mallocfb"
|
||||
path = "fuzz_targets/vec_secret_alloc_memfdsec_mallocfb.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
@@ -15,8 +15,7 @@ pub struct Input {
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Input| {
|
||||
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
|
||||
ciphertext.resize(input.plaintext.len() + 16, 0);
|
||||
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
|
||||
|
||||
aead::encrypt(
|
||||
ciphertext.as_mut_slice(),
|
||||
|
||||
12
fuzz/fuzz_targets/box_secret_alloc_malloc.rs
Normal file
12
fuzz/fuzz_targets/box_secret_alloc_malloc.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_secret_memory::alloc::secret_box;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
use std::sync::Once;
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
ONCE.call_once(secret_policy_use_only_malloc_secrets);
|
||||
let _ = secret_box(data);
|
||||
});
|
||||
13
fuzz/fuzz_targets/box_secret_alloc_memfdsec.rs
Normal file
13
fuzz/fuzz_targets/box_secret_alloc_memfdsec.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_secret_memory::alloc::secret_box;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
use std::sync::Once;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
ONCE.call_once(secret_policy_use_only_memfd_secrets);
|
||||
let _ = secret_box(data);
|
||||
});
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_secret_memory::alloc::secret_box;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
use std::sync::Once;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
ONCE.call_once(secret_policy_try_use_memfd_secrets);
|
||||
let _ = secret_box(data);
|
||||
});
|
||||
@@ -4,11 +4,17 @@ extern crate rosenpass;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass::protocol::CryptoServer;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
use rosenpass_secret_memory::{PublicBox, Secret};
|
||||
use std::sync::Once;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
fuzz_target!(|rx_buf: &[u8]| {
|
||||
let sk = Secret::from_slice(&[0; 13568]);
|
||||
let pk = Secret::from_slice(&[0; 524160]);
|
||||
ONCE.call_once(secret_policy_use_only_malloc_secrets);
|
||||
let sk = Secret::from_slice(&[0; StaticKem::SK_LEN]);
|
||||
let pk = PublicBox::from_slice(&[0; StaticKem::PK_LEN]);
|
||||
|
||||
let mut cs = CryptoServer::new(sk, pk);
|
||||
let mut tx_buf = [0; 10240];
|
||||
|
||||
@@ -9,12 +9,12 @@ use rosenpass_ciphers::kem::EphemeralKem;
|
||||
|
||||
#[derive(arbitrary::Arbitrary, Debug)]
|
||||
pub struct Input {
|
||||
pub pk: [u8; 800],
|
||||
pub pk: [u8; EphemeralKem::PK_LEN],
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Input| {
|
||||
let mut ciphertext = [0u8; 768];
|
||||
let mut shared_secret = [0u8; 32];
|
||||
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
||||
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
|
||||
|
||||
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
||||
});
|
||||
|
||||
@@ -7,8 +7,8 @@ use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
|
||||
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
|
||||
let mut ciphertext = [0u8; 188];
|
||||
let mut shared_secret = [0u8; 32];
|
||||
let mut ciphertext = [0u8; StaticKem::CT_LEN];
|
||||
let mut shared_secret = [0u8; StaticKem::SHK_LEN];
|
||||
|
||||
// We expect errors while fuzzing therefore we do not check the result.
|
||||
let _ = StaticKem::encaps(&mut shared_secret, &mut ciphertext, &input);
|
||||
|
||||
15
fuzz/fuzz_targets/vec_secret_alloc_malloc.rs
Normal file
15
fuzz/fuzz_targets/vec_secret_alloc_malloc.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
#![no_main]
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_secret_memory::alloc::secret_vec;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
ONCE.call_once(secret_policy_use_only_malloc_secrets);
|
||||
let mut vec = secret_vec();
|
||||
vec.extend_from_slice(data);
|
||||
});
|
||||
15
fuzz/fuzz_targets/vec_secret_alloc_memfdsec.rs
Normal file
15
fuzz/fuzz_targets/vec_secret_alloc_memfdsec.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
#![no_main]
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_secret_memory::alloc::secret_vec;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
ONCE.call_once(secret_policy_use_only_memfd_secrets);
|
||||
let mut vec = secret_vec();
|
||||
vec.extend_from_slice(data);
|
||||
});
|
||||
@@ -1,9 +1,15 @@
|
||||
#![no_main]
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_secret_memory::alloc::secret_vec;
|
||||
use rosenpass_secret_memory::policy::*;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
ONCE.call_once(secret_policy_try_use_memfd_secrets);
|
||||
let mut vec = secret_vec();
|
||||
vec.extend_from_slice(data);
|
||||
});
|
||||
13
manual_tests/psk_broker/peer_a.rp.config
Normal file
13
manual_tests/psk_broker/peer_a.rp.config
Normal file
@@ -0,0 +1,13 @@
|
||||
secret_key = "peer_a.rp.sk"
|
||||
public_key = "peer_a.rp.pk"
|
||||
listen = ["[::1]:46127"]
|
||||
verbosity = "Verbose"
|
||||
|
||||
[api]
|
||||
listen_path = []
|
||||
listen_fd = []
|
||||
stream_fd = []
|
||||
|
||||
[[peers]]
|
||||
public_key = "peer_b.rp.pk"
|
||||
device = "rpPskBrkTestA"
|
||||
14
manual_tests/psk_broker/peer_b.rp.config
Normal file
14
manual_tests/psk_broker/peer_b.rp.config
Normal file
@@ -0,0 +1,14 @@
|
||||
secret_key = "peer_b.rp.sk"
|
||||
public_key = "peer_b.rp.pk"
|
||||
listen = []
|
||||
verbosity = "Verbose"
|
||||
|
||||
[api]
|
||||
listen_path = []
|
||||
listen_fd = []
|
||||
stream_fd = []
|
||||
|
||||
[[peers]]
|
||||
public_key = "peer_a.rp.pk"
|
||||
endpoint = "[::1]:46127"
|
||||
device = "rpPskBrkTestB"
|
||||
215
manual_tests/psk_broker/run_test.sh
Executable file
215
manual_tests/psk_broker/run_test.sh
Executable file
@@ -0,0 +1,215 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
enquote() {
|
||||
while (( "$#" > 1)); do
|
||||
printf "%q " "$1"
|
||||
shift
|
||||
done
|
||||
if (("$#" > 0)); then
|
||||
printf "%q" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
CLEANUP_HOOKS=()
|
||||
hook_cleanup() {
|
||||
local hook
|
||||
set +e +o pipefail
|
||||
for hook in "${CLEANUP_HOOKS[@]}"; do
|
||||
eval "${hook}"
|
||||
done
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
CLEANUP_HOOKS=("$(enquote exc_with_ctx cleanup "$@")" "${CLEANUP_HOOKS[@]}")
|
||||
}
|
||||
|
||||
cleanup_eval() {
|
||||
cleanup eval "$*"
|
||||
}
|
||||
|
||||
stderr() {
|
||||
echo >&2 "$@"
|
||||
}
|
||||
|
||||
log() {
|
||||
local level; level="$1"; shift || fatal "USAGE: log LVL MESSAGE.."
|
||||
stderr "[${level}]" "$@"
|
||||
}
|
||||
|
||||
info() {
|
||||
log "INFO" "$@"
|
||||
}
|
||||
|
||||
debug() {
|
||||
log "DEBUG" "$@"
|
||||
}
|
||||
|
||||
fatal() {
|
||||
log "FATAL" "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert() {
|
||||
local msg; msg="$1"; shift || fatal "USAGE: assert_cmd MESSAGE COMMAND.."
|
||||
"$@" || fatal "${msg}"
|
||||
}
|
||||
|
||||
abs_dir() {
|
||||
local dir; dir="$1"; shift || fatal "USAGE: abs_dir DIR"
|
||||
(
|
||||
cd "${dir}"
|
||||
pwd -P
|
||||
)
|
||||
}
|
||||
|
||||
exc_with_ctx() {
|
||||
local ctx; ctx="$1"; shift || fatal "USAGE: exc_with_ctx CONTEXT COMMAND.."
|
||||
if [[ -z "${ctx}" ]]; then
|
||||
info '$' "$@"
|
||||
else
|
||||
info "${ctx}\$" "$@"
|
||||
fi
|
||||
|
||||
"$@"
|
||||
}
|
||||
|
||||
exc() {
|
||||
exc_with_ctx "" "$@"
|
||||
}
|
||||
|
||||
exc_eval() {
|
||||
exc eval "$*"
|
||||
}
|
||||
|
||||
exc_eval_with_ctx() {
|
||||
local ctx; ctx="$1"; shift || fatal "USAGE: exc_eval_with_ctx CONTEXT EVAL_COMMAND.."
|
||||
exc_with_ctx "eval:${ctx}" "$*"
|
||||
}
|
||||
|
||||
exc_as_user() {
|
||||
exc sudo -u "${SUDO_USER}" "$@"
|
||||
}
|
||||
|
||||
exc_eval_as_user() {
|
||||
exc_as_user bash -c "$*"
|
||||
}
|
||||
|
||||
fork_eval_as_user() {
|
||||
exc sudo -u "${SUDO_USER}" bash -c "$*" &
|
||||
local pid; pid="$!"
|
||||
cleanup wait "${pid}"
|
||||
cleanup pkill -2 -P "${pid}" # Reverse ordering
|
||||
}
|
||||
|
||||
info_success() {
|
||||
stderr
|
||||
stderr
|
||||
if [[ "${SUCCESS}" = 1 ]]; then
|
||||
stderr " Test was a success!"
|
||||
else
|
||||
stderr " !!! TEST WAS A FAILURE!!!"
|
||||
fi
|
||||
stderr
|
||||
}
|
||||
|
||||
main() {
|
||||
assert "Use as root with sudo" [ "$(id -u)" -eq 0 ]
|
||||
assert "Use as root with sudo" [ -n "${SUDO_UID}" ]
|
||||
assert "SUDO_UID is 0; refusing to build as root" [ "${SUDO_UID}" -ne 0 ]
|
||||
|
||||
cleanup info_success
|
||||
|
||||
trap hook_cleanup EXIT
|
||||
|
||||
SCRIPT="$0"
|
||||
CFG_TEMPLATE_DIR="$(abs_dir "$(dirname "${SCRIPT}")")"
|
||||
REPO="$(abs_dir "${CFG_TEMPLATE_DIR}/../..")"
|
||||
BINS="${REPO}/target/debug"
|
||||
|
||||
# Create temp dir
|
||||
TMP_DIR="/tmp/rosenpass-psk-broker-test-$(date +%s)-$(uuidgen)"
|
||||
cleanup rm -rf "${TMP_DIR}"
|
||||
exc_as_user mkdir -p "${TMP_DIR}"
|
||||
|
||||
# Copy config
|
||||
CFG_DIR="${TMP_DIR}/cfg"
|
||||
exc_as_user cp -R "${CFG_TEMPLATE_DIR}" "${CFG_DIR}"
|
||||
|
||||
exc umask 077
|
||||
|
||||
exc cd "${REPO}"
|
||||
local build_cmd; build_cmd=(cargo build --workspace --color=always --all-features --bins --profile dev)
|
||||
if test -e "${BINS}/rosenpass-wireguard-broker-privileged" -a -e "${BINS}/rosenpass"; then
|
||||
info "Found the binaries rosenpass-wireguard-broker-privileged and rosenpass." \
|
||||
"Run following commands as a regular user to recompile the binaries with the right options" \
|
||||
"in case of an error:" '$' "${build_cmd[@]}"
|
||||
else
|
||||
exc_as_user "${build_cmd[@]}"
|
||||
fi
|
||||
exc sudo setcap CAP_NET_ADMIN=+eip "${BINS}/rosenpass-wireguard-broker-privileged"
|
||||
|
||||
exc cd "${CFG_DIR}"
|
||||
exc_eval_as_user "wg genkey > peer_a.wg.sk"
|
||||
exc_eval_as_user "wg pubkey < peer_a.wg.sk > peer_a.wg.pk"
|
||||
exc_eval_as_user "wg genkey > peer_b.wg.sk"
|
||||
exc_eval_as_user "wg pubkey < peer_b.wg.sk > peer_b.wg.pk"
|
||||
exc_eval_as_user "wg genpsk > peer_a_invalid.psk"
|
||||
exc_eval_as_user "wg genpsk > peer_b_invalid.psk"
|
||||
exc_eval_as_user "echo $(enquote "peer = \"$(cat peer_b.wg.pk)\"") >> peer_a.rp.config"
|
||||
exc_eval_as_user "echo $(enquote "peer = \"$(cat peer_a.wg.pk)\"") >> peer_b.rp.config"
|
||||
exc_as_user "${BINS}"/rosenpass gen-keys peer_a.rp.config
|
||||
exc_as_user "${BINS}"/rosenpass gen-keys peer_b.rp.config
|
||||
|
||||
cleanup ip l del dev rpPskBrkTestA
|
||||
cleanup ip l del dev rpPskBrkTestB
|
||||
exc ip l add dev rpPskBrkTestA type wireguard
|
||||
exc ip l add dev rpPskBrkTestB type wireguard
|
||||
|
||||
exc wg set rpPskBrkTestA \
|
||||
listen-port 46125 \
|
||||
private-key peer_a.wg.sk \
|
||||
peer "$(cat peer_b.wg.pk)" \
|
||||
endpoint 'localhost:46126' \
|
||||
preshared-key peer_a_invalid.psk \
|
||||
allowed-ips fe80::2/64
|
||||
exc wg set rpPskBrkTestB \
|
||||
listen-port 46126 \
|
||||
private-key peer_b.wg.sk \
|
||||
peer "$(cat peer_a.wg.pk)" \
|
||||
endpoint 'localhost:46125' \
|
||||
preshared-key peer_b_invalid.psk \
|
||||
allowed-ips fe80::1/64
|
||||
|
||||
exc ip l set rpPskBrkTestA up
|
||||
exc ip l set rpPskBrkTestB up
|
||||
|
||||
exc ip a add fe80::1/64 dev rpPskBrkTestA
|
||||
exc ip a add fe80::2/64 dev rpPskBrkTestB
|
||||
|
||||
fork_eval_as_user "\
|
||||
RUST_LOG='info' \
|
||||
PATH=$(enquote "${REPO}/target/debug:${PATH}") \
|
||||
$(enquote "${BINS}/rosenpass") --psk-broker-spawn \
|
||||
exchange-config peer_a.rp.config"
|
||||
fork_eval_as_user "\
|
||||
RUST_LOG='info' \
|
||||
PATH=$(enquote "${REPO}/target/debug:${PATH}") \
|
||||
$(enquote "${BINS}/rosenpass-wireguard-broker-socket-handler") \
|
||||
--listen-path broker.sock"
|
||||
fork_eval_as_user "\
|
||||
RUST_LOG='info' \
|
||||
PATH=$(enquote "$PWD/target/debug:${PATH}") \
|
||||
$(enquote "${BINS}/rosenpass") --psk-broker-path broker.sock \
|
||||
exchange-config peer_b.rp.config"
|
||||
|
||||
exc_as_user ping -c 2 -w 10 fe80::1%rpPskBrkTestA
|
||||
exc_as_user ping -c 2 -w 10 fe80::2%rpPskBrkTestB
|
||||
exc_as_user ping -c 2 -w 10 fe80::2%rpPskBrkTestA
|
||||
exc_as_user ping -c 2 -w 10 fe80::1%rpPskBrkTestB
|
||||
|
||||
SUCCESS=1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
40
misc/README.md
Normal file
40
misc/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Additional files
|
||||
|
||||
This folder contains additional files that are used in the project.
|
||||
|
||||
## `generate_configs.py`
|
||||
|
||||
The script is used to generate configuration files for a benchmark setup
|
||||
consisting of a device under testing (DUT) and automatic test equipment (ATE),
|
||||
basically a strong machine capable of running multiple Rosenpass instances at
|
||||
once.
|
||||
|
||||
At the top of the script multiple variables can be set to configure the DUT IP
|
||||
address and more. Once configured you may run `python3 generate_configs.py` to
|
||||
create the configuration files.
|
||||
|
||||
A new folder called `output/` is created containing the subfolder `dut/` and
|
||||
`ate/`. The former has to be copied on the DUT, ideally reproducible hardware
|
||||
like a Raspberry Pi, while the latter is copied to the ATE, i.e. a laptop.
|
||||
|
||||
### Running a benchmark
|
||||
|
||||
On the ATE a run script is required since multiple instances of `rosenpass` are
|
||||
started with different configurations in parallel. The scripts are named after
|
||||
the number of instances they start, e.g. `run-50.sh` starts 50 instances.
|
||||
|
||||
```shell
|
||||
# on the ATE aka laptop
|
||||
cd output/ate
|
||||
./run-10.sh
|
||||
```
|
||||
|
||||
On the DUT you start a single Rosenpass instance with the configuration matching
|
||||
the ATE number of peers.
|
||||
|
||||
```shell
|
||||
# on the DUT aka Raspberry Pi
|
||||
rosenpass exchange-config configs/dut-10.toml
|
||||
```
|
||||
|
||||
Use whatever measurement tool you like to monitor the DUT and ATE.
|
||||
105
misc/generate_configs.py
Normal file
105
misc/generate_configs.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from pathlib import Path
|
||||
from subprocess import run
|
||||
import os
|
||||
|
||||
config = dict(
|
||||
peer_counts=[1, 5, 10, 50, 100, 500],
|
||||
peer_count_max=100,
|
||||
ate_ip="127.0.0.1",
|
||||
dut_ip="127.0.0.1",
|
||||
dut_port=9999,
|
||||
path_to_rosenpass_bin=os.getcwd() + "/target/release/rosenpass",
|
||||
)
|
||||
|
||||
print(config)
|
||||
|
||||
output_dir = Path("output")
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
|
||||
template_dut = """
|
||||
public_key = "keys/dut-public-key"
|
||||
secret_key = "keys/dut-secret-key"
|
||||
listen = ["{dut_ip}:{dut_port}"]
|
||||
verbosity = "Quiet"
|
||||
"""
|
||||
template_dut_peer = """
|
||||
[[peers]] # ATE-{i}
|
||||
public_key = "keys/ate-{i}-public-key"
|
||||
endpoint = "{ate_ip}:{ate_port}"
|
||||
key_out = "out/key_out_{i}"
|
||||
"""
|
||||
|
||||
template_ate = """
|
||||
public_key = "keys/ate-{i}-public-key"
|
||||
secret_key = "keys/ate-{i}-secret-key"
|
||||
listen = ["{ate_ip}:{ate_port}"]
|
||||
verbosity = "Quiet"
|
||||
|
||||
[[peers]] # DUT
|
||||
public_key = "keys/dut-public-key"
|
||||
endpoint = "{dut_ip}:{dut_port}"
|
||||
key_out = "out/key_out_{i}"
|
||||
"""
|
||||
|
||||
(output_dir / "dut" / "keys").mkdir(exist_ok=True, parents=True)
|
||||
(output_dir / "dut" / "out").mkdir(exist_ok=True, parents=True)
|
||||
(output_dir / "dut" / "configs").mkdir(exist_ok=True, parents=True)
|
||||
(output_dir / "ate" / "keys").mkdir(exist_ok=True, parents=True)
|
||||
(output_dir / "ate" / "out").mkdir(exist_ok=True, parents=True)
|
||||
(output_dir / "ate" / "configs").mkdir(exist_ok=True, parents=True)
|
||||
|
||||
for peer_count in config["peer_counts"]:
|
||||
dut_config = template_dut.format(**config)
|
||||
for i in range(peer_count):
|
||||
dut_config += template_dut_peer.format(**config, i=i, ate_port=50000 + i)
|
||||
|
||||
(output_dir / "dut" / "configs" / f"dut-{peer_count}.toml").write_text(dut_config)
|
||||
|
||||
if not (output_dir / "dut" / "keys" / "dut-public-key").exists():
|
||||
print("Generate DUT keys")
|
||||
run(
|
||||
[
|
||||
config["path_to_rosenpass_bin"],
|
||||
"gen-keys",
|
||||
f"configs/dut-{peer_count}.toml",
|
||||
],
|
||||
cwd=output_dir / "dut",
|
||||
)
|
||||
else:
|
||||
print("DUT keys already exist")
|
||||
|
||||
# copy the DUT public key to the ATE
|
||||
(output_dir / "ate" / "keys" / "dut-public-key").write_bytes(
|
||||
(output_dir / "dut" / "keys" / "dut-public-key").read_bytes()
|
||||
)
|
||||
|
||||
ate_script = "(trap 'kill 0' SIGINT; \\\n"
|
||||
|
||||
for i in range(config["peer_count_max"]):
|
||||
(output_dir / "ate" / "configs" / f"ate-{i}.toml").write_text(
|
||||
template_ate.format(**config, i=i, ate_port=50000 + i)
|
||||
)
|
||||
|
||||
if not (output_dir / "ate" / "keys" / f"ate-{i}-public-key").exists():
|
||||
# generate ATE keys
|
||||
run(
|
||||
[config["path_to_rosenpass_bin"], "gen-keys", f"configs/ate-{i}.toml"],
|
||||
cwd=output_dir / "ate",
|
||||
)
|
||||
else:
|
||||
print(f"ATE-{i} keys already exist")
|
||||
|
||||
# copy the ATE public keys to the DUT
|
||||
(output_dir / "dut" / "keys" / f"ate-{i}-public-key").write_bytes(
|
||||
(output_dir / "ate" / "keys" / f"ate-{i}-public-key").read_bytes()
|
||||
)
|
||||
|
||||
ate_script += (
|
||||
f"{config['path_to_rosenpass_bin']} exchange-config configs/ate-{i}.toml & \\\n"
|
||||
)
|
||||
|
||||
if (i + 1) in config["peer_counts"]:
|
||||
write_script = ate_script
|
||||
write_script += "wait)"
|
||||
|
||||
(output_dir / "ate" / f"run-{i+1}.sh").write_text(write_script)
|
||||
39
overlay.nix
Normal file
39
overlay.nix
Normal file
@@ -0,0 +1,39 @@
|
||||
final: prev: {
|
||||
|
||||
|
||||
#
|
||||
### Actual rosenpass software ###
|
||||
#
|
||||
rosenpass = final.callPackage ./pkgs/rosenpass.nix { };
|
||||
rosenpass-oci-image = final.callPackage ./pkgs/rosenpass-oci-image.nix { };
|
||||
rp = final.callPackage ./pkgs/rosenpass.nix { package = "rp"; };
|
||||
|
||||
release-package = final.callPackage ./pkgs/release-package.nix { };
|
||||
|
||||
#
|
||||
### Appendix ###
|
||||
#
|
||||
proverif-patched = prev.proverif.overrideAttrs (old: {
|
||||
postInstall = ''
|
||||
install -D -t $out/lib cryptoverif.pvl
|
||||
'';
|
||||
});
|
||||
|
||||
proof-proverif = final.stdenv.mkDerivation {
|
||||
name = "rosenpass-proverif-proof";
|
||||
version = "unstable";
|
||||
src = final.lib.sources.sourceByRegex ./. [
|
||||
"analyze.sh"
|
||||
"marzipan(/marzipan.awk)?"
|
||||
"analysis(/.*)?"
|
||||
];
|
||||
nativeBuildInputs = [ final.proverif final.graphviz ];
|
||||
CRYPTOVERIF_LIB = final.proverif-patched + "/lib/cryptoverif.pvl";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
bash analyze.sh -color -html $out
|
||||
'';
|
||||
};
|
||||
|
||||
whitepaper = final.callPackage ./pkgs/whitepaper.nix { };
|
||||
}
|
||||
@@ -6,6 +6,7 @@ author:
|
||||
- Benjamin Lipp = Max Planck Institute for Security and Privacy (MPI-SP)
|
||||
- Wanja Zaeske
|
||||
- Lisa Schmidt = {Scientific Illustrator – \\url{mullana.de}}
|
||||
- Prabhpreet Dua
|
||||
abstract: |
|
||||
Rosenpass is used to create post-quantum-secure VPNs. Rosenpass computes a shared key, WireGuard (WG) [@wg] uses the shared key to establish a secure connection. Rosenpass can also be used without WireGuard, deriving post-quantum-secure symmetric keys for another application. The Rosenpass protocol builds on “Post-quantum WireGuard” (PQWG) [@pqwg] and improves it by using a cookie mechanism to provide security against state disruption attacks.
|
||||
|
||||
@@ -218,6 +219,7 @@ The server needs to store the following variables:
|
||||
* `spkm`
|
||||
* `biscuit_key` – Randomly chosen key used to encrypt biscuits
|
||||
* `biscuit_ctr` – Retransmission protection for biscuits
|
||||
* `cookie_secret`- A randomized cookie secret to derive cookies sent to peer when under load. This secret changes every 120 seconds
|
||||
|
||||
Not mandated per se, but required in practice:
|
||||
|
||||
@@ -243,6 +245,7 @@ The initiator stores the following local state for each ongoing handshake:
|
||||
* `ck` – The chaining key
|
||||
* `eski` – The initiator's ephemeral secret key
|
||||
* `epki` – The initiator's ephemeral public key
|
||||
* `cookie_value`- Cookie value sent by an initiator peer under load, used to compute cookie field in outgoing handshake to peer under load. This value expires 120 seconds from when a peer sends this value using the CookieReply message
|
||||
|
||||
The responder stores no state. While the responder has access to all of the above variables except for `eski`, the responder discards them after generating the RespHello message. Instead, the responder state is contained inside a cookie called a biscuit. This value is returned to the responder inside the InitConf packet. The biscuit consists of:
|
||||
|
||||
@@ -428,12 +431,92 @@ The responder code handling InitConf needs to deal with the biscuits and package
|
||||
|
||||
ICR5 and ICR6 perform biscuit replay protection using the biscuit number. This is not handled in `load_biscuit()` itself because there is the case that `biscuit_no = biscuit_used` which needs to be dealt with for retransmission handling.
|
||||
|
||||
### Denial of Service Mitigation and Cookies
|
||||
|
||||
Rosenpass derives its cookie-based DoS mitigation technique for a responder when receiving InitHello messages from Wireguard [@wg].
|
||||
|
||||
When the responder is under load, it may choose to not process further InitHello handshake messages, but instead to respond with a cookie reply message (see Figure \ref{img:MessageTypes}).
|
||||
|
||||
The sender of the exchange then uses this cookie in order to resend the message and have it accepted the following time by the reciever.
|
||||
|
||||
For an initiator, Rosenpass ignores all messages when under load.
|
||||
|
||||
#### Cookie Reply Message
|
||||
|
||||
The cookie reply message is sent by the responder on receiving an InitHello message when under load. It consists of the `sidi` of the initiator, a random 24-byte bitstring `nonce` and encrypting `cookie_value` into a `cookie_encrypted` reply field which consists of the following:
|
||||
|
||||
```pseudorust
|
||||
cookie_value = lhash("cookie-value", cookie_secret, initiator_host_info)[0..16]
|
||||
cookie_encrypted = XAEAD(lhash("cookie-key", spkm), nonce, cookie_value, mac_peer)
|
||||
```
|
||||
|
||||
where `cookie_secret` is a secret variable that changes every two minutes to a random value. `initiator_host_info` is used to identify the initiator host, and is implementation-specific for the client. This paramaters used to identify the host must be carefully chosen to ensure there is a unique mapping, especially when using IPv4 and IPv6 addresses to identify the host (such as taking care of IPv6 link-local addresses). `cookie_value` is a truncated 16 byte value from the above hash operation. `mac_peer` is the `mac` field of the peer's handshake message to which message is the reply.
|
||||
|
||||
#### Envelope `mac` Field
|
||||
|
||||
Similar to `mac.1` in Wireguard handshake messages, the `mac` field of a Rosenpass envelope from a handshake packet sender's point of view consists of the following:
|
||||
|
||||
```pseudorust
|
||||
mac = lhash("mac", spkt, MAC_WIRE_DATA)[0..16]
|
||||
```
|
||||
|
||||
where `MAC_WIRE_DATA` represents all bytes of msg prior to `mac` field in the envelope.
|
||||
|
||||
If a client receives an invalid `mac` value for any message, it will discard the message.
|
||||
|
||||
#### Envelope cookie field
|
||||
|
||||
The initiator, on receiving a CookieReply message, decrypts `cookie_encrypted` and stores the `cookie_value` for the session into `peer[sid].cookie_value` for a limited time (120 seconds). This value is then used to set `cookie` field set for subsequent messages and retransmissions to the responder as follows:
|
||||
|
||||
```pseudorust
|
||||
if (peer.cookie_value.is_none() || seconds_since_update(peer[sid].cookie_value) >= 120) {
|
||||
cookie.zeroize(); //zeroed out 16 bytes bitstring
|
||||
}
|
||||
else {
|
||||
cookie = lhash("cookie",peer.cookie_value.unwrap(),COOKIE_WIRE_DATA)
|
||||
}
|
||||
```
|
||||
|
||||
Here, `seconds_since_update(peer.cookie_value)` is the amount of time in seconds ellapsed since last cookie was received, and `COOKIE_WIRE_DATA` are the message contents of all bytes of the retransmitted message prior to the `cookie` field.
|
||||
|
||||
The inititator can use an invalid value for the `cookie` value, when the responder is not under load, and the responder must ignore this value.
|
||||
However, when the responder is under load, it may reject InitHello messages with the invalid `cookie` value, and issue a cookie reply message.
|
||||
|
||||
### Conditions to trigger DoS Mechanism
|
||||
|
||||
This whitepaper does not mandate any specific mechanism to detect responder contention (also mentioned as the under load condition) that would trigger use of the cookie mechanism.
|
||||
|
||||
For the reference implemenation, Rosenpass has derived inspiration from the linux implementation of Wireguard. This implementation suggests that the reciever keep track of the number of messages it is processing at a given time.
|
||||
|
||||
On receiving an incoming message, if the length of the message queue to be processed exceeds a threshold `MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD`, the client is considered under load and its state is stored as under load. In addition, the timestamp of this instant when the client was last under load is stored. When recieving subsequent messages, if the client is still in an under load state, the client will check if the time ellpased since the client was last under load has exceeded `LAST_UNDER_LOAD_WINDOW` seconds. If this is the case, the client will update its state to normal operation, and process the message in a normal fashion.
|
||||
|
||||
Currently, the following constants are derived from the Linux kernel implementation of Wireguard:
|
||||
|
||||
```pseudorust
|
||||
MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD = 4096
|
||||
LAST_UNDER_LOAD_WINDOW = 1 //seconds
|
||||
```
|
||||
|
||||
## Dealing with Packet Loss
|
||||
|
||||
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.
|
||||
|
||||
### Interaction with cookie reply system
|
||||
|
||||
The cookie reply system does not interfere with the retransmission logic discussed above.
|
||||
|
||||
When the initator is under load, it will ignore processing any incoming messages.
|
||||
|
||||
When a responder is under load and it receives an InitHello handshake message, the InitHello message will be discarded and a cookie reply message is sent. The initiator, then on the reciept of the cookie reply message, will store a decrypted `cookie_value` to set the `cookie` field to subsequently sent messages. As per the retransmission mechanism above, the initiator will send a retransmitted InitHello message with a valid `cookie` value appended. On receiving the retransmitted handshake message, the responder will validate the `cookie` value and resume with the handshake process.
|
||||
|
||||
When the responder is under load and it recieves an InitConf message, the message will be directly processed without checking the validity of the cookie field.
|
||||
|
||||
# Changelog
|
||||
|
||||
- Added section "Denial of Service Mitigation and Cookies", and modify "Dealing with Packet Loss" for DoS cookie mechanism
|
||||
|
||||
\printbibliography
|
||||
|
||||
\setupimage{landscape,fullpage,label=img:HandlingCode}
|
||||
|
||||
27
pkgs/release-package.nix
Normal file
27
pkgs/release-package.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{ lib, stdenvNoCC, runCommandNoCC, pkgsStatic, rosenpass, rosenpass-oci-image, rp } @ args:
|
||||
|
||||
let
|
||||
version = rosenpass.version;
|
||||
|
||||
# select static packages on Linux, default packages otherwise
|
||||
package =
|
||||
if stdenvNoCC.hostPlatform.isLinux then
|
||||
pkgsStatic.rosenpass
|
||||
else args.rosenpass;
|
||||
rp =
|
||||
if stdenvNoCC.hostPlatform.isLinux then
|
||||
pkgsStatic.rp
|
||||
else args.rp;
|
||||
oci-image =
|
||||
if stdenvNoCC.hostPlatform.isLinux then
|
||||
pkgsStatic.rosenpass-oci-image
|
||||
else args.rosenpass-oci-image;
|
||||
in
|
||||
runCommandNoCC "lace-result" { } ''
|
||||
mkdir {bin,$out}
|
||||
tar -cvf $out/rosenpass-${stdenvNoCC.hostPlatform.system}-${version}.tar \
|
||||
-C ${package} bin/rosenpass \
|
||||
-C ${rp} bin/rp
|
||||
cp ${oci-image} \
|
||||
$out/rosenpass-oci-image-${stdenvNoCC.hostPlatform.system}-${version}.tar.gz
|
||||
''
|
||||
11
pkgs/rosenpass-oci-image.nix
Normal file
11
pkgs/rosenpass-oci-image.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{ dockerTools, buildEnv, rosenpass }:
|
||||
|
||||
dockerTools.buildImage {
|
||||
name = rosenpass.name + "-oci";
|
||||
copyToRoot = buildEnv {
|
||||
name = "image-root";
|
||||
paths = [ rosenpass ];
|
||||
pathsToLink = [ "/bin" ];
|
||||
};
|
||||
config.Cmd = [ "/bin/rosenpass" ];
|
||||
}
|
||||
78
pkgs/rosenpass.nix
Normal file
78
pkgs/rosenpass.nix
Normal file
@@ -0,0 +1,78 @@
|
||||
{ lib, stdenv, rustPlatform, cmake, mandoc, removeReferencesTo, bash, package ? "rosenpass" }:
|
||||
|
||||
let
|
||||
# whether we want to build a statically linked binary
|
||||
isStatic = stdenv.targetPlatform.isStatic;
|
||||
|
||||
scoped = (scope: scope.result);
|
||||
|
||||
# source files relevant for rust
|
||||
src = scoped rec {
|
||||
# File suffices to include
|
||||
extensions = [
|
||||
"lock"
|
||||
"rs"
|
||||
"toml"
|
||||
];
|
||||
# Files to explicitly include
|
||||
files = [
|
||||
"to/README.md"
|
||||
];
|
||||
|
||||
src = ../.;
|
||||
filter = (path: type: scoped rec {
|
||||
inherit (lib) any id removePrefix hasSuffix;
|
||||
anyof = (any id);
|
||||
|
||||
basename = baseNameOf (toString path);
|
||||
relative = removePrefix (toString src + "/") (toString path);
|
||||
|
||||
result = anyof [
|
||||
(type == "directory")
|
||||
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
||||
(any (file: file == relative) files)
|
||||
];
|
||||
});
|
||||
|
||||
result = lib.sources.cleanSourceWith { inherit src filter; };
|
||||
};
|
||||
|
||||
# parsed Cargo.toml
|
||||
cargoToml = builtins.fromTOML (builtins.readFile (src + "/rosenpass/Cargo.toml"));
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
name = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
inherit src;
|
||||
|
||||
cargoBuildOptions = [ "--package" package ];
|
||||
cargoTestOptions = [ "--package" package ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
cargoLock = {
|
||||
lockFile = src + "/Cargo.lock";
|
||||
outputHashes = {
|
||||
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
||||
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
||||
};
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
stdenv.cc
|
||||
cmake # for oqs build in the oqs-sys crate
|
||||
mandoc # for the built-in manual
|
||||
removeReferencesTo
|
||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
||||
];
|
||||
buildInputs = [ bash ];
|
||||
|
||||
hardeningDisable = lib.optional isStatic "fortify";
|
||||
|
||||
meta = {
|
||||
inherit (cargoToml.package) description homepage;
|
||||
license = with lib.licenses; [ mit asl20 ];
|
||||
maintainers = [ lib.maintainers.wucke13 ];
|
||||
platforms = lib.platforms.all;
|
||||
};
|
||||
}
|
||||
29
pkgs/whitepaper.nix
Normal file
29
pkgs/whitepaper.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{ stdenvNoCC, texlive, ncurses, python3Packages, which }:
|
||||
|
||||
let
|
||||
customTexLiveSetup = (texlive.combine {
|
||||
inherit (texlive) acmart amsfonts biber biblatex biblatex-software
|
||||
biblatex-trad ccicons csquotes csvsimple doclicense eso-pic fancyvrb
|
||||
fontspec gitinfo2 gobble ifmtarg koma-script latexmk lm lualatex-math
|
||||
markdown mathtools minted noto nunito paralist pgf scheme-basic soul
|
||||
unicode-math upquote xifthen xkeyval xurl;
|
||||
});
|
||||
in
|
||||
stdenvNoCC.mkDerivation {
|
||||
name = "whitepaper";
|
||||
src = ../papers;
|
||||
nativeBuildInputs = [
|
||||
ncurses # tput
|
||||
python3Packages.pygments
|
||||
customTexLiveSetup # custom tex live scheme
|
||||
which
|
||||
];
|
||||
buildPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
latexmk -r tex/CI.rc
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
mv *.pdf readme.md $out/
|
||||
'';
|
||||
}
|
||||
@@ -25,11 +25,11 @@ Follow [quick start instructions](https://rosenpass.eu/#start) to get a VPN up a
|
||||
|
||||
## Software architecture
|
||||
|
||||
The [rosenpass tool](./src/) is written in Rust and uses liboqs[^liboqs] and libsodium[^libsodium]. 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.
|
||||
|
||||
As with any application a small risk of critical security issues (such as buffer overflows, remote code execution) exists; the Rosenpass application is written in the Rust programming language which is much less prone to such issues. Rosenpass can also write keys to files instead of supplying them to WireGuard With a bit of scripting the stand alone mode of the implementation can be used to run the application in a Container, VM or on another host. This mode can also be used to integrate tools other than WireGuard with Rosenpass.
|
||||
|
||||
The [`rp`](./rp) tool written in bash makes it easy to create a VPN using WireGuard and Rosenpass.
|
||||
The [`rp`](./rp) tool written in Rust makes it easy to create a VPN using WireGuard and Rosenpass.
|
||||
|
||||
`rp` is easy to get started with but has a few drawbacks; it runs as root, demanding access to both WireGuard
|
||||
and Rosenpass private keys, takes control of the interface and works with exactly one interface. If you do not feel confident about running Rosenpass as root, you should use the stand-alone mode to create a more secure setup using containers, jails, or virtual machines.
|
||||
@@ -59,7 +59,6 @@ The code uses a variety of optimizations to speed up analysis such as using secr
|
||||
A wrapper script provides instant feedback about which queries execute as expected in color: A red cross if a query fails and a green check if it succeeds.
|
||||
|
||||
[^liboqs]: https://openquantumsafe.org/liboqs/
|
||||
[^libsodium]: https://doc.libsodium.org/
|
||||
[^wg]: https://www.wireguard.com/
|
||||
[^pqwg]: https://eprint.iacr.org/2020/379
|
||||
[^pqwg-statedis]: Unless supplied with a pre-shared-key, but this defeats the purpose of a key exchange protocol
|
||||
@@ -67,6 +66,8 @@ A wrapper script provides instant feedback about which queries execute as expect
|
||||
|
||||
# Getting Rosenpass
|
||||
|
||||
Documentation and installation guides can be found at the [Rosenpass website](https://rosenpass.eu/docs).
|
||||
|
||||
Rosenpass is packaged for more and more distributions, maybe also for the distribution of your choice?
|
||||
|
||||
[](https://repology.org/project/rosenpass/versions)
|
||||
|
||||
@@ -9,6 +9,23 @@ homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[[bin]]
|
||||
name = "rosenpass"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rosenpass-gen-ipc-msg-types"
|
||||
path = "src/bin/gen-ipc-msg-types.rs"
|
||||
required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"]
|
||||
|
||||
[[test]]
|
||||
name = "api-integration-tests"
|
||||
required-features = ["experiment_api", "internal_testing"]
|
||||
|
||||
[[test]]
|
||||
name = "api-integration-tests-api-setup"
|
||||
required-features = ["experiment_api", "internal_testing"]
|
||||
|
||||
[[bench]]
|
||||
name = "handshake"
|
||||
harness = false
|
||||
@@ -34,6 +51,15 @@ mio = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
zerocopy = { workspace = true }
|
||||
home = { workspace = true }
|
||||
derive_builder = { workspace = true }
|
||||
rosenpass-wireguard-broker = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
hex-literal = { workspace = true, optional = true }
|
||||
hex = { workspace = true, optional = true }
|
||||
heck = { workspace = true, optional = true }
|
||||
command-fds = { workspace = true, optional = true }
|
||||
rustix = { workspace = true }
|
||||
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
@@ -42,3 +68,21 @@ anyhow = { workspace = true }
|
||||
criterion = { workspace = true }
|
||||
test_bin = { workspace = true }
|
||||
stacker = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
procspawn = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["experiment_api"]
|
||||
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||
experiment_api = [
|
||||
"hex-literal",
|
||||
"uds",
|
||||
"command-fds",
|
||||
"rosenpass-util/experiment_file_descriptor_passing",
|
||||
"rosenpass-wireguard-broker/experiment_api",
|
||||
]
|
||||
internal_testing = []
|
||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use anyhow::Result;
|
||||
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
|
||||
|
||||
fn handle(
|
||||
tx: &mut CryptoServer,
|
||||
@@ -39,7 +41,7 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
|
||||
|
||||
fn keygen() -> Result<(SSk, SPk)> {
|
||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
|
||||
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||
Ok((sk, pk))
|
||||
}
|
||||
|
||||
@@ -56,6 +58,7 @@ fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
secret_policy_try_use_memfd_secrets();
|
||||
let (mut a, mut b) = make_server_pair().unwrap();
|
||||
c.bench_function("cca_secret_alloc", |bench| {
|
||||
bench.iter(|| {
|
||||
|
||||
295
rosenpass/src/api/api_handler.rs
Normal file
295
rosenpass/src/api/api_handler.rs
Normal file
@@ -0,0 +1,295 @@
|
||||
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
|
||||
|
||||
use anyhow::Context;
|
||||
use rosenpass_to::{ops::copy_slice, To};
|
||||
use rosenpass_util::{
|
||||
fd::FdIo,
|
||||
functional::{run, ApplyExt},
|
||||
io::ReadExt,
|
||||
mem::DiscardResultExt,
|
||||
mio::UnixStreamExt,
|
||||
result::OkExt,
|
||||
};
|
||||
use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
|
||||
|
||||
use crate::{
|
||||
api::{add_listen_socket_response_status, add_psk_broker_response_status},
|
||||
app_server::AppServer,
|
||||
protocol::BuildCryptoServer,
|
||||
};
|
||||
|
||||
use super::{supply_keypair_response_status, Server as ApiServer};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApiHandler {
|
||||
_dummy: (),
|
||||
}
|
||||
|
||||
impl ApiHandler {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self { _dummy: () }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ApiHandlerContext {
|
||||
fn api_handler(&self) -> &ApiHandler;
|
||||
fn app_server(&self) -> &AppServer;
|
||||
fn api_handler_mut(&mut self) -> &mut ApiHandler;
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("Error in SupplyKeypair")]
|
||||
struct SupplyKeypairError {
|
||||
status: u128,
|
||||
#[source]
|
||||
cause: anyhow::Error,
|
||||
}
|
||||
|
||||
trait SupplyKeypairErrorExt<T> {
|
||||
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError>;
|
||||
fn einternal(self) -> Result<T, SupplyKeypairError>;
|
||||
fn ealready_supplied(self) -> Result<T, SupplyKeypairError>;
|
||||
fn einvalid_req(self) -> Result<T, SupplyKeypairError>;
|
||||
}
|
||||
|
||||
impl<T, E: Into<anyhow::Error>> SupplyKeypairErrorExt<T> for Result<T, E> {
|
||||
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError> {
|
||||
self.map_err(|e| SupplyKeypairError {
|
||||
status,
|
||||
cause: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn einternal(self) -> Result<T, SupplyKeypairError> {
|
||||
self.e_custom(supply_keypair_response_status::INTERNAL_ERROR)
|
||||
}
|
||||
|
||||
fn ealready_supplied(self) -> Result<T, SupplyKeypairError> {
|
||||
self.e_custom(supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED)
|
||||
}
|
||||
|
||||
fn einvalid_req(self) -> Result<T, SupplyKeypairError> {
|
||||
self.e_custom(supply_keypair_response_status::INVALID_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ApiServer for T
|
||||
where
|
||||
T: ?Sized + ApiHandlerContext,
|
||||
{
|
||||
fn ping(
|
||||
&mut self,
|
||||
req: &super::PingRequest,
|
||||
_req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::PingResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
let (req, res) = (&req.payload, &mut res.payload);
|
||||
copy_slice(&req.echo).to(&mut res.echo);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn supply_keypair(
|
||||
&mut self,
|
||||
req: &super::SupplyKeypairRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::SupplyKeypairResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
let outcome: Result<(), SupplyKeypairError> = run(|| {
|
||||
// Acquire the file descriptors
|
||||
let mut sk_io = FdIo(
|
||||
req_fds
|
||||
.front()
|
||||
.context("First file descriptor, secret key, missing.")
|
||||
.einvalid_req()?,
|
||||
);
|
||||
let mut pk_io = FdIo(
|
||||
req_fds
|
||||
.get(1)
|
||||
.context("Second file descriptor, public key, missing.")
|
||||
.einvalid_req()?,
|
||||
);
|
||||
|
||||
// Actually read the secrets
|
||||
let mut sk = crate::protocol::SSk::zero();
|
||||
sk_io.read_exact_til_end(sk.secret_mut()).einvalid_req()?;
|
||||
|
||||
let mut pk = crate::protocol::SPk::zero();
|
||||
pk_io.read_exact_til_end(pk.borrow_mut()).einvalid_req()?;
|
||||
|
||||
// Retrieve the construction site
|
||||
let construction_site = self.app_server_mut().crypto_site.borrow_mut();
|
||||
|
||||
// Retrieve the builder
|
||||
use rosenpass_util::build::ConstructionSite as C;
|
||||
let maybe_builder = match construction_site {
|
||||
C::Builder(builder) => Some(builder),
|
||||
C::Product(_) => None,
|
||||
C::Void => {
|
||||
return Err(anyhow::Error::msg("CryptoServer construction side is void"))
|
||||
.einternal();
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve a reference to the keypair
|
||||
let Some(BuildCryptoServer {
|
||||
ref mut keypair, ..
|
||||
}) = maybe_builder
|
||||
else {
|
||||
return Err(anyhow::Error::msg("CryptoServer already built")).ealready_supplied();
|
||||
};
|
||||
|
||||
// Supply the keypair to the CryptoServer
|
||||
keypair
|
||||
.insert(crate::protocol::Keypair { sk, pk })
|
||||
.discard_result();
|
||||
|
||||
// Actually construct the CryptoServer
|
||||
construction_site
|
||||
.erect()
|
||||
.map_err(|e| anyhow::Error::msg(format!("Error erecting the CryptoServer {e:?}")))
|
||||
.einternal()?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
use supply_keypair_response_status as status;
|
||||
let status = match outcome {
|
||||
Ok(()) => status::OK,
|
||||
Err(e) => {
|
||||
let lvl = match e.status {
|
||||
status::INTERNAL_ERROR => log::Level::Warn,
|
||||
_ => log::Level::Debug,
|
||||
};
|
||||
|
||||
log::log!(
|
||||
lvl,
|
||||
"Error while processing API Request.\n Request: {:?}\n Error: {:?}",
|
||||
req,
|
||||
e.cause
|
||||
);
|
||||
|
||||
if e.status == status::INTERNAL_ERROR {
|
||||
return Err(e.cause);
|
||||
}
|
||||
|
||||
e.status
|
||||
}
|
||||
};
|
||||
|
||||
res.payload.status = status;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_listen_socket(
|
||||
&mut self,
|
||||
_req: &super::boilerplate::AddListenSocketRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::boilerplate::AddListenSocketResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
// Retrieve file descriptor
|
||||
let sock_res = run(|| -> anyhow::Result<mio::net::UdpSocket> {
|
||||
let sock = req_fds
|
||||
.pop_front()
|
||||
.context("Invalid request – socket missing.")?;
|
||||
// TODO: We need to have this outside linux
|
||||
#[cfg(target_os = "linux")]
|
||||
rosenpass_util::fd::GetSocketProtocol::demand_udp_socket(&sock)?;
|
||||
let sock = std::net::UdpSocket::from(sock);
|
||||
sock.set_nonblocking(true)?;
|
||||
mio::net::UdpSocket::from_std(sock).ok()
|
||||
});
|
||||
|
||||
let sock = match sock_res {
|
||||
Ok(sock) => sock,
|
||||
Err(e) => {
|
||||
log::debug!("Error processing AddListenSocket API request: {e:?}");
|
||||
res.payload.status = add_listen_socket_response_status::INVALID_REQUEST;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Register socket
|
||||
let reg_result = self.app_server_mut().register_listen_socket(sock);
|
||||
|
||||
if let Err(internal_error) = reg_result {
|
||||
log::warn!("Internal error processing AddListenSocket API request: {internal_error:?}");
|
||||
res.payload.status = add_listen_socket_response_status::INTERNAL_ERROR;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
res.payload.status = add_listen_socket_response_status::OK;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_psk_broker(
|
||||
&mut self,
|
||||
_req: &super::boilerplate::AddPskBrokerRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::boilerplate::AddPskBrokerResponse,
|
||||
) -> anyhow::Result<()> {
|
||||
// Retrieve file descriptor
|
||||
let sock_res = run(|| {
|
||||
let sock = req_fds
|
||||
.pop_front()
|
||||
.context("Invalid request – socket missing.")?;
|
||||
mio::net::UnixStream::from_fd(sock)
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
let sock = match sock_res {
|
||||
Ok(sock) => sock,
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Request found to be invalid while processing AddPskBroker API request: {e:?}"
|
||||
);
|
||||
res.payload.status = add_psk_broker_response_status::INVALID_REQUEST;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Register Socket
|
||||
let client = Box::new(MioBrokerClient::new(sock));
|
||||
|
||||
// Workaround: The broker code is currently impressively overcomplicated. Brokers are
|
||||
// stored in a hash map but the hash map key used is just a counter so a vector could
|
||||
// have been used. Broker configuration is abstracted, different peers can have different
|
||||
// brokers but there is no facility to add multiple brokers in practice. The broker index
|
||||
// uses a `Public` wrapper without actually holding any cryptographic data. Even the broker
|
||||
// configuration uses a trait abstraction for no discernible reason and a lot of the code
|
||||
// introduces pointless, single-field wrapper structs.
|
||||
// We should use an implement-what-is-actually-needed strategy next time.
|
||||
// The Broker code needs to be slimmed down, the right direction to go is probably to
|
||||
// just add event and capability support to the API and use the API to deliver OSK events.
|
||||
//
|
||||
// For now, we just replace the latest broker.
|
||||
let erase_ptr = {
|
||||
use crate::app_server::BrokerStorePtr;
|
||||
//
|
||||
use rosenpass_secret_memory::Public;
|
||||
use zerocopy::AsBytes;
|
||||
(self.app_server().brokers.store.len() - 1)
|
||||
.apply(|x| x as u64)
|
||||
.apply(|x| Public::from_slice(x.as_bytes()))
|
||||
.apply(BrokerStorePtr)
|
||||
};
|
||||
|
||||
let register_result = run(|| {
|
||||
let srv = self.app_server_mut();
|
||||
srv.unregister_broker(erase_ptr)?;
|
||||
srv.register_broker(client)
|
||||
});
|
||||
|
||||
if let Err(e) = register_result {
|
||||
log::warn!("Internal error while processing AddPskBroker API request: {e:?}");
|
||||
res.payload.status = add_psk_broker_response_status::INTERNAL_ERROR;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
res.payload.status = add_psk_broker_response_status::OK;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
222
rosenpass/src/api/boilerplate/byte_slice_ext.rs
Normal file
222
rosenpass/src/api/boilerplate/byte_slice_ext.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use zerocopy::{ByteSlice, Ref};
|
||||
|
||||
use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt};
|
||||
|
||||
use super::{
|
||||
PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef,
|
||||
ResponseMsgType, ResponseRef, SupplyKeypairRequest, SupplyKeypairResponse,
|
||||
};
|
||||
|
||||
pub trait ByteSliceRefExt: ByteSlice {
|
||||
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker().parse_request_msg_type()
|
||||
}
|
||||
|
||||
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_prefix()?
|
||||
.parse_request_msg_type()
|
||||
}
|
||||
|
||||
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_suffix()?
|
||||
.parse_request_msg_type()
|
||||
}
|
||||
|
||||
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker().parse_response_msg_type()
|
||||
}
|
||||
|
||||
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_prefix()?
|
||||
.parse_response_msg_type()
|
||||
}
|
||||
|
||||
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_suffix()?
|
||||
.parse_response_msg_type()
|
||||
}
|
||||
|
||||
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse(self)
|
||||
}
|
||||
|
||||
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse_from_prefix(self)
|
||||
}
|
||||
|
||||
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse_from_suffix(self)
|
||||
}
|
||||
|
||||
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse(self)
|
||||
}
|
||||
|
||||
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse_from_prefix(self)
|
||||
}
|
||||
|
||||
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse_from_suffix(self)
|
||||
}
|
||||
|
||||
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn supply_keypair_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn supply_keypair_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn add_listen_socket_request_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn add_listen_socket_request_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn add_listen_socket_response(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn add_listen_socket_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn add_listen_socket_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn add_psk_broker_request_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn add_psk_broker_request_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn add_psk_broker_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn add_psk_broker_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> ByteSliceRefExt for B {}
|
||||
29
rosenpass/src/api/boilerplate/message_trait.rs
Normal file
29
rosenpass/src/api/boilerplate/message_trait.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use zerocopy::{ByteSliceMut, Ref};
|
||||
|
||||
use rosenpass_util::zerocopy::RefMaker;
|
||||
|
||||
use super::RawMsgType;
|
||||
|
||||
pub trait Message {
|
||||
type Payload;
|
||||
type MessageClass: Into<RawMsgType>;
|
||||
const MESSAGE_TYPE: Self::MessageClass;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self;
|
||||
fn init(&mut self);
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
|
||||
}
|
||||
|
||||
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
||||
}
|
||||
|
||||
impl<B, T> ZerocopyResponseMakerSetupMessageExt<B, T> for RefMaker<B, T>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
T: Message,
|
||||
{
|
||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
||||
T::setup(self.into_buf())
|
||||
}
|
||||
}
|
||||
162
rosenpass/src/api/boilerplate/message_type.rs
Normal file
162
rosenpass/src/api/boilerplate/message_type.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use hex_literal::hex;
|
||||
use rosenpass_util::zerocopy::RefMaker;
|
||||
use zerocopy::ByteSlice;
|
||||
|
||||
use crate::RosenpassError::{self, InvalidApiMessageType};
|
||||
|
||||
pub type RawMsgType = u128;
|
||||
|
||||
// constants generated by gen-ipc-msg-types:
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request
|
||||
pub const PING_REQUEST: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d 0b02 ea31 45d3 4999"));
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Response
|
||||
pub const PING_RESPONSE: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260"));
|
||||
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Request
|
||||
const SUPPLY_KEYPAIR_REQUEST: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("ac91 a5a6 4f4b 21d0 ac7f 9b55 74f7 3529"));
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Response
|
||||
const SUPPLY_KEYPAIR_RESPONSE: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9"));
|
||||
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Listen Socket Request
|
||||
const ADD_LISTEN_SOCKET_REQUEST: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("3f21 434f 87cc a08c 02c4 61e4 0816 c7da"));
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Listen Socket Response
|
||||
const ADD_LISTEN_SOCKET_RESPONSE: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("45d5 0f0d 93f0 6105 98f2 9469 5dfd 5f36"));
|
||||
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Psk Broker Request
|
||||
const ADD_PSK_BROKER_REQUEST: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("d798 b8dc bd61 5cab 8df1 c63d e4eb a2d1"));
|
||||
// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Add Psk Broker Response
|
||||
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
||||
|
||||
pub trait MessageAttributes {
|
||||
fn message_size(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum RequestMsgType {
|
||||
Ping,
|
||||
SupplyKeypair,
|
||||
AddListenSocket,
|
||||
AddPskBroker,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum ResponseMsgType {
|
||||
Ping,
|
||||
SupplyKeypair,
|
||||
AddListenSocket,
|
||||
AddPskBroker,
|
||||
}
|
||||
|
||||
impl MessageAttributes for RequestMsgType {
|
||||
fn message_size(&self) -> usize {
|
||||
match self {
|
||||
Self::Ping => std::mem::size_of::<super::PingRequest>(),
|
||||
Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairRequest>(),
|
||||
Self::AddListenSocket => std::mem::size_of::<super::AddListenSocketRequest>(),
|
||||
Self::AddPskBroker => std::mem::size_of::<super::AddPskBrokerRequest>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageAttributes for ResponseMsgType {
|
||||
fn message_size(&self) -> usize {
|
||||
match self {
|
||||
Self::Ping => std::mem::size_of::<super::PingResponse>(),
|
||||
Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairResponse>(),
|
||||
Self::AddListenSocket => std::mem::size_of::<super::AddListenSocketResponse>(),
|
||||
Self::AddPskBroker => std::mem::size_of::<super::AddPskBrokerResponse>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawMsgType> for RequestMsgType {
|
||||
type Error = RosenpassError;
|
||||
|
||||
fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
|
||||
use RequestMsgType as E;
|
||||
Ok(match value {
|
||||
self::PING_REQUEST => E::Ping,
|
||||
self::SUPPLY_KEYPAIR_REQUEST => E::SupplyKeypair,
|
||||
self::ADD_LISTEN_SOCKET_REQUEST => E::AddListenSocket,
|
||||
self::ADD_PSK_BROKER_REQUEST => E::AddPskBroker,
|
||||
_ => return Err(InvalidApiMessageType(value)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestMsgType> for RawMsgType {
|
||||
fn from(val: RequestMsgType) -> Self {
|
||||
use RequestMsgType as E;
|
||||
match val {
|
||||
E::Ping => self::PING_REQUEST,
|
||||
E::SupplyKeypair => self::SUPPLY_KEYPAIR_REQUEST,
|
||||
E::AddListenSocket => self::ADD_LISTEN_SOCKET_REQUEST,
|
||||
E::AddPskBroker => self::ADD_PSK_BROKER_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawMsgType> for ResponseMsgType {
|
||||
type Error = RosenpassError;
|
||||
|
||||
fn try_from(value: RawMsgType) -> Result<Self, Self::Error> {
|
||||
use ResponseMsgType as E;
|
||||
Ok(match value {
|
||||
self::PING_RESPONSE => E::Ping,
|
||||
self::SUPPLY_KEYPAIR_RESPONSE => E::SupplyKeypair,
|
||||
self::ADD_LISTEN_SOCKET_RESPONSE => E::AddListenSocket,
|
||||
self::ADD_PSK_BROKER_RESPONSE => E::AddPskBroker,
|
||||
_ => return Err(InvalidApiMessageType(value)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseMsgType> for RawMsgType {
|
||||
fn from(val: ResponseMsgType) -> Self {
|
||||
use ResponseMsgType as E;
|
||||
match val {
|
||||
E::Ping => self::PING_RESPONSE,
|
||||
E::SupplyKeypair => self::SUPPLY_KEYPAIR_RESPONSE,
|
||||
E::AddListenSocket => self::ADD_LISTEN_SOCKET_RESPONSE,
|
||||
E::AddPskBroker => self::ADD_PSK_BROKER_RESPONSE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RawMsgTypeExt {
|
||||
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
|
||||
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
||||
}
|
||||
|
||||
impl RawMsgTypeExt for RawMsgType {
|
||||
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError> {
|
||||
self.try_into()
|
||||
}
|
||||
|
||||
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError> {
|
||||
self.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RefMakerRawMsgTypeExt {
|
||||
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
||||
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> RefMakerRawMsgTypeExt for RefMaker<B, RawMsgType> {
|
||||
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||
Ok(self.parse()?.read().try_into()?)
|
||||
}
|
||||
|
||||
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||
Ok(self.parse()?.read().try_into()?)
|
||||
}
|
||||
}
|
||||
17
rosenpass/src/api/boilerplate/mod.rs
Normal file
17
rosenpass/src/api/boilerplate/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
mod byte_slice_ext;
|
||||
mod message_trait;
|
||||
mod message_type;
|
||||
mod payload;
|
||||
mod request_ref;
|
||||
mod request_response;
|
||||
mod response_ref;
|
||||
mod server;
|
||||
|
||||
pub use byte_slice_ext::*;
|
||||
pub use message_trait::*;
|
||||
pub use message_type::*;
|
||||
pub use payload::*;
|
||||
pub use request_ref::*;
|
||||
pub use request_response::*;
|
||||
pub use response_ref::*;
|
||||
pub use server::*;
|
||||
351
rosenpass/src/api/boilerplate/payload.rs
Normal file
351
rosenpass/src/api/boilerplate/payload.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
use rosenpass_util::zerocopy::ZerocopyMutSliceExt;
|
||||
use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
|
||||
|
||||
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
||||
|
||||
/// Size required to fit any message in binary form
|
||||
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
|
||||
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
|
||||
pub const MAX_REQUEST_FDS: usize = 2;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
/// Which message this is
|
||||
pub msg_type: RawMsgType,
|
||||
/// The actual Paylod
|
||||
pub payload: M,
|
||||
}
|
||||
|
||||
pub type RequestEnvelope<M> = Envelope<M>;
|
||||
pub type ResponseEnvelope<M> = Envelope<M>;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct PingRequestPayload {
|
||||
/// Randomly generated connection id
|
||||
pub echo: [u8; 256],
|
||||
}
|
||||
|
||||
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
||||
|
||||
impl PingRequest {
|
||||
pub fn new(echo: [u8; 256]) -> Self {
|
||||
Self::from_payload(PingRequestPayload { echo })
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for PingRequest {
|
||||
type Payload = PingRequestPayload;
|
||||
type MessageClass = RequestMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::Ping;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct PingResponsePayload {
|
||||
/// Randomly generated connection id
|
||||
pub echo: [u8; 256],
|
||||
}
|
||||
|
||||
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
||||
|
||||
impl PingResponse {
|
||||
pub fn new(echo: [u8; 256]) -> Self {
|
||||
Self::from_payload(PingResponsePayload { echo })
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for PingResponse {
|
||||
type Payload = PingResponsePayload;
|
||||
type MessageClass = ResponseMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::Ping;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct SupplyKeypairRequestPayload {}
|
||||
|
||||
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
||||
|
||||
impl Default for SupplyKeypairRequest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SupplyKeypairRequest {
|
||||
pub fn new() -> Self {
|
||||
Self::from_payload(SupplyKeypairRequestPayload {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for SupplyKeypairRequest {
|
||||
type Payload = SupplyKeypairRequestPayload;
|
||||
type MessageClass = RequestMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::SupplyKeypair;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub mod supply_keypair_response_status {
|
||||
pub const OK: u128 = 0;
|
||||
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
pub const INVALID_REQUEST: u128 = 3;
|
||||
pub const IO_ERROR: u128 = 4;
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct SupplyKeypairResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
||||
|
||||
impl SupplyKeypairResponse {
|
||||
pub fn new(status: u128) -> Self {
|
||||
Self::from_payload(SupplyKeypairResponsePayload { status })
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for SupplyKeypairResponse {
|
||||
type Payload = SupplyKeypairResponsePayload;
|
||||
type MessageClass = ResponseMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::SupplyKeypair;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddListenSocketRequestPayload {}
|
||||
|
||||
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
||||
|
||||
impl Default for AddListenSocketRequest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddListenSocketRequest {
|
||||
pub fn new() -> Self {
|
||||
Self::from_payload(AddListenSocketRequestPayload {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for AddListenSocketRequest {
|
||||
type Payload = AddListenSocketRequestPayload;
|
||||
type MessageClass = RequestMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::AddListenSocket;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub mod add_listen_socket_response_status {
|
||||
pub const OK: u128 = 0;
|
||||
pub const INVALID_REQUEST: u128 = 1;
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddListenSocketResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
||||
|
||||
impl AddListenSocketResponse {
|
||||
pub fn new(status: u128) -> Self {
|
||||
Self::from_payload(AddListenSocketResponsePayload { status })
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for AddListenSocketResponse {
|
||||
type Payload = AddListenSocketResponsePayload;
|
||||
type MessageClass = ResponseMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::AddListenSocket;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddPskBrokerRequestPayload {}
|
||||
|
||||
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
||||
|
||||
impl Default for AddPskBrokerRequest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddPskBrokerRequest {
|
||||
pub fn new() -> Self {
|
||||
Self::from_payload(AddPskBrokerRequestPayload {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for AddPskBrokerRequest {
|
||||
type Payload = AddPskBrokerRequestPayload;
|
||||
type MessageClass = RequestMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::AddPskBroker;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub mod add_psk_broker_response_status {
|
||||
pub const OK: u128 = 0;
|
||||
pub const INVALID_REQUEST: u128 = 1;
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddPskBrokerResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
||||
|
||||
impl AddPskBrokerResponse {
|
||||
pub fn new(status: u128) -> Self {
|
||||
Self::from_payload(AddPskBrokerResponsePayload { status })
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for AddPskBrokerResponse {
|
||||
type Payload = AddPskBrokerResponsePayload;
|
||||
type MessageClass = ResponseMsgType;
|
||||
const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::AddPskBroker;
|
||||
|
||||
fn from_payload(payload: Self::Payload) -> Self {
|
||||
Self {
|
||||
msg_type: Self::MESSAGE_TYPE.into(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
|
||||
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
|
||||
r.init();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.msg_type = Self::MESSAGE_TYPE.into();
|
||||
}
|
||||
}
|
||||
146
rosenpass/src/api/boilerplate/request_ref.rs
Normal file
146
rosenpass/src/api/boilerplate/request_ref.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use anyhow::ensure;
|
||||
|
||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
||||
|
||||
struct RequestRefMaker<B> {
|
||||
buf: B,
|
||||
msg_type: RequestMsgType,
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> RequestRef<B> {
|
||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.parse()
|
||||
}
|
||||
|
||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
||||
}
|
||||
|
||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
||||
}
|
||||
|
||||
pub fn message_type(&self) -> RequestMsgType {
|
||||
match self {
|
||||
Self::Ping(_) => RequestMsgType::Ping,
|
||||
Self::SupplyKeypair(_) => RequestMsgType::SupplyKeypair,
|
||||
Self::AddListenSocket(_) => RequestMsgType::AddListenSocket,
|
||||
Self::AddPskBroker(_) => RequestMsgType::AddPskBroker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, PingRequest>> for RequestRef<B> {
|
||||
fn from(v: Ref<B, PingRequest>) -> Self {
|
||||
Self::Ping(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, super::SupplyKeypairRequest>> for RequestRef<B> {
|
||||
fn from(v: Ref<B, super::SupplyKeypairRequest>) -> Self {
|
||||
Self::SupplyKeypair(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, super::AddListenSocketRequest>> for RequestRef<B> {
|
||||
fn from(v: Ref<B, super::AddListenSocketRequest>) -> Self {
|
||||
Self::AddListenSocket(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, super::AddPskBrokerRequest>> for RequestRef<B> {
|
||||
fn from(v: Ref<B, super::AddPskBrokerRequest>) -> Self {
|
||||
Self::AddPskBroker(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> RequestRefMaker<B> {
|
||||
fn new(buf: B) -> anyhow::Result<Self> {
|
||||
let msg_type = buf.deref().request_msg_type_from_prefix()?;
|
||||
Ok(Self { buf, msg_type })
|
||||
}
|
||||
|
||||
fn target_size(&self) -> usize {
|
||||
self.msg_type.message_size()
|
||||
}
|
||||
|
||||
fn parse(self) -> anyhow::Result<RequestRef<B>> {
|
||||
Ok(match self.msg_type {
|
||||
RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?),
|
||||
RequestMsgType::SupplyKeypair => {
|
||||
RequestRef::SupplyKeypair(self.buf.supply_keypair_request()?)
|
||||
}
|
||||
RequestMsgType::AddListenSocket => {
|
||||
RequestRef::AddListenSocket(self.buf.add_listen_socket_request()?)
|
||||
}
|
||||
RequestMsgType::AddPskBroker => {
|
||||
RequestRef::AddPskBroker(self.buf.add_psk_broker_request()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_prefix(self) -> anyhow::Result<Self> {
|
||||
self.ensure_fit()?;
|
||||
let point = self.target_size();
|
||||
let Self { buf, msg_type } = self;
|
||||
let (buf, _) = buf.split_at(point);
|
||||
Ok(Self { buf, msg_type })
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_suffix(self) -> anyhow::Result<Self> {
|
||||
self.ensure_fit()?;
|
||||
let point = self.buf.len() - self.target_size();
|
||||
let Self { buf, msg_type } = self;
|
||||
let (buf, _) = buf.split_at(point);
|
||||
Ok(Self { buf, msg_type })
|
||||
}
|
||||
|
||||
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
||||
let have = self.buf.len();
|
||||
let need = self.target_size();
|
||||
ensure!(
|
||||
need <= have,
|
||||
"Buffer is undersized at {have} bytes (need {need} bytes)!"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RequestRef<B> {
|
||||
Ping(Ref<B, PingRequest>),
|
||||
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
||||
AddListenSocket(Ref<B, super::AddListenSocketRequest>),
|
||||
AddPskBroker(Ref<B, super::AddPskBrokerRequest>),
|
||||
}
|
||||
|
||||
impl<B> RequestRef<B>
|
||||
where
|
||||
B: ByteSlice,
|
||||
{
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes(),
|
||||
Self::SupplyKeypair(r) => r.bytes(),
|
||||
Self::AddListenSocket(r) => r.bytes(),
|
||||
Self::AddPskBroker(r) => r.bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> RequestRef<B>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
{
|
||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes_mut(),
|
||||
Self::SupplyKeypair(r) => r.bytes_mut(),
|
||||
Self::AddListenSocket(r) => r.bytes_mut(),
|
||||
Self::AddPskBroker(r) => r.bytes_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
190
rosenpass/src/api/boilerplate/request_response.rs
Normal file
190
rosenpass/src/api/boilerplate/request_response.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
use rosenpass_util::zerocopy::{
|
||||
RefMaker, ZerocopyEmancipateExt, ZerocopyEmancipateMutExt, ZerocopySliceExt,
|
||||
};
|
||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{Message, PingRequest, PingResponse};
|
||||
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
||||
|
||||
pub trait RequestMsg: Sized + Message {
|
||||
type ResponseMsg: ResponseMsg;
|
||||
|
||||
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
||||
buf.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).setup_msg()
|
||||
}
|
||||
|
||||
fn setup_response_from_prefix<B: ByteSliceMut>(
|
||||
buf: B,
|
||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||
}
|
||||
|
||||
fn setup_response_from_suffix<B: ByteSliceMut>(
|
||||
buf: B,
|
||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ResponseMsg: Message {
|
||||
type RequestMsg: RequestMsg;
|
||||
}
|
||||
|
||||
impl RequestMsg for PingRequest {
|
||||
type ResponseMsg = PingResponse;
|
||||
}
|
||||
|
||||
impl ResponseMsg for PingResponse {
|
||||
type RequestMsg = PingRequest;
|
||||
}
|
||||
|
||||
impl RequestMsg for super::SupplyKeypairRequest {
|
||||
type ResponseMsg = super::SupplyKeypairResponse;
|
||||
}
|
||||
|
||||
impl ResponseMsg for super::SupplyKeypairResponse {
|
||||
type RequestMsg = super::SupplyKeypairRequest;
|
||||
}
|
||||
|
||||
impl RequestMsg for super::AddListenSocketRequest {
|
||||
type ResponseMsg = super::AddListenSocketResponse;
|
||||
}
|
||||
|
||||
impl ResponseMsg for super::AddListenSocketResponse {
|
||||
type RequestMsg = super::AddListenSocketRequest;
|
||||
}
|
||||
|
||||
impl RequestMsg for super::AddPskBrokerRequest {
|
||||
type ResponseMsg = super::AddPskBrokerResponse;
|
||||
}
|
||||
|
||||
impl ResponseMsg for super::AddPskBrokerResponse {
|
||||
type RequestMsg = super::AddPskBrokerRequest;
|
||||
}
|
||||
|
||||
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
||||
pub type SupplyKeypairPair<B1, B2> = (
|
||||
Ref<B1, super::SupplyKeypairRequest>,
|
||||
Ref<B2, super::SupplyKeypairResponse>,
|
||||
);
|
||||
pub type AddListenSocketPair<B1, B2> = (
|
||||
Ref<B1, super::AddListenSocketRequest>,
|
||||
Ref<B2, super::AddListenSocketResponse>,
|
||||
);
|
||||
pub type AddPskBrokerPair<B1, B2> = (
|
||||
Ref<B1, super::AddPskBrokerRequest>,
|
||||
Ref<B2, super::AddPskBrokerResponse>,
|
||||
);
|
||||
|
||||
pub enum RequestResponsePair<B1, B2> {
|
||||
Ping(PingPair<B1, B2>),
|
||||
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
||||
AddListenSocket(AddListenSocketPair<B1, B2>),
|
||||
AddPskBroker(AddPskBrokerPair<B1, B2>),
|
||||
}
|
||||
|
||||
impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||
fn from(v: PingPair<B1, B2>) -> Self {
|
||||
RequestResponsePair::Ping(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B1, B2> From<SupplyKeypairPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||
fn from(v: SupplyKeypairPair<B1, B2>) -> Self {
|
||||
RequestResponsePair::SupplyKeypair(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B1, B2> From<AddListenSocketPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||
fn from(v: AddListenSocketPair<B1, B2>) -> Self {
|
||||
RequestResponsePair::AddListenSocket(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B1, B2> From<AddPskBrokerPair<B1, B2>> for RequestResponsePair<B1, B2> {
|
||||
fn from(v: AddPskBrokerPair<B1, B2>) -> Self {
|
||||
RequestResponsePair::AddPskBroker(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B1, B2> RequestResponsePair<B1, B2>
|
||||
where
|
||||
B1: ByteSlice,
|
||||
B2: ByteSlice,
|
||||
{
|
||||
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
||||
match self {
|
||||
Self::Ping((req, res)) => {
|
||||
let req = RequestRef::Ping(req.emancipate());
|
||||
let res = ResponseRef::Ping(res.emancipate());
|
||||
(req, res)
|
||||
}
|
||||
Self::SupplyKeypair((req, res)) => {
|
||||
let req = RequestRef::SupplyKeypair(req.emancipate());
|
||||
let res = ResponseRef::SupplyKeypair(res.emancipate());
|
||||
(req, res)
|
||||
}
|
||||
Self::AddListenSocket((req, res)) => {
|
||||
let req = RequestRef::AddListenSocket(req.emancipate());
|
||||
let res = ResponseRef::AddListenSocket(res.emancipate());
|
||||
(req, res)
|
||||
}
|
||||
Self::AddPskBroker((req, res)) => {
|
||||
let req = RequestRef::AddPskBroker(req.emancipate());
|
||||
let res = ResponseRef::AddPskBroker(res.emancipate());
|
||||
(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request(&self) -> RequestRef<&[u8]> {
|
||||
self.both().0
|
||||
}
|
||||
|
||||
pub fn response(&self) -> ResponseRef<&[u8]> {
|
||||
self.both().1
|
||||
}
|
||||
}
|
||||
|
||||
impl<B1, B2> RequestResponsePair<B1, B2>
|
||||
where
|
||||
B1: ByteSliceMut,
|
||||
B2: ByteSliceMut,
|
||||
{
|
||||
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
||||
match self {
|
||||
Self::Ping((req, res)) => {
|
||||
let req = RequestRef::Ping(req.emancipate_mut());
|
||||
let res = ResponseRef::Ping(res.emancipate_mut());
|
||||
(req, res)
|
||||
}
|
||||
Self::SupplyKeypair((req, res)) => {
|
||||
let req = RequestRef::SupplyKeypair(req.emancipate_mut());
|
||||
let res = ResponseRef::SupplyKeypair(res.emancipate_mut());
|
||||
(req, res)
|
||||
}
|
||||
Self::AddListenSocket((req, res)) => {
|
||||
let req = RequestRef::AddListenSocket(req.emancipate_mut());
|
||||
let res = ResponseRef::AddListenSocket(res.emancipate_mut());
|
||||
(req, res)
|
||||
}
|
||||
Self::AddPskBroker((req, res)) => {
|
||||
let req = RequestRef::AddPskBroker(req.emancipate_mut());
|
||||
let res = ResponseRef::AddPskBroker(res.emancipate_mut());
|
||||
(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
||||
self.both_mut().0
|
||||
}
|
||||
|
||||
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
||||
self.both_mut().1
|
||||
}
|
||||
}
|
||||
147
rosenpass/src/api/boilerplate/response_ref.rs
Normal file
147
rosenpass/src/api/boilerplate/response_ref.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
// TODO: This is copied verbatim from ResponseRef…not pretty
|
||||
use anyhow::ensure;
|
||||
|
||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
||||
|
||||
struct ResponseRefMaker<B> {
|
||||
buf: B,
|
||||
msg_type: ResponseMsgType,
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> ResponseRef<B> {
|
||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.parse()
|
||||
}
|
||||
|
||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
||||
}
|
||||
|
||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
||||
}
|
||||
|
||||
pub fn message_type(&self) -> ResponseMsgType {
|
||||
match self {
|
||||
Self::Ping(_) => ResponseMsgType::Ping,
|
||||
Self::SupplyKeypair(_) => ResponseMsgType::SupplyKeypair,
|
||||
Self::AddListenSocket(_) => ResponseMsgType::AddListenSocket,
|
||||
Self::AddPskBroker(_) => ResponseMsgType::AddPskBroker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, PingResponse>> for ResponseRef<B> {
|
||||
fn from(v: Ref<B, PingResponse>) -> Self {
|
||||
Self::Ping(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, super::SupplyKeypairResponse>> for ResponseRef<B> {
|
||||
fn from(v: Ref<B, super::SupplyKeypairResponse>) -> Self {
|
||||
Self::SupplyKeypair(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, super::AddListenSocketResponse>> for ResponseRef<B> {
|
||||
fn from(v: Ref<B, super::AddListenSocketResponse>) -> Self {
|
||||
Self::AddListenSocket(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Ref<B, super::AddPskBrokerResponse>> for ResponseRef<B> {
|
||||
fn from(v: Ref<B, super::AddPskBrokerResponse>) -> Self {
|
||||
Self::AddPskBroker(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> ResponseRefMaker<B> {
|
||||
fn new(buf: B) -> anyhow::Result<Self> {
|
||||
let msg_type = buf.deref().response_msg_type_from_prefix()?;
|
||||
Ok(Self { buf, msg_type })
|
||||
}
|
||||
|
||||
fn target_size(&self) -> usize {
|
||||
self.msg_type.message_size()
|
||||
}
|
||||
|
||||
fn parse(self) -> anyhow::Result<ResponseRef<B>> {
|
||||
Ok(match self.msg_type {
|
||||
ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?),
|
||||
ResponseMsgType::SupplyKeypair => {
|
||||
ResponseRef::SupplyKeypair(self.buf.supply_keypair_response()?)
|
||||
}
|
||||
ResponseMsgType::AddListenSocket => {
|
||||
ResponseRef::AddListenSocket(self.buf.add_listen_socket_response()?)
|
||||
}
|
||||
ResponseMsgType::AddPskBroker => {
|
||||
ResponseRef::AddPskBroker(self.buf.add_psk_broker_response()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_prefix(self) -> anyhow::Result<Self> {
|
||||
self.ensure_fit()?;
|
||||
let point = self.target_size();
|
||||
let Self { buf, msg_type } = self;
|
||||
let (buf, _) = buf.split_at(point);
|
||||
Ok(Self { buf, msg_type })
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_suffix(self) -> anyhow::Result<Self> {
|
||||
self.ensure_fit()?;
|
||||
let point = self.buf.len() - self.target_size();
|
||||
let Self { buf, msg_type } = self;
|
||||
let (buf, _) = buf.split_at(point);
|
||||
Ok(Self { buf, msg_type })
|
||||
}
|
||||
|
||||
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
||||
let have = self.buf.len();
|
||||
let need = self.target_size();
|
||||
ensure!(
|
||||
need <= have,
|
||||
"Buffer is undersized at {have} bytes (need {need} bytes)!"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseRef<B> {
|
||||
Ping(Ref<B, PingResponse>),
|
||||
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
||||
AddListenSocket(Ref<B, super::AddListenSocketResponse>),
|
||||
AddPskBroker(Ref<B, super::AddPskBrokerResponse>),
|
||||
}
|
||||
|
||||
impl<B> ResponseRef<B>
|
||||
where
|
||||
B: ByteSlice,
|
||||
{
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes(),
|
||||
Self::SupplyKeypair(r) => r.bytes(),
|
||||
Self::AddListenSocket(r) => r.bytes(),
|
||||
Self::AddPskBroker(r) => r.bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> ResponseRef<B>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
{
|
||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes_mut(),
|
||||
Self::SupplyKeypair(r) => r.bytes_mut(),
|
||||
Self::AddListenSocket(r) => r.bytes_mut(),
|
||||
Self::AddPskBroker(r) => r.bytes_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
94
rosenpass/src/api/boilerplate/server.rs
Normal file
94
rosenpass/src/api/boilerplate/server.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair};
|
||||
use std::{collections::VecDeque, os::fd::OwnedFd};
|
||||
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||
|
||||
pub trait Server {
|
||||
fn ping(
|
||||
&mut self,
|
||||
req: &PingRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut PingResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
fn supply_keypair(
|
||||
&mut self,
|
||||
req: &super::SupplyKeypairRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::SupplyKeypairResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
fn add_listen_socket(
|
||||
&mut self,
|
||||
req: &super::AddListenSocketRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::AddListenSocketResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
fn add_psk_broker(
|
||||
&mut self,
|
||||
req: &super::AddPskBrokerRequest,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: &mut super::AddPskBrokerResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
fn dispatch<ReqBuf, ResBuf>(
|
||||
&mut self,
|
||||
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
ReqBuf: ByteSlice,
|
||||
ResBuf: ByteSliceMut,
|
||||
{
|
||||
match p {
|
||||
RequestResponsePair::Ping((req, res)) => self.ping(req, req_fds, res),
|
||||
RequestResponsePair::SupplyKeypair((req, res)) => {
|
||||
self.supply_keypair(req, req_fds, res)
|
||||
}
|
||||
RequestResponsePair::AddListenSocket((req, res)) => {
|
||||
self.add_listen_socket(req, req_fds, res)
|
||||
}
|
||||
RequestResponsePair::AddPskBroker((req, res)) => self.add_psk_broker(req, req_fds, res),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_message<ReqBuf, ResBuf>(
|
||||
&mut self,
|
||||
req: ReqBuf,
|
||||
req_fds: &mut VecDeque<OwnedFd>,
|
||||
res: ResBuf,
|
||||
) -> anyhow::Result<usize>
|
||||
where
|
||||
ReqBuf: ByteSlice,
|
||||
ResBuf: ByteSliceMut,
|
||||
{
|
||||
let req = req.parse_request_from_prefix()?;
|
||||
// TODO: This is not pretty; This match should be moved into RequestRef
|
||||
let mut pair = match req {
|
||||
RequestRef::Ping(req) => {
|
||||
let mut res = res.ping_response_from_prefix()?;
|
||||
res.init();
|
||||
RequestResponsePair::Ping((req, res))
|
||||
}
|
||||
RequestRef::SupplyKeypair(req) => {
|
||||
let mut res = res.supply_keypair_response_from_prefix()?;
|
||||
res.init();
|
||||
RequestResponsePair::SupplyKeypair((req, res))
|
||||
}
|
||||
RequestRef::AddListenSocket(req) => {
|
||||
let mut res = res.add_listen_socket_response_from_prefix()?;
|
||||
res.init();
|
||||
RequestResponsePair::AddListenSocket((req, res))
|
||||
}
|
||||
RequestRef::AddPskBroker(req) => {
|
||||
let mut res = res.add_psk_broker_response_from_prefix()?;
|
||||
res.init();
|
||||
RequestResponsePair::AddPskBroker((req, res))
|
||||
}
|
||||
};
|
||||
self.dispatch(&mut pair, req_fds)?;
|
||||
|
||||
let res_len = pair.response().bytes().len();
|
||||
Ok(res_len)
|
||||
}
|
||||
}
|
||||
40
rosenpass/src/api/cli.rs
Normal file
40
rosenpass/src/api/cli.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Args;
|
||||
|
||||
use crate::config::Rosenpass as RosenpassConfig;
|
||||
|
||||
use super::config::ApiConfig;
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ApiCli {
|
||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||
/// connections on.
|
||||
#[arg(long)]
|
||||
api_listen_path: Vec<PathBuf>,
|
||||
|
||||
/// When rosenpass is called from another process, the other process can open and bind the
|
||||
/// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved
|
||||
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
|
||||
#[arg(long)]
|
||||
api_listen_fd: Vec<i32>,
|
||||
|
||||
/// When rosenpass is called from another process, the other process can connect the unix socket for the API
|
||||
/// themselves, for instance using the `socketpair(2)` system call.
|
||||
#[arg(long)]
|
||||
api_stream_fd: Vec<i32>,
|
||||
}
|
||||
|
||||
impl ApiCli {
|
||||
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
||||
self.apply_to_api_config(&mut cfg.api)
|
||||
}
|
||||
|
||||
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
|
||||
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
||||
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
||||
cfg.stream_fd.extend_from_slice(&self.api_stream_fd);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
49
rosenpass/src/api/config.rs
Normal file
49
rosenpass/src/api/config.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use mio::net::UnixListener;
|
||||
use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app_server::AppServer;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct ApiConfig {
|
||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||
/// connections on
|
||||
pub listen_path: Vec<PathBuf>,
|
||||
|
||||
/// When rosenpass is called from another process, the other process can open and bind the
|
||||
/// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved
|
||||
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate.
|
||||
pub listen_fd: Vec<i32>,
|
||||
|
||||
/// When rosenpass is called from another process, the other process can connect the unix socket for the API
|
||||
/// themselves, for instance using the `socketpair(2)` system call.
|
||||
pub stream_fd: Vec<i32>,
|
||||
}
|
||||
|
||||
impl ApiConfig {
|
||||
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
|
||||
for path in self.listen_path.iter() {
|
||||
srv.add_api_listener(UnixListener::bind(path)?)?;
|
||||
}
|
||||
|
||||
for fd in self.listen_fd.iter() {
|
||||
srv.add_api_listener(UnixListenerExt::claim_fd(*fd)?)?;
|
||||
}
|
||||
|
||||
for fd in self.stream_fd.iter() {
|
||||
srv.add_api_connection(UnixStreamExt::claim_fd(*fd)?)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn count_api_sources(&self) -> usize {
|
||||
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
||||
}
|
||||
|
||||
pub fn has_api_sources(&self) -> bool {
|
||||
self.count_api_sources() > 0
|
||||
}
|
||||
}
|
||||
321
rosenpass/src/api/mio/connection.rs
Normal file
321
rosenpass/src/api/mio/connection.rs
Normal file
@@ -0,0 +1,321 @@
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::os::fd::OwnedFd;
|
||||
|
||||
use mio::net::UnixStream;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_util::mio::ReadWithFileDescriptors;
|
||||
use rosenpass_util::{
|
||||
io::{IoResultKindHintExt, TryIoResultKindHintExt},
|
||||
length_prefix_encoding::{
|
||||
decoder::{self as lpe_decoder, LengthPrefixDecoder},
|
||||
encoder::{self as lpe_encoder, LengthPrefixEncoder},
|
||||
},
|
||||
mio::interest::RW as MIO_RW,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::api::MAX_REQUEST_FDS;
|
||||
use crate::{api::Server, app_server::AppServer};
|
||||
|
||||
use super::super::{ApiHandler, ApiHandlerContext};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SecretBuffer<const N: usize>(pub Secret<N>);
|
||||
|
||||
impl<const N: usize> SecretBuffer<N> {
|
||||
fn new() -> Self {
|
||||
Self(Secret::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for SecretBuffer<N> {
|
||||
fn borrow(&self) -> &[u8] {
|
||||
self.0.secret()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> BorrowMut<[u8]> for SecretBuffer<N> {
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
self.0.secret_mut()
|
||||
}
|
||||
}
|
||||
// TODO: Unfortunately, zerocopy is quite particular about alignment, hence the 4096
|
||||
type ReadBuffer = LengthPrefixDecoder<SecretBuffer<4096>>;
|
||||
type WriteBuffer = LengthPrefixEncoder<SecretBuffer<4096>>;
|
||||
type ReadFdBuffer = VecDeque<OwnedFd>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MioConnectionBuffers {
|
||||
read_buffer: ReadBuffer,
|
||||
write_buffer: WriteBuffer,
|
||||
read_fd_buffer: ReadFdBuffer,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MioConnection {
|
||||
io: UnixStream,
|
||||
mio_token: mio::Token,
|
||||
invalid_read: bool,
|
||||
buffers: Option<MioConnectionBuffers>,
|
||||
api_handler: ApiHandler,
|
||||
}
|
||||
|
||||
impl MioConnection {
|
||||
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
|
||||
let mio_token = app_server.mio_token_dispenser.dispense();
|
||||
app_server
|
||||
.mio_poll
|
||||
.registry()
|
||||
.register(&mut io, mio_token, MIO_RW)?;
|
||||
|
||||
let invalid_read = false;
|
||||
let read_buffer = LengthPrefixDecoder::new(SecretBuffer::new());
|
||||
let write_buffer = LengthPrefixEncoder::from_buffer(SecretBuffer::new());
|
||||
let read_fd_buffer = VecDeque::new();
|
||||
let buffers = Some(MioConnectionBuffers {
|
||||
read_buffer,
|
||||
write_buffer,
|
||||
read_fd_buffer,
|
||||
});
|
||||
let api_state = ApiHandler::new();
|
||||
Ok(Self {
|
||||
io,
|
||||
mio_token,
|
||||
invalid_read,
|
||||
buffers,
|
||||
api_handler: api_state,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shoud_close(&self) -> bool {
|
||||
let exhausted = self
|
||||
.buffers
|
||||
.as_ref()
|
||||
.map(|b| b.write_buffer.exhausted())
|
||||
.unwrap_or(false);
|
||||
self.invalid_read && exhausted
|
||||
}
|
||||
|
||||
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
||||
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mio_token(&self) -> mio::Token {
|
||||
self.mio_token
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MioConnectionContext {
|
||||
fn mio_connection(&self) -> &MioConnection;
|
||||
fn app_server(&self) -> &AppServer;
|
||||
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
|
||||
fn poll(&mut self) -> anyhow::Result<()> {
|
||||
macro_rules! short {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
None => return Ok(()),
|
||||
Some(()) => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// All of these functions return an error, None ("operation incomplete")
|
||||
// or some ("operation complete, keep processing")
|
||||
short!(self.flush_write_buffer()?); // Flush last message
|
||||
short!(self.recv()?); // Receive new message
|
||||
short!(self.handle_incoming_message()?); // Process new message with API
|
||||
short!(self.flush_write_buffer()?); // Begin flushing response
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
||||
self.with_buffers_stolen(|this, bufs| {
|
||||
// Acquire request & response. Caller is responsible to make sure
|
||||
// that read buffer holds a message and that write buffer is cleared.
|
||||
// Hence the unwraps and assertions
|
||||
assert!(bufs.write_buffer.exhausted());
|
||||
let req = bufs.read_buffer.message().unwrap().unwrap();
|
||||
let req_fds = &mut bufs.read_fd_buffer;
|
||||
let res = bufs.write_buffer.buffer_bytes_mut();
|
||||
|
||||
// Call API handler
|
||||
// Transitive trait implementations: MioConnectionContext -> ApiHandlerContext -> as ApiServer
|
||||
let response_len = this.handle_message(req, req_fds, res)?;
|
||||
|
||||
bufs.write_buffer
|
||||
.restart_write_with_new_message(response_len)?;
|
||||
bufs.read_buffer.zeroize(); // clear for new message to read
|
||||
bufs.read_fd_buffer.clear();
|
||||
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
||||
if self.write_buf_mut().exhausted() {
|
||||
return Ok(Some(()));
|
||||
}
|
||||
|
||||
use lpe_encoder::WriteToIoReturn as Ret;
|
||||
use std::io::ErrorKind as K;
|
||||
|
||||
loop {
|
||||
let conn = self.mio_connection_mut();
|
||||
let bufs = conn.buffers.as_mut().unwrap();
|
||||
|
||||
let sock = &conn.io;
|
||||
let write_buf = &mut bufs.write_buffer;
|
||||
|
||||
match write_buf.write_to_stdio(sock).io_err_kind_hint() {
|
||||
// Done
|
||||
Ok(Ret { done: true, .. }) => {
|
||||
write_buf.zeroize(); // clear for new message to write
|
||||
break Ok(Some(()));
|
||||
}
|
||||
|
||||
// Would block
|
||||
Ok(Ret {
|
||||
bytes_written: 0, ..
|
||||
}) => break Ok(None),
|
||||
Err((_e, K::WouldBlock)) => break Ok(None),
|
||||
|
||||
// Just continue
|
||||
Ok(_) => continue, /* Ret { bytes_written > 0, done = false } acc. to previous cases*/
|
||||
Err((_e, K::Interrupted)) => continue,
|
||||
|
||||
// Other errors
|
||||
Err((e, _ek)) => Err(e)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
||||
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret};
|
||||
use std::io::ErrorKind as K;
|
||||
|
||||
loop {
|
||||
let conn = self.mio_connection_mut();
|
||||
let bufs = conn.buffers.as_mut().unwrap();
|
||||
|
||||
let read_buf = &mut bufs.read_buffer;
|
||||
let read_fd_buf = &mut bufs.read_fd_buffer;
|
||||
|
||||
let sock = &conn.io;
|
||||
let fd_passing_sock = ReadWithFileDescriptors::<MAX_REQUEST_FDS, UnixStream, _, _>::new(
|
||||
sock,
|
||||
read_fd_buf,
|
||||
);
|
||||
|
||||
match read_buf
|
||||
.read_from_stdio(fd_passing_sock)
|
||||
.try_io_err_kind_hint()
|
||||
{
|
||||
// We actually received a proper message
|
||||
// (Impl below match to appease borrow checker)
|
||||
Ok(Ret {
|
||||
message: Some(_msg),
|
||||
..
|
||||
}) => break Ok(Some(())),
|
||||
|
||||
// Message does not fit in buffer
|
||||
Err((e @ E::MessageTooLargeError { .. }, _)) => {
|
||||
log::warn!("Received message on API that was too big to fit in our buffers; \
|
||||
looks like the client is broken. Stopping to process messages of the client.\n\
|
||||
Error: {e:?}");
|
||||
conn.invalid_read = true; // Closed mio_manager
|
||||
break Ok(None);
|
||||
}
|
||||
|
||||
// Would block
|
||||
Ok(Ret { bytes_read: 0, .. }) => break Ok(None),
|
||||
Err((_, Some(K::WouldBlock))) => break Ok(None),
|
||||
|
||||
// Just keep going
|
||||
Ok(Ret { bytes_read: _, .. }) => continue,
|
||||
Err((_, Some(K::Interrupted))) => continue,
|
||||
|
||||
// Other IO Error (just pass on to the caller)
|
||||
Err((E::IoError(e), _)) => {
|
||||
log::warn!(
|
||||
"IO error while trying to read message from API socket. \
|
||||
The connection is broken. Stopping to process messages of the client.\n\
|
||||
Error: {e:?}"
|
||||
);
|
||||
conn.invalid_read = true; // closed later by mio_manager
|
||||
break Err(e.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn mio_token(&self) -> mio::Token {
|
||||
self.mio_connection().mio_token()
|
||||
}
|
||||
|
||||
fn should_close(&self) -> bool {
|
||||
self.mio_connection().shoud_close()
|
||||
}
|
||||
}
|
||||
|
||||
trait MioConnectionContextPrivate: MioConnectionContext {
|
||||
fn steal_buffers(&mut self) -> MioConnectionBuffers {
|
||||
self.mio_connection_mut().buffers.take().unwrap()
|
||||
}
|
||||
|
||||
fn return_buffers(&mut self, buffers: MioConnectionBuffers) {
|
||||
let opt = &mut self.mio_connection_mut().buffers;
|
||||
assert!(opt.is_none());
|
||||
let _ = opt.insert(buffers);
|
||||
}
|
||||
|
||||
fn with_buffers_stolen<R, F: FnOnce(&mut Self, &mut MioConnectionBuffers) -> R>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> R {
|
||||
let mut bufs = self.steal_buffers();
|
||||
let res = f(self, &mut bufs);
|
||||
self.return_buffers(bufs);
|
||||
res
|
||||
}
|
||||
|
||||
fn write_buf_mut(&mut self) -> &mut WriteBuffer {
|
||||
self.mio_connection_mut()
|
||||
.buffers
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_buffer
|
||||
.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
||||
|
||||
impl<T> ApiHandlerContext for T
|
||||
where
|
||||
T: ?Sized + MioConnectionContext,
|
||||
{
|
||||
fn api_handler(&self) -> &ApiHandler {
|
||||
&self.mio_connection().api_handler
|
||||
}
|
||||
|
||||
fn app_server(&self) -> &AppServer {
|
||||
MioConnectionContext::app_server(self)
|
||||
}
|
||||
|
||||
fn api_handler_mut(&mut self) -> &mut ApiHandler {
|
||||
&mut self.mio_connection_mut().api_handler
|
||||
}
|
||||
|
||||
fn app_server_mut(&mut self) -> &mut AppServer {
|
||||
MioConnectionContext::app_server_mut(self)
|
||||
}
|
||||
}
|
||||
173
rosenpass/src/api/mio/manager.rs
Normal file
173
rosenpass/src/api/mio/manager.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use std::{borrow::BorrowMut, io};
|
||||
|
||||
use mio::net::{UnixListener, UnixStream};
|
||||
|
||||
use rosenpass_util::{
|
||||
functional::ApplyExt, io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW,
|
||||
};
|
||||
|
||||
use crate::app_server::{AppServer, AppServerIoSource};
|
||||
|
||||
use super::{MioConnection, MioConnectionContext};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MioManager {
|
||||
listeners: Vec<UnixListener>,
|
||||
connections: Vec<Option<MioConnection>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum MioManagerIoSource {
|
||||
Listener(usize),
|
||||
Connection(usize),
|
||||
}
|
||||
|
||||
impl MioManager {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
||||
ctx: &'a mut T,
|
||||
conn_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
||||
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
||||
Self { ctx, conn_idx }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MioManagerContext {
|
||||
fn mio_manager(&self) -> &MioManager;
|
||||
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
||||
fn app_server(&self) -> &AppServer;
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
|
||||
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
|
||||
let srv = self.app_server_mut();
|
||||
let mio_token = srv.mio_token_dispenser.dispense();
|
||||
srv.mio_poll
|
||||
.registry()
|
||||
.register(&mut listener, mio_token, MIO_RW)?;
|
||||
let io_source = self
|
||||
.mio_manager()
|
||||
.listeners
|
||||
.len()
|
||||
.apply(MioManagerIoSource::Listener)
|
||||
.apply(AppServerIoSource::MioManager);
|
||||
self.mio_manager_mut().listeners.push(listener);
|
||||
self.app_server_mut()
|
||||
.register_io_source(mio_token, io_source);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
||||
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
||||
let mio_token = connection.mio_token();
|
||||
let conns: &mut Vec<Option<MioConnection>> =
|
||||
self.mio_manager_mut().connections.borrow_mut();
|
||||
let idx = conns
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(_, slot)| slot.is_some())
|
||||
.map(|(idx, _)| idx)
|
||||
.unwrap_or(conns.len());
|
||||
conns.insert(idx, Some(connection));
|
||||
let io_source = idx
|
||||
.apply(MioManagerIoSource::Listener)
|
||||
.apply(AppServerIoSource::MioManager);
|
||||
self.app_server_mut()
|
||||
.register_io_source(mio_token, io_source);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
||||
use MioManagerIoSource as S;
|
||||
match io_source {
|
||||
S::Listener(idx) => self.accept_from(idx)?,
|
||||
S::Connection(idx) => self.poll_particular_connection(idx)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> anyhow::Result<()> {
|
||||
self.accept_connections()?;
|
||||
self.poll_connections()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn accept_connections(&mut self) -> io::Result<()> {
|
||||
for idx in 0..self.mio_manager_mut().listeners.len() {
|
||||
self.accept_from(idx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
||||
// Accept connection until the socket would block or returns another error
|
||||
// TODO: This currently only adds connections--we eventually need the ability to remove
|
||||
// them as well, see the note in connection.rs
|
||||
loop {
|
||||
match nonblocking_handle_io_errors(|| self.mio_manager().listeners[idx].accept())? {
|
||||
None => break,
|
||||
Some((conn, _addr)) => {
|
||||
self.add_connection(conn)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
||||
for idx in 0..self.mio_manager().connections.len() {
|
||||
self.poll_particular_connection(idx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
||||
if self.mio_manager().connections[idx].is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut conn = MioConnectionFocus::new(self, idx);
|
||||
conn.poll()?;
|
||||
|
||||
if conn.should_close() {
|
||||
let conn = self.mio_manager_mut().connections[idx].take().unwrap();
|
||||
let mio_token = conn.mio_token();
|
||||
if let Err(e) = conn.close(self.app_server_mut()) {
|
||||
log::warn!("Error while closing API connection {e:?}");
|
||||
};
|
||||
self.app_server_mut().unregister_io_source(mio_token);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + MioManagerContext> MioConnectionContext for MioConnectionFocus<'_, T> {
|
||||
fn mio_connection(&self) -> &MioConnection {
|
||||
self.ctx.mio_manager().connections[self.conn_idx]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn app_server(&self) -> &AppServer {
|
||||
self.ctx.app_server()
|
||||
}
|
||||
|
||||
fn mio_connection_mut(&mut self) -> &mut MioConnection {
|
||||
self.ctx.mio_manager_mut().connections[self.conn_idx]
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn app_server_mut(&mut self) -> &mut AppServer {
|
||||
self.ctx.app_server_mut()
|
||||
}
|
||||
}
|
||||
5
rosenpass/src/api/mio/mod.rs
Normal file
5
rosenpass/src/api/mio/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod connection;
|
||||
mod manager;
|
||||
|
||||
pub use connection::*;
|
||||
pub use manager::*;
|
||||
9
rosenpass/src/api/mod.rs
Normal file
9
rosenpass/src/api/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod api_handler;
|
||||
mod boilerplate;
|
||||
|
||||
pub use api_handler::*;
|
||||
pub use boilerplate::*;
|
||||
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod mio;
|
||||
@@ -1,15 +1,34 @@
|
||||
use anyhow::bail;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use log::{debug, error, info, warn};
|
||||
use derive_builder::Builder;
|
||||
use log::{error, info, warn};
|
||||
use mio::Interest;
|
||||
use mio::Token;
|
||||
use rosenpass_util::file::fopen_w;
|
||||
use rosenpass_secret_memory::Public;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_util::build::ConstructionSite;
|
||||
use rosenpass_util::file::StoreValueB64;
|
||||
use rosenpass_util::functional::run;
|
||||
use rosenpass_util::functional::ApplyExt;
|
||||
use rosenpass_util::io::IoResultKindHintExt;
|
||||
use rosenpass_util::io::SubstituteForIoErrorKindExt;
|
||||
use rosenpass_util::option::SomeExt;
|
||||
use rosenpass_util::result::OkExt;
|
||||
use rosenpass_wireguard_broker::WireguardBrokerMio;
|
||||
use rosenpass_wireguard_broker::{WireguardBrokerCfg, WG_KEY_LEN};
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io::Write;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::io::stdout;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::SocketAddr;
|
||||
@@ -17,22 +36,30 @@ use std::net::SocketAddrV4;
|
||||
use std::net::SocketAddrV6;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
use std::slice;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::protocol::BuildCryptoServer;
|
||||
use crate::protocol::HostIdentification;
|
||||
use crate::{
|
||||
config::Verbosity,
|
||||
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
||||
};
|
||||
use rosenpass_util::attempt;
|
||||
use rosenpass_util::b64::{b64_writer, fmt_b64};
|
||||
use rosenpass_util::b64::B64Display;
|
||||
|
||||
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
||||
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
||||
|
||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
const UNDER_LOAD_RATIO: f64 = 0.5;
|
||||
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
|
||||
|
||||
const BROKER_ID_BYTES: usize = 8;
|
||||
|
||||
fn ipv4_any_binding() -> SocketAddr {
|
||||
// addr, port
|
||||
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
||||
@@ -43,10 +70,50 @@ fn ipv6_any_binding() -> SocketAddr {
|
||||
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MioTokenDispenser {
|
||||
counter: usize,
|
||||
}
|
||||
|
||||
impl MioTokenDispenser {
|
||||
pub fn dispense(&mut self) -> Token {
|
||||
let r = self.counter;
|
||||
self.counter += 1;
|
||||
Token(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BrokerStore {
|
||||
pub store: HashMap<
|
||||
Public<BROKER_ID_BYTES>,
|
||||
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||
>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BrokerPeer {
|
||||
ptr: BrokerStorePtr,
|
||||
peer_cfg: Box<dyn WireguardBrokerCfg>,
|
||||
}
|
||||
|
||||
impl BrokerPeer {
|
||||
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
|
||||
Self { ptr, peer_cfg }
|
||||
}
|
||||
|
||||
pub fn ptr(&self) -> &BrokerStorePtr {
|
||||
&self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AppPeer {
|
||||
pub outfile: Option<PathBuf>,
|
||||
pub outwg: Option<WireguardOut>, // TODO make this a generic command
|
||||
pub broker_peer: Option<BrokerPeer>,
|
||||
pub initial_endpoint: Option<Endpoint>,
|
||||
pub current_endpoint: Option<Endpoint>,
|
||||
}
|
||||
@@ -67,19 +134,60 @@ pub struct WireguardOut {
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum DoSOperation {
|
||||
UnderLoad,
|
||||
Normal,
|
||||
}
|
||||
/// Integration test helpers for AppServer
|
||||
#[derive(Debug, Builder)]
|
||||
#[builder(pattern = "owned")]
|
||||
pub struct AppServerTest {
|
||||
/// Enable DoS operation permanently
|
||||
#[builder(default = "false")]
|
||||
pub enable_dos_permanently: bool,
|
||||
/// Terminate application signal
|
||||
#[builder(default = "None")]
|
||||
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum AppServerIoSource {
|
||||
Socket(usize),
|
||||
#[cfg(feature = "experiment_api")]
|
||||
PskBroker(Public<BROKER_ID_BYTES>),
|
||||
#[cfg(feature = "experiment_api")]
|
||||
MioManager(crate::api::mio::MioManagerIoSource),
|
||||
}
|
||||
|
||||
const EVENT_CAPACITY: usize = 20;
|
||||
|
||||
/// Holds the state of the application, namely the external IO
|
||||
///
|
||||
/// Responsible for file IO, network IO
|
||||
// TODO add user control via unix domain socket and stdin/stdout
|
||||
#[derive(Debug)]
|
||||
pub struct AppServer {
|
||||
pub crypt: CryptoServer,
|
||||
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
|
||||
pub sockets: Vec<mio::net::UdpSocket>,
|
||||
pub events: mio::Events,
|
||||
pub short_poll_queue: VecDeque<mio::event::Event>,
|
||||
pub performed_long_poll: bool,
|
||||
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
|
||||
pub mio_poll: mio::Poll,
|
||||
pub mio_token_dispenser: MioTokenDispenser,
|
||||
pub brokers: BrokerStore,
|
||||
pub peers: Vec<AppPeer>,
|
||||
pub verbosity: Verbosity,
|
||||
pub all_sockets_drained: bool,
|
||||
pub under_load: DoSOperation,
|
||||
pub blocking_polls_count: usize,
|
||||
pub non_blocking_polls_count: usize,
|
||||
pub unpolled_count: usize,
|
||||
pub last_update_time: Instant,
|
||||
pub test_helpers: Option<AppServerTest>,
|
||||
#[cfg(feature = "experiment_api")]
|
||||
pub api_manager: crate::api::mio::MioManager,
|
||||
}
|
||||
|
||||
/// A socket pointer is an index assigned to a socket;
|
||||
@@ -128,6 +236,17 @@ impl AppPeerPtr {
|
||||
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
||||
&mut srv.peers[self.0]
|
||||
}
|
||||
|
||||
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() {
|
||||
let config = broker.peer_cfg.create_config(psk);
|
||||
let broker = server.brokers.store.get_mut(&broker.ptr().0).unwrap();
|
||||
broker.set_psk(config)?;
|
||||
} else if server.peers[self.0].outfile.is_none() {
|
||||
log::warn!("No broker peer found for peer {}", self.0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -162,13 +281,7 @@ pub enum Endpoint {
|
||||
/// at the same time. It also would reply on the same port RespHello was
|
||||
/// sent to when listening on multiple ports on the same interface. This
|
||||
/// may be required for some arcane firewall setups.
|
||||
SocketBoundAddress {
|
||||
/// The socket the address can be reached under; this is generally
|
||||
/// determined when we actually receive an RespHello message
|
||||
socket: SocketPtr,
|
||||
/// Just the address
|
||||
addr: SocketAddr,
|
||||
},
|
||||
SocketBoundAddress(SocketBoundEndpoint),
|
||||
// A host name or IP address; storing the hostname here instead of an
|
||||
// ip address makes sure that we look up the host name whenever we try
|
||||
// to make a connection; this may be beneficial in some setups where a host-name
|
||||
@@ -176,6 +289,85 @@ pub enum Endpoint {
|
||||
Discovery(HostPathDiscoveryEndpoint),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Endpoint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Endpoint::SocketBoundAddress(host) => write!(f, "{}", host),
|
||||
Endpoint::Discovery(host) => write!(f, "{}", host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SocketBoundEndpoint {
|
||||
/// The socket the address can be reached under; this is generally
|
||||
/// determined when we actually receive an RespHello message
|
||||
socket: SocketPtr,
|
||||
/// Just the address
|
||||
addr: SocketAddr,
|
||||
/// identifier
|
||||
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SocketBoundEndpoint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl SocketBoundEndpoint {
|
||||
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
|
||||
const IPV6_SIZE: usize = 16;
|
||||
const PORT_SIZE: usize = 2;
|
||||
const SCOPE_ID_SIZE: usize = 4;
|
||||
|
||||
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
|
||||
+ SocketBoundEndpoint::IPV6_SIZE
|
||||
+ SocketBoundEndpoint::PORT_SIZE
|
||||
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
|
||||
|
||||
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
|
||||
let bytes = Self::to_bytes(&socket, &addr);
|
||||
Self {
|
||||
socket,
|
||||
addr,
|
||||
bytes,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_bytes(
|
||||
socket: &SocketPtr,
|
||||
addr: &SocketAddr,
|
||||
) -> (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]) {
|
||||
let mut buf = [0u8; SocketBoundEndpoint::BUFFER_SIZE];
|
||||
let addr = match addr {
|
||||
SocketAddr::V4(addr) => {
|
||||
//Map IPv4-mapped to IPv6 addresses
|
||||
let ip = addr.ip().to_ipv6_mapped();
|
||||
SocketAddrV6::new(ip, addr.port(), 0, 0)
|
||||
}
|
||||
SocketAddr::V6(addr) => *addr,
|
||||
};
|
||||
let mut len: usize = 0;
|
||||
buf[len..len + SocketBoundEndpoint::SOCKET_SIZE].copy_from_slice(&socket.0.to_be_bytes());
|
||||
len += SocketBoundEndpoint::SOCKET_SIZE;
|
||||
buf[len..len + SocketBoundEndpoint::IPV6_SIZE].copy_from_slice(&addr.ip().octets());
|
||||
len += SocketBoundEndpoint::IPV6_SIZE;
|
||||
buf[len..len + SocketBoundEndpoint::PORT_SIZE].copy_from_slice(&addr.port().to_be_bytes());
|
||||
len += SocketBoundEndpoint::PORT_SIZE;
|
||||
buf[len..len + SocketBoundEndpoint::SCOPE_ID_SIZE]
|
||||
.copy_from_slice(&addr.scope_id().to_be_bytes());
|
||||
len += SocketBoundEndpoint::SCOPE_ID_SIZE;
|
||||
(len, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl HostIdentification for SocketBoundEndpoint {
|
||||
fn encode(&self) -> &[u8] {
|
||||
&self.bytes.1[0..self.bytes.0]
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// Start discovery from some addresses
|
||||
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||
@@ -216,7 +408,7 @@ impl Endpoint {
|
||||
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
|
||||
use Endpoint::*;
|
||||
match self {
|
||||
SocketBoundAddress { socket, addr } => socket.send_to(srv, buf, *addr),
|
||||
SocketBoundAddress(host) => host.socket.send_to(srv, buf, host.addr),
|
||||
Discovery(host) => host.send_scouting(srv, buf),
|
||||
}
|
||||
}
|
||||
@@ -224,7 +416,7 @@ impl Endpoint {
|
||||
fn addresses(&self) -> &[SocketAddr] {
|
||||
use Endpoint::*;
|
||||
match self {
|
||||
SocketBoundAddress { addr, .. } => slice::from_ref(addr),
|
||||
SocketBoundAddress(host) => slice::from_ref(&host.addr),
|
||||
Discovery(host) => host.addresses(),
|
||||
}
|
||||
}
|
||||
@@ -262,6 +454,12 @@ pub struct HostPathDiscoveryEndpoint {
|
||||
addresses: Vec<SocketAddr>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for HostPathDiscoveryEndpoint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.addresses)
|
||||
}
|
||||
}
|
||||
|
||||
impl HostPathDiscoveryEndpoint {
|
||||
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||
let scouting_state = Cell::new((0, 0));
|
||||
@@ -327,7 +525,7 @@ impl HostPathDiscoveryEndpoint {
|
||||
.to_string()
|
||||
.starts_with("Address family not supported by protocol");
|
||||
if !ignore {
|
||||
warn!("Socket #{} refusing to send to {}: ", sock_no, addr);
|
||||
warn!("Socket #{} refusing to send to {}: {}", sock_no, addr, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,14 +536,15 @@ impl HostPathDiscoveryEndpoint {
|
||||
|
||||
impl AppServer {
|
||||
pub fn new(
|
||||
sk: SSk,
|
||||
pk: SPk,
|
||||
keypair: Option<(SSk, SPk)>,
|
||||
addrs: Vec<SocketAddr>,
|
||||
verbosity: Verbosity,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<Self> {
|
||||
// setup mio
|
||||
let mio_poll = mio::Poll::new()?;
|
||||
let events = mio::Events::with_capacity(8);
|
||||
let events = mio::Events::with_capacity(EVENT_CAPACITY);
|
||||
let mut mio_token_dispenser = MioTokenDispenser::default();
|
||||
|
||||
// bind each SocketAddr to a socket
|
||||
let maybe_sockets: Result<Vec<_>, _> =
|
||||
@@ -419,46 +618,137 @@ impl AppServer {
|
||||
}
|
||||
|
||||
// register all sockets to mio
|
||||
for (i, socket) in sockets.iter_mut().enumerate() {
|
||||
let mut io_source_index = HashMap::new();
|
||||
for (idx, socket) in sockets.iter_mut().enumerate() {
|
||||
let mio_token = mio_token_dispenser.dispense();
|
||||
mio_poll
|
||||
.registry()
|
||||
.register(socket, Token(i), Interest::READABLE)?;
|
||||
.register(socket, mio_token, Interest::READABLE)?;
|
||||
let prev = io_source_index.insert(mio_token, AppServerIoSource::Socket(idx));
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
|
||||
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux
|
||||
let crypto_site = match keypair {
|
||||
Some((sk, pk)) => ConstructionSite::from_product(CryptoServer::new(sk, pk)),
|
||||
None => ConstructionSite::new(BuildCryptoServer::empty()),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
crypt: CryptoServer::new(sk, pk),
|
||||
crypto_site,
|
||||
peers: Vec::new(),
|
||||
verbosity,
|
||||
sockets,
|
||||
events,
|
||||
short_poll_queue: Default::default(),
|
||||
performed_long_poll: false,
|
||||
io_source_index,
|
||||
mio_poll,
|
||||
mio_token_dispenser,
|
||||
brokers: BrokerStore::default(),
|
||||
all_sockets_drained: false,
|
||||
under_load: DoSOperation::Normal,
|
||||
blocking_polls_count: 0,
|
||||
non_blocking_polls_count: 0,
|
||||
unpolled_count: 0,
|
||||
last_update_time: Instant::now(),
|
||||
test_helpers,
|
||||
#[cfg(feature = "experiment_api")]
|
||||
api_manager: crate::api::mio::MioManager::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
||||
self.crypto_site
|
||||
.product_ref()
|
||||
.context("Cryptography handler not initialized")
|
||||
}
|
||||
|
||||
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
||||
self.crypto_site
|
||||
.product_mut()
|
||||
.context("Cryptography handler not initialized")
|
||||
}
|
||||
|
||||
pub fn verbose(&self) -> bool {
|
||||
matches!(self.verbosity, Verbosity::Verbose)
|
||||
}
|
||||
|
||||
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
||||
let mio_token = self.mio_token_dispenser.dispense();
|
||||
self.mio_poll
|
||||
.registry()
|
||||
.register(&mut sock, mio_token, mio::Interest::READABLE)?;
|
||||
let io_source = self.sockets.len().apply(AppServerIoSource::Socket);
|
||||
self.sockets.push(sock);
|
||||
self.register_io_source(mio_token, io_source);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
||||
let prev = self.io_source_index.insert(token, io_source);
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
|
||||
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
||||
let value = self.io_source_index.remove(&token);
|
||||
assert!(value.is_some(), "Removed IO source that does not exist");
|
||||
}
|
||||
|
||||
pub fn register_broker(
|
||||
&mut self,
|
||||
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||
) -> Result<BrokerStorePtr> {
|
||||
let ptr = Public::from_slice((self.brokers.store.len() as u64).as_bytes());
|
||||
if self.brokers.store.insert(ptr, broker).is_some() {
|
||||
bail!("Broker already registered");
|
||||
}
|
||||
|
||||
let mio_token = self.mio_token_dispenser.dispense();
|
||||
let io_source = ptr.apply(AppServerIoSource::PskBroker);
|
||||
//Register broker
|
||||
self.brokers
|
||||
.store
|
||||
.get_mut(&ptr)
|
||||
.ok_or(anyhow::format_err!("Broker wasn't added to registry"))?
|
||||
.register(self.mio_poll.registry(), mio_token)?;
|
||||
self.register_io_source(mio_token, io_source);
|
||||
|
||||
Ok(BrokerStorePtr(ptr))
|
||||
}
|
||||
|
||||
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
||||
let mut broker = self
|
||||
.brokers
|
||||
.store
|
||||
.remove(&ptr.0)
|
||||
.context("Broker not found")?;
|
||||
self.unregister_io_source(broker.mio_token().unwrap());
|
||||
broker.unregister(self.mio_poll.registry())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_peer(
|
||||
&mut self,
|
||||
psk: Option<SymKey>,
|
||||
pk: SPk,
|
||||
outfile: Option<PathBuf>,
|
||||
outwg: Option<WireguardOut>,
|
||||
broker_peer: Option<BrokerPeer>,
|
||||
hostname: Option<String>,
|
||||
) -> anyhow::Result<AppPeerPtr> {
|
||||
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
|
||||
let PeerPtr(pn) = match &mut self.crypto_site {
|
||||
ConstructionSite::Void => bail!("Crypto server construction site is void"),
|
||||
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk),
|
||||
ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?,
|
||||
};
|
||||
assert!(pn == self.peers.len());
|
||||
|
||||
let initial_endpoint = hostname
|
||||
.map(Endpoint::discovery_from_hostname)
|
||||
.transpose()?;
|
||||
let current_endpoint = None;
|
||||
self.peers.push(AppPeer {
|
||||
outfile,
|
||||
outwg,
|
||||
broker_peer,
|
||||
initial_endpoint,
|
||||
current_endpoint,
|
||||
});
|
||||
@@ -493,7 +783,7 @@ impl AppServer {
|
||||
);
|
||||
if tries_left > 0 {
|
||||
error!("re-initializing networking in {sleep}! {tries_left} tries left.");
|
||||
std::thread::sleep(self.crypt.timebase.dur(sleep));
|
||||
std::thread::sleep(Duration::from_secs_f64(sleep));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -525,16 +815,42 @@ impl AppServer {
|
||||
use crate::protocol::HandleMsgResult;
|
||||
use AppPollResult::*;
|
||||
use KeyOutputReason::*;
|
||||
match self.poll(&mut *rx)? {
|
||||
|
||||
if let Some(AppServerTest {
|
||||
termination_handler: Some(terminate),
|
||||
..
|
||||
}) = &self.test_helpers
|
||||
{
|
||||
if terminate.try_recv().is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
enum CryptoSrv {
|
||||
Avail,
|
||||
Missing,
|
||||
}
|
||||
|
||||
let poll_result = self.poll(&mut *rx)?;
|
||||
let have_crypto = match self.crypto_site.is_available() {
|
||||
true => CryptoSrv::Avail,
|
||||
false => CryptoSrv::Missing,
|
||||
};
|
||||
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
SendInitiation(peer) => tx_maybe_with!(peer, || self
|
||||
.crypt
|
||||
match (have_crypto, poll_result) {
|
||||
(CryptoSrv::Missing, SendInitiation(_)) => {}
|
||||
(CryptoSrv::Avail, SendInitiation(peer)) => tx_maybe_with!(peer, || self
|
||||
.crypto_server_mut()?
|
||||
.initiate_handshake(peer.lower(), &mut *tx))?,
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
SendRetransmission(peer) => tx_maybe_with!(peer, || self
|
||||
.crypt
|
||||
|
||||
(CryptoSrv::Missing, SendRetransmission(_)) => {}
|
||||
(CryptoSrv::Avail, SendRetransmission(peer)) => tx_maybe_with!(peer, || self
|
||||
.crypto_server_mut()?
|
||||
.retransmit_handshake(peer.lower(), &mut *tx))?,
|
||||
DeleteKey(peer) => {
|
||||
|
||||
(CryptoSrv::Missing, DeleteKey(_)) => {}
|
||||
(CryptoSrv::Avail, DeleteKey(peer)) => {
|
||||
self.output_key(peer, Stale, &SymKey::random())?;
|
||||
|
||||
// There was a loss of connection apparently; restart host discovery
|
||||
@@ -548,12 +864,21 @@ impl AppServer {
|
||||
);
|
||||
}
|
||||
|
||||
ReceivedMessage(len, endpoint) => {
|
||||
match self.crypt.handle_msg(&rx[..len], &mut *tx) {
|
||||
(CryptoSrv::Missing, ReceivedMessage(_, _)) => {}
|
||||
(CryptoSrv::Avail, ReceivedMessage(len, endpoint)) => {
|
||||
let msg_result = match self.under_load {
|
||||
DoSOperation::UnderLoad => {
|
||||
self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx)
|
||||
}
|
||||
DoSOperation::Normal => {
|
||||
self.crypto_server_mut()?.handle_msg(&rx[..len], &mut *tx)
|
||||
}
|
||||
};
|
||||
match msg_result {
|
||||
Err(ref e) => {
|
||||
self.verbose().then(|| {
|
||||
info!(
|
||||
"error processing incoming message from {:?}: {:?} {}",
|
||||
"error processing incoming message from {}: {:?} {}",
|
||||
endpoint,
|
||||
e,
|
||||
e.backtrace()
|
||||
@@ -575,7 +900,8 @@ impl AppServer {
|
||||
ap.get_app_mut(self).current_endpoint = Some(endpoint);
|
||||
|
||||
// TODO: Maybe we should rather call the key "rosenpass output"?
|
||||
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
|
||||
let osk = &self.crypto_server_mut()?.osk(p)?;
|
||||
self.output_key(ap, Exchanged, osk)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,23 +910,40 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_msg_under_load(
|
||||
&mut self,
|
||||
endpoint: &Endpoint,
|
||||
rx: &[u8],
|
||||
tx: &mut [u8],
|
||||
) -> Result<crate::protocol::HandleMsgResult> {
|
||||
match endpoint {
|
||||
Endpoint::SocketBoundAddress(socket) => self
|
||||
.crypto_server_mut()?
|
||||
.handle_msg_under_load(rx, &mut *tx, socket),
|
||||
Endpoint::Discovery(_) => {
|
||||
anyhow::bail!("Host-path discovery is not supported under load")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_key(
|
||||
&self,
|
||||
&mut self,
|
||||
peer: AppPeerPtr,
|
||||
why: KeyOutputReason,
|
||||
key: &SymKey,
|
||||
) -> anyhow::Result<()> {
|
||||
let peerid = peer.lower().get(&self.crypt).pidt()?;
|
||||
let ap = peer.get_app(self);
|
||||
let peerid = peer.lower().get(self.crypto_server()?).pidt()?;
|
||||
|
||||
if self.verbose() {
|
||||
let msg = match why {
|
||||
KeyOutputReason::Exchanged => "Exchanged key with peer",
|
||||
KeyOutputReason::Stale => "Erasing outdated key from peer",
|
||||
};
|
||||
info!("{} {}", msg, fmt_b64(&*peerid));
|
||||
info!("{} {}", msg, peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>());
|
||||
}
|
||||
|
||||
let ap = peer.get_app(self);
|
||||
|
||||
if let Some(of) = ap.outfile.as_ref() {
|
||||
// This might leave some fragments of the secret on the stack;
|
||||
// in practice this is likely not a problem because the stack likely
|
||||
@@ -609,7 +952,7 @@ impl AppServer {
|
||||
// data will linger in the linux page cache anyways with the current
|
||||
// implementation, going to great length to erase the secret here is
|
||||
// not worth it right now.
|
||||
b64_writer(fopen_w(of)?).write_all(key.secret())?;
|
||||
key.store_b64::<MAX_B64_KEY_SIZE, _>(of)?;
|
||||
let why = match why {
|
||||
KeyOutputReason::Exchanged => "exchanged",
|
||||
KeyOutputReason::Stale => "stale",
|
||||
@@ -617,39 +960,17 @@ impl AppServer {
|
||||
|
||||
// this is intentionally writing to stdout instead of stderr, because
|
||||
// it is meant to allow external detection of a successful key-exchange
|
||||
println!(
|
||||
let stdout = stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
writeln!(
|
||||
stdout,
|
||||
"output-key peer {} key-file {of:?} {why}",
|
||||
fmt_b64(&*peerid)
|
||||
);
|
||||
peerid.fmt_b64::<MAX_B64_PEER_ID_SIZE>()
|
||||
)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
if let Some(owg) = ap.outwg.as_ref() {
|
||||
let mut child = Command::new("wg")
|
||||
.arg("set")
|
||||
.arg(&owg.dev)
|
||||
.arg("peer")
|
||||
.arg(&owg.pk)
|
||||
.arg("preshared-key")
|
||||
.arg("/dev/stdin")
|
||||
.stdin(Stdio::piped())
|
||||
.args(&owg.extra_params)
|
||||
.spawn()?;
|
||||
b64_writer(child.stdin.take().unwrap()).write_all(key.secret())?;
|
||||
|
||||
thread::spawn(move || {
|
||||
let status = child.wait();
|
||||
|
||||
if let Ok(status) = status {
|
||||
if status.success() {
|
||||
debug!("successfully passed psk to wg")
|
||||
} else {
|
||||
error!("could not pass psk to wg {:?}", status)
|
||||
}
|
||||
} else {
|
||||
error!("wait failed: {:?}", status)
|
||||
}
|
||||
});
|
||||
}
|
||||
peer.set_psk(self, key)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -657,17 +978,32 @@ impl AppServer {
|
||||
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
||||
use crate::protocol::PollResult as C;
|
||||
use AppPollResult as A;
|
||||
loop {
|
||||
return Ok(match self.crypt.poll()? {
|
||||
C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)),
|
||||
C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)),
|
||||
C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)),
|
||||
C::Sleep(timeout) => match self.try_recv(rx_buf, timeout)? {
|
||||
Some((len, addr)) => A::ReceivedMessage(len, addr),
|
||||
None => continue,
|
||||
},
|
||||
});
|
||||
let res = loop {
|
||||
// Call CryptoServer's poll (if available)
|
||||
let crypto_poll = self
|
||||
.crypto_site
|
||||
.product_mut()
|
||||
.map(|crypto| crypto.poll())
|
||||
.transpose()?;
|
||||
|
||||
// Map crypto server's poll result to our poll result
|
||||
let io_poll_timeout = match crypto_poll {
|
||||
Some(C::DeleteKey(PeerPtr(no))) => break A::DeleteKey(AppPeerPtr(no)),
|
||||
Some(C::SendInitiation(PeerPtr(no))) => break A::SendInitiation(AppPeerPtr(no)),
|
||||
Some(C::SendRetransmission(PeerPtr(no))) => {
|
||||
break A::SendRetransmission(AppPeerPtr(no))
|
||||
}
|
||||
Some(C::Sleep(timeout)) => timeout, // No event from crypto-server, do IO
|
||||
None => crate::protocol::UNENDING, // Crypto server is uninitialized, do IO
|
||||
};
|
||||
|
||||
// Perform IO (look for a message)
|
||||
if let Some((len, addr)) = self.try_recv(rx_buf, io_poll_timeout)? {
|
||||
break A::ReceivedMessage(len, addr);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Tries to receive a new message
|
||||
@@ -705,35 +1041,250 @@ impl AppServer {
|
||||
// readiness event seems to be good enough™ for now.
|
||||
|
||||
// only poll if we drained all sockets before
|
||||
if self.all_sockets_drained {
|
||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||
run(|| -> anyhow::Result<()> {
|
||||
if !self.all_sockets_drained || !self.short_poll_queue.is_empty() {
|
||||
self.unpolled_count += 1;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.perform_mio_poll_and_register_events(Duration::from_secs(0))?; // Non-blocking poll
|
||||
if !self.short_poll_queue.is_empty() {
|
||||
// Got some events in non-blocking mode
|
||||
self.non_blocking_polls_count += 1;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !self.performed_long_poll {
|
||||
// pass – go perform a full long poll before we enter blocking poll mode
|
||||
// to make sure our experimental short poll feature did not miss any events
|
||||
// due to being buggy.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Perform and register blocking poll
|
||||
self.blocking_polls_count += 1;
|
||||
self.perform_mio_poll_and_register_events(timeout)?;
|
||||
self.performed_long_poll = false;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if let Some(AppServerTest {
|
||||
enable_dos_permanently: true,
|
||||
..
|
||||
}) = self.test_helpers
|
||||
{
|
||||
self.under_load = DoSOperation::UnderLoad;
|
||||
} else {
|
||||
//Reset blocking poll count if waiting for more than BLOCKING_POLL_COUNT_DURATION
|
||||
if self.last_update_time.elapsed() > DURATION_UPDATE_UNDER_LOAD_STATUS {
|
||||
self.last_update_time = Instant::now();
|
||||
let total_polls = self.blocking_polls_count + self.non_blocking_polls_count;
|
||||
|
||||
let load_ratio = if total_polls > 0 {
|
||||
self.non_blocking_polls_count as f64 / total_polls as f64
|
||||
} else if self.unpolled_count > 0 {
|
||||
//There are no polls, so we are under load
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
if load_ratio > UNDER_LOAD_RATIO {
|
||||
self.under_load = DoSOperation::UnderLoad;
|
||||
} else {
|
||||
self.under_load = DoSOperation::Normal;
|
||||
}
|
||||
|
||||
self.blocking_polls_count = 0;
|
||||
self.non_blocking_polls_count = 0;
|
||||
self.unpolled_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Focused polling – i.e. actually using mio::Token – is experimental for now.
|
||||
// The reason for this is that we need to figure out how to integrate load detection
|
||||
// and focused polling for one. Mio event-based polling also does not play nice with
|
||||
// the current function signature and its reentrant design which is focused around receiving UDP socket packages
|
||||
// for processing by the crypto protocol server.
|
||||
// Besides that, there are also some parts of the code which intentionally block
|
||||
// despite available data. This is the correct behavior; e.g. api::mio::Connection blocks
|
||||
// further reads from its unix socket until the write buffer is flushed. In other words
|
||||
// the connection handler makes sure that there is a buffer to put the response in while
|
||||
// before reading further request.
|
||||
// The potential problem with this behavior is that we end up ignoring instructions from
|
||||
// epoll() to read from the particular sockets, so epoll will return information about that
|
||||
// particular – blocked – file descriptor every call. We have only so many event slots and
|
||||
// in theory, the event array could fill up entirely with intentionally blocked sockets.
|
||||
// We need to figure out how to deal with this situation.
|
||||
// Mio uses uses epoll in level-triggered mode, so we could handle taint-tracking for ignored
|
||||
// sockets ourselves. The facilities are available in epoll and Mio, but we need to figure out how mio uses those
|
||||
// facilities and how we can integrate them here.
|
||||
// This will involve rewriting a lot of IO code and we should probably have integration
|
||||
// tests before we approach that.
|
||||
//
|
||||
// This hybrid approach is not without merit though; the short poll implementation covers
|
||||
// all our IO sources, so under contention, rosenpass should generally not hit the long
|
||||
// poll mode below. We keep short polling and calling epoll() in non-blocking mode (timeout
|
||||
// of zero) until we run out of IO events processed. Then, just before we would perform a
|
||||
// blocking poll, we go through all available IO sources to see if we missed anything.
|
||||
{
|
||||
while let Some(ev) = self.short_poll_queue.pop_front() {
|
||||
if let Some(v) = self.try_recv_from_mio_token(buf, ev.token())? {
|
||||
return Ok(Some(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drain all sockets
|
||||
let mut would_block_count = 0;
|
||||
for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
|
||||
match socket.recv_from(buf) {
|
||||
Ok((n, addr)) => {
|
||||
for sock_no in 0..self.sockets.len() {
|
||||
match self
|
||||
.try_recv_from_listen_socket(buf, sock_no)
|
||||
.io_err_kind_hint()
|
||||
{
|
||||
Ok(None) => continue,
|
||||
Ok(Some(v)) => {
|
||||
// at least one socket was not drained...
|
||||
self.all_sockets_drained = false;
|
||||
return Ok(Some((
|
||||
n,
|
||||
Endpoint::SocketBoundAddress {
|
||||
socket: SocketPtr(sock_no),
|
||||
addr,
|
||||
},
|
||||
)));
|
||||
return Ok(Some(v));
|
||||
}
|
||||
Err(e) if e.kind() == ErrorKind::WouldBlock => {
|
||||
Err((_, ErrorKind::WouldBlock)) => {
|
||||
would_block_count += 1;
|
||||
}
|
||||
// TODO if one socket continuously returns an error, then we never poll, thus we never wait for a timeout, thus we have a spin-lock
|
||||
Err(e) => return Err(e.into()),
|
||||
Err((e, _)) => return Err(e)?,
|
||||
}
|
||||
}
|
||||
|
||||
// if each socket returned WouldBlock, then we drained them all at least once indeed
|
||||
self.all_sockets_drained = would_block_count == self.sockets.len();
|
||||
|
||||
// Process brokers poll
|
||||
for (_, broker) in self.brokers.store.iter_mut() {
|
||||
broker.process_poll()?;
|
||||
}
|
||||
|
||||
// API poll
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
{
|
||||
use crate::api::mio::MioManagerContext;
|
||||
MioManagerFocus(self).poll()?;
|
||||
}
|
||||
|
||||
self.performed_long_poll = true;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||
// Fill the short poll buffer with the acquired events
|
||||
self.events
|
||||
.iter()
|
||||
.cloned()
|
||||
.for_each(|v| self.short_poll_queue.push_back(v));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_recv_from_mio_token(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
token: mio::Token,
|
||||
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
||||
let io_source = match self.io_source_index.get(&token) {
|
||||
Some(io_source) => *io_source,
|
||||
None => {
|
||||
log::warn!("No IO source assiociated with mio token ({token:?}). Polling using mio tokens directly is an experimental feature and IO handler should recover when all available io sources are polled. This is a developer error. Please report it.");
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
self.try_recv_from_io_source(buf, io_source)
|
||||
}
|
||||
|
||||
fn try_recv_from_io_source(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
io_source: AppServerIoSource,
|
||||
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
||||
use crate::api::mio::MioManagerContext;
|
||||
|
||||
match io_source {
|
||||
AppServerIoSource::Socket(idx) => self
|
||||
.try_recv_from_listen_socket(buf, idx)
|
||||
.substitute_for_ioerr_wouldblock(None)?
|
||||
.ok(),
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
AppServerIoSource::PskBroker(key) => self
|
||||
.brokers
|
||||
.store
|
||||
.get_mut(&key)
|
||||
.with_context(|| format!("No PSK broker under key {key:?}"))?
|
||||
.process_poll()
|
||||
.map(|_| None),
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
AppServerIoSource::MioManager(mmio_src) => MioManagerFocus(self)
|
||||
.poll_particular(mmio_src)
|
||||
.map(|_| None),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_recv_from_listen_socket(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
idx: usize,
|
||||
) -> io::Result<Option<(usize, Endpoint)>> {
|
||||
use std::io::ErrorKind as K;
|
||||
let (n, addr) = loop {
|
||||
match self.sockets[idx].recv_from(buf).io_err_kind_hint() {
|
||||
Ok(v) => break v,
|
||||
Err((_, K::Interrupted)) => continue,
|
||||
Err((e, _)) => return Err(e)?,
|
||||
}
|
||||
};
|
||||
SocketPtr(idx)
|
||||
.apply(|sp| SocketBoundEndpoint::new(sp, addr))
|
||||
.apply(Endpoint::SocketBoundAddress)
|
||||
.apply(|ep| (n, ep))
|
||||
.some()
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
pub fn add_api_connection(&mut self, connection: mio::net::UnixStream) -> std::io::Result<()> {
|
||||
use crate::api::mio::MioManagerContext;
|
||||
MioManagerFocus(self).add_connection(connection)
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
pub fn add_api_listener(&mut self, listener: mio::net::UnixListener) -> std::io::Result<()> {
|
||||
use crate::api::mio::MioManagerContext;
|
||||
MioManagerFocus(self).add_listener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
struct MioManagerFocus<'a>(&'a mut AppServer);
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
impl crate::api::mio::MioManagerContext for MioManagerFocus<'_> {
|
||||
fn mio_manager(&self) -> &crate::api::mio::MioManager {
|
||||
&self.0.api_manager
|
||||
}
|
||||
|
||||
fn mio_manager_mut(&mut self) -> &mut crate::api::mio::MioManager {
|
||||
&mut self.0.api_manager
|
||||
}
|
||||
|
||||
fn app_server(&self) -> &AppServer {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn app_server_mut(&mut self) -> &mut AppServer {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
92
rosenpass/src/bin/gen-ipc-msg-types.rs
Normal file
92
rosenpass/src/bin/gen-ipc-msg-types.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use anyhow::{Context, Result};
|
||||
use heck::ToShoutySnakeCase;
|
||||
|
||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||
|
||||
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
|
||||
match values.split_first() {
|
||||
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
|
||||
None => Ok(hd.into_value()),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_literal(path: &[&str]) -> Result<()> {
|
||||
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
||||
let (last, prefix) = path.split_last().context("developer error!")?;
|
||||
let var_name = last.to_shouty_snake_case();
|
||||
|
||||
print!("// hash domain hash of: ");
|
||||
for n in prefix.iter() {
|
||||
print!("{n} -> ");
|
||||
}
|
||||
println!("{last}");
|
||||
|
||||
let c = hex::encode(val)
|
||||
.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| chunk.iter().collect::<String>())
|
||||
.collect::<Vec<_>>();
|
||||
println!("const {var_name} : RawMsgType = RawMsgType::from_le_bytes(hex!(\"{} {} {} {} {} {} {} {}\"));",
|
||||
c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Tree {
|
||||
Branch(String, Vec<Tree>),
|
||||
Leaf(String),
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Branch(name, _) => name,
|
||||
Self::Leaf(name) => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> {
|
||||
let mut path = prefix.to_owned();
|
||||
path.push(self.name());
|
||||
|
||||
match self {
|
||||
Self::Branch(_, ref children) => {
|
||||
for c in children.iter() {
|
||||
c.gen_code_inner(&path)?
|
||||
}
|
||||
}
|
||||
Self::Leaf(_) => print_literal(&path)?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_code(&self) -> Result<()> {
|
||||
self.gen_code_inner(&[])
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let tree = Tree::Branch(
|
||||
"Rosenpass IPC API".to_owned(),
|
||||
vec![Tree::Branch(
|
||||
"Rosenpass Protocol Server".to_owned(),
|
||||
vec![
|
||||
Tree::Leaf("Ping Request".to_owned()),
|
||||
Tree::Leaf("Ping Response".to_owned()),
|
||||
Tree::Leaf("Supply Keypair Request".to_owned()),
|
||||
Tree::Leaf("Supply Keypair Response".to_owned()),
|
||||
Tree::Leaf("Add Listen Socket Request".to_owned()),
|
||||
Tree::Leaf("Add Listen Socket Response".to_owned()),
|
||||
Tree::Leaf("Add Psk Broker Request".to_owned()),
|
||||
Tree::Leaf("Add Psk Broker Response".to_owned()),
|
||||
],
|
||||
)],
|
||||
);
|
||||
|
||||
println!("type RawMsgType = u128;");
|
||||
println!();
|
||||
tree.gen_code()
|
||||
}
|
||||
@@ -1,17 +1,44 @@
|
||||
use anyhow::{bail, ensure};
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use clap::{Parser, Subcommand};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::file::StoreSecret;
|
||||
use rosenpass_util::file::{LoadValue, LoadValueB64};
|
||||
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
|
||||
use rosenpass_wireguard_broker::brokers::native_unix::{
|
||||
NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError,
|
||||
};
|
||||
use std::ops::DerefMut;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::app_server;
|
||||
use crate::app_server::AppServer;
|
||||
use crate::app_server::AppServerTest;
|
||||
use crate::app_server::{AppServer, BrokerPeer};
|
||||
use crate::protocol::{SPk, SSk, SymKey};
|
||||
|
||||
use super::config;
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
use {
|
||||
command_fds::{CommandFdExt, FdMapping},
|
||||
log::{error, info},
|
||||
mio::net::UnixStream,
|
||||
rosenpass_util::fd::claim_fd,
|
||||
rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient,
|
||||
rosenpass_wireguard_broker::WireguardBrokerMio,
|
||||
rustix::fd::AsRawFd,
|
||||
rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType},
|
||||
std::os::unix::net,
|
||||
std::process::Command,
|
||||
std::thread,
|
||||
};
|
||||
|
||||
/// enum representing a choice of interface to a WireGuard broker
|
||||
#[derive(Debug)]
|
||||
pub enum BrokerInterface {
|
||||
Socket(PathBuf),
|
||||
FileDescriptor(i32),
|
||||
SocketPair,
|
||||
}
|
||||
|
||||
/// struct holding all CLI arguments for `clap` crate to parse
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about)]
|
||||
@@ -28,11 +55,41 @@ pub struct CliArgs {
|
||||
#[arg(short, long, group = "log-level")]
|
||||
quiet: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
#[cfg(feature = "experiment_api")]
|
||||
api: crate::api::cli::ApiCli,
|
||||
|
||||
/// path of the wireguard_psk broker socket to connect to
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[arg(long, group = "psk-broker-specs")]
|
||||
psk_broker_path: Option<PathBuf>,
|
||||
|
||||
/// fd of the wireguard_spk broker socket to connect to
|
||||
///
|
||||
/// when this command is called from another process, the other process can open and bind the
|
||||
/// Unix socket for the psk broker connection to use themselves, passing it to this process --
|
||||
/// in Rust this can be achieved using the
|
||||
/// [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[arg(long, group = "psk-broker-specs")]
|
||||
psk_broker_fd: Option<i32>,
|
||||
|
||||
/// spawn a psk broker locally using a socket pair
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[arg(short, long, group = "psk-broker-specs")]
|
||||
psk_broker_spawn: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: CliCommand,
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_config(_cfg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// returns the log level filter set by CLI args
|
||||
/// returns `None` if the user did not specify any log level filter via CLI
|
||||
///
|
||||
@@ -44,13 +101,35 @@ impl CliArgs {
|
||||
return Some(log::LevelFilter::Info);
|
||||
}
|
||||
if self.quiet {
|
||||
return Some(log::LevelFilter::Error);
|
||||
return Some(log::LevelFilter::Warn);
|
||||
}
|
||||
if let Some(level_filter) = self.log_level {
|
||||
return Some(level_filter);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
/// returns the broker interface set by CLI args
|
||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
||||
} else if let Some(fd) = self.psk_broker_fd {
|
||||
Some(BrokerInterface::FileDescriptor(fd))
|
||||
} else if self.psk_broker_spawn {
|
||||
Some(BrokerInterface::SocketPair)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
/// returns the broker interface set by CLI args
|
||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// represents a command specified via CLI
|
||||
@@ -145,14 +224,18 @@ pub enum CliCommand {
|
||||
Man,
|
||||
}
|
||||
|
||||
impl CliCommand {
|
||||
impl CliArgs {
|
||||
/// runs the command specified via CLI
|
||||
///
|
||||
/// ## TODO
|
||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
pub fn run(self) -> anyhow::Result<()> {
|
||||
pub fn run(
|
||||
self,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<()> {
|
||||
use CliCommand::*;
|
||||
match self {
|
||||
match &self.command {
|
||||
Man => {
|
||||
let man_cmd = std::process::Command::new("man")
|
||||
.args(["1", "rosenpass"])
|
||||
@@ -164,7 +247,7 @@ impl CliCommand {
|
||||
}
|
||||
GenConfig { config_file, force } => {
|
||||
ensure!(
|
||||
force || !config_file.exists(),
|
||||
*force || !config_file.exists(),
|
||||
"config file {config_file:?} already exists"
|
||||
);
|
||||
|
||||
@@ -179,9 +262,9 @@ impl CliCommand {
|
||||
let mut secret_key: Option<PathBuf> = None;
|
||||
|
||||
// Manual arg parsing, since clap wants to prefix flags with "--"
|
||||
let mut args = args.into_iter();
|
||||
let mut args = args.iter();
|
||||
loop {
|
||||
match (args.next().as_deref(), args.next()) {
|
||||
match (args.next().map(|x| x.as_str()), args.next()) {
|
||||
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
|
||||
secret_key = Some(opt.into());
|
||||
}
|
||||
@@ -220,10 +303,13 @@ impl CliCommand {
|
||||
);
|
||||
|
||||
let config = config::Rosenpass::load(config_file)?;
|
||||
let keypair = config
|
||||
.keypair
|
||||
.context("Config file present, but no keypair is specified.")?;
|
||||
|
||||
(config.public_key, config.secret_key)
|
||||
(keypair.public_key, keypair.secret_key)
|
||||
}
|
||||
(_, Some(pkf), Some(skf)) => (pkf, skf),
|
||||
(_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()),
|
||||
_ => {
|
||||
bail!("either a config-file or both public-key and secret-key file are required")
|
||||
}
|
||||
@@ -233,12 +319,14 @@ impl CliCommand {
|
||||
let mut problems = vec![];
|
||||
if !force && pkf.is_file() {
|
||||
problems.push(format!(
|
||||
"public-key file {pkf:?} exist, refusing to overwrite it"
|
||||
"public-key file {:?} exists, refusing to overwrite",
|
||||
std::fs::canonicalize(&pkf)?,
|
||||
));
|
||||
}
|
||||
if !force && skf.is_file() {
|
||||
problems.push(format!(
|
||||
"secret-key file {skf:?} exist, refusing to overwrite it"
|
||||
"secret-key file {:?} exists, refusing to overwrite",
|
||||
std::fs::canonicalize(&skf)?,
|
||||
));
|
||||
}
|
||||
if !problems.is_empty() {
|
||||
@@ -255,31 +343,38 @@ impl CliCommand {
|
||||
"config file '{config_file:?}' does not exist"
|
||||
);
|
||||
|
||||
let config = config::Rosenpass::load(config_file)?;
|
||||
let mut config = config::Rosenpass::load(config_file)?;
|
||||
config.validate()?;
|
||||
Self::event_loop(config)?;
|
||||
self.apply_to_config(&mut config)?;
|
||||
config.check_usefullness()?;
|
||||
|
||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||
}
|
||||
|
||||
Exchange {
|
||||
first_arg,
|
||||
mut rest_of_args,
|
||||
rest_of_args,
|
||||
config_file,
|
||||
} => {
|
||||
rest_of_args.insert(0, first_arg);
|
||||
let mut rest_of_args = rest_of_args.clone();
|
||||
rest_of_args.insert(0, first_arg.clone());
|
||||
let args = rest_of_args;
|
||||
let mut config = config::Rosenpass::parse_args(args)?;
|
||||
|
||||
if let Some(p) = config_file {
|
||||
config.store(&p)?;
|
||||
config.config_file_path = p;
|
||||
config.store(p)?;
|
||||
config.config_file_path.clone_from(p);
|
||||
}
|
||||
config.validate()?;
|
||||
Self::event_loop(config)?;
|
||||
self.apply_to_config(&mut config)?;
|
||||
config.check_usefullness()?;
|
||||
|
||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||
}
|
||||
|
||||
Validate { config_files } => {
|
||||
for file in config_files {
|
||||
match config::Rosenpass::load(&file) {
|
||||
match config::Rosenpass::load(file) {
|
||||
Ok(config) => {
|
||||
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
||||
match config.validate() {
|
||||
@@ -296,43 +391,168 @@ impl CliCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn event_loop(config: config::Rosenpass) -> anyhow::Result<()> {
|
||||
fn event_loop(
|
||||
config: config::Rosenpass,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<()> {
|
||||
const MAX_PSK_SIZE: usize = 1000;
|
||||
|
||||
// load own keys
|
||||
let sk = SSk::load(&config.secret_key)?;
|
||||
let pk = SPk::load(&config.public_key)?;
|
||||
let keypair = config
|
||||
.keypair
|
||||
.as_ref()
|
||||
.map(|kp| -> anyhow::Result<_> {
|
||||
let sk = SSk::load(&kp.secret_key)?;
|
||||
let pk = SPk::load(&kp.public_key)?;
|
||||
Ok((sk, pk))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// start an application server
|
||||
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
||||
sk,
|
||||
pk,
|
||||
config.listen,
|
||||
keypair,
|
||||
config.listen.clone(),
|
||||
config.verbosity,
|
||||
test_helpers,
|
||||
)?);
|
||||
|
||||
config.apply_to_app_server(&mut srv)?;
|
||||
|
||||
let broker = Self::create_broker(broker_interface)?;
|
||||
let broker_store_ptr = srv.register_broker(broker)?;
|
||||
|
||||
fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error {
|
||||
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
||||
}
|
||||
|
||||
for cfg_peer in config.peers {
|
||||
let broker_peer = if let Some(wg) = &cfg_peer.wg {
|
||||
let peer_cfg = NativeUnixBrokerConfigBaseBuilder::default()
|
||||
.peer_id_b64(&wg.peer)?
|
||||
.interface(wg.device.clone())
|
||||
.extra_params_ser(&wg.extra_params)?
|
||||
.build()
|
||||
.map_err(cfg_err_map)?;
|
||||
|
||||
let broker_peer = BrokerPeer::new(broker_store_ptr.clone(), Box::new(peer_cfg));
|
||||
|
||||
Some(broker_peer)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
srv.add_peer(
|
||||
// psk, pk, outfile, outwg, tx_addr
|
||||
cfg_peer.pre_shared_key.map(SymKey::load_b64).transpose()?,
|
||||
cfg_peer
|
||||
.pre_shared_key
|
||||
.map(SymKey::load_b64::<MAX_PSK_SIZE, _>)
|
||||
.transpose()?,
|
||||
SPk::load(&cfg_peer.public_key)?,
|
||||
cfg_peer.key_out,
|
||||
cfg_peer.wg.map(|cfg| app_server::WireguardOut {
|
||||
dev: cfg.device,
|
||||
pk: cfg.peer,
|
||||
extra_params: cfg.extra_params,
|
||||
}),
|
||||
broker_peer,
|
||||
cfg_peer.endpoint.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
srv.event_loop()
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn create_broker(
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
) -> Result<
|
||||
Box<dyn WireguardBrokerMio<MioError = anyhow::Error, Error = anyhow::Error>>,
|
||||
anyhow::Error,
|
||||
> {
|
||||
if let Some(interface) = broker_interface {
|
||||
let socket = Self::get_broker_socket(interface)?;
|
||||
Ok(Box::new(MioBrokerClient::new(socket)))
|
||||
} else {
|
||||
Ok(Box::new(NativeUnixBroker::new()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
fn create_broker(
|
||||
_broker_interface: Option<BrokerInterface>,
|
||||
) -> Result<Box<NativeUnixBroker>, anyhow::Error> {
|
||||
Ok(Box::new(NativeUnixBroker::new()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||
// Connect to the psk broker unix socket if one was specified
|
||||
// OR OTHERWISE spawn the psk broker and use socketpair(2) to connect with them
|
||||
match broker_interface {
|
||||
BrokerInterface::Socket(broker_path) => Ok(UnixStream::connect(broker_path)?),
|
||||
BrokerInterface::FileDescriptor(broker_fd) => {
|
||||
// mio::net::UnixStream doesn't implement From<OwnedFd>, so we have to go through std
|
||||
let sock = net::UnixStream::from(claim_fd(broker_fd)?);
|
||||
sock.set_nonblocking(true)?;
|
||||
Ok(UnixStream::from_std(sock))
|
||||
}
|
||||
BrokerInterface::SocketPair => {
|
||||
// Form a socketpair for communicating to the broker
|
||||
let (ours, theirs) = socketpair(
|
||||
AddressFamily::UNIX,
|
||||
SocketType::STREAM,
|
||||
SocketFlags::empty(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Setup our end of the socketpair
|
||||
let ours = net::UnixStream::from(ours);
|
||||
ours.set_nonblocking(true)?;
|
||||
|
||||
// Start the PSK broker
|
||||
let mut child = Command::new("rosenpass-wireguard-broker-socket-handler")
|
||||
.args(["--stream-fd", "3"])
|
||||
.fd_mappings(vec![FdMapping {
|
||||
parent_fd: theirs.as_raw_fd(),
|
||||
child_fd: 3,
|
||||
}])?
|
||||
.spawn()?;
|
||||
|
||||
// Handle the PSK broker crashing
|
||||
thread::spawn(move || {
|
||||
let status = child.wait();
|
||||
|
||||
if let Ok(status) = status {
|
||||
if status.success() {
|
||||
// Maybe they are doing double forking?
|
||||
info!("PSK broker exited.");
|
||||
} else {
|
||||
error!("PSK broker exited with an error ({status:?})");
|
||||
}
|
||||
} else {
|
||||
error!("Wait on PSK broker process failed ({status:?})");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(UnixStream::from_std(ours))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
let mut ssk = crate::protocol::SSk::random();
|
||||
let mut spk = crate::protocol::SPk::random();
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||
ssk.store_secret(secret_key)?;
|
||||
spk.store_secret(public_key)
|
||||
spk.store(public_key)
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal_testing")]
|
||||
pub mod testing {
|
||||
use super::*;
|
||||
|
||||
pub fn generate_and_save_keypair(
|
||||
secret_key: PathBuf,
|
||||
public_key: PathBuf,
|
||||
) -> anyhow::Result<()> {
|
||||
super::generate_and_save_keypair(secret_key, public_key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,31 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure};
|
||||
use rosenpass_util::file::fopen_w;
|
||||
use rosenpass_util::file::{fopen_w, Visibility};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app_server::AppServer;
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn empty_api_config() -> crate::api::config::ApiConfig {
|
||||
crate::api::config::ApiConfig {
|
||||
listen_path: Vec::new(),
|
||||
listen_fd: Vec::new(),
|
||||
stream_fd: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Rosenpass {
|
||||
/// path to the public key file
|
||||
pub public_key: PathBuf,
|
||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||
#[serde(flatten)]
|
||||
pub keypair: Option<Keypair>,
|
||||
|
||||
/// path to the secret key file
|
||||
pub secret_key: PathBuf,
|
||||
/// Location of the API listen sockets
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[serde(default = "empty_api_config")]
|
||||
pub api: crate::api::config::ApiConfig,
|
||||
|
||||
/// list of [`SocketAddr`] to listen on
|
||||
///
|
||||
@@ -52,9 +67,29 @@ pub struct Rosenpass {
|
||||
pub config_file_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
pub struct Keypair {
|
||||
/// path to the public key file
|
||||
pub public_key: PathBuf,
|
||||
|
||||
/// path to the secret key file
|
||||
pub secret_key: PathBuf,
|
||||
}
|
||||
|
||||
impl Keypair {
|
||||
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
||||
let public_key = public_key.as_ref().to_path_buf();
|
||||
let secret_key = secret_key.as_ref().to_path_buf();
|
||||
Self {
|
||||
public_key,
|
||||
secret_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum Verbosity {
|
||||
Quiet,
|
||||
Verbose,
|
||||
@@ -107,6 +142,12 @@ pub struct WireGuard {
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Rosenpass {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rosenpass {
|
||||
/// load configuration from a TOML file
|
||||
///
|
||||
@@ -122,8 +163,10 @@ impl Rosenpass {
|
||||
|
||||
// resolve `~` (see https://github.com/rosenpass/rosenpass/issues/237)
|
||||
use util::resolve_path_with_tilde;
|
||||
resolve_path_with_tilde(&mut config.public_key);
|
||||
resolve_path_with_tilde(&mut config.secret_key);
|
||||
if let Some(ref mut keypair) = config.keypair {
|
||||
resolve_path_with_tilde(&mut keypair.public_key);
|
||||
resolve_path_with_tilde(&mut keypair.secret_key);
|
||||
}
|
||||
for peer in config.peers.iter_mut() {
|
||||
resolve_path_with_tilde(&mut peer.public_key);
|
||||
if let Some(ref mut psk) = &mut peer.pre_shared_key {
|
||||
@@ -135,7 +178,7 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
// add path to "self"
|
||||
config.config_file_path = p.as_ref().to_owned();
|
||||
p.as_ref().clone_into(&mut config.config_file_path);
|
||||
|
||||
// return
|
||||
Ok(config)
|
||||
@@ -151,31 +194,39 @@ impl Rosenpass {
|
||||
|
||||
/// Commit the configuration to where it came from, overwriting the original file
|
||||
pub fn commit(&self) -> anyhow::Result<()> {
|
||||
let mut f = fopen_w(&self.config_file_path)?;
|
||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||
|
||||
self.store(&self.config_file_path)
|
||||
}
|
||||
|
||||
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_app_server(_srv)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a configuration
|
||||
///
|
||||
/// ## TODO
|
||||
/// - check that files do not just exist but are also readable
|
||||
/// - warn if neither out_key nor exchange_command of a peer is defined (v.i.)
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
if let Some(ref keypair) = self.keypair {
|
||||
// check the public key file exists
|
||||
ensure!(
|
||||
self.public_key.is_file(),
|
||||
keypair.public_key.is_file(),
|
||||
"could not find public-key file {:?}: no such file",
|
||||
self.public_key
|
||||
keypair.public_key
|
||||
);
|
||||
|
||||
// check the secret-key file exists
|
||||
ensure!(
|
||||
self.secret_key.is_file(),
|
||||
keypair.secret_key.is_file(),
|
||||
"could not find secret-key file {:?}: no such file",
|
||||
self.secret_key
|
||||
keypair.secret_key
|
||||
);
|
||||
}
|
||||
|
||||
for (i, peer) in self.peers.iter().enumerate() {
|
||||
// check peer's public-key file exists
|
||||
@@ -200,12 +251,36 @@ impl Rosenpass {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
ensure!(
|
||||
self.keypair.is_some() || self.api.has_api_sources(),
|
||||
"{}{}",
|
||||
"Specify a server keypair or some API connections to configure the keypair with.",
|
||||
"Without a keypair, rosenpass can not operate."
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
|
||||
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||
Self::new(Some(Keypair::new(pk, sk)))
|
||||
}
|
||||
|
||||
/// Creates a new configuration
|
||||
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(public_key: P1, secret_key: P2) -> Self {
|
||||
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||
Self {
|
||||
public_key: PathBuf::from(public_key.as_ref()),
|
||||
secret_key: PathBuf::from(secret_key.as_ref()),
|
||||
keypair,
|
||||
listen: vec![],
|
||||
#[cfg(feature = "experiment_api")]
|
||||
api: crate::api::config::ApiConfig::default(),
|
||||
verbosity: Verbosity::Quiet,
|
||||
peers: vec![],
|
||||
config_file_path: PathBuf::new(),
|
||||
@@ -228,7 +303,7 @@ impl Rosenpass {
|
||||
/// from chaotic args
|
||||
/// Quest: the grammar is undecideable, what do we do here?
|
||||
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||
let mut config = Self::new("", "");
|
||||
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
enum State {
|
||||
@@ -289,7 +364,7 @@ impl Rosenpass {
|
||||
already_set.insert(OwnPublicKey),
|
||||
"public-key was already set"
|
||||
);
|
||||
config.public_key = pk.into();
|
||||
config.keypair.as_mut().unwrap().public_key = pk.into();
|
||||
Own
|
||||
}
|
||||
(OwnSecretKey, sk, None) => {
|
||||
@@ -297,7 +372,7 @@ impl Rosenpass {
|
||||
already_set.insert(OwnSecretKey),
|
||||
"secret-key was already set"
|
||||
);
|
||||
config.secret_key = sk.into();
|
||||
config.keypair.as_mut().unwrap().secret_key = sk.into();
|
||||
Own
|
||||
}
|
||||
(OwnListen, l, None) => {
|
||||
@@ -432,10 +507,12 @@ impl Rosenpass {
|
||||
};
|
||||
|
||||
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("", "")
|
||||
..Self::new(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,14 +525,119 @@ impl Default for Verbosity {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::net::IpAddr;
|
||||
|
||||
use super::*;
|
||||
use std::{borrow::Borrow, net::IpAddr};
|
||||
|
||||
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||
toml::from_str(s.borrow())
|
||||
}
|
||||
|
||||
fn toml_ser<S: Serialize>(s: S) -> Result<toml::Table, toml::ser::Error> {
|
||||
toml::Table::try_from(s)
|
||||
}
|
||||
|
||||
fn assert_toml<L: Serialize, R: Borrow<str>>(l: L, r: R, info: &str) -> anyhow::Result<()> {
|
||||
fn lines_prepend(prefix: &str, s: &str) -> anyhow::Result<String> {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut buf = String::new();
|
||||
for line in s.lines() {
|
||||
writeln!(&mut buf, "{prefix}{line}")?;
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
let l = toml_ser(l)?;
|
||||
let r = toml_des(r.borrow())?;
|
||||
ensure!(
|
||||
l == r,
|
||||
"{}{}TOML value mismatch.\n Have:\n{}\n Expected:\n{}",
|
||||
info,
|
||||
if info.is_empty() { "" } else { ": " },
|
||||
lines_prepend(" ", &toml::to_string_pretty(&l)?)?,
|
||||
lines_prepend(" ", &toml::to_string_pretty(&r)?)?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assert_toml_round<'de, L: Serialize + Deserialize<'de>, R: Borrow<str>>(
|
||||
l: L,
|
||||
r: R,
|
||||
) -> anyhow::Result<()> {
|
||||
let l = toml_ser(l)?;
|
||||
assert_toml(&l, r.borrow(), "Straight deserialization")?;
|
||||
|
||||
let l: L = l.try_into().unwrap();
|
||||
let l = toml_ser(l).unwrap();
|
||||
assert_toml(l, r.borrow(), "Roundtrip deserialization")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn split_str(s: &str) -> Vec<String> {
|
||||
s.split(' ').map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toml_serialization() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
assert_toml_round(
|
||||
Rosenpass::empty(),
|
||||
r#"
|
||||
listen = []
|
||||
verbosity = "Quiet"
|
||||
peers = []
|
||||
|
||||
[api]
|
||||
listen_path = []
|
||||
listen_fd = []
|
||||
stream_fd = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
assert_toml_round(
|
||||
Rosenpass::empty(),
|
||||
r#"
|
||||
listen = []
|
||||
verbosity = "Quiet"
|
||||
peers = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
assert_toml_round(
|
||||
Rosenpass::from_sk_pk("/my/sk", "/my/pk"),
|
||||
r#"
|
||||
public_key = "/my/pk"
|
||||
secret_key = "/my/sk"
|
||||
listen = []
|
||||
verbosity = "Quiet"
|
||||
peers = []
|
||||
|
||||
[api]
|
||||
listen_path = []
|
||||
listen_fd = []
|
||||
stream_fd = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
assert_toml_round(
|
||||
Rosenpass::from_sk_pk("/my/sk", "/my/pk"),
|
||||
r#"
|
||||
public_key = "/my/pk"
|
||||
secret_key = "/my/sk"
|
||||
listen = []
|
||||
verbosity = "Quiet"
|
||||
peers = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_cli_parse() {
|
||||
let args = split_str(
|
||||
@@ -466,8 +648,10 @@ mod test {
|
||||
|
||||
let config = Rosenpass::parse_args(args).unwrap();
|
||||
|
||||
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
||||
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
@@ -496,8 +680,10 @@ mod test {
|
||||
|
||||
let config = Rosenpass::parse_args(args).unwrap();
|
||||
|
||||
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
||||
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert!(&config.listen.is_empty());
|
||||
assert_eq!(
|
||||
|
||||
@@ -31,6 +31,8 @@ pub fn protocol() -> Result<HashDomain> {
|
||||
|
||||
hash_domain_ns!(protocol, mac, "mac");
|
||||
hash_domain_ns!(protocol, cookie, "cookie");
|
||||
hash_domain_ns!(protocol, cookie_value, "cookie-value");
|
||||
hash_domain_ns!(protocol, cookie_key, "cookie-key");
|
||||
hash_domain_ns!(protocol, peerid, "peer id");
|
||||
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
|
||||
hash_domain_ns!(protocol, ckinit, "chaining key init");
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(feature = "experiment_api")]
|
||||
pub mod api;
|
||||
pub mod app_server;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
@@ -11,4 +13,8 @@ pub enum RosenpassError {
|
||||
BufferSizeMismatch,
|
||||
#[error("invalid message type")]
|
||||
InvalidMessageType(u8),
|
||||
#[error("invalid API message type")]
|
||||
InvalidApiMessageType(u128),
|
||||
#[error("could not parse API message")]
|
||||
InvalidApiMessage,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ pub fn main() {
|
||||
// parse CLI arguments
|
||||
let args = CliArgs::parse();
|
||||
|
||||
{
|
||||
use rosenpass_secret_memory as SM;
|
||||
#[cfg(feature = "experiment_memfd_secret")]
|
||||
SM::secret_policy_try_use_memfd_secrets();
|
||||
#[cfg(not(feature = "experiment_memfd_secret"))]
|
||||
SM::secret_policy_use_only_malloc_secrets();
|
||||
}
|
||||
|
||||
// init logging
|
||||
{
|
||||
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
|
||||
@@ -26,10 +34,11 @@ pub fn main() {
|
||||
// error!("error dummy");
|
||||
}
|
||||
|
||||
match args.command.run() {
|
||||
let broker_interface = args.get_broker_interface();
|
||||
match args.run(broker_interface, None) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
error!("{e:?}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,19 @@
|
||||
//! to the concept of lenses in function programming; more on that here:
|
||||
//! [https://sinusoid.es/misc/lager/lenses.pdf](https://sinusoid.es/misc/lager/lenses.pdf)
|
||||
//! To achieve this we utilize the zerocopy library.
|
||||
//!
|
||||
use std::mem::size_of;
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
use super::RosenpassError;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||
use std::mem::size_of;
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
pub const MSG_SIZE_LEN: usize = 1;
|
||||
pub const RESERVED_LEN: usize = 3;
|
||||
pub const MAC_SIZE: usize = 16;
|
||||
pub const COOKIE_SIZE: usize = 16;
|
||||
pub const SID_LEN: usize = 4;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
@@ -104,10 +110,24 @@ pub struct DataMsg {
|
||||
pub dummy: [u8; 4],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct CookieReplyInner {
|
||||
/// [MsgType] of this message
|
||||
pub msg_type: u8,
|
||||
/// Reserved for future use
|
||||
pub reserved: [u8; 3],
|
||||
/// Session ID of the sender (initiator)
|
||||
pub sid: [u8; 4],
|
||||
/// Encrypted cookie with authenticated initiator `mac`
|
||||
pub cookie_encrypted: [u8; xaead::NONCE_LEN + COOKIE_SIZE + xaead::TAG_LEN],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct CookieReply {
|
||||
pub dummy: [u8; 4],
|
||||
pub inner: CookieReplyInner,
|
||||
pub padding: [u8; size_of::<Envelope<InitHello>>() - size_of::<CookieReplyInner>()],
|
||||
}
|
||||
|
||||
// Traits /////////////////////////////////////////////////////////////////////
|
||||
@@ -156,6 +176,12 @@ impl TryFrom<u8> for MsgType {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MsgType> for u8 {
|
||||
fn from(val: MsgType) -> Self {
|
||||
val as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// length in bytes of an unencrypted Biscuit (plain text)
|
||||
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
||||
|
||||
|
||||
127
rosenpass/src/protocol/build_crypto_server.rs
Normal file
127
rosenpass/src/protocol/build_crypto_server.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use rosenpass_util::{
|
||||
build::Build,
|
||||
mem::{DiscardResultExt, SwapWithDefaultExt},
|
||||
result::ensure_or,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Keypair {
|
||||
pub sk: SSk,
|
||||
pub pk: SPk,
|
||||
}
|
||||
|
||||
// TODO: We need a named tuple derive
|
||||
impl Keypair {
|
||||
pub fn new(sk: SSk, pk: SPk) -> Self {
|
||||
Self { sk, pk }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new(SSk::zero(), SPk::zero())
|
||||
}
|
||||
|
||||
pub fn random() -> Self {
|
||||
Self::new(SSk::random(), SPk::random())
|
||||
}
|
||||
|
||||
pub fn from_parts(parts: (SSk, SPk)) -> Self {
|
||||
Self::new(parts.0, parts.1)
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (SSk, SPk) {
|
||||
(self.sk, self.pk)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("PSK already set in BuildCryptoServer")]
|
||||
pub struct PskAlreadySet;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Keypair already set in BuildCryptoServer")]
|
||||
pub struct KeypairAlreadySet;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Can not construct CryptoServer: Missing keypair")]
|
||||
pub struct MissingKeypair;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BuildCryptoServer {
|
||||
pub keypair: Option<Keypair>,
|
||||
pub peers: Vec<PeerParams>,
|
||||
}
|
||||
|
||||
impl Build<CryptoServer> for BuildCryptoServer {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn build(self) -> Result<CryptoServer, Self::Error> {
|
||||
let Some(Keypair { sk, pk }) = self.keypair else {
|
||||
return Err(MissingKeypair)?;
|
||||
};
|
||||
|
||||
let mut srv = CryptoServer::new(sk, pk);
|
||||
|
||||
for (idx, PeerParams { psk, pk }) in self.peers.into_iter().enumerate() {
|
||||
let PeerPtr(idx2) = srv.add_peer(psk, pk)?;
|
||||
assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.")
|
||||
}
|
||||
|
||||
Ok(srv)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PeerParams {
|
||||
pub psk: Option<SymKey>,
|
||||
pub pk: SPk,
|
||||
}
|
||||
|
||||
impl BuildCryptoServer {
|
||||
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
|
||||
Self { keypair, peers }
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None, Vec::new())
|
||||
}
|
||||
|
||||
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
|
||||
Self {
|
||||
keypair: parts.0,
|
||||
peers: parts.1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||
(self.keypair.take(), self.peers.swap_with_default())
|
||||
}
|
||||
|
||||
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||
self.take_parts()
|
||||
}
|
||||
|
||||
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
|
||||
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
|
||||
self.keypair.insert(keypair).discard_result();
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self {
|
||||
// TODO: Check here already whether peer was already added
|
||||
self.peers.push(PeerParams { psk, pk });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr {
|
||||
let id = PeerPtr(self.peers.len());
|
||||
self.with_added_peer(psk, pk);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn emancipate(&mut self) -> Self {
|
||||
Self::from_parts(self.take_parts())
|
||||
}
|
||||
}
|
||||
6
rosenpass/src/protocol/mod.rs
Normal file
6
rosenpass/src/protocol/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod build_crypto_server;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod protocol;
|
||||
|
||||
pub use build_crypto_server::*;
|
||||
pub use protocol::*;
|
||||
File diff suppressed because it is too large
Load Diff
332
rosenpass/tests/api-integration-tests-api-setup.rs
Normal file
332
rosenpass/tests/api-integration-tests-api-setup.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
io::{BufRead, BufReader, Write},
|
||||
os::unix::net::UnixStream,
|
||||
process::Stdio,
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use command_fds::{CommandFdExt, FdMapping};
|
||||
use hex_literal::hex;
|
||||
use rosenpass::api::{
|
||||
self, add_listen_socket_response_status, add_psk_broker_response_status,
|
||||
supply_keypair_response_status,
|
||||
};
|
||||
use rosenpass_util::{
|
||||
b64::B64Display,
|
||||
file::LoadValueB64,
|
||||
io::IoErrorKind,
|
||||
length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder},
|
||||
mem::{DiscardResultExt, MoveExt},
|
||||
mio::WriteWithFileDescriptors,
|
||||
zerocopy::ZerocopySliceExt,
|
||||
};
|
||||
use rustix::fd::{AsFd, AsRawFd};
|
||||
use tempfile::TempDir;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
use rosenpass::protocol::SymKey;
|
||||
|
||||
struct KillChild(std::process::Child);
|
||||
|
||||
impl Drop for KillChild {
|
||||
fn drop(&mut self) {
|
||||
self.0.kill().discard_result();
|
||||
self.0.wait().discard_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_integration_api_setup() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
||||
|
||||
macro_rules! tempfile {
|
||||
($($lst:expr),+) => {{
|
||||
let mut buf = dir.path().to_path_buf();
|
||||
$(buf.push($lst);)*
|
||||
buf
|
||||
}}
|
||||
}
|
||||
|
||||
let peer_a_endpoint = "[::1]:0";
|
||||
let peer_a_listen = std::net::UdpSocket::bind(peer_a_endpoint)?;
|
||||
let peer_a_endpoint = format!("{}", peer_a_listen.local_addr()?);
|
||||
let peer_a_keypair = config::Keypair::new(tempfile!("a.pk"), tempfile!("a.sk"));
|
||||
|
||||
let peer_b_osk = tempfile!("b.osk");
|
||||
let peer_b_wg_device = "mock_device";
|
||||
let peer_b_wg_peer_id = hex!(
|
||||
"
|
||||
93 0f ee 77 0c 6b 54 7e 13 5f 13 92 21 97 26 53
|
||||
7d 77 4a 6a 0f 6c eb 1a dd 6e 5b c4 1b 92 cd 99
|
||||
"
|
||||
);
|
||||
|
||||
use rosenpass::config;
|
||||
let peer_a = config::Rosenpass {
|
||||
config_file_path: tempfile!("a.config"),
|
||||
keypair: None,
|
||||
listen: vec![], // TODO: This could collide by accident
|
||||
verbosity: config::Verbosity::Verbose,
|
||||
api: api::config::ApiConfig {
|
||||
listen_path: vec![tempfile!("a.sock")],
|
||||
listen_fd: vec![],
|
||||
stream_fd: vec![],
|
||||
},
|
||||
peers: vec![config::RosenpassPeer {
|
||||
public_key: tempfile!("b.pk"),
|
||||
key_out: None,
|
||||
endpoint: None,
|
||||
pre_shared_key: None,
|
||||
wg: Some(config::WireGuard {
|
||||
device: peer_b_wg_device.to_string(),
|
||||
peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()),
|
||||
extra_params: vec![],
|
||||
}),
|
||||
}],
|
||||
};
|
||||
|
||||
let peer_b_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk"));
|
||||
let peer_b = config::Rosenpass {
|
||||
config_file_path: tempfile!("b.config"),
|
||||
keypair: Some(peer_b_keypair.clone()),
|
||||
listen: vec![],
|
||||
verbosity: config::Verbosity::Verbose,
|
||||
api: api::config::ApiConfig {
|
||||
listen_path: vec![tempfile!("b.sock")],
|
||||
listen_fd: vec![],
|
||||
stream_fd: vec![],
|
||||
},
|
||||
peers: vec![config::RosenpassPeer {
|
||||
public_key: tempfile!("a.pk"),
|
||||
key_out: Some(peer_b_osk.clone()),
|
||||
endpoint: Some(peer_a_endpoint.to_owned()),
|
||||
pre_shared_key: None,
|
||||
wg: None,
|
||||
}],
|
||||
};
|
||||
|
||||
// Generate the keys
|
||||
rosenpass::cli::testing::generate_and_save_keypair(
|
||||
peer_a_keypair.secret_key.clone(),
|
||||
peer_a_keypair.public_key.clone(),
|
||||
)?;
|
||||
rosenpass::cli::testing::generate_and_save_keypair(
|
||||
peer_b_keypair.secret_key.clone(),
|
||||
peer_b_keypair.public_key.clone(),
|
||||
)?;
|
||||
|
||||
// Write the configuration files
|
||||
peer_a.commit()?;
|
||||
peer_b.commit()?;
|
||||
|
||||
let (deliberate_fail_api_client, deliberate_fail_api_server) =
|
||||
std::os::unix::net::UnixStream::pair()?;
|
||||
let deliberate_fail_child_fd = 3;
|
||||
|
||||
// Start peer a
|
||||
let _proc_a = KillChild(
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||
.args(["--api-stream-fd", &deliberate_fail_child_fd.to_string()])
|
||||
.fd_mappings(vec![FdMapping {
|
||||
parent_fd: deliberate_fail_api_server.move_here().as_raw_fd(),
|
||||
child_fd: 3,
|
||||
}])?
|
||||
.args([
|
||||
"exchange-config",
|
||||
peer_a.config_file_path.to_str().context("")?,
|
||||
])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.spawn()?,
|
||||
);
|
||||
|
||||
// Start peer b
|
||||
let mut proc_b = KillChild(
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||
.args([
|
||||
"exchange-config",
|
||||
peer_b.config_file_path.to_str().context("")?,
|
||||
])
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?,
|
||||
);
|
||||
|
||||
// Acquire stdout
|
||||
let mut out_b = BufReader::new(proc_b.0.stdout.take().context("")?).lines();
|
||||
|
||||
// Now connect to the peers
|
||||
let api_path = peer_a.api.listen_path[0].as_path();
|
||||
|
||||
// Wait for the socket to be created
|
||||
let attempt = 0;
|
||||
while !api_path.exists() {
|
||||
sleep(Duration::from_millis(200));
|
||||
assert!(
|
||||
attempt < 50,
|
||||
"Api failed to be created even after 50 seconds"
|
||||
);
|
||||
}
|
||||
|
||||
let api = UnixStream::connect(api_path)?;
|
||||
let (psk_broker_sock, psk_broker_server_sock) = UnixStream::pair()?;
|
||||
|
||||
// Send AddListenSocket request
|
||||
{
|
||||
let fd = peer_a_listen.as_fd();
|
||||
|
||||
let mut fds = vec![&fd].into();
|
||||
let mut api = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(&api, &mut fds);
|
||||
LengthPrefixEncoder::from_message(api::AddListenSocketRequest::new().as_bytes())
|
||||
.write_all_to_stdio(&mut api)?;
|
||||
assert!(fds.is_empty(), "Failed to write all file descriptors");
|
||||
std::mem::forget(peer_a_listen);
|
||||
}
|
||||
|
||||
// Read response
|
||||
{
|
||||
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||
let res = decoder.read_all_from_stdio(&api)?;
|
||||
let res = res.zk_parse::<api::AddListenSocketResponse>()?;
|
||||
assert_eq!(
|
||||
*res,
|
||||
api::AddListenSocketResponse::new(add_listen_socket_response_status::OK)
|
||||
);
|
||||
}
|
||||
|
||||
// Deliberately break API connection given via FD; this checks that the
|
||||
// API connections are closed when invalid data is received and it also
|
||||
// implicitly checks that other connections are unaffected
|
||||
{
|
||||
use std::io::ErrorKind as K;
|
||||
let client = deliberate_fail_api_client;
|
||||
let err = loop {
|
||||
if let Err(e) = client.borrow().write(&[0xffu8; 16]) {
|
||||
break e;
|
||||
}
|
||||
};
|
||||
// NotConnected happens on Mac
|
||||
assert!(matches!(
|
||||
err.io_error_kind(),
|
||||
K::ConnectionReset | K::BrokenPipe | K::NotConnected
|
||||
));
|
||||
}
|
||||
|
||||
// Send SupplyKeypairRequest
|
||||
{
|
||||
use rustix::fs::{open, Mode, OFlags};
|
||||
let sk = open(peer_a_keypair.secret_key, OFlags::RDONLY, Mode::empty())?;
|
||||
let pk = open(peer_a_keypair.public_key, OFlags::RDONLY, Mode::empty())?;
|
||||
|
||||
let mut fds = vec![&sk, &pk].into();
|
||||
let mut api = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(&api, &mut fds);
|
||||
LengthPrefixEncoder::from_message(api::SupplyKeypairRequest::new().as_bytes())
|
||||
.write_all_to_stdio(&mut api)?;
|
||||
assert!(fds.is_empty(), "Failed to write all file descriptors");
|
||||
}
|
||||
|
||||
// Read response
|
||||
{
|
||||
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||
let res = decoder.read_all_from_stdio(&api)?;
|
||||
let res = res.zk_parse::<api::SupplyKeypairResponse>()?;
|
||||
assert_eq!(
|
||||
*res,
|
||||
api::SupplyKeypairResponse::new(supply_keypair_response_status::OK)
|
||||
);
|
||||
}
|
||||
|
||||
// Send AddPskBroker request
|
||||
{
|
||||
let mut fds = vec![psk_broker_server_sock.as_fd()].into();
|
||||
let mut api = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(&api, &mut fds);
|
||||
LengthPrefixEncoder::from_message(api::AddPskBrokerRequest::new().as_bytes())
|
||||
.write_all_to_stdio(&mut api)?;
|
||||
assert!(fds.is_empty(), "Failed to write all file descriptors");
|
||||
}
|
||||
|
||||
// Read response
|
||||
{
|
||||
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||
let res = decoder.read_all_from_stdio(&api)?;
|
||||
let res = res.zk_parse::<api::AddPskBrokerResponse>()?;
|
||||
assert_eq!(
|
||||
*res,
|
||||
api::AddPskBrokerResponse::new(add_psk_broker_response_status::OK)
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for the keys to successfully exchange a key
|
||||
let mut attempt = 0;
|
||||
loop {
|
||||
// Read OSK generated by A
|
||||
let osk_a = {
|
||||
use rosenpass_wireguard_broker::api::msgs as M;
|
||||
type SetPskReqPkg = M::Envelope<M::SetPskRequest>;
|
||||
type SetPskResPkg = M::Envelope<M::SetPskResponse>;
|
||||
|
||||
// Receive request
|
||||
let mut decoder = LengthPrefixDecoder::new([0u8; M::REQUEST_MSG_BUFFER_SIZE]);
|
||||
let req = decoder.read_all_from_stdio(&psk_broker_sock)?;
|
||||
|
||||
let req = req.zk_parse::<SetPskReqPkg>()?;
|
||||
assert_eq!(req.msg_type, M::MsgType::SetPsk as u8);
|
||||
assert_eq!(req.payload.peer_id, peer_b_wg_peer_id);
|
||||
assert_eq!(req.payload.iface()?, peer_b_wg_device);
|
||||
|
||||
// Send response
|
||||
let res = SetPskResPkg {
|
||||
msg_type: M::MsgType::SetPsk as u8,
|
||||
reserved: [0u8; 3],
|
||||
payload: M::SetPskResponse {
|
||||
return_code: M::SetPskResponseReturnCode::Success as u8,
|
||||
},
|
||||
};
|
||||
LengthPrefixEncoder::from_message(res.as_bytes())
|
||||
.write_all_to_stdio(&psk_broker_sock)?;
|
||||
|
||||
SymKey::from_slice(&req.payload.psk)
|
||||
};
|
||||
|
||||
// Read OSK generated by B
|
||||
let osk_b = {
|
||||
let line = out_b.next().context("")??;
|
||||
let words = line.split(' ').collect::<Vec<_>>();
|
||||
|
||||
// FIXED FIXED PEER-ID FIXED FILENAME STATUS
|
||||
// output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged
|
||||
let peer_id = words
|
||||
.get(2)
|
||||
.with_context(|| format!("Bad rosenpass output: `{line}`"))?;
|
||||
assert_eq!(
|
||||
line,
|
||||
format!(
|
||||
"output-key peer {peer_id} key-file \"{}\" exchanged",
|
||||
peer_b_osk.to_str().context("")?
|
||||
)
|
||||
);
|
||||
|
||||
SymKey::load_b64::<64, _>(peer_b_osk.clone())?
|
||||
};
|
||||
|
||||
// TODO: This may be flaky. Both rosenpass instances are not guaranteed to produce
|
||||
// the same number of output events; they merely guarantee eventual consistency of OSK.
|
||||
// Correctly, we should use tokio to read any number of generated OSKs and indicate
|
||||
// success on consensus
|
||||
match osk_a.secret() == osk_b.secret() {
|
||||
true => break,
|
||||
false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"),
|
||||
false => {},
|
||||
};
|
||||
|
||||
attempt += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
194
rosenpass/tests/api-integration-tests.rs
Normal file
194
rosenpass/tests/api-integration-tests.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use std::{
|
||||
io::{BufRead, BufReader},
|
||||
net::ToSocketAddrs,
|
||||
os::unix::net::UnixStream,
|
||||
process::Stdio,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use rosenpass::api;
|
||||
use rosenpass_to::{ops::copy_slice_least_src, To};
|
||||
use rosenpass_util::{
|
||||
file::LoadValueB64,
|
||||
length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder},
|
||||
};
|
||||
use rosenpass_util::{mem::DiscardResultExt, zerocopy::ZerocopySliceExt};
|
||||
use tempfile::TempDir;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
use rosenpass::protocol::SymKey;
|
||||
|
||||
struct KillChild(std::process::Child);
|
||||
|
||||
impl Drop for KillChild {
|
||||
fn drop(&mut self) {
|
||||
self.0.kill().discard_result();
|
||||
self.0.wait().discard_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_integration_test() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
||||
|
||||
macro_rules! tempfile {
|
||||
($($lst:expr),+) => {{
|
||||
let mut buf = dir.path().to_path_buf();
|
||||
$(buf.push($lst);)*
|
||||
buf
|
||||
}}
|
||||
}
|
||||
|
||||
let peer_a_endpoint = "[::1]:61423";
|
||||
let peer_a_osk = tempfile!("a.osk");
|
||||
let peer_b_osk = tempfile!("b.osk");
|
||||
|
||||
use rosenpass::config;
|
||||
|
||||
let peer_a_keypair = config::Keypair::new(tempfile!("a.pk"), tempfile!("a.sk"));
|
||||
let peer_a = config::Rosenpass {
|
||||
config_file_path: tempfile!("a.config"),
|
||||
keypair: Some(peer_a_keypair.clone()),
|
||||
listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident
|
||||
verbosity: config::Verbosity::Verbose,
|
||||
api: api::config::ApiConfig {
|
||||
listen_path: vec![tempfile!("a.sock")],
|
||||
listen_fd: vec![],
|
||||
stream_fd: vec![],
|
||||
},
|
||||
peers: vec![config::RosenpassPeer {
|
||||
public_key: tempfile!("b.pk"),
|
||||
key_out: Some(peer_a_osk.clone()),
|
||||
endpoint: None,
|
||||
pre_shared_key: None,
|
||||
wg: None,
|
||||
}],
|
||||
};
|
||||
|
||||
let peer_b_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk"));
|
||||
let peer_b = config::Rosenpass {
|
||||
config_file_path: tempfile!("b.config"),
|
||||
keypair: Some(peer_b_keypair.clone()),
|
||||
listen: vec![],
|
||||
verbosity: config::Verbosity::Verbose,
|
||||
api: api::config::ApiConfig {
|
||||
listen_path: vec![tempfile!("b.sock")],
|
||||
listen_fd: vec![],
|
||||
stream_fd: vec![],
|
||||
},
|
||||
peers: vec![config::RosenpassPeer {
|
||||
public_key: tempfile!("a.pk"),
|
||||
key_out: Some(peer_b_osk.clone()),
|
||||
endpoint: Some(peer_a_endpoint.to_owned()),
|
||||
pre_shared_key: None,
|
||||
wg: None,
|
||||
}],
|
||||
};
|
||||
|
||||
// Generate the keys
|
||||
rosenpass::cli::testing::generate_and_save_keypair(
|
||||
peer_a_keypair.secret_key.clone(),
|
||||
peer_a_keypair.public_key.clone(),
|
||||
)?;
|
||||
rosenpass::cli::testing::generate_and_save_keypair(
|
||||
peer_b_keypair.secret_key.clone(),
|
||||
peer_b_keypair.public_key.clone(),
|
||||
)?;
|
||||
|
||||
// Write the configuration files
|
||||
peer_a.commit()?;
|
||||
peer_b.commit()?;
|
||||
|
||||
// Start peer a
|
||||
let mut proc_a = KillChild(
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||
.args([
|
||||
"exchange-config",
|
||||
peer_a.config_file_path.to_str().context("")?,
|
||||
])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?,
|
||||
);
|
||||
|
||||
// Start peer b
|
||||
let mut proc_b = KillChild(
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass"))
|
||||
.args([
|
||||
"exchange-config",
|
||||
peer_b.config_file_path.to_str().context("")?,
|
||||
])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?,
|
||||
);
|
||||
|
||||
// Acquire stdout
|
||||
let mut out_a = BufReader::new(proc_a.0.stdout.take().context("")?).lines();
|
||||
let mut out_b = BufReader::new(proc_b.0.stdout.take().context("")?).lines();
|
||||
|
||||
// Wait for the keys to successfully exchange a key
|
||||
let mut attempt = 0;
|
||||
loop {
|
||||
let line_a = out_a.next().context("")??;
|
||||
let line_b = out_b.next().context("")??;
|
||||
|
||||
let words_a = line_a.split(' ').collect::<Vec<_>>();
|
||||
let words_b = line_b.split(' ').collect::<Vec<_>>();
|
||||
|
||||
// FIXED FIXED PEER-ID FIXED FILENAME STATUS
|
||||
// output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged
|
||||
let peer_a_id = words_b
|
||||
.get(2)
|
||||
.with_context(|| format!("Bad rosenpass output: `{line_b}`"))?;
|
||||
let peer_b_id = words_a
|
||||
.get(2)
|
||||
.with_context(|| format!("Bad rosenpass output: `{line_a}`"))?;
|
||||
assert_eq!(
|
||||
line_a,
|
||||
format!(
|
||||
"output-key peer {peer_b_id} key-file \"{}\" exchanged",
|
||||
peer_a_osk.to_str().context("")?
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
line_b,
|
||||
format!(
|
||||
"output-key peer {peer_a_id} key-file \"{}\" exchanged",
|
||||
peer_b_osk.to_str().context("")?
|
||||
)
|
||||
);
|
||||
|
||||
// Read OSKs
|
||||
let osk_a = SymKey::load_b64::<64, _>(peer_a_osk.clone())?;
|
||||
let osk_b = SymKey::load_b64::<64, _>(peer_b_osk.clone())?;
|
||||
match osk_a.secret() == osk_b.secret() {
|
||||
true => break,
|
||||
false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"),
|
||||
false => {},
|
||||
};
|
||||
|
||||
attempt += 1;
|
||||
}
|
||||
|
||||
// Now connect to the peers
|
||||
let api_a = UnixStream::connect(&peer_a.api.listen_path[0])?;
|
||||
let api_b = UnixStream::connect(&peer_b.api.listen_path[0])?;
|
||||
|
||||
for conn in ([api_a, api_b]).iter() {
|
||||
let mut echo = [0u8; 256];
|
||||
copy_slice_least_src("Hello World".as_bytes()).to(&mut echo);
|
||||
|
||||
let req = api::PingRequest::new(echo);
|
||||
LengthPrefixEncoder::from_message(req.as_bytes()).write_all_to_stdio(conn)?;
|
||||
|
||||
let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]);
|
||||
let res = decoder.read_all_from_stdio(conn)?;
|
||||
let res = res.zk_parse::<api::PingResponse>()?;
|
||||
assert_eq!(*res, api::PingResponse::new(echo));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,10 +1,33 @@
|
||||
use std::{fs, net::UdpSocket, path::PathBuf, process::Stdio, time::Duration};
|
||||
use std::{
|
||||
fs,
|
||||
net::UdpSocket,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use rosenpass::{app_server::AppServerTestBuilder, cli::CliArgs};
|
||||
use rosenpass_secret_memory::{Public, Secret};
|
||||
use rosenpass_wireguard_broker::{WireguardBrokerMio, WG_KEY_LEN, WG_PEER_LEN};
|
||||
use serial_test::serial;
|
||||
use std::io::Write;
|
||||
|
||||
const BIN: &str = "rosenpass";
|
||||
|
||||
fn setup_tests() {
|
||||
use rosenpass_secret_memory as SM;
|
||||
#[cfg(feature = "experiment_memfd_secret")]
|
||||
SM::secret_policy_try_use_memfd_secrets();
|
||||
#[cfg(not(feature = "experiment_memfd_secret"))]
|
||||
SM::secret_policy_use_only_malloc_secrets();
|
||||
}
|
||||
|
||||
// check that we can generate keys
|
||||
#[test]
|
||||
fn generate_keys() {
|
||||
setup_tests();
|
||||
|
||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen");
|
||||
fs::create_dir_all(&tmpdir).unwrap();
|
||||
|
||||
@@ -28,26 +51,29 @@ fn generate_keys() {
|
||||
fs::remove_dir_all(&tmpdir).unwrap();
|
||||
}
|
||||
|
||||
fn find_udp_socket() -> u16 {
|
||||
for port in 1025..=u16::MAX {
|
||||
if UdpSocket::bind(("127.0.0.1", port)).is_ok() {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
panic!("no free UDP port found");
|
||||
fn find_udp_socket() -> Option<u16> {
|
||||
(1025..=u16::MAX).find(|&port| UdpSocket::bind(("::1", port)).is_ok())
|
||||
}
|
||||
|
||||
// check that we can exchange keys
|
||||
#[test]
|
||||
fn check_exchange() {
|
||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
|
||||
fs::create_dir_all(&tmpdir).unwrap();
|
||||
fn setup_logging() {
|
||||
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
|
||||
log_builder.filter_level(log::LevelFilter::Debug);
|
||||
log_builder.format_timestamp_nanos();
|
||||
log_builder.format(|buf, record| {
|
||||
let ts_format = buf.timestamp_nanos().to_string();
|
||||
writeln!(
|
||||
buf,
|
||||
"\x1b[1m{:?}\x1b[0m {}: {}",
|
||||
std::thread::current().id(),
|
||||
&ts_format[14..],
|
||||
record.args()
|
||||
)
|
||||
});
|
||||
|
||||
let secret_key_paths = [tmpdir.join("secret-key-0"), tmpdir.join("secret-key-1")];
|
||||
let public_key_paths = [tmpdir.join("public-key-0"), tmpdir.join("public-key-1")];
|
||||
let shared_key_paths = [tmpdir.join("shared-key-0"), tmpdir.join("shared-key-1")];
|
||||
let _ = log_builder.try_init();
|
||||
}
|
||||
|
||||
// generate key pairs
|
||||
fn generate_key_pairs(secret_key_paths: &[PathBuf], public_key_paths: &[PathBuf]) {
|
||||
for (secret_key_path, pub_key_path) in secret_key_paths.iter().zip(public_key_paths.iter()) {
|
||||
let output = test_bin::get_test_bin(BIN)
|
||||
.args(["gen-keys", "--secret-key"])
|
||||
@@ -61,11 +87,81 @@ fn check_exchange() {
|
||||
assert!(secret_key_path.is_file());
|
||||
assert!(pub_key_path.is_file());
|
||||
}
|
||||
}
|
||||
|
||||
fn run_server_client_exchange(
|
||||
(server_cmd, server_test_builder): (&std::process::Command, AppServerTestBuilder),
|
||||
(client_cmd, client_test_builder): (&std::process::Command, AppServerTestBuilder),
|
||||
) {
|
||||
let (server_terminate, server_terminate_rx) = std::sync::mpsc::channel();
|
||||
let (client_terminate, client_terminate_rx) = std::sync::mpsc::channel();
|
||||
|
||||
let cli = CliArgs::try_parse_from(
|
||||
[server_cmd.get_program()]
|
||||
.into_iter()
|
||||
.chain(server_cmd.get_args()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let test_helpers = server_test_builder
|
||||
.termination_handler(Some(server_terminate_rx))
|
||||
.build()
|
||||
.unwrap();
|
||||
cli.run(None, Some(test_helpers)).unwrap();
|
||||
});
|
||||
|
||||
let cli = CliArgs::try_parse_from(
|
||||
[client_cmd.get_program()]
|
||||
.into_iter()
|
||||
.chain(client_cmd.get_args()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let test_helpers = client_test_builder
|
||||
.termination_handler(Some(client_terminate_rx))
|
||||
.build()
|
||||
.unwrap();
|
||||
cli.run(None, Some(test_helpers)).unwrap();
|
||||
});
|
||||
|
||||
// give them some time to do the key exchange under load
|
||||
std::thread::sleep(Duration::from_secs(10));
|
||||
|
||||
// time's up, kill the childs
|
||||
server_terminate.send(()).unwrap();
|
||||
client_terminate.send(()).unwrap();
|
||||
}
|
||||
|
||||
// check that we can exchange keys
|
||||
#[test]
|
||||
#[serial]
|
||||
fn check_exchange_under_normal() {
|
||||
setup_tests();
|
||||
setup_logging();
|
||||
|
||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
|
||||
fs::create_dir_all(&tmpdir).unwrap();
|
||||
|
||||
let secret_key_paths = [tmpdir.join("secret-key-0"), tmpdir.join("secret-key-1")];
|
||||
let public_key_paths = [tmpdir.join("public-key-0"), tmpdir.join("public-key-1")];
|
||||
let shared_key_paths = [tmpdir.join("shared-key-0"), tmpdir.join("shared-key-1")];
|
||||
|
||||
// generate key pairs
|
||||
generate_key_pairs(&secret_key_paths, &public_key_paths);
|
||||
|
||||
// start first process, the server
|
||||
let port = find_udp_socket();
|
||||
let listen_addr = format!("localhost:{port}");
|
||||
let mut server = test_bin::get_test_bin(BIN)
|
||||
let port = loop {
|
||||
if let Some(port) = find_udp_socket() {
|
||||
break port;
|
||||
}
|
||||
};
|
||||
|
||||
let listen_addr = format!("::1:{port}");
|
||||
let mut server_cmd = std::process::Command::new(BIN);
|
||||
|
||||
server_cmd
|
||||
.args(["exchange", "secret-key"])
|
||||
.arg(&secret_key_paths[0])
|
||||
.arg("public-key")
|
||||
@@ -73,16 +169,12 @@ fn check_exchange() {
|
||||
.args(["listen", &listen_addr, "verbose", "peer", "public-key"])
|
||||
.arg(&public_key_paths[1])
|
||||
.arg("outfile")
|
||||
.arg(&shared_key_paths[0])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("Failed to start {BIN}");
|
||||
.arg(&shared_key_paths[0]);
|
||||
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
let server_test_builder = AppServerTestBuilder::default();
|
||||
|
||||
// start second process, the client
|
||||
let mut client = test_bin::get_test_bin(BIN)
|
||||
let mut client_cmd = std::process::Command::new(BIN);
|
||||
client_cmd
|
||||
.args(["exchange", "secret-key"])
|
||||
.arg(&secret_key_paths[1])
|
||||
.arg("public-key")
|
||||
@@ -91,18 +183,14 @@ fn check_exchange() {
|
||||
.arg(&public_key_paths[0])
|
||||
.args(["endpoint", &listen_addr])
|
||||
.arg("outfile")
|
||||
.arg(&shared_key_paths[1])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("Failed to start {BIN}");
|
||||
.arg(&shared_key_paths[1]);
|
||||
|
||||
// give them some time to do the key exchange
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
let client_test_builder = AppServerTestBuilder::default();
|
||||
|
||||
// time's up, kill the childs
|
||||
server.kill().unwrap();
|
||||
client.kill().unwrap();
|
||||
run_server_client_exchange(
|
||||
(&server_cmd, server_test_builder),
|
||||
(&client_cmd, client_test_builder),
|
||||
);
|
||||
|
||||
// read the shared keys they created
|
||||
let shared_keys: Vec<_> = shared_key_paths
|
||||
@@ -117,3 +205,141 @@ fn check_exchange() {
|
||||
// cleanup
|
||||
fs::remove_dir_all(&tmpdir).unwrap();
|
||||
}
|
||||
|
||||
// check that we can trigger a DoS condition and we can exchange keys under DoS
|
||||
// This test creates a responder (server) with the feature flag "integration_test_always_under_load" to always be under load condition for the test.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn check_exchange_under_dos() {
|
||||
setup_tests();
|
||||
setup_logging();
|
||||
|
||||
//Generate binary with responder with feature integration_test
|
||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange-dos");
|
||||
fs::create_dir_all(&tmpdir).unwrap();
|
||||
|
||||
let secret_key_paths = [tmpdir.join("secret-key-0"), tmpdir.join("secret-key-1")];
|
||||
let public_key_paths = [tmpdir.join("public-key-0"), tmpdir.join("public-key-1")];
|
||||
let shared_key_paths = [tmpdir.join("shared-key-0"), tmpdir.join("shared-key-1")];
|
||||
|
||||
// generate key pairs
|
||||
generate_key_pairs(&secret_key_paths, &public_key_paths);
|
||||
|
||||
// start first process, the server
|
||||
let port = loop {
|
||||
if let Some(port) = find_udp_socket() {
|
||||
break port;
|
||||
}
|
||||
};
|
||||
|
||||
let listen_addr = format!("::1:{port}");
|
||||
|
||||
let mut server_cmd = std::process::Command::new(BIN);
|
||||
|
||||
server_cmd
|
||||
.args(["exchange", "secret-key"])
|
||||
.arg(&secret_key_paths[0])
|
||||
.arg("public-key")
|
||||
.arg(&public_key_paths[0])
|
||||
.args(["listen", &listen_addr, "verbose", "peer", "public-key"])
|
||||
.arg(&public_key_paths[1])
|
||||
.arg("outfile")
|
||||
.arg(&shared_key_paths[0]);
|
||||
|
||||
let server_test_builder = AppServerTestBuilder::default().enable_dos_permanently(true);
|
||||
|
||||
let mut client_cmd = std::process::Command::new(BIN);
|
||||
client_cmd
|
||||
.args(["exchange", "secret-key"])
|
||||
.arg(&secret_key_paths[1])
|
||||
.arg("public-key")
|
||||
.arg(&public_key_paths[1])
|
||||
.args(["verbose", "peer", "public-key"])
|
||||
.arg(&public_key_paths[0])
|
||||
.args(["endpoint", &listen_addr])
|
||||
.arg("outfile")
|
||||
.arg(&shared_key_paths[1]);
|
||||
|
||||
let client_test_builder = AppServerTestBuilder::default();
|
||||
|
||||
run_server_client_exchange(
|
||||
(&server_cmd, server_test_builder),
|
||||
(&client_cmd, client_test_builder),
|
||||
);
|
||||
|
||||
// read the shared keys they created
|
||||
let shared_keys: Vec<_> = shared_key_paths
|
||||
.iter()
|
||||
.map(|p| fs::read_to_string(p).unwrap())
|
||||
.collect();
|
||||
|
||||
// check that they created two equal keys
|
||||
assert_eq!(shared_keys.len(), 2);
|
||||
assert_eq!(shared_keys[0], shared_keys[1]);
|
||||
|
||||
// cleanup
|
||||
fs::remove_dir_all(&tmpdir).unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Default)]
|
||||
struct MockBrokerInner {
|
||||
psk: Option<Secret<WG_KEY_LEN>>,
|
||||
peer_id: Option<Public<WG_PEER_LEN>>,
|
||||
interface: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Default)]
|
||||
struct MockBroker {
|
||||
inner: Arc<Mutex<MockBrokerInner>>,
|
||||
mio_token: Option<mio::Token>,
|
||||
}
|
||||
|
||||
impl WireguardBrokerMio for MockBroker {
|
||||
type MioError = anyhow::Error;
|
||||
|
||||
fn register(
|
||||
&mut self,
|
||||
_registry: &mio::Registry,
|
||||
token: mio::Token,
|
||||
) -> Result<(), Self::MioError> {
|
||||
self.mio_token = Some(token);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_poll(&mut self) -> Result<(), Self::MioError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister(&mut self, _registry: &mio::Registry) -> Result<(), Self::MioError> {
|
||||
self.mio_token = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mio_token(&self) -> Option<mio::Token> {
|
||||
self.mio_token
|
||||
}
|
||||
}
|
||||
|
||||
impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn set_psk(
|
||||
&mut self,
|
||||
config: rosenpass_wireguard_broker::SerializedBrokerConfig<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
loop {
|
||||
let mut lock = self.inner.try_lock();
|
||||
|
||||
if let Ok(ref mut mutex) = lock {
|
||||
**mutex = MockBrokerInner {
|
||||
psk: Some(config.psk.clone()),
|
||||
peer_id: Some(*config.peer_id),
|
||||
interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()),
|
||||
};
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
391
rp
391
rp
@@ -1,391 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# String formatting subsystem
|
||||
|
||||
formatting_init() {
|
||||
endl=$'\n'
|
||||
}
|
||||
|
||||
enquote() {
|
||||
while (( $# > 1 )); do
|
||||
printf "%q " "${1}"; shift
|
||||
done
|
||||
if (( $# == 1 )); then
|
||||
printf "%q" "${1}"; shift
|
||||
fi
|
||||
}
|
||||
|
||||
multiline() {
|
||||
# shellcheck disable=SC1004
|
||||
echo "${1} " | awk '
|
||||
function pm(a, b, l) {
|
||||
return length(a) > l \
|
||||
&& length(b) > l \
|
||||
&& substr(a, 1, l+1) == substr(b, 1, l+1) \
|
||||
? pm(a, b, l+1) : l;
|
||||
}
|
||||
|
||||
!started && $0 !~ /^[ \t]*$/ {
|
||||
started=1
|
||||
match($0, /^[ \t]*/)
|
||||
prefix=substr($0, 1, RLENGTH)
|
||||
}
|
||||
|
||||
started {
|
||||
print(substr($0, 1 + pm($0, prefix)));
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
dbg() {
|
||||
echo >&2 "$@"
|
||||
}
|
||||
|
||||
|
||||
detect_git_dir() {
|
||||
# https://stackoverflow.com/questions/3618078/pipe-only-stderr-through-a-filter
|
||||
(
|
||||
git -C "${scriptdir}" rev-parse --show-toplevel 3>&1 1>&2 2>&3 3>&- \
|
||||
| sed '
|
||||
/not a git repository/d;
|
||||
s/^/WARNING: /'
|
||||
) 3>&1 1>&2 2>&3 3>&-
|
||||
}
|
||||
|
||||
# Cleanup subsystem (sigterm)
|
||||
|
||||
cleanup_init() {
|
||||
cleanup_actions=()
|
||||
trap cleanup_apply exit
|
||||
}
|
||||
|
||||
cleanup_apply() {
|
||||
local f
|
||||
for f in "${cleanup_actions[@]}"; do
|
||||
eval "${f}"
|
||||
done
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
cleanup_actions+=("$(multiline "${1}")")
|
||||
}
|
||||
|
||||
# Transactional execution subsystem
|
||||
|
||||
frag_init() {
|
||||
explain=0
|
||||
frag_transaction=()
|
||||
frag "
|
||||
#! /bin/bash
|
||||
set -e"
|
||||
}
|
||||
|
||||
frag_apply() {
|
||||
local f
|
||||
for f in "${frag_transaction[@]}"; do
|
||||
if (( explain == 1 )); then
|
||||
dbg "${f}"
|
||||
fi
|
||||
eval "${f}"
|
||||
done
|
||||
}
|
||||
|
||||
frag() {
|
||||
frag_transaction+=("$(multiline "${1}")")
|
||||
}
|
||||
|
||||
frag_append() {
|
||||
local len; len="${#frag_transaction[@]}"
|
||||
frag_transaction=("${frag_transaction[@]:0:len-1}" "${frag_transaction[len-1]}${1}")
|
||||
}
|
||||
|
||||
frag_append_esc() {
|
||||
frag_append " \\${endl}${1}"
|
||||
}
|
||||
|
||||
# Usage documentation subsystem
|
||||
usage_init() {
|
||||
usagestack=("${script}")
|
||||
}
|
||||
|
||||
usage_snap() {
|
||||
echo "${#usagestack}"
|
||||
}
|
||||
|
||||
usage_restore() {
|
||||
local n; n="${1}"
|
||||
dbg REST "${1}"
|
||||
usagestack=("${usagestack[@]:0:n-2}")
|
||||
}
|
||||
|
||||
|
||||
usage() {
|
||||
dbg "Usage: ${usagestack[*]}"
|
||||
}
|
||||
|
||||
fatal() {
|
||||
dbg "FATAL: $*"
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
genkey() {
|
||||
usagestack+=("PRIVATE_KEYS_DIR")
|
||||
local skdir
|
||||
skdir="${1%/}"; shift || fatal "Required positional argument: PRIVATE_KEYS_DIR"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
local arg; arg="$1"; shift
|
||||
case "${arg}" in
|
||||
-h | -help | --help | help) usage; return 0 ;;
|
||||
*) fatal "Unknown option ${arg}";;
|
||||
esac
|
||||
done
|
||||
|
||||
if test -e "${skdir}"; then
|
||||
fatal "PRIVATE_KEYS_DIR \"${skdir}\" already exists"
|
||||
fi
|
||||
|
||||
frag "
|
||||
umask 077
|
||||
mkdir -p $(enquote "${skdir}")
|
||||
wg genkey > $(enquote "${skdir}"/wgsk)
|
||||
$(enquote "${binary}") gen-keys \\
|
||||
-s $(enquote "${skdir}"/pqsk) \\
|
||||
-p $(enquote "${skdir}"/pqpk)"
|
||||
}
|
||||
|
||||
pubkey() {
|
||||
usagestack+=("PRIVATE_KEYS_DIR" "PUBLIC_KEYS_DIR")
|
||||
local skdir pkdir
|
||||
skdir="${1%/}"; shift || fatal "Required positional argument: PRIVATE_KEYS_DIR"
|
||||
pkdir="${1%/}"; shift || fatal "Required positional argument: PUBLIC_KEYS_DIR"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
local arg; arg="$1"; shift
|
||||
case "${arg}" in
|
||||
-h | -help | --help | help) usage; exit 0;;
|
||||
*) fatal "Unknown option ${arg}";;
|
||||
esac
|
||||
done
|
||||
|
||||
if test -e "${pkdir}"; then
|
||||
fatal "PUBLIC_KEYS_DIR \"${pkdir}\" already exists"
|
||||
fi
|
||||
|
||||
frag "
|
||||
mkdir -p $(enquote "${pkdir}")
|
||||
wg pubkey < $(enquote "${skdir}"/wgsk) > $(enquote "${pkdir}/wgpk")
|
||||
cp $(enquote "${skdir}"/pqpk) $(enquote "${pkdir}/pqpk")"
|
||||
}
|
||||
|
||||
exchange() {
|
||||
usagestack+=("PRIVATE_KEYS_DIR" "[dev <device>]" "[listen <ip>:<port>]" "[peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...")
|
||||
local skdir dev lport
|
||||
dev="${project_name}0"
|
||||
skdir="${1%/}"; shift || fatal "Required positional argument: PRIVATE_KEYS_DIR"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
local arg; arg="$1"; shift
|
||||
case "${arg}" in
|
||||
dev) dev="${1}"; shift || fatal "dev option requires parameter";;
|
||||
peer) set -- "peer" "$@"; break;; # Parsed down below
|
||||
listen)
|
||||
local listen; listen="${1}";
|
||||
lip="${listen%:*}";
|
||||
lport="${listen/*:/}";
|
||||
if [[ "$lip" = "$lport" ]]; then
|
||||
lip="[::]"
|
||||
fi
|
||||
shift;;
|
||||
-h | -help | --help | help) usage; return 0;;
|
||||
*) fatal "Unknown option ${arg}";;
|
||||
esac
|
||||
done
|
||||
|
||||
if (( $# == 0 )); then
|
||||
fatal "Needs at least one peer specified"
|
||||
fi
|
||||
|
||||
# os dependent setup
|
||||
case "$OSTYPE" in
|
||||
linux-*) # could be linux-gnu or linux-musl
|
||||
frag "
|
||||
# Create the WireGuard interface
|
||||
ip link add dev $(enquote "${dev}") type wireguard || true"
|
||||
|
||||
cleanup "
|
||||
ip link del dev $(enquote "${dev}") || true"
|
||||
|
||||
frag "
|
||||
ip link set dev $(enquote "${dev}") up"
|
||||
;;
|
||||
|
||||
freebsd*)
|
||||
frag "
|
||||
# load the WireGuard kernel module
|
||||
kldload -n if_wg || fatal 'Cannot load if_wg kernel module'"
|
||||
|
||||
frag "
|
||||
# Create the WireGuard interface
|
||||
ifconfig wg create name $(enquote "${dev}") || true"
|
||||
|
||||
cleanup "
|
||||
ifconfig $(enquote "${dev}") destroy || true"
|
||||
|
||||
frag "
|
||||
ifconfig $(enquote "${dev}") up"
|
||||
;;
|
||||
|
||||
*)
|
||||
fatal "Your system $OSTYPE is not yet supported. We are happy to receive patches to address this :)"
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
frag "
|
||||
# Deploy the classic wireguard private key
|
||||
wg set $(enquote "${dev}") private-key $(enquote "${skdir}/wgsk")"
|
||||
|
||||
|
||||
if test -n "${lport}"; then
|
||||
frag_append "listen-port $(enquote "$(( lport + 1 ))")"
|
||||
fi
|
||||
|
||||
frag "
|
||||
# Launch the post quantum wireguard exchange daemon
|
||||
$(enquote "${binary}") exchange"
|
||||
|
||||
if (( verbose == 1 )); then
|
||||
frag_append "verbose"
|
||||
fi
|
||||
|
||||
frag_append_esc " secret-key $(enquote "${skdir}/pqsk")"
|
||||
frag_append_esc " public-key $(enquote "${skdir}/pqpk")"
|
||||
|
||||
if test -n "${lport}"; then
|
||||
frag_append_esc " listen $(enquote "${lip}:${lport}")"
|
||||
fi
|
||||
|
||||
usagestack+=("peer" "PUBLIC_KEYS_DIR endpoint IP:PORT")
|
||||
|
||||
while (( $# > 0 )); do
|
||||
shift; # Skip "peer" argument
|
||||
|
||||
local peerdir ip port keepalive allowedips
|
||||
peerdir="${1%/}"; shift || fatal "Required peer argument: PUBLIC_KEYS_DIR"
|
||||
|
||||
while (( $# > 0 )); do
|
||||
local arg; arg="$1"; shift
|
||||
case "${arg}" in
|
||||
peer) set -- "peer" "$@"; break;; # Next peer
|
||||
endpoint) ip="${1%:*}"; port="${1##*:}"; shift;;
|
||||
persistent-keepalive) keepalive="${1}"; shift;;
|
||||
allowed-ips) allowedips="${1}"; shift;;
|
||||
-h | -help | --help | help) usage; return 0;;
|
||||
*) fatal "Unknown option ${arg}";;
|
||||
esac
|
||||
done
|
||||
|
||||
# Public key
|
||||
frag_append_esc " peer public-key $(enquote "${peerdir}/pqpk")"
|
||||
|
||||
# PSK
|
||||
local pskfile; pskfile="${peerdir}/psk"
|
||||
if test -f "${pskfile}"; then
|
||||
frag_append_esc " preshared-key $(enquote "${pskfile}")"
|
||||
fi
|
||||
|
||||
|
||||
if test -n "${ip}"; then
|
||||
frag_append_esc " endpoint $(enquote "${ip}:${port}")"
|
||||
fi
|
||||
|
||||
frag_append_esc " wireguard $(enquote "${dev}") $(enquote "$(cat "${peerdir}/wgpk")")"
|
||||
|
||||
if test -n "${ip}"; then
|
||||
frag_append_esc " endpoint $(enquote "${ip}:$(( port + 1 ))")"
|
||||
fi
|
||||
|
||||
if test -n "${keepalive}"; then
|
||||
frag_append_esc " persistent-keepalive $(enquote "${keepalive}")"
|
||||
fi
|
||||
|
||||
if test -n "${allowedips}"; then
|
||||
frag_append_esc " allowed-ips $(enquote "${allowedips}")"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
find_rosenpass_binary() {
|
||||
local binary; binary=""
|
||||
if [[ -n "${gitdir}" ]]; then
|
||||
# If rp is run from the git repo, use the newest build artifact
|
||||
binary=$(
|
||||
find "${gitdir}/result/bin/${project_name}" \
|
||||
"${gitdir}"/target/{release,debug}/"${project_name}" \
|
||||
-printf "%T@ %p\n" 2>/dev/null \
|
||||
| sort -nr \
|
||||
| awk 'NR==1 { print($2) }'
|
||||
)
|
||||
elif [[ -n "${nixdir}" ]]; then
|
||||
# If rp is run from nix, use the nix-installed rosenpass version
|
||||
binary="${nixdir}/bin/${project_name}"
|
||||
fi
|
||||
|
||||
if [[ -z "${binary}" ]]; then
|
||||
binary="${project_name}"
|
||||
fi
|
||||
|
||||
echo "${binary}"
|
||||
}
|
||||
|
||||
main() {
|
||||
formatting_init
|
||||
cleanup_init
|
||||
usage_init
|
||||
frag_init
|
||||
|
||||
project_name="rosenpass"
|
||||
verbose=0
|
||||
scriptdir="$(dirname "${script}")"
|
||||
gitdir="$(detect_git_dir)" || true
|
||||
if [[ -d /nix ]]; then
|
||||
nixdir="$(readlink -f result/bin/rp | grep -Pio '^/nix/store/[^/]+(?=/bin/[^/]+)')" || true
|
||||
fi
|
||||
binary="$(find_rosenpass_binary)"
|
||||
|
||||
# Parse command
|
||||
|
||||
usagestack+=("[explain]" "[verbose]" "genkey|pubkey|exchange" "[ARGS]...")
|
||||
|
||||
local cmd
|
||||
while (( $# > 0 )); do
|
||||
local arg; arg="$1"; shift
|
||||
case "${arg}" in
|
||||
genkey|pubkey|exchange) cmd="${arg}"; break;;
|
||||
explain) explain=1;;
|
||||
verbose) verbose=1;;
|
||||
-h | -help | --help | help) usage; return 0 ;;
|
||||
*) fatal "Unknown command ${arg}";;
|
||||
esac
|
||||
done
|
||||
|
||||
test -n "${cmd}" || fatal "No command supplied"
|
||||
usagestack=("${script}")
|
||||
|
||||
# Execute command
|
||||
|
||||
usagestack+=("${cmd}")
|
||||
"${cmd}" "$@"
|
||||
usagestack=("${script}")
|
||||
|
||||
# Apply transaction
|
||||
|
||||
frag_apply
|
||||
}
|
||||
|
||||
script="$0"
|
||||
main "$@"
|
||||
43
rp/Cargo.toml
Normal file
43
rp/Cargo.toml
Normal file
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "rp"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Build post-quantum-secure VPNs with WireGuard!"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64ct = { workspace = true }
|
||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
rosenpass = { workspace = true }
|
||||
rosenpass-ciphers = { workspace = true }
|
||||
rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
rosenpass-wireguard-broker = { workspace = true }
|
||||
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
ctrlc-async = "3.2"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
genetlink = "0.2"
|
||||
rtnetlink = "0.14"
|
||||
netlink-packet-core = "0.7"
|
||||
netlink-packet-generic = "0.3"
|
||||
netlink-packet-wireguard = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
stacker = { workspace = true }
|
||||
|
||||
[features]
|
||||
experiment_memfd_secret = []
|
||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||
462
rp/src/cli.rs
Normal file
462
rp/src/cli.rs
Normal file
@@ -0,0 +1,462 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{iter::Peekable, net::SocketAddr};
|
||||
|
||||
use crate::exchange::{ExchangeOptions, ExchangePeer};
|
||||
|
||||
pub enum Command {
|
||||
GenKey {
|
||||
private_keys_dir: PathBuf,
|
||||
},
|
||||
PubKey {
|
||||
private_keys_dir: PathBuf,
|
||||
public_keys_dir: PathBuf,
|
||||
},
|
||||
Exchange(ExchangeOptions),
|
||||
Help,
|
||||
}
|
||||
|
||||
enum CommandType {
|
||||
GenKey,
|
||||
PubKey,
|
||||
Exchange,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cli {
|
||||
pub verbose: bool,
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
||||
match command {
|
||||
Some(command) => match command {
|
||||
CommandType::GenKey => Err(format!("{}\nUsage: rp genkey PRIVATE_KEYS_DIR", note)),
|
||||
CommandType::PubKey => Err(format!("{}\nUsage: rp pubkey PRIVATE_KEYS_DIR PUBLIC_KEYS_DIR", note)),
|
||||
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
||||
},
|
||||
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange [ARGS]...", note)),
|
||||
}
|
||||
}
|
||||
|
||||
impl ExchangePeer {
|
||||
pub fn parse(args: &mut &mut Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut peer = ExchangePeer::default();
|
||||
|
||||
if let Some(public_keys_dir) = args.next() {
|
||||
peer.public_keys_dir = PathBuf::from(public_keys_dir);
|
||||
} else {
|
||||
return fatal(
|
||||
"Required positional argument: PUBLIC_KEYS_DIR",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
|
||||
while let Some(x) = args.peek() {
|
||||
let x = x.as_str();
|
||||
|
||||
// break if next peer is being defined
|
||||
if x == "peer" {
|
||||
break;
|
||||
}
|
||||
|
||||
let x = args.next().unwrap();
|
||||
let x = x.as_str();
|
||||
|
||||
match x {
|
||||
"endpoint" => {
|
||||
if let Some(addr) = args.next() {
|
||||
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
||||
peer.endpoint = Some(addr);
|
||||
} else {
|
||||
return fatal(
|
||||
"invalid parameter for endpoint option",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return fatal(
|
||||
"endpoint option requires parameter",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
}
|
||||
"persistent-keepalive" => {
|
||||
if let Some(ka) = args.next() {
|
||||
if let Ok(ka) = ka.parse::<u32>() {
|
||||
peer.persistent_keepalive = Some(ka);
|
||||
} else {
|
||||
return fatal(
|
||||
"invalid parameter for persistent-keepalive option",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return fatal(
|
||||
"persistent-keepalive option requires parameter",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
}
|
||||
"allowed-ips" => {
|
||||
if let Some(ips) = args.next() {
|
||||
peer.allowed_ips = Some(ips);
|
||||
} else {
|
||||
return fatal(
|
||||
"allowed-ips option requires parameter",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return fatal(
|
||||
&format!("Unknown option {}", x),
|
||||
Some(CommandType::Exchange),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(peer)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExchangeOptions {
|
||||
pub fn parse(mut args: &mut Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut options = ExchangeOptions::default();
|
||||
|
||||
if let Some(private_keys_dir) = args.next() {
|
||||
options.private_keys_dir = PathBuf::from(private_keys_dir);
|
||||
} else {
|
||||
return fatal(
|
||||
"Required positional argument: PRIVATE_KEYS_DIR",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
|
||||
while let Some(x) = args.next() {
|
||||
let x = x.as_str();
|
||||
|
||||
match x {
|
||||
"dev" => {
|
||||
if let Some(device) = args.next() {
|
||||
options.dev = Some(device);
|
||||
} else {
|
||||
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
||||
}
|
||||
}
|
||||
"listen" => {
|
||||
if let Some(addr) = args.next() {
|
||||
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
||||
options.listen = Some(addr);
|
||||
} else {
|
||||
return fatal(
|
||||
"invalid parameter for listen option",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return fatal(
|
||||
"listen option requires parameter",
|
||||
Some(CommandType::Exchange),
|
||||
);
|
||||
}
|
||||
}
|
||||
"peer" => {
|
||||
let peer = ExchangePeer::parse(&mut args)?;
|
||||
options.peers.push(peer);
|
||||
}
|
||||
_ => {
|
||||
return fatal(
|
||||
&format!("Unknown option {}", x),
|
||||
Some(CommandType::Exchange),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn parse(mut args: Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut cli = Cli::default();
|
||||
|
||||
let _ = args.next(); // skip executable name
|
||||
|
||||
while let Some(x) = args.next() {
|
||||
let x = x.as_str();
|
||||
|
||||
match x {
|
||||
"verbose" => {
|
||||
cli.verbose = true;
|
||||
}
|
||||
"explain" => {
|
||||
eprintln!("WARN: the explain argument is no longer supported");
|
||||
}
|
||||
"genkey" => {
|
||||
if cli.command.is_some() {
|
||||
return fatal("Too many commands supplied", None);
|
||||
}
|
||||
|
||||
if let Some(private_keys_dir) = args.next() {
|
||||
let private_keys_dir = PathBuf::from(private_keys_dir);
|
||||
|
||||
cli.command = Some(Command::GenKey { private_keys_dir });
|
||||
} else {
|
||||
return fatal(
|
||||
"Required positional argument: PRIVATE_KEYS_DIR",
|
||||
Some(CommandType::GenKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
"pubkey" => {
|
||||
if cli.command.is_some() {
|
||||
return fatal("Too many commands supplied", None);
|
||||
}
|
||||
|
||||
if let Some(private_keys_dir) = args.next() {
|
||||
let private_keys_dir = PathBuf::from(private_keys_dir);
|
||||
|
||||
if let Some(public_keys_dir) = args.next() {
|
||||
let public_keys_dir = PathBuf::from(public_keys_dir);
|
||||
|
||||
cli.command = Some(Command::PubKey {
|
||||
private_keys_dir,
|
||||
public_keys_dir,
|
||||
});
|
||||
} else {
|
||||
return fatal(
|
||||
"Required positional argument: PUBLIC_KEYS_DIR",
|
||||
Some(CommandType::PubKey),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return fatal(
|
||||
"Required positional argument: PRIVATE_KEYS_DIR",
|
||||
Some(CommandType::PubKey),
|
||||
);
|
||||
}
|
||||
}
|
||||
"exchange" => {
|
||||
if cli.command.is_some() {
|
||||
return fatal("Too many commands supplied", None);
|
||||
}
|
||||
|
||||
let options = ExchangeOptions::parse(&mut args)?;
|
||||
cli.command = Some(Command::Exchange(options));
|
||||
}
|
||||
"help" => {
|
||||
cli.command = Some(Command::Help);
|
||||
}
|
||||
_ => return fatal(&format!("Unknown command {}", x), None),
|
||||
};
|
||||
}
|
||||
|
||||
if cli.command.is_none() {
|
||||
return fatal("No command supplied", None);
|
||||
}
|
||||
|
||||
Ok(cli)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{Cli, Command};
|
||||
|
||||
#[inline]
|
||||
fn parse(arr: &[&str]) -> Result<Cli, String> {
|
||||
Cli::parse(arr.iter().map(|x| x.to_string()).peekable())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse_err(arr: &[&str]) -> bool {
|
||||
parse(arr).is_err()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_errors() {
|
||||
assert!(parse_err(&["rp"]));
|
||||
assert!(parse_err(&["rp", "verbose"]));
|
||||
assert!(parse_err(&["rp", "thiscommanddoesntexist"]));
|
||||
assert!(parse_err(&[
|
||||
"rp",
|
||||
"thiscommanddoesntexist",
|
||||
"genkey",
|
||||
"./fakedir/"
|
||||
]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genkey_errors() {
|
||||
assert!(parse_err(&["rp", "genkey"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genkey_works() {
|
||||
let cli = parse(&["rp", "genkey", "./fakedir"]);
|
||||
|
||||
assert!(cli.is_ok());
|
||||
let cli = cli.unwrap();
|
||||
|
||||
assert!(!cli.verbose);
|
||||
assert!(matches!(cli.command, Some(Command::GenKey { .. })));
|
||||
|
||||
match cli.command {
|
||||
Some(Command::GenKey { private_keys_dir }) => {
|
||||
assert_eq!(private_keys_dir.to_str().unwrap(), "./fakedir");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pubkey_errors() {
|
||||
assert!(parse_err(&["rp", "pubkey"]));
|
||||
assert!(parse_err(&["rp", "pubkey", "./fakedir"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pubkey_works() {
|
||||
let cli = parse(&["rp", "pubkey", "./fakedir", "./fakedir2"]);
|
||||
|
||||
assert!(cli.is_ok());
|
||||
let cli = cli.unwrap();
|
||||
|
||||
assert!(!cli.verbose);
|
||||
assert!(matches!(cli.command, Some(Command::PubKey { .. })));
|
||||
|
||||
match cli.command {
|
||||
Some(Command::PubKey {
|
||||
private_keys_dir,
|
||||
public_keys_dir,
|
||||
}) => {
|
||||
assert_eq!(private_keys_dir.to_str().unwrap(), "./fakedir");
|
||||
assert_eq!(public_keys_dir.to_str().unwrap(), "./fakedir2");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exchange_errors() {
|
||||
assert!(parse_err(&["rp", "exchange"]));
|
||||
assert!(parse_err(&[
|
||||
"rp",
|
||||
"exchange",
|
||||
"./fakedir",
|
||||
"notarealoption"
|
||||
]));
|
||||
assert!(parse_err(&["rp", "exchange", "./fakedir", "listen"]));
|
||||
assert!(parse_err(&[
|
||||
"rp",
|
||||
"exchange",
|
||||
"./fakedir",
|
||||
"listen",
|
||||
"notarealip"
|
||||
]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exchange_works() {
|
||||
let cli = parse(&["rp", "exchange", "./fakedir"]);
|
||||
|
||||
assert!(cli.is_ok());
|
||||
let cli = cli.unwrap();
|
||||
|
||||
assert!(!cli.verbose);
|
||||
assert!(matches!(cli.command, Some(Command::Exchange(_))));
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Exchange(options)) => {
|
||||
assert_eq!(options.private_keys_dir.to_str().unwrap(), "./fakedir");
|
||||
assert!(options.dev.is_none());
|
||||
assert!(options.listen.is_none());
|
||||
assert_eq!(options.peers.len(), 0);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let cli = parse(&[
|
||||
"rp",
|
||||
"exchange",
|
||||
"./fakedir",
|
||||
"dev",
|
||||
"devname",
|
||||
"listen",
|
||||
"127.0.0.1:1234",
|
||||
]);
|
||||
|
||||
assert!(cli.is_ok());
|
||||
let cli = cli.unwrap();
|
||||
|
||||
assert!(!cli.verbose);
|
||||
assert!(matches!(cli.command, Some(Command::Exchange(_))));
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Exchange(options)) => {
|
||||
assert_eq!(options.private_keys_dir.to_str().unwrap(), "./fakedir");
|
||||
assert_eq!(options.dev, Some("devname".to_string()));
|
||||
assert_eq!(options.listen, Some("127.0.0.1:1234".parse().unwrap()));
|
||||
assert_eq!(options.peers.len(), 0);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let cli = parse(&[
|
||||
"rp",
|
||||
"exchange",
|
||||
"./fakedir",
|
||||
"dev",
|
||||
"devname",
|
||||
"listen",
|
||||
"127.0.0.1:1234",
|
||||
"peer",
|
||||
"./fakedir2",
|
||||
"endpoint",
|
||||
"127.0.0.1:2345",
|
||||
"persistent-keepalive",
|
||||
"15",
|
||||
"allowed-ips",
|
||||
"123.234.11.0/24,1.1.1.0/24",
|
||||
"peer",
|
||||
"./fakedir3",
|
||||
"endpoint",
|
||||
"127.0.0.1:5432",
|
||||
"persistent-keepalive",
|
||||
"30",
|
||||
]);
|
||||
|
||||
assert!(cli.is_ok());
|
||||
let cli = cli.unwrap();
|
||||
|
||||
assert!(!cli.verbose);
|
||||
assert!(matches!(cli.command, Some(Command::Exchange(_))));
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Exchange(options)) => {
|
||||
assert_eq!(options.private_keys_dir.to_str().unwrap(), "./fakedir");
|
||||
assert_eq!(options.dev, Some("devname".to_string()));
|
||||
assert_eq!(options.listen, Some("127.0.0.1:1234".parse().unwrap()));
|
||||
assert_eq!(options.peers.len(), 2);
|
||||
|
||||
let peer = &options.peers[0];
|
||||
assert_eq!(peer.public_keys_dir.to_str().unwrap(), "./fakedir2");
|
||||
assert_eq!(peer.endpoint, Some("127.0.0.1:2345".parse().unwrap()));
|
||||
assert_eq!(peer.persistent_keepalive, Some(15));
|
||||
assert_eq!(
|
||||
peer.allowed_ips,
|
||||
Some("123.234.11.0/24,1.1.1.0/24".to_string())
|
||||
);
|
||||
|
||||
let peer = &options.peers[1];
|
||||
assert_eq!(peer.public_keys_dir.to_str().unwrap(), "./fakedir3");
|
||||
assert_eq!(peer.endpoint, Some("127.0.0.1:5432".parse().unwrap()));
|
||||
assert_eq!(peer.persistent_keepalive, Some(30));
|
||||
assert!(peer.allowed_ips.is_none());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
280
rp/src/exchange.rs
Normal file
280
rp/src/exchange.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use crate::key::WG_B64_LEN;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExchangePeer {
|
||||
pub public_keys_dir: PathBuf,
|
||||
pub endpoint: Option<SocketAddr>,
|
||||
pub persistent_keepalive: Option<u32>,
|
||||
pub allowed_ips: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExchangeOptions {
|
||||
pub verbose: bool,
|
||||
pub private_keys_dir: PathBuf,
|
||||
pub dev: Option<String>,
|
||||
pub listen: Option<SocketAddr>,
|
||||
pub peers: Vec<ExchangePeer>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
pub async fn exchange(_: ExchangeOptions) -> Result<()> {
|
||||
use anyhow::anyhow;
|
||||
|
||||
Err(anyhow!(
|
||||
"Your system {} is not yet supported. We are happy to receive patches to address this :)",
|
||||
std::env::consts::OS
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
mod netlink {
|
||||
use anyhow::Result;
|
||||
use futures_util::{StreamExt as _, TryStreamExt as _};
|
||||
use genetlink::GenetlinkHandle;
|
||||
use netlink_packet_core::{NLM_F_ACK, NLM_F_REQUEST};
|
||||
use netlink_packet_wireguard::nlas::WgDeviceAttrs;
|
||||
use rtnetlink::Handle;
|
||||
|
||||
pub async fn link_create_and_up(rtnetlink: &Handle, link_name: String) -> Result<u32> {
|
||||
// add the link
|
||||
rtnetlink
|
||||
.link()
|
||||
.add()
|
||||
.wireguard(link_name.clone())
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
// retrieve the link to be able to up it
|
||||
let link = rtnetlink
|
||||
.link()
|
||||
.get()
|
||||
.match_name(link_name.clone())
|
||||
.execute()
|
||||
.into_stream()
|
||||
.into_future()
|
||||
.await
|
||||
.0
|
||||
.unwrap()?;
|
||||
|
||||
// up the link
|
||||
rtnetlink
|
||||
.link()
|
||||
.set(link.header.index)
|
||||
.up()
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
Ok(link.header.index)
|
||||
}
|
||||
|
||||
pub async fn link_cleanup(rtnetlink: &Handle, index: u32) -> Result<()> {
|
||||
rtnetlink.link().del(index).execute().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn link_cleanup_standalone(index: u32) -> Result<()> {
|
||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
// We don't care if this fails, as the device may already have been auto-cleaned up.
|
||||
let _ = rtnetlink.link().del(index).execute().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This replicates the functionality of the `wg set` command line tool.
|
||||
///
|
||||
/// It sets the specified WireGuard attributes of the indexed device by
|
||||
/// communicating with WireGuard's generic netlink interface, like the
|
||||
/// `wg` tool does.
|
||||
pub async fn wg_set(
|
||||
genetlink: &mut GenetlinkHandle,
|
||||
index: u32,
|
||||
mut attr: Vec<WgDeviceAttrs>,
|
||||
) -> Result<()> {
|
||||
use futures_util::StreamExt as _;
|
||||
use netlink_packet_core::{NetlinkMessage, NetlinkPayload};
|
||||
use netlink_packet_generic::GenlMessage;
|
||||
use netlink_packet_wireguard::{Wireguard, WireguardCmd};
|
||||
|
||||
// Scope our `set` command to only the device of the specified index
|
||||
attr.insert(0, WgDeviceAttrs::IfIndex(index));
|
||||
|
||||
// Construct the WireGuard-specific netlink packet
|
||||
let wgc = Wireguard {
|
||||
cmd: WireguardCmd::SetDevice,
|
||||
nlas: attr,
|
||||
};
|
||||
|
||||
// Construct final message
|
||||
let genl = GenlMessage::from_payload(wgc);
|
||||
let mut nlmsg = NetlinkMessage::from(genl);
|
||||
nlmsg.header.flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||
|
||||
// Send and wait for the ACK or error
|
||||
let (res, _) = genetlink.request(nlmsg).await?.into_future().await;
|
||||
if let Some(res) = res {
|
||||
let res = res?;
|
||||
if let NetlinkPayload::Error(err) = res.payload {
|
||||
return Err(err.to_io().into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
use std::fs;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use netlink_packet_wireguard::{constants::WG_KEY_LEN, nlas::WgDeviceAttrs};
|
||||
use rosenpass::{
|
||||
app_server::{AppServer, BrokerPeer},
|
||||
config::Verbosity,
|
||||
protocol::{SPk, SSk, SymKey},
|
||||
};
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_util::file::{LoadValue as _, LoadValueB64};
|
||||
use rosenpass_wireguard_broker::brokers::native_unix::{
|
||||
NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError,
|
||||
};
|
||||
|
||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
let link_name = options.dev.unwrap_or("rosenpass0".to_string());
|
||||
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
||||
|
||||
ctrlc_async::set_async_handler(async move {
|
||||
netlink::link_cleanup_standalone(link_index)
|
||||
.await
|
||||
.expect("Failed to clean up");
|
||||
})?;
|
||||
|
||||
// Deploy the classic wireguard private key
|
||||
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
let wgsk_path = options.private_keys_dir.join("wgsk");
|
||||
|
||||
let wgsk = Secret::<WG_KEY_LEN>::load_b64::<WG_B64_LEN, _>(wgsk_path)?;
|
||||
|
||||
let mut attr: Vec<WgDeviceAttrs> = Vec::with_capacity(2);
|
||||
attr.push(WgDeviceAttrs::PrivateKey(*wgsk.secret()));
|
||||
|
||||
if let Some(listen) = options.listen {
|
||||
if listen.port() == u16::MAX {
|
||||
return Err(anyhow!("You may not use {} as the listen port.", u16::MAX));
|
||||
}
|
||||
|
||||
attr.push(WgDeviceAttrs::ListenPort(listen.port() + 1));
|
||||
}
|
||||
|
||||
netlink::wg_set(&mut genetlink, link_index, attr).await?;
|
||||
|
||||
let pqsk = options.private_keys_dir.join("pqsk");
|
||||
let pqpk = options.private_keys_dir.join("pqpk");
|
||||
|
||||
let sk = SSk::load(&pqsk)?;
|
||||
let pk = SPk::load(&pqpk)?;
|
||||
|
||||
let mut srv = Box::new(AppServer::new(
|
||||
Some((sk, pk)),
|
||||
if let Some(listen) = options.listen {
|
||||
vec![listen]
|
||||
} else {
|
||||
Vec::with_capacity(0)
|
||||
},
|
||||
if options.verbose {
|
||||
Verbosity::Verbose
|
||||
} else {
|
||||
Verbosity::Quiet
|
||||
},
|
||||
None,
|
||||
)?);
|
||||
|
||||
let broker_store_ptr = srv.register_broker(Box::new(NativeUnixBroker::new()))?;
|
||||
|
||||
fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error {
|
||||
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
||||
}
|
||||
|
||||
for peer in options.peers {
|
||||
let wgpk = peer.public_keys_dir.join("wgpk");
|
||||
let pqpk = peer.public_keys_dir.join("pqpk");
|
||||
let psk = peer.public_keys_dir.join("psk");
|
||||
|
||||
let mut extra_params: Vec<String> = Vec::with_capacity(6);
|
||||
if let Some(endpoint) = peer.endpoint {
|
||||
extra_params.push("endpoint".to_string());
|
||||
|
||||
// Peer endpoints always use (port + 1) in wg set params
|
||||
let endpoint = SocketAddr::new(endpoint.ip(), endpoint.port() + 1);
|
||||
extra_params.push(endpoint.to_string());
|
||||
}
|
||||
if let Some(persistent_keepalive) = peer.persistent_keepalive {
|
||||
extra_params.push("persistent-keepalive".to_string());
|
||||
extra_params.push(persistent_keepalive.to_string());
|
||||
}
|
||||
if let Some(allowed_ips) = &peer.allowed_ips {
|
||||
extra_params.push("allowed-ips".to_string());
|
||||
extra_params.push(allowed_ips.clone());
|
||||
}
|
||||
|
||||
let peer_cfg = NativeUnixBrokerConfigBaseBuilder::default()
|
||||
.peer_id_b64(&fs::read_to_string(wgpk)?)?
|
||||
.interface(link_name.clone())
|
||||
.extra_params_ser(&extra_params)?
|
||||
.build()
|
||||
.map_err(cfg_err_map)?;
|
||||
|
||||
let broker_peer = Some(BrokerPeer::new(
|
||||
broker_store_ptr.clone(),
|
||||
Box::new(peer_cfg),
|
||||
));
|
||||
|
||||
srv.add_peer(
|
||||
if psk.exists() {
|
||||
Some(SymKey::load_b64::<WG_B64_LEN, _>(psk))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.transpose()?,
|
||||
SPk::load(&pqpk)?,
|
||||
None,
|
||||
broker_peer,
|
||||
peer.endpoint.map(|x| x.to_string()),
|
||||
)?;
|
||||
}
|
||||
|
||||
let out = srv.event_loop();
|
||||
|
||||
netlink::link_cleanup(&rtnetlink, link_index).await?;
|
||||
|
||||
match out {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
// Check if the returned error is actually EINTR, in which case, the run actually succeeded.
|
||||
let is_ok = if let Some(e) = e.root_cause().downcast_ref::<std::io::Error>() {
|
||||
matches!(e.kind(), std::io::ErrorKind::Interrupted)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_ok {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
rp/src/key.rs
Normal file
152
rp/src/key.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use std::{
|
||||
fs::{self, DirBuilder},
|
||||
ops::DerefMut,
|
||||
os::unix::fs::{DirBuilderExt, PermissionsExt},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use rosenpass_util::file::{LoadValueB64, StoreValue, StoreValueB64};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use rosenpass::protocol::{SPk, SSk};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret};
|
||||
|
||||
pub const WG_B64_LEN: usize = 32 * 5 / 3;
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
pub fn genkey(_: &Path) -> Result<()> {
|
||||
Err(anyhow!(
|
||||
"Your system {} is not yet supported. We are happy to receive patches to address this :)",
|
||||
std::env::consts::OS
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
||||
if private_keys_dir.exists() {
|
||||
if fs::metadata(private_keys_dir)?.permissions().mode() != 0o700 {
|
||||
return Err(anyhow!(
|
||||
"Directory {:?} has incorrect permissions: please use 0700 for proper security.",
|
||||
private_keys_dir
|
||||
));
|
||||
}
|
||||
} else {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.mode(0o700)
|
||||
.create(private_keys_dir)?;
|
||||
}
|
||||
|
||||
let wgsk_path = private_keys_dir.join("wgsk");
|
||||
let pqsk_path = private_keys_dir.join("pqsk");
|
||||
let pqpk_path = private_keys_dir.join("pqpk");
|
||||
|
||||
if !wgsk_path.exists() {
|
||||
let wgsk: Secret<32> = Secret::random();
|
||||
wgsk.store_b64::<WG_B64_LEN, _>(wgsk_path)?;
|
||||
} else {
|
||||
eprintln!(
|
||||
"WireGuard secret key already exists at {:#?}: not regenerating",
|
||||
wgsk_path
|
||||
);
|
||||
}
|
||||
|
||||
if !pqsk_path.exists() && !pqpk_path.exists() {
|
||||
let mut pqsk = SSk::random();
|
||||
let mut pqpk = SPk::random();
|
||||
StaticKem::keygen(pqsk.secret_mut(), pqpk.deref_mut())?;
|
||||
pqpk.store(pqpk_path)?;
|
||||
pqsk.store_secret(pqsk_path)?;
|
||||
} else {
|
||||
eprintln!(
|
||||
"Rosenpass keys already exist in {:#?}: not regenerating",
|
||||
private_keys_dir
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
||||
if public_keys_dir.exists() {
|
||||
return Err(anyhow!("Directory {:?} already exists", public_keys_dir));
|
||||
}
|
||||
|
||||
fs::create_dir_all(public_keys_dir)?;
|
||||
|
||||
let private_wgsk = private_keys_dir.join("wgsk");
|
||||
let public_wgpk = public_keys_dir.join("wgpk");
|
||||
let private_pqpk = private_keys_dir.join("pqpk");
|
||||
let public_pqpk = public_keys_dir.join("pqpk");
|
||||
|
||||
let wgsk = Secret::load_b64::<WG_B64_LEN, _>(private_wgsk)?;
|
||||
let mut wgpk: Public<32> = {
|
||||
let mut secret = x25519_dalek::StaticSecret::from(*wgsk.secret());
|
||||
let public = x25519_dalek::PublicKey::from(&secret);
|
||||
secret.zeroize();
|
||||
Public::from_slice(public.as_bytes())
|
||||
};
|
||||
|
||||
wgpk.store_b64::<WG_B64_LEN, _>(public_wgpk)?;
|
||||
wgpk.zeroize();
|
||||
|
||||
fs::copy(private_pqpk, public_pqpk)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
|
||||
use rosenpass::protocol::{SPk, SSk};
|
||||
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_util::file::LoadValue;
|
||||
use rosenpass_util::file::LoadValueB64;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::key::{genkey, pubkey, WG_B64_LEN};
|
||||
|
||||
#[test]
|
||||
fn test_key_loopback() {
|
||||
secret_policy_try_use_memfd_secrets();
|
||||
let private_keys_dir = tempdir().unwrap();
|
||||
fs::remove_dir(private_keys_dir.path()).unwrap();
|
||||
|
||||
// Guranteed to have 16MB of stack size
|
||||
stacker::grow(8 * 1024 * 1024, || {
|
||||
assert!(genkey(private_keys_dir.path()).is_ok());
|
||||
});
|
||||
|
||||
assert!(private_keys_dir.path().exists());
|
||||
assert!(private_keys_dir.path().is_dir());
|
||||
assert!(SPk::load(private_keys_dir.path().join("pqpk")).is_ok());
|
||||
assert!(SSk::load(private_keys_dir.path().join("pqsk")).is_ok());
|
||||
assert!(
|
||||
Secret::<32>::load_b64::<WG_B64_LEN, _>(private_keys_dir.path().join("wgsk")).is_ok()
|
||||
);
|
||||
|
||||
let public_keys_dir = tempdir().unwrap();
|
||||
fs::remove_dir(public_keys_dir.path()).unwrap();
|
||||
|
||||
// Guranteed to have 16MB of stack size
|
||||
stacker::grow(8 * 1024 * 1024, || {
|
||||
assert!(pubkey(private_keys_dir.path(), public_keys_dir.path()).is_ok());
|
||||
});
|
||||
|
||||
assert!(public_keys_dir.path().exists());
|
||||
assert!(public_keys_dir.path().is_dir());
|
||||
assert!(SPk::load(public_keys_dir.path().join("pqpk")).is_ok());
|
||||
assert!(
|
||||
Secret::<32>::load_b64::<WG_B64_LEN, _>(public_keys_dir.path().join("wgpk")).is_ok()
|
||||
);
|
||||
|
||||
let pk_1 = fs::read(private_keys_dir.path().join("pqpk")).unwrap();
|
||||
let pk_2 = fs::read(public_keys_dir.path().join("pqpk")).unwrap();
|
||||
assert_eq!(pk_1, pk_2);
|
||||
}
|
||||
}
|
||||
52
rp/src/main.rs
Normal file
52
rp/src/main.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::process::exit;
|
||||
|
||||
use cli::{Cli, Command};
|
||||
use exchange::exchange;
|
||||
use key::{genkey, pubkey};
|
||||
use rosenpass_secret_memory::policy;
|
||||
|
||||
mod cli;
|
||||
mod exchange;
|
||||
mod key;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
#[cfg(feature = "experiment_memfd_secret")]
|
||||
policy::secret_policy_try_use_memfd_secrets();
|
||||
#[cfg(not(feature = "experiment_memfd_secret"))]
|
||||
policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let cli = match Cli::parse(std::env::args().peekable()) {
|
||||
Ok(cli) => cli,
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let command = cli.command.unwrap();
|
||||
|
||||
let res = match command {
|
||||
Command::GenKey { private_keys_dir } => genkey(&private_keys_dir),
|
||||
Command::PubKey {
|
||||
private_keys_dir,
|
||||
public_keys_dir,
|
||||
} => pubkey(&private_keys_dir, &public_keys_dir),
|
||||
Command::Exchange(mut options) => {
|
||||
options.verbose = cli.verbose;
|
||||
exchange(options).await
|
||||
}
|
||||
Command::Help => {
|
||||
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("An error occurred: {}", err);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,3 +21,6 @@ log = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
allocator-api2-tests = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
base64ct = { workspace = true }
|
||||
procspawn = { workspace = true }
|
||||
|
||||
@@ -4,37 +4,41 @@ use std::ptr::NonNull;
|
||||
use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemsecAllocatorContents;
|
||||
struct MallocAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MemsecAllocator {
|
||||
_dummy_private_data: MemsecAllocatorContents,
|
||||
pub struct MallocAllocator {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
pub type MemsecBox<T> = allocator_api2::boxed::Box<T, MemsecAllocator>;
|
||||
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
pub type MemsecVec<T> = allocator_api2::vec::Vec<T, MemsecAllocator>;
|
||||
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
|
||||
|
||||
pub fn memsec_box<T>(x: T) -> MemsecBox<T> {
|
||||
MemsecBox::<T>::new_in(x, MemsecAllocator::new())
|
||||
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
|
||||
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
pub fn memsec_vec<T>() -> MemsecVec<T> {
|
||||
MemsecVec::<T>::new_in(MemsecAllocator::new())
|
||||
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
|
||||
MallocBox::<T>::new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
impl MemsecAllocator {
|
||||
pub fn malloc_vec<T>() -> MallocVec<T> {
|
||||
MallocVec::<T>::new_in(MallocAllocator::new())
|
||||
}
|
||||
|
||||
impl MallocAllocator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MemsecAllocatorContents,
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Allocator for MemsecAllocator {
|
||||
unsafe impl Allocator for MallocAllocator {
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
// Call memsec allocator
|
||||
let mem: Option<NonNull<[u8]>> = unsafe { memsec::malloc_sized(layout.size()) };
|
||||
@@ -48,8 +52,8 @@ unsafe impl Allocator for MemsecAllocator {
|
||||
// Ensure the right alignment is used
|
||||
let off = (mem.as_ptr() as *const u8).align_offset(layout.align());
|
||||
if off != 0 {
|
||||
log::error!("Allocation {layout:?} was requested but memsec returned allocation \
|
||||
with offset {off} from the requested alignment. Memsec always allocates values \
|
||||
log::error!("Allocation {layout:?} was requested but malloc-based memsec returned allocation \
|
||||
with offset {off} from the requested alignment. Malloc always allocates values \
|
||||
at the end of a memory page for security reasons, custom alignments are not supported. \
|
||||
You could try allocating an oversized value.");
|
||||
unsafe { memsec::free(mem) };
|
||||
@@ -66,7 +70,7 @@ unsafe impl Allocator for MemsecAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MemsecAllocator {
|
||||
impl fmt::Debug for MallocAllocator {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("<memsec based Rust allocator>")
|
||||
}
|
||||
@@ -78,21 +82,21 @@ mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
make_test! { test_sizes(MemsecAllocator::new()) }
|
||||
make_test! { test_vec(MemsecAllocator::new()) }
|
||||
make_test! { test_many_boxes(MemsecAllocator::new()) }
|
||||
make_test! { test_sizes(MallocAllocator::new()) }
|
||||
make_test! { test_vec(MallocAllocator::new()) }
|
||||
make_test! { test_many_boxes(MallocAllocator::new()) }
|
||||
|
||||
#[test]
|
||||
fn memsec_allocation() {
|
||||
let alloc = MemsecAllocator::new();
|
||||
memsec_allocation_impl::<0>(&alloc);
|
||||
memsec_allocation_impl::<7>(&alloc);
|
||||
memsec_allocation_impl::<8>(&alloc);
|
||||
memsec_allocation_impl::<64>(&alloc);
|
||||
memsec_allocation_impl::<999>(&alloc);
|
||||
fn malloc_allocation() {
|
||||
let alloc = MallocAllocator::new();
|
||||
malloc_allocation_impl::<0>(&alloc);
|
||||
malloc_allocation_impl::<7>(&alloc);
|
||||
malloc_allocation_impl::<8>(&alloc);
|
||||
malloc_allocation_impl::<64>(&alloc);
|
||||
malloc_allocation_impl::<999>(&alloc);
|
||||
}
|
||||
|
||||
fn memsec_allocation_impl<const N: usize>(alloc: &MemsecAllocator) {
|
||||
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {
|
||||
let layout = Layout::new::<[u8; N]>();
|
||||
let mem = alloc.allocate(layout).unwrap();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user