mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 21:34:37 +03:00
Compare commits
192 Commits
docu-tests
...
dev/karo/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e9519e26 | ||
|
|
c3def9744f | ||
|
|
e3d3584adb | ||
|
|
a1982e0245 | ||
|
|
4896cd6130 | ||
|
|
9aab9d2d2a | ||
|
|
108ca440fe | ||
|
|
03e408b7c2 | ||
|
|
67f387a190 | ||
|
|
745c3962bb | ||
|
|
f6971aa5ad | ||
|
|
b46cd636d2 | ||
|
|
f22f4aad7d | ||
|
|
a83589d76a | ||
|
|
508d46f2bc | ||
|
|
3fc3083a54 | ||
|
|
faa45a8540 | ||
|
|
77632d0725 | ||
|
|
7218b0a3f4 | ||
|
|
4266cbfb72 | ||
|
|
070d299329 | ||
|
|
15699710a0 | ||
|
|
ae418ffba7 | ||
|
|
e3f7773bac | ||
|
|
9ab754eb0b | ||
|
|
b055457d01 | ||
|
|
b3403e7120 | ||
|
|
abd5210ae4 | ||
|
|
03464e1be7 | ||
|
|
54fc904c15 | ||
|
|
ceff8b711a | ||
|
|
c84bbed3bd | ||
|
|
d453002230 | ||
|
|
e81612d2e3 | ||
|
|
d558bdb633 | ||
|
|
e8fb7206fc | ||
|
|
b47d3a9deb | ||
|
|
f7fb09bc44 | ||
|
|
db6530ef77 | ||
|
|
8f519b042d | ||
|
|
954162b61f | ||
|
|
c65abe7bd9 | ||
|
|
80885d81d7 | ||
|
|
d023108d3b | ||
|
|
417df7aa7f | ||
|
|
9dd00e04c1 | ||
|
|
1a8e220aa8 | ||
|
|
de0022f092 | ||
|
|
dbb891a2ed | ||
|
|
531ae0ef70 | ||
|
|
8bb54b9cca | ||
|
|
7566eadef8 | ||
|
|
ebf6403ea7 | ||
|
|
62d408eade | ||
|
|
d1cf6af531 | ||
|
|
5e6c85d73d | ||
|
|
3205f8c572 | ||
|
|
b21a95dbbd | ||
|
|
006946442a | ||
|
|
33901d598a | ||
|
|
944be10bd2 | ||
|
|
23cf60c7ec | ||
|
|
6f71767529 | ||
|
|
38f371e3d7 | ||
|
|
2dba9205e7 | ||
|
|
30c3de3f87 | ||
|
|
b16619b1d3 | ||
|
|
576ad5f6d0 | ||
|
|
6494518460 | ||
|
|
185e92108e | ||
|
|
253243a8c8 | ||
|
|
075d9ffff3 | ||
|
|
01a1408044 | ||
|
|
b84e0beae8 | ||
|
|
949a3e4d23 | ||
|
|
d61b137761 | ||
|
|
a1f41953b7 | ||
|
|
46ebb6f46c | ||
|
|
32ae8f7051 | ||
|
|
b94ddd980d | ||
|
|
44e46895aa | ||
|
|
2ddd1488b3 | ||
|
|
c9aad280b2 | ||
|
|
d7398d9bcf | ||
|
|
6d25c13fd1 | ||
|
|
2d2d109246 | ||
|
|
30e158f594 | ||
|
|
cf74584f51 | ||
|
|
793cfd227f | ||
|
|
54c8e91db4 | ||
|
|
1b0179e751 | ||
|
|
760ecdc457 | ||
|
|
6a9bbddde3 | ||
|
|
530f81b9d5 | ||
|
|
b96df1588c | ||
|
|
5a2555a327 | ||
|
|
ac3f21c4bd | ||
|
|
b36d30d89d | ||
|
|
62fe529d36 | ||
|
|
76d01ffaf9 | ||
|
|
576b17cd9c | ||
|
|
cbc1bb4be2 | ||
|
|
c8a084157e | ||
|
|
09f1353dcc | ||
|
|
43225c1fe8 | ||
|
|
8e41cfc0b4 | ||
|
|
69538622b4 | ||
|
|
45a7c17cdd | ||
|
|
b8ecdab8dc | ||
|
|
af9d83b472 | ||
|
|
f81e329a11 | ||
|
|
5e2c72ef99 | ||
|
|
88e7d1d1cb | ||
|
|
43a930d3f7 | ||
|
|
b5f6d07650 | ||
|
|
be3c3d3d61 | ||
|
|
fe60cea959 | ||
|
|
441988cf43 | ||
|
|
b40b7f4f2f | ||
|
|
da76d88170 | ||
|
|
e35955f99c | ||
|
|
87587399ed | ||
|
|
9fdba31b32 | ||
|
|
0bfe47e5b8 | ||
|
|
771dce3ac7 | ||
|
|
436c6e6f87 | ||
|
|
f093406c34 | ||
|
|
eadf70ee38 | ||
|
|
7ac0883970 | ||
|
|
b1658b83a0 | ||
|
|
27650e95a7 | ||
|
|
6ab4e1152c | ||
|
|
2c64da23f1 | ||
|
|
03cc609a1e | ||
|
|
3effcb313e | ||
|
|
fded3b2e79 | ||
|
|
1471bb6a9f | ||
|
|
7edf84bd4a | ||
|
|
5187e50bb7 | ||
|
|
fd5806ba55 | ||
|
|
8e50d38b38 | ||
|
|
377f2f40d2 | ||
|
|
9bae080c4d | ||
|
|
3392da5163 | ||
|
|
3109cf1ffc | ||
|
|
d2539e445f | ||
|
|
6dc58cc6c1 | ||
|
|
e3d16966c9 | ||
|
|
a5e6af4b49 | ||
|
|
24a71977f0 | ||
|
|
5f0ac579d7 | ||
|
|
4df994b5f0 | ||
|
|
e4e0a9e661 | ||
|
|
742e037936 | ||
|
|
b5848af799 | ||
|
|
4982e40084 | ||
|
|
c1ae3268c6 | ||
|
|
524ec68f3f | ||
|
|
184603aa2c | ||
|
|
ec6706ffeb | ||
|
|
7571670e71 | ||
|
|
0d7dd99d96 | ||
|
|
c78a9cb777 | ||
|
|
dd0db53e8b | ||
|
|
422acf9891 | ||
|
|
877c15a018 | ||
|
|
55d7f8b1c1 | ||
|
|
199ff63a06 | ||
|
|
47b556e317 | ||
|
|
f87e2cb31b | ||
|
|
58e1c8fbff | ||
|
|
c89c7d7acf | ||
|
|
a5b876f119 | ||
|
|
c2f50f47b3 | ||
|
|
53168dc62d | ||
|
|
2cfe703118 | ||
|
|
a2d7c3aaa6 | ||
|
|
1aa111570e | ||
|
|
a91d61f9f0 | ||
|
|
ff7827c24e | ||
|
|
255e377d29 | ||
|
|
50505d81cc | ||
|
|
10484cc6d4 | ||
|
|
d27e602f43 | ||
|
|
73f6b33dbb | ||
|
|
a279dfc0b1 | ||
|
|
caf2f6bfec | ||
|
|
d398ad369e | ||
|
|
00696321ff | ||
|
|
35519e7baa | ||
|
|
78af5d1dc4 | ||
|
|
0353c82729 |
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
.gitignore
|
||||||
288
.github/workflows/docker.yaml
vendored
Normal file
288
.github/workflows/docker.yaml
vendored
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
name: Build Docker Images
|
||||||
|
|
||||||
|
# Run this job on all non-pull-request events,
|
||||||
|
# or if Docker-related files are changed in a pull request.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "docker/Dockerfile"
|
||||||
|
- ".github/workflows/docker.yaml"
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# --------------------------------
|
||||||
|
# 1. BUILD & TEST
|
||||||
|
# --------------------------------
|
||||||
|
build-and-test-rp:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build (no push) and Load
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
# no pushing here, so we can test locally
|
||||||
|
push: false
|
||||||
|
# load the built image into the local Docker daemon on the runner
|
||||||
|
load: true
|
||||||
|
target: rosenpass
|
||||||
|
tags: rosenpass:test
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
- name: Integration Test - Standalone Key Exchange
|
||||||
|
run: |
|
||||||
|
# Create separate workdirs
|
||||||
|
mkdir -p workdir-server workdir-client
|
||||||
|
|
||||||
|
# Create a Docker network
|
||||||
|
docker network create -d bridge rp
|
||||||
|
|
||||||
|
echo "=== GENERATE SERVER KEYS ==="
|
||||||
|
docker run --rm \
|
||||||
|
-v $PWD/workdir-server:/workdir \
|
||||||
|
rosenpass:test gen-keys \
|
||||||
|
--public-key=workdir/server-public \
|
||||||
|
--secret-key=workdir/server-secret
|
||||||
|
|
||||||
|
echo "=== GENERATE CLIENT KEYS ==="
|
||||||
|
docker run --rm \
|
||||||
|
-v $PWD/workdir-client:/workdir \
|
||||||
|
rosenpass:test gen-keys \
|
||||||
|
--public-key=workdir/client-public \
|
||||||
|
--secret-key=workdir/client-secret
|
||||||
|
|
||||||
|
echo "=== SHARE PUBLIC KEYS ==="
|
||||||
|
cp workdir-client/client-public workdir-server/client-public
|
||||||
|
cp workdir-server/server-public workdir-client/server-public
|
||||||
|
|
||||||
|
echo "=== START SERVER CONTAINER ==="
|
||||||
|
docker run -d --rm \
|
||||||
|
--name rpserver \
|
||||||
|
--network rp \
|
||||||
|
-v $PWD/workdir-server:/workdir \
|
||||||
|
rosenpass:test exchange \
|
||||||
|
private-key workdir/server-secret \
|
||||||
|
public-key workdir/server-public \
|
||||||
|
listen 0.0.0.0:9999 \
|
||||||
|
peer public-key workdir/client-public \
|
||||||
|
outfile workdir/server-sharedkey
|
||||||
|
|
||||||
|
# Get the container IP of the server
|
||||||
|
SERVER_IP=$(docker inspect --format='{{.NetworkSettings.Networks.rp.IPAddress}}' rpserver)
|
||||||
|
echo "SERVER_IP=$SERVER_IP"
|
||||||
|
|
||||||
|
echo "=== START CLIENT CONTAINER ==="
|
||||||
|
docker run -d --rm \
|
||||||
|
--name rpclient \
|
||||||
|
--network rp \
|
||||||
|
-v $PWD/workdir-client:/workdir \
|
||||||
|
rosenpass:test exchange \
|
||||||
|
private-key workdir/client-secret \
|
||||||
|
public-key workdir/client-public \
|
||||||
|
peer public-key workdir/server-public \
|
||||||
|
endpoint ${SERVER_IP}:9999 \
|
||||||
|
outfile workdir/client-sharedkey
|
||||||
|
|
||||||
|
echo "=== COMPARE SHARED KEYS ==="
|
||||||
|
echo "Waiting up to 30 seconds for the server to generate 'server-sharedkey'..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if [ -f "workdir-server/server-sharedkey" ]; then
|
||||||
|
echo "server-sharedkey found!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
sudo cmp workdir-server/server-sharedkey workdir-client/client-sharedkey
|
||||||
|
|
||||||
|
echo "Standalone Key Exchange test OK."
|
||||||
|
# --------------------------------
|
||||||
|
# 2. PUSH (only if tests pass)
|
||||||
|
# --------------------------------
|
||||||
|
docker-image-rp:
|
||||||
|
needs:
|
||||||
|
- build-and-test-rp
|
||||||
|
# Skip if this is not a PR. Then we want to push this image.
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
# Use a matrix to build for both AMD64 and ARM64
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
# Switch the runner based on the architecture
|
||||||
|
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/rp
|
||||||
|
labels: |
|
||||||
|
maintainer=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.authors=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.title=Rosenpass
|
||||||
|
org.opencontainers.image.description=The rp command-line integrates Rosenpass and WireGuard to help you create a VPN
|
||||||
|
org.opencontainers.image.vendor=Rosenpass e.V.
|
||||||
|
org.opencontainers.image.licenses=MIT OR Apache-2.0
|
||||||
|
org.opencontainers.image.url=https://rosenpass.eu
|
||||||
|
org.opencontainers.image.documentation=https://rosenpass.eu/docs/
|
||||||
|
org.opencontainers.image.source=https://github.com/rosenpass/rosenpass
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/${{ github.repository_owner }}/rp
|
||||||
|
target: rp
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-rp-${{ matrix.arch }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-image-rosenpass:
|
||||||
|
needs:
|
||||||
|
- build-and-test-rp
|
||||||
|
# Skip if this is not a PR. Then we want to push this image.
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
# Use a matrix to build for both AMD64 and ARM64
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
# Switch the runner based on the architecture
|
||||||
|
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/rosenpass
|
||||||
|
labels: |
|
||||||
|
maintainer=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.authors=Karolin Varner <karo@cupdev.net>, wucke13 <wucke13@gmail.com>
|
||||||
|
org.opencontainers.image.title=Rosenpass
|
||||||
|
org.opencontainers.image.description=Reference implementation of the protocol rosenpass protocol
|
||||||
|
org.opencontainers.image.vendor=Rosenpass e.V.
|
||||||
|
org.opencontainers.image.licenses=MIT OR Apache-2.0
|
||||||
|
org.opencontainers.image.url=https://rosenpass.eu
|
||||||
|
org.opencontainers.image.documentation=https://rosenpass.eu/docs/
|
||||||
|
org.opencontainers.image.source=https://github.com/rosenpass/rosenpass
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/${{ github.repository_owner }}/rosenpass
|
||||||
|
target: rosenpass
|
||||||
|
platforms: linux/${{ matrix.arch }}
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-rosenpass-${{ matrix.arch }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
merge-digests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- docker-image-rosenpass
|
||||||
|
- docker-image-rp
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [rp, rosenpass]
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-${{ matrix.target }}-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.target }}
|
||||||
|
tags: |
|
||||||
|
type=edge,branch=main
|
||||||
|
type=sha,branch=main
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf 'ghcr.io/${{ github.repository_owner }}/${{ matrix.target }}@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/${{ matrix.target }}:${{ steps.meta.outputs.version }}
|
||||||
19
.github/workflows/manual-mac-pr.yaml
vendored
Normal file
19
.github/workflows/manual-mac-pr.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: PR Validation on Mac
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: write
|
||||||
|
concurrency:
|
||||||
|
group: manual-mac-${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
qc:
|
||||||
|
uses: ./.github/workflows/qc-mac.yaml
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
nix:
|
||||||
|
uses: ./.github/workflows/nix-mac.yaml
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
114
.github/workflows/nix-mac.yaml
vendored
Normal file
114
.github/workflows/nix-mac.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
name: Nix on Mac
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
aarch64-darwin---default:
|
||||||
|
name: Build aarch64-darwin.default
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs:
|
||||||
|
- aarch64-darwin---rosenpass
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.default --print-build-logs
|
||||||
|
aarch64-darwin---release-package:
|
||||||
|
name: Build aarch64-darwin.release-package
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs:
|
||||||
|
- aarch64-darwin---rosenpass
|
||||||
|
- aarch64-darwin---rp
|
||||||
|
- aarch64-darwin---rosenpass-oci-image
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.release-package --print-build-logs
|
||||||
|
aarch64-darwin---rosenpass:
|
||||||
|
name: Build aarch64-darwin.rosenpass
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs: []
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.rosenpass --print-build-logs
|
||||||
|
aarch64-darwin---rp:
|
||||||
|
name: Build aarch64-darwin.rp
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs: []
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.rp --print-build-logs
|
||||||
|
aarch64-darwin---rosenpass-oci-image:
|
||||||
|
name: Build aarch64-darwin.rosenpass-oci-image
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
needs:
|
||||||
|
- aarch64-darwin---rosenpass
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build .#packages.aarch64-darwin.rosenpass-oci-image --print-build-logs
|
||||||
|
aarch64-darwin---check:
|
||||||
|
name: Run Nix checks on aarch64-darwin
|
||||||
|
runs-on:
|
||||||
|
- warp-macos-13-arm64-6x
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Check
|
||||||
|
run: nix flake check . --print-build-logs
|
||||||
146
.github/workflows/nix.yaml
vendored
146
.github/workflows/nix.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
i686-linux---default:
|
i686-linux---default:
|
||||||
name: Build i686-linux.default
|
name: Build i686-linux.default
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
i686-linux---rosenpass:
|
i686-linux---rosenpass:
|
||||||
name: Build i686-linux.rosenpass
|
name: Build i686-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
i686-linux---rosenpass-oci-image:
|
i686-linux---rosenpass-oci-image:
|
||||||
name: Build i686-linux.rosenpass-oci-image
|
name: Build i686-linux.rosenpass-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
@@ -65,107 +65,7 @@ jobs:
|
|||||||
i686-linux---check:
|
i686-linux---check:
|
||||||
name: Run Nix checks on i686-linux
|
name: Run Nix checks on i686-linux
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Check
|
|
||||||
run: nix flake check . --print-build-logs
|
|
||||||
x86_64-darwin---default:
|
|
||||||
name: Build x86_64-darwin.default
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs:
|
|
||||||
- x86_64-darwin---rosenpass
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.default --print-build-logs
|
|
||||||
x86_64-darwin---release-package:
|
|
||||||
name: Build x86_64-darwin.release-package
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs:
|
|
||||||
- x86_64-darwin---rosenpass
|
|
||||||
- x86_64-darwin---rp
|
|
||||||
- x86_64-darwin---rosenpass-oci-image
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.release-package --print-build-logs
|
|
||||||
x86_64-darwin---rosenpass:
|
|
||||||
name: Build x86_64-darwin.rosenpass
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs: []
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.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@v4
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.rp --print-build-logs
|
|
||||||
x86_64-darwin---rosenpass-oci-image:
|
|
||||||
name: Build x86_64-darwin.rosenpass-oci-image
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
needs:
|
|
||||||
- x86_64-darwin---rosenpass
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: rosenpass
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
run: nix build .#packages.x86_64-darwin.rosenpass-oci-image --print-build-logs
|
|
||||||
x86_64-darwin---check:
|
|
||||||
name: Run Nix checks on x86_64-darwin
|
|
||||||
runs-on:
|
|
||||||
- macos-13
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
@@ -180,7 +80,7 @@ jobs:
|
|||||||
x86_64-linux---default:
|
x86_64-linux---default:
|
||||||
name: Build x86_64-linux.default
|
name: Build x86_64-linux.default
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
@@ -197,7 +97,7 @@ jobs:
|
|||||||
x86_64-linux---proof-proverif:
|
x86_64-linux---proof-proverif:
|
||||||
name: Build x86_64-linux.proof-proverif
|
name: Build x86_64-linux.proof-proverif
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---proverif-patched
|
- x86_64-linux---proverif-patched
|
||||||
steps:
|
steps:
|
||||||
@@ -214,7 +114,7 @@ jobs:
|
|||||||
x86_64-linux---proverif-patched:
|
x86_64-linux---proverif-patched:
|
||||||
name: Build x86_64-linux.proverif-patched
|
name: Build x86_64-linux.proverif-patched
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -230,7 +130,7 @@ jobs:
|
|||||||
x86_64-linux---release-package:
|
x86_64-linux---release-package:
|
||||||
name: Build x86_64-linux.release-package
|
name: Build x86_64-linux.release-package
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass-static
|
- x86_64-linux---rosenpass-static
|
||||||
- x86_64-linux---rosenpass-static-oci-image
|
- x86_64-linux---rosenpass-static-oci-image
|
||||||
@@ -249,7 +149,7 @@ jobs:
|
|||||||
# aarch64-linux---release-package:
|
# aarch64-linux---release-package:
|
||||||
# name: Build aarch64-linux.release-package
|
# name: Build aarch64-linux.release-package
|
||||||
# runs-on:
|
# runs-on:
|
||||||
# - ubuntu-latest
|
# - ubicloud-standard-2-arm-ubuntu-2204
|
||||||
# needs:
|
# needs:
|
||||||
# - aarch64-linux---rosenpass-oci-image
|
# - aarch64-linux---rosenpass-oci-image
|
||||||
# - aarch64-linux---rosenpass
|
# - aarch64-linux---rosenpass
|
||||||
@@ -273,7 +173,7 @@ jobs:
|
|||||||
x86_64-linux---rosenpass:
|
x86_64-linux---rosenpass:
|
||||||
name: Build x86_64-linux.rosenpass
|
name: Build x86_64-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -289,12 +189,12 @@ jobs:
|
|||||||
aarch64-linux---rosenpass:
|
aarch64-linux---rosenpass:
|
||||||
name: Build aarch64-linux.rosenpass
|
name: Build aarch64-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-arm-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi-aarch64 binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
@@ -310,12 +210,12 @@ jobs:
|
|||||||
aarch64-linux---rp:
|
aarch64-linux---rp:
|
||||||
name: Build aarch64-linux.rp
|
name: Build aarch64-linux.rp
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-arm-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi-aarch64 binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
@@ -331,7 +231,7 @@ jobs:
|
|||||||
x86_64-linux---rosenpass-oci-image:
|
x86_64-linux---rosenpass-oci-image:
|
||||||
name: Build x86_64-linux.rosenpass-oci-image
|
name: Build x86_64-linux.rosenpass-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
@@ -348,13 +248,13 @@ jobs:
|
|||||||
aarch64-linux---rosenpass-oci-image:
|
aarch64-linux---rosenpass-oci-image:
|
||||||
name: Build aarch64-linux.rosenpass-oci-image
|
name: Build aarch64-linux.rosenpass-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-arm-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- aarch64-linux---rosenpass
|
- aarch64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi-aarch64 binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
@@ -370,7 +270,7 @@ jobs:
|
|||||||
x86_64-linux---rosenpass-static:
|
x86_64-linux---rosenpass-static:
|
||||||
name: Build x86_64-linux.rosenpass-static
|
name: Build x86_64-linux.rosenpass-static
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -386,7 +286,7 @@ jobs:
|
|||||||
x86_64-linux---rp-static:
|
x86_64-linux---rp-static:
|
||||||
name: Build x86_64-linux.rp-static
|
name: Build x86_64-linux.rp-static
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -402,7 +302,7 @@ jobs:
|
|||||||
x86_64-linux---rosenpass-static-oci-image:
|
x86_64-linux---rosenpass-static-oci-image:
|
||||||
name: Build x86_64-linux.rosenpass-static-oci-image
|
name: Build x86_64-linux.rosenpass-static-oci-image
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass-static
|
- x86_64-linux---rosenpass-static
|
||||||
steps:
|
steps:
|
||||||
@@ -419,7 +319,7 @@ jobs:
|
|||||||
x86_64-linux---whitepaper:
|
x86_64-linux---whitepaper:
|
||||||
name: Build x86_64-linux.whitepaper
|
name: Build x86_64-linux.whitepaper
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -435,7 +335,7 @@ jobs:
|
|||||||
x86_64-linux---check:
|
x86_64-linux---check:
|
||||||
name: Run Nix checks on x86_64-linux
|
name: Run Nix checks on x86_64-linux
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
@@ -449,7 +349,7 @@ jobs:
|
|||||||
run: nix flake check . --print-build-logs
|
run: nix flake check . --print-build-logs
|
||||||
x86_64-linux---whitepaper-upload:
|
x86_64-linux---whitepaper-upload:
|
||||||
name: Upload whitepaper x86_64-linux
|
name: Upload whitepaper x86_64-linux
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
if: ${{ github.ref == 'refs/heads/main' }}
|
if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
32
.github/workflows/qc-mac.yaml
vendored
Normal file
32
.github/workflows/qc-mac.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: QC Mac
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo-test-mac:
|
||||||
|
runs-on: warp-macos-13-arm64-6x
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
# liboqs requires quite a lot of stack memory, thus we adjust
|
||||||
|
# the default stack size picked for new threads (which is used
|
||||||
|
# by `cargo test`) to be _big enough_. Setting it to 8 MiB
|
||||||
|
- run: RUST_MIN_STACK=8388608 cargo test --workspace --all-features
|
||||||
28
.github/workflows/qc.yaml
vendored
28
.github/workflows/qc.yaml
vendored
@@ -14,7 +14,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prettier:
|
prettier:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actionsx/prettier@v3
|
- uses: actionsx/prettier@v3
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
shellcheck:
|
shellcheck:
|
||||||
name: Shellcheck
|
name: Shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
@@ -31,14 +31,14 @@ jobs:
|
|||||||
|
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: Rust Format
|
name: Rust Format
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Run Rust Formatting Script
|
- name: Run Rust Formatting Script
|
||||||
run: bash format_rust_code.sh --mode check
|
run: bash format_rust_code.sh --mode check
|
||||||
|
|
||||||
cargo-bench:
|
cargo-bench:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
|
|
||||||
mandoc:
|
mandoc:
|
||||||
name: mandoc
|
name: mandoc
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install mandoc
|
- name: Install mandoc
|
||||||
run: sudo apt-get install -y mandoc
|
run: sudo apt-get install -y mandoc
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
run: doc/check.sh doc/rp.1
|
run: doc/check.sh doc/rp.1
|
||||||
|
|
||||||
cargo-audit:
|
cargo-audit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rs/audit-check@v1
|
- uses: actions-rs/audit-check@v1
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
cargo-clippy:
|
cargo-clippy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
@@ -93,7 +93,7 @@ jobs:
|
|||||||
args: --all-features
|
args: --all-features
|
||||||
|
|
||||||
cargo-doc:
|
cargo-doc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-13]
|
os: [ubicloud-standard-2-ubuntu-2204, warp-macos-13-arm64-6x]
|
||||||
# - ubuntu is x86-64
|
# - ubuntu is x86-64
|
||||||
# - macos-13 is also x86-64 architecture
|
# - macos-13 is also x86-64 architecture
|
||||||
steps:
|
steps:
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
|
|
||||||
cargo-test-nix-devshell-x86_64-linux:
|
cargo-test-nix-devshell-x86_64-linux:
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
@@ -158,7 +158,8 @@ jobs:
|
|||||||
- run: nix develop --command cargo test --workspace --all-features
|
- run: nix develop --command cargo test --workspace --all-features
|
||||||
|
|
||||||
cargo-fuzz:
|
cargo-fuzz:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
|
env:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
@@ -173,7 +174,7 @@ jobs:
|
|||||||
- name: Install nightly toolchain
|
- name: Install nightly toolchain
|
||||||
run: |
|
run: |
|
||||||
rustup toolchain install nightly
|
rustup toolchain install nightly
|
||||||
rustup default nightly
|
rustup override nightly
|
||||||
- name: Install cargo-fuzz
|
- name: Install cargo-fuzz
|
||||||
run: cargo install cargo-fuzz
|
run: cargo install cargo-fuzz
|
||||||
- name: Run fuzzing
|
- name: Run fuzzing
|
||||||
@@ -191,7 +192,7 @@ jobs:
|
|||||||
cargo fuzz run fuzz_vec_secret_alloc_memfdsec_mallocfb -- -max_total_time=5
|
cargo fuzz run fuzz_vec_secret_alloc_memfdsec_mallocfb -- -max_total_time=5
|
||||||
|
|
||||||
codecov:
|
codecov:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: rustup default nightly
|
- run: rustup default nightly
|
||||||
@@ -209,4 +210,5 @@ jobs:
|
|||||||
files: ./target/grcov/lcov
|
files: ./target/grcov/lcov
|
||||||
verbose: true
|
verbose: true
|
||||||
env:
|
env:
|
||||||
|
RUSTUP_TOOLCHAIN: 1.81
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
4
.github/workflows/regressions.yml
vendored
4
.github/workflows/regressions.yml
vendored
@@ -14,7 +14,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
multi-peer:
|
multi-peer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: cargo build --bin rosenpass --release
|
- run: cargo build --bin rosenpass --release
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
||||||
|
|
||||||
boot-race:
|
boot-race:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubicloud-standard-2-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: cargo build --bin rosenpass --release
|
- run: cargo build --bin rosenpass --release
|
||||||
|
|||||||
25
.github/workflows/release.yaml
vendored
25
.github/workflows/release.yaml
vendored
@@ -13,8 +13,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
@@ -34,8 +32,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
@@ -69,3 +65,24 @@ jobs:
|
|||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
files: result/*
|
files: result/*
|
||||||
|
linux-packages:
|
||||||
|
name: Build and upload DEB and RPM packages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: rosenpass
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
- name: Build DEB & RPM package
|
||||||
|
run: |
|
||||||
|
mkdir packages
|
||||||
|
for f in $(nix build .#package-deb .#package-rpm --print-out-paths); do cp "$f" "packages/${f#*-}"; done
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
|
files: |
|
||||||
|
packages/*
|
||||||
|
|||||||
91
.github/workflows/supply-chain.yml
vendored
Normal file
91
.github/workflows/supply-chain.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
name: Supply-Chain
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo-deny:
|
||||||
|
name: Deny dependencies with vulnerabilities or incompatible licenses
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
|
cargo-supply-chain:
|
||||||
|
name: Supply Chain Report
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cache/cargo-supply-chain/
|
||||||
|
key: cargo-supply-chain-cache
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.tool_cache }}/cargo-supply-chain
|
||||||
|
key: cargo-supply-chain-bin
|
||||||
|
- name: Add the tool cache directory to the search path
|
||||||
|
run: echo "${{ runner.tool_cache }}/cargo-supply-chain/bin" >> $GITHUB_PATH
|
||||||
|
- name: Ensure that the tool cache is populated with the cargo-supply-chain binary
|
||||||
|
run: cargo install --root ${{ runner.tool_cache }}/cargo-supply-chain cargo-supply-chain
|
||||||
|
- name: Update data for cargo-supply-chain
|
||||||
|
run: cargo supply-chain update
|
||||||
|
- name: Generate cargo-supply-chain report about publishers
|
||||||
|
run: cargo supply-chain publishers
|
||||||
|
- name: Generate cargo-supply-chain report about crates
|
||||||
|
run: cargo supply-chain crates
|
||||||
|
# The setup for cargo-vet follows the recommendations in the cargo-vet documentation: https://mozilla.github.io/cargo-vet/configuring-ci.html
|
||||||
|
cargo-vet:
|
||||||
|
name: Vet Dependencies
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
key: cargo-vet-cache
|
||||||
|
- name: Install stable toolchain # Since we are running/compiling cargo-vet, we should rely on the stable toolchain.
|
||||||
|
run: |
|
||||||
|
rustup toolchain install stable
|
||||||
|
rustup default stable
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.tool_cache }}/cargo-vet
|
||||||
|
key: cargo-vet-bin
|
||||||
|
- name: Add the tool cache directory to the search path
|
||||||
|
run: echo "${{ runner.tool_cache }}/cargo-vet/bin" >> $GITHUB_PATH
|
||||||
|
- name: Ensure that the tool cache is populated with the cargo-vet binary
|
||||||
|
run: cargo install --root ${{ runner.tool_cache }}/cargo-vet cargo-vet
|
||||||
|
- name: Regenerate vet exemptions for dependabot PRs
|
||||||
|
if: github.actor == 'dependabot[bot]' # Run only for Dependabot PRs
|
||||||
|
run: cargo vet regenerate exemptions
|
||||||
|
- name: Check for changes in case of dependabot PR
|
||||||
|
if: github.actor == 'dependabot[bot]' # Run only for Dependabot PRs
|
||||||
|
run: git diff --exit-code || echo "Changes detected, committing..."
|
||||||
|
- name: Commit and push changes for dependabot PRs
|
||||||
|
if: success() && github.actor == 'dependabot[bot]'
|
||||||
|
run: |
|
||||||
|
git fetch origin ${{ github.head_ref }}
|
||||||
|
git switch ${{ github.head_ref }}
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions@github.com"
|
||||||
|
git add supply-chain/*
|
||||||
|
git commit -m "Regenerate cargo vet exemptions"
|
||||||
|
git push origin ${{ github.head_ref }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Invoke cargo-vet
|
||||||
|
run: cargo vet --locked
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ _markdown_*
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
/output
|
/output
|
||||||
|
.nixos-test-history
|
||||||
|
|||||||
853
Cargo.lock
generated
853
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -50,25 +50,29 @@ log = { version = "0.4.22" }
|
|||||||
clap = { version = "4.5.23", features = ["derive"] }
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
clap_mangen = "0.2.24"
|
clap_mangen = "0.2.24"
|
||||||
clap_complete = "4.5.40"
|
clap_complete = "4.5.40"
|
||||||
serde = { version = "1.0.216", features = ["derive"] }
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
arbitrary = { version = "1.4.1", features = ["derive"] }
|
arbitrary = { version = "1.4.1", features = ["derive"] }
|
||||||
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
|
anyhow = { version = "1.0.95", features = ["backtrace", "std"] }
|
||||||
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
||||||
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||||
'classic_mceliece',
|
'classic_mceliece',
|
||||||
'kyber',
|
'kyber',
|
||||||
] }
|
] }
|
||||||
blake2 = "0.10.6"
|
blake2 = "0.10.6"
|
||||||
|
sha3 = "0.10.8"
|
||||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"heapless",
|
"heapless",
|
||||||
] }
|
] }
|
||||||
zerocopy = { version = "0.7.35", features = ["derive"] }
|
zerocopy = { version = "0.7.35", features = ["derive"] }
|
||||||
home = "0.5.9"
|
home = "=0.5.9" # 5.11 requires rustc 1.81
|
||||||
derive_builder = "0.20.1"
|
derive_builder = "0.20.1"
|
||||||
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] }
|
||||||
postcard = { version = "1.1.1", features = ["alloc"] }
|
postcard = { version = "1.1.1", features = ["alloc"] }
|
||||||
libcrux = { version = "0.0.2-pre.2" }
|
libcrux = { version = "0.0.2-pre.2" }
|
||||||
|
libcrux-chacha20poly1305 = { version = "0.0.2-beta.3" }
|
||||||
|
libcrux-ml-kem = { version = "0.0.2-beta.3" }
|
||||||
|
libcrux-blake2 = { git = "https://github.com/cryspen/libcrux.git", rev = "10ce653e9476"}
|
||||||
hex-literal = { version = "0.4.1" }
|
hex-literal = { version = "0.4.1" }
|
||||||
hex = { version = "0.4.3" }
|
hex = { version = "0.4.3" }
|
||||||
heck = { version = "0.5.0" }
|
heck = { version = "0.5.0" }
|
||||||
@@ -82,7 +86,7 @@ tempfile = "3"
|
|||||||
stacker = "0.1.17"
|
stacker = "0.1.17"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
test_bin = "0.4.0"
|
test_bin = "0.4.0"
|
||||||
criterion = "0.4.0"
|
criterion = "0.5.1"
|
||||||
allocator-api2-tests = "0.2.15"
|
allocator-api2-tests = "0.2.15"
|
||||||
procspawn = { version = "1.0.1", features = ["test-support"] }
|
procspawn = { version = "1.0.1", features = ["test-support"] }
|
||||||
|
|
||||||
@@ -91,3 +95,6 @@ procspawn = { version = "1.0.1", features = ["test-support"] }
|
|||||||
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||||
command-fds = "0.2.3"
|
command-fds = "0.2.3"
|
||||||
rustix = { version = "0.38.42", features = ["net", "fs", "process"] }
|
rustix = { version = "0.38.42", features = ["net", "fs", "process"] }
|
||||||
|
futures = "0.3"
|
||||||
|
futures-util = "0.3"
|
||||||
|
x25519-dalek = "2"
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ description = "Rosenpass internal traits for cryptographic primitives"
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
rosenpass-to = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rosenpass-oqs = { workspace = true }
|
rosenpass-oqs = { workspace = true }
|
||||||
rosenpass-secret-memory = { workspace = true }
|
rosenpass-secret-memory = { workspace = true }
|
||||||
anyhow = {workspace = true}
|
anyhow = { workspace = true }
|
||||||
|
|||||||
137
cipher-traits/src/algorithms.rs
Normal file
137
cipher-traits/src/algorithms.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
//! This module contains the traits for all the cryptographic algorithms used throughout Rosenpass.
|
||||||
|
//! These traits are marker traits that signal intent. They can also be used for trait objects.
|
||||||
|
|
||||||
|
/// Constants and trait for the Incorrect HMAC over Blake2b, with 256 key and hash length.
|
||||||
|
pub mod keyed_hash_incorrect_hmac_blake2b {
|
||||||
|
use crate::primitives::keyed_hash::*;
|
||||||
|
|
||||||
|
// These constants describe how they are used here, not what the algorithm defines.
|
||||||
|
|
||||||
|
/// The key length used in [`KeyedHashIncorrectHmacBlake2b`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The hash length used in [`KeyedHashIncorrectHmacBlake2b`].
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`KeyedHash`] that is an incorrect HMAC over Blake2 (a custom Rosenpass construction)
|
||||||
|
pub trait KeyedHashIncorrectHmacBlake2b: KeyedHash<KEY_LEN, HASH_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for Blake2b, with 256 key and hash length.
|
||||||
|
pub mod keyed_hash_blake2b {
|
||||||
|
use crate::primitives::keyed_hash::*;
|
||||||
|
|
||||||
|
// These constants describe how they are used here, not what the algorithm defines.
|
||||||
|
|
||||||
|
/// The key length used in [`KeyedHashBlake2b`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The hash length used in [`KeyedHashBlake2b`].
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`KeyedHash`] that is Blake2b
|
||||||
|
pub trait KeyedHashBlake2b: KeyedHash<KEY_LEN, HASH_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for SHAKE256, with 256 key and hash length.
|
||||||
|
pub mod keyed_hash_shake256 {
|
||||||
|
use crate::primitives::keyed_hash::*;
|
||||||
|
|
||||||
|
// These constants describe how they are used here, not what the algorithm defines.
|
||||||
|
|
||||||
|
/// The key length used in [`KeyedHashShake256`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The hash length used in [`KeyedHashShake256`].
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`KeyedHash`] that is SHAKE256.
|
||||||
|
pub trait KeyedHashShake256: KeyedHash<KEY_LEN, HASH_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the ChaCha20Poly1305 AEAD
|
||||||
|
pub mod aead_chacha20poly1305 {
|
||||||
|
use crate::primitives::aead::*;
|
||||||
|
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc7539#section-2.8
|
||||||
|
|
||||||
|
/// The key length used in [`AeadChaCha20Poly1305`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The nonce length used in [`AeadChaCha20Poly1305`].
|
||||||
|
pub const NONCE_LEN: usize = 12;
|
||||||
|
/// The tag length used in [`AeadChaCha20Poly1305`].
|
||||||
|
pub const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
|
/// An [`Aead`] that is ChaCha20Poly1305.
|
||||||
|
pub trait AeadChaCha20Poly1305: Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the XChaCha20Poly1305 AEAD (i.e. ChaCha20Poly1305 with extended nonce
|
||||||
|
/// lengths)
|
||||||
|
pub mod aead_xchacha20poly1305 {
|
||||||
|
use crate::primitives::aead::*;
|
||||||
|
|
||||||
|
// See https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03
|
||||||
|
|
||||||
|
/// The key length used in [`AeadXChaCha20Poly1305`].
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
/// The nonce length used in [`AeadXChaCha20Poly1305`].
|
||||||
|
pub const NONCE_LEN: usize = 24;
|
||||||
|
/// The tag length used in [`AeadXChaCha20Poly1305`].
|
||||||
|
pub const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
|
/// An [`Aead`] that is XChaCha20Poly1305.
|
||||||
|
pub trait AeadXChaCha20Poly1305: Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the Kyber512 KEM
|
||||||
|
pub mod kem_kyber512 {
|
||||||
|
use crate::primitives::kem::*;
|
||||||
|
|
||||||
|
// page 39 of https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf
|
||||||
|
// (which is ml-kem instead of kyber, but it's the same)
|
||||||
|
|
||||||
|
/// The secret key length used in [`KemKyber512`].
|
||||||
|
pub const SK_LEN: usize = 1632;
|
||||||
|
|
||||||
|
/// The public key length used in [`KemKyber512`].
|
||||||
|
pub const PK_LEN: usize = 800;
|
||||||
|
|
||||||
|
/// The ciphertext length used in [`KemKyber512`].
|
||||||
|
pub const CT_LEN: usize = 768;
|
||||||
|
|
||||||
|
/// The shared key length used in [`KemKyber512`].
|
||||||
|
pub const SHK_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`Kem`] that is Kyber512.
|
||||||
|
pub trait KemKyber512: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants and trait for the Classic McEliece 460896 KEM
|
||||||
|
pub mod kem_classic_mceliece460896 {
|
||||||
|
use crate::primitives::kem::*;
|
||||||
|
|
||||||
|
// page 6 of https://classic.mceliece.org/mceliece-impl-20221023.pdf
|
||||||
|
|
||||||
|
/// The secret key length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const SK_LEN: usize = 13608;
|
||||||
|
|
||||||
|
/// The public key length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const PK_LEN: usize = 524160;
|
||||||
|
|
||||||
|
/// The ciphertext length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const CT_LEN: usize = 156;
|
||||||
|
|
||||||
|
/// The shared key length used in [`KemClassicMceliece460896`].
|
||||||
|
pub const SHK_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// A [`Kem`] that is ClassicMceliece460896.
|
||||||
|
pub trait KemClassicMceliece460896: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use aead_chacha20poly1305::AeadChaCha20Poly1305;
|
||||||
|
pub use aead_xchacha20poly1305::AeadXChaCha20Poly1305;
|
||||||
|
|
||||||
|
pub use kem_classic_mceliece460896::KemClassicMceliece460896;
|
||||||
|
pub use kem_kyber512::KemKyber512;
|
||||||
|
|
||||||
|
pub use keyed_hash_blake2b::KeyedHashBlake2b;
|
||||||
|
pub use keyed_hash_incorrect_hmac_blake2b::KeyedHashIncorrectHmacBlake2b;
|
||||||
|
pub use keyed_hash_shake256::KeyedHashShake256;
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
mod kem;
|
//! This trait contains traits, constants and wrappers that provid= the interface between Rosenpass
|
||||||
pub use kem::Kem;
|
//! as a consumer of cryptographic libraries and the implementations of cryptographic algorithms.
|
||||||
|
|
||||||
|
pub mod algorithms;
|
||||||
|
pub mod primitives;
|
||||||
|
|||||||
10
cipher-traits/src/primitives.rs
Normal file
10
cipher-traits/src/primitives.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//! Traits for cryptographic primitives used in Rosenpass, specifically KEM, AEAD and keyed
|
||||||
|
//! hashing.
|
||||||
|
|
||||||
|
pub(crate) mod aead;
|
||||||
|
pub(crate) mod kem;
|
||||||
|
pub(crate) mod keyed_hash;
|
||||||
|
|
||||||
|
pub use aead::{Aead, AeadWithNonceInCiphertext, Error as AeadError};
|
||||||
|
pub use kem::{Error as KemError, Kem};
|
||||||
|
pub use keyed_hash::*;
|
||||||
175
cipher-traits/src/primitives/aead.rs
Normal file
175
cipher-traits/src/primitives/aead.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use rosenpass_to::{ops::copy_slice, To as _};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Models authenticated encryption with assiciated data (AEAD) functionality.
|
||||||
|
///
|
||||||
|
/// The methods of this trait take a `&self` argument as a receiver. This has two reasons:
|
||||||
|
/// 1. It makes type inference a lot smoother
|
||||||
|
/// 2. It allows to use the functionality through a trait object or having an enum that has
|
||||||
|
/// variants for multiple options (like e.g. the `KeyedHash` enum in `rosenpass-ciphers`).
|
||||||
|
///
|
||||||
|
/// Since the caller needs an instance of the type to use the functionality, implementors are
|
||||||
|
/// adviced to implement the [`Default`] trait where possible.
|
||||||
|
///
|
||||||
|
/// Example for encrypting a message with a specific [`Aead`] instance:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Aead;
|
||||||
|
///
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const NONCE_LEN: usize = 12;
|
||||||
|
/// const TAG_LEN: usize = 16;
|
||||||
|
///
|
||||||
|
/// fn encrypt_message_given_an_aead<AeadImpl>(
|
||||||
|
/// aead: &AeadImpl,
|
||||||
|
/// msg: &str,
|
||||||
|
/// nonce: &[u8; NONCE_LEN],
|
||||||
|
/// encrypted: &mut [u8]
|
||||||
|
/// ) where AeadImpl: Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {
|
||||||
|
/// let key = [0u8; KEY_LEN]; // This is not a secure key!
|
||||||
|
/// let ad = b""; // we don't need associated data here
|
||||||
|
/// aead.encrypt(encrypted, &key, nonce, ad, msg.as_bytes()).unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If only the type (but no instance) is available, then we can still encrypt, as long as the type
|
||||||
|
/// also is [`Default`]:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Aead;
|
||||||
|
///
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const NONCE_LEN: usize = 12;
|
||||||
|
/// const TAG_LEN: usize = 16;
|
||||||
|
///
|
||||||
|
/// fn encrypt_message_without_aead<AeadImpl>(
|
||||||
|
/// msg: &str,
|
||||||
|
/// nonce: &[u8; NONCE_LEN],
|
||||||
|
/// encrypted: &mut [u8]
|
||||||
|
/// ) where AeadImpl: Default + Aead<KEY_LEN, NONCE_LEN, TAG_LEN> {
|
||||||
|
/// let key = [0u8; KEY_LEN]; // This is not a secure key!
|
||||||
|
/// let ad = b""; // we don't need associated data here
|
||||||
|
/// AeadImpl::default().encrypt(encrypted, &key, nonce, ad, msg.as_bytes()).unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait Aead<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> {
|
||||||
|
const KEY_LEN: usize = KEY_LEN;
|
||||||
|
const NONCE_LEN: usize = NONCE_LEN;
|
||||||
|
const TAG_LEN: usize = TAG_LEN;
|
||||||
|
|
||||||
|
/// Encrypts `plaintext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `ciphertext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` longer than `plaintext`.
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Decrypts `ciphertexttext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `plaintext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` longer than `plaintext`.
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides an AEAD API where the nonce is part of the ciphertext.
|
||||||
|
///
|
||||||
|
/// The old xaead API had the ciphertext begin with the `nonce`. In order to not having to change
|
||||||
|
/// the calling code too much, we add a wrapper trait that provides this API and implement it for
|
||||||
|
/// all AEAD.
|
||||||
|
pub trait AeadWithNonceInCiphertext<
|
||||||
|
const KEY_LEN: usize,
|
||||||
|
const NONCE_LEN: usize,
|
||||||
|
const TAG_LEN: usize,
|
||||||
|
>: Aead<KEY_LEN, NONCE_LEN, TAG_LEN>
|
||||||
|
{
|
||||||
|
/// Encrypts `plaintext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `ciphertext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` + `NONCE_LEN` longer than `plaintext`.
|
||||||
|
fn encrypt_with_nonce_in_ctxt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < NONCE_LEN + TAG_LEN
|
||||||
|
|| ciphertext.len() - TAG_LEN - NONCE_LEN < plaintext.len()
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (n, rest) = ciphertext.split_at_mut(NONCE_LEN);
|
||||||
|
copy_slice(nonce).to(n);
|
||||||
|
|
||||||
|
self.encrypt(rest, key, nonce, ad, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts `ciphertexttext` using the given `key` and `nonce`, taking into account the additional
|
||||||
|
/// data `ad` and writes the result into `plaintext`.
|
||||||
|
///
|
||||||
|
/// `ciphertext` must be exactly `TAG_LEN` + `NONCE_LEN` longer than `plaintext`.
|
||||||
|
fn decrypt_with_nonce_in_ctxt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < NONCE_LEN + TAG_LEN
|
||||||
|
|| ciphertext.len() - TAG_LEN - NONCE_LEN < plaintext.len()
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (nonce, rest) = ciphertext.split_at(NONCE_LEN);
|
||||||
|
// We know this should be the right length (we just split it), and everything else would be
|
||||||
|
// very unexpected.
|
||||||
|
let nonce = nonce.try_into().map_err(|_| Error::InternalError)?;
|
||||||
|
|
||||||
|
self.decrypt(plaintext, key, nonce, ad, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const KEY_LEN: usize,
|
||||||
|
const NONCE_LEN: usize,
|
||||||
|
const TAG_LEN: usize,
|
||||||
|
T: Aead<KEY_LEN, NONCE_LEN, TAG_LEN>,
|
||||||
|
> AeadWithNonceInCiphertext<KEY_LEN, NONCE_LEN, TAG_LEN> for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error returned by AEAD operations
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An internal error occurred. This should never be happen and indicates an error in the
|
||||||
|
/// AEAD implementation.
|
||||||
|
#[error("internal error")]
|
||||||
|
InternalError,
|
||||||
|
|
||||||
|
/// Could not decrypt a message because the message is not a valid ciphertext for the given
|
||||||
|
/// key.
|
||||||
|
#[error("decryption error")]
|
||||||
|
DecryptError,
|
||||||
|
|
||||||
|
/// The provided buffers have the wrong lengths.
|
||||||
|
#[error("buffers have invalid length")]
|
||||||
|
InvalidLengths,
|
||||||
|
}
|
||||||
212
cipher-traits/src/primitives/kem.rs
Normal file
212
cipher-traits/src/primitives/kem.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
|
||||||
|
//!
|
||||||
|
//! KEMs are the interface provided by almost all post-quantum
|
||||||
|
//! secure key exchange mechanisms.
|
||||||
|
//!
|
||||||
|
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
|
||||||
|
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
|
||||||
|
//! encapsulation.
|
||||||
|
//!
|
||||||
|
//! The [Kem] Trait describes the basic API offered by a Key Encapsulation
|
||||||
|
//! Mechanism. Two implementations for it are provided:
|
||||||
|
//! [Kyber512](../../rosenpass_oqs/kyber_512/enum.Kyber512.html) and
|
||||||
|
//! [ClassicMceliece460896](../../rosenpass_oqs/classic_mceliece_460896/enum.ClassicMceliece460896.html).
|
||||||
|
//!
|
||||||
|
//! An example where Alice generates a keypair and gives her public key to Bob, for Bob to
|
||||||
|
//! encapsulate a symmetric key and Alice to decapsulate it would look as follows.
|
||||||
|
//! In the example, we are using Kyber512, but any KEM that correctly implements the [Kem]
|
||||||
|
//! trait could be used as well.
|
||||||
|
//!```rust
|
||||||
|
//! use rosenpass_cipher_traits::primitives::Kem;
|
||||||
|
//! use rosenpass_oqs::Kyber512;
|
||||||
|
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||||
|
//!
|
||||||
|
//! type MyKem = Kyber512;
|
||||||
|
//! secret_policy_use_only_malloc_secrets();
|
||||||
|
//! let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
|
||||||
|
//! let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
|
||||||
|
//! MyKem::default().keygen(alice_sk.secret_mut(), &mut alice_pk)?;
|
||||||
|
//!
|
||||||
|
//! let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||||
|
//! let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
|
||||||
|
//! MyKem::default().encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||||
|
//!
|
||||||
|
//! let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||||
|
//! MyKem::default().decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||||
|
//!
|
||||||
|
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||||
|
//! # Ok::<(), anyhow::Error>(())
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
//! Implementing the [Kem]-trait for a KEM is easy. Mostly, you must format the KEM's
|
||||||
|
//! keys, and ciphertext as `u8` slices. Below, we provide an example for how the trait can
|
||||||
|
//! be implemented using a **HORRIBLY INSECURE** DummyKem that only uses static values for keys
|
||||||
|
//! and ciphertexts as an example.
|
||||||
|
//!```rust
|
||||||
|
//!# use rosenpass_cipher_traits::primitives::{Kem, KemError as Error};
|
||||||
|
//!
|
||||||
|
//! struct DummyKem {}
|
||||||
|
//! impl Kem<1,1,1,1> for DummyKem {
|
||||||
|
//!
|
||||||
|
//! // For this DummyKem, we will use a single `u8` for everything
|
||||||
|
//! const SK_LEN: usize = 1;
|
||||||
|
//! const PK_LEN: usize = 1;
|
||||||
|
//! const CT_LEN: usize = 1;
|
||||||
|
//! const SHK_LEN: usize = 1;
|
||||||
|
//!
|
||||||
|
//! fn keygen(&self, sk: &mut [u8;1], pk: &mut [u8;1]) -> Result<(), Error> {
|
||||||
|
//! sk[0] = 42;
|
||||||
|
//! pk[0] = 21;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn encaps(&self, shk: &mut [u8;1], ct: &mut [u8;1], pk: &[u8;1]) -> Result<(), Error> {
|
||||||
|
//! if pk[0] != 21 {
|
||||||
|
//! return Err(Error::InvalidArgument);
|
||||||
|
//! }
|
||||||
|
//! ct[0] = 7;
|
||||||
|
//! shk[0] = 17;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn decaps(&self, shk: &mut [u8;1 ], sk: &[u8;1], ct: &[u8;1]) -> Result<(), Error> {
|
||||||
|
//! if sk[0] != 42 {
|
||||||
|
//! return Err(Error::InvalidArgument);
|
||||||
|
//! }
|
||||||
|
//! if ct[0] != 7 {
|
||||||
|
//! return Err(Error::InvalidArgument);
|
||||||
|
//! }
|
||||||
|
//! shk[0] = 17;
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl Default for DummyKem {
|
||||||
|
//! fn default() -> Self {
|
||||||
|
//! Self{}
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||||
|
//! #
|
||||||
|
//! # type MyKem = DummyKem;
|
||||||
|
//! # secret_policy_use_only_malloc_secrets();
|
||||||
|
//! # let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
|
||||||
|
//! # let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
|
||||||
|
//! # MyKem::default().keygen(alice_sk.secret_mut(), &mut alice_pk)?;
|
||||||
|
//!
|
||||||
|
//! # let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||||
|
//! # let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
|
||||||
|
//! # MyKem::default().encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||||
|
//! #
|
||||||
|
//! # let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||||
|
//! # MyKem::default().decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||||
|
//! #
|
||||||
|
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||||
|
//! #
|
||||||
|
//! # Ok::<(), Error>(())
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Key Encapsulation Mechanism
|
||||||
|
///
|
||||||
|
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
||||||
|
/// decapsulation. The parameters are made available as associated constants for convenience.
|
||||||
|
///
|
||||||
|
/// The methods of this trait take a `&self` argument as a receiver. This has two reasons:
|
||||||
|
/// 1. It makes type inference a lot smoother
|
||||||
|
/// 2. It allows to use the functionality through a trait object or having an enum that has
|
||||||
|
/// variants for multiple options (like e.g. the `KeyedHash` enum in `rosenpass-ciphers`).
|
||||||
|
///
|
||||||
|
/// Since the caller needs an instance of the type to use the functionality, implementors are
|
||||||
|
/// adviced to implement the [`Default`] trait where possible.
|
||||||
|
///
|
||||||
|
/// Example for encrypting a message with a specific [`Kem`] instance:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Kem;
|
||||||
|
///
|
||||||
|
/// const SK_LEN: usize = 1632;
|
||||||
|
/// const PK_LEN: usize = 800;
|
||||||
|
/// const CT_LEN: usize = 768;
|
||||||
|
/// const SHK_LEN: usize = 32;
|
||||||
|
///
|
||||||
|
/// fn encaps_given_a_kem<KemImpl>(
|
||||||
|
/// kem: &KemImpl,
|
||||||
|
/// pk: &[u8; PK_LEN],
|
||||||
|
/// ct: &mut [u8; CT_LEN]
|
||||||
|
/// ) -> [u8; SHK_LEN] where KemImpl: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN>{
|
||||||
|
/// let mut shk = [0u8; SHK_LEN];
|
||||||
|
/// kem.encaps(&mut shk, ct, pk).unwrap();
|
||||||
|
/// shk
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If only the type (but no instance) is available, then we can still use the trait, as long as
|
||||||
|
/// the type also is [`Default`]:
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_cipher_traits::primitives::Kem;
|
||||||
|
///
|
||||||
|
/// const SK_LEN: usize = 1632;
|
||||||
|
/// const PK_LEN: usize = 800;
|
||||||
|
/// const CT_LEN: usize = 768;
|
||||||
|
/// const SHK_LEN: usize = 32;
|
||||||
|
///
|
||||||
|
/// fn encaps_without_kem<KemImpl>(
|
||||||
|
/// pk: &[u8; PK_LEN],
|
||||||
|
/// ct: &mut [u8; CT_LEN]
|
||||||
|
/// ) -> [u8; SHK_LEN]
|
||||||
|
/// where KemImpl: Default + Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> {
|
||||||
|
/// let mut shk = [0u8; SHK_LEN];
|
||||||
|
/// KemImpl::default().encaps(&mut shk, ct, pk).unwrap();
|
||||||
|
/// shk
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait Kem<const SK_LEN: usize, const PK_LEN: usize, const CT_LEN: usize, const SHK_LEN: usize> {
|
||||||
|
/// The length of the secret (decapsulation) key.
|
||||||
|
const SK_LEN: usize = SK_LEN;
|
||||||
|
|
||||||
|
/// The length of the public (encapsulation) key.
|
||||||
|
const PK_LEN: usize = PK_LEN;
|
||||||
|
|
||||||
|
/// The length of the ciphertext.
|
||||||
|
const CT_LEN: usize = CT_LEN;
|
||||||
|
|
||||||
|
/// The legnth of the resulting shared key.
|
||||||
|
const SHK_LEN: usize = SHK_LEN;
|
||||||
|
|
||||||
|
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
|
||||||
|
///
|
||||||
|
/// `keygen() -> sk, pk`
|
||||||
|
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
|
||||||
|
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
|
||||||
|
///
|
||||||
|
/// `encaps(pk) -> shk, ct`
|
||||||
|
fn encaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
ct: &mut [u8; CT_LEN],
|
||||||
|
pk: &[u8; PK_LEN],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
|
||||||
|
/// (`shk`)
|
||||||
|
///
|
||||||
|
/// `decaps(sk, ct) -> shk`
|
||||||
|
fn decaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
sk: &[u8; SK_LEN],
|
||||||
|
ct: &[u8; CT_LEN],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("invalid argument")]
|
||||||
|
InvalidArgument,
|
||||||
|
#[error("internal error")]
|
||||||
|
InternalError,
|
||||||
|
}
|
||||||
159
cipher-traits/src/primitives/keyed_hash.rs
Normal file
159
cipher-traits/src/primitives/keyed_hash.rs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// Models a keyed hash function using an associated function (i.e. without `&self` receiver).
|
||||||
|
pub trait KeyedHash<const KEY_LEN: usize, const HASH_LEN: usize> {
|
||||||
|
/// The error type used to signal what went wrong.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Performs a keyed hash using `key` and `data` and writes the output to `out`
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Models a keyed hash function using a method (i.e. with a `&self` receiver).
|
||||||
|
///
|
||||||
|
/// This makes type inference easier, but also requires having a [`KeyedHashInstance`] value,
|
||||||
|
/// instead of just the [`KeyedHash`] type.
|
||||||
|
pub trait KeyedHashInstance<const KEY_LEN: usize, const HASH_LEN: usize> {
|
||||||
|
/// The error type used to signal what went wrong.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Performs a keyed hash using `key` and `data` and writes the output to `out`
|
||||||
|
fn keyed_hash(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a helper to allow for type parameter inference when calling functions
|
||||||
|
/// that need a [KeyedHash].
|
||||||
|
///
|
||||||
|
/// Really just binds the [KeyedHash] trait to a dummy variable, so the type of this dummy variable
|
||||||
|
/// can be used for type inference. Less typing work.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct InferKeyedHash<Static, const KEY_LEN: usize, const HASH_LEN: usize>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, HASH_LEN>,
|
||||||
|
{
|
||||||
|
pub _phantom_keyed_hasher: PhantomData<*const Static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const HASH_LEN: usize> InferKeyedHash<Static, KEY_LEN, HASH_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, HASH_LEN>,
|
||||||
|
{
|
||||||
|
pub const KEY_LEN: usize = KEY_LEN;
|
||||||
|
pub const HASH_LEN: usize = HASH_LEN;
|
||||||
|
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_phantom_keyed_hasher: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This just forwards to [KeyedHash::keyed_hash] of the type parameter `Static`
|
||||||
|
fn keyed_hash_internal<'a>(
|
||||||
|
&self,
|
||||||
|
key: &'a [u8; KEY_LEN],
|
||||||
|
data: &'a [u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Static::Error> {
|
||||||
|
Static::keyed_hash(key, data, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the key length of the keyed hash function.
|
||||||
|
pub const fn key_len(self) -> usize {
|
||||||
|
Self::KEY_LEN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the hash length of the keyed hash function.
|
||||||
|
pub const fn hash_len(self) -> usize {
|
||||||
|
Self::HASH_LEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize, Static: KeyedHash<KEY_LEN, HASH_LEN>>
|
||||||
|
KeyedHashInstance<KEY_LEN, HASH_LEN> for InferKeyedHash<Static, KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
type Error = Static::Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Static::Error> {
|
||||||
|
self.keyed_hash_internal(key, data, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper traits /////////////////////////////////////////////
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const OUT_LEN: usize> Default
|
||||||
|
for InferKeyedHash<Static, KEY_LEN, OUT_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, OUT_LEN>,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const OUT_LEN: usize> Clone
|
||||||
|
for InferKeyedHash<Static, KEY_LEN, OUT_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, OUT_LEN>,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Static, const KEY_LEN: usize, const OUT_LEN: usize> Copy
|
||||||
|
for InferKeyedHash<Static, KEY_LEN, OUT_LEN>
|
||||||
|
where
|
||||||
|
Static: KeyedHash<KEY_LEN, OUT_LEN>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
use rosenpass_to::{with_destination, To};
|
||||||
|
|
||||||
|
/// Extends the [`KeyedHash`] trait with a [`To`]-flavoured function.
|
||||||
|
pub trait KeyedHashTo<const KEY_LEN: usize, const HASH_LEN: usize>:
|
||||||
|
KeyedHash<KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
fn keyed_hash_to(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
) -> impl To<[u8; HASH_LEN], Result<(), Self::Error>> {
|
||||||
|
with_destination(|out| Self::keyed_hash(key, data, out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize, T: KeyedHash<KEY_LEN, HASH_LEN>>
|
||||||
|
KeyedHashTo<KEY_LEN, HASH_LEN> for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extends the [`KeyedHashInstance`] trait with a [`To`]-flavoured function.
|
||||||
|
pub trait KeyedHashInstanceTo<const KEY_LEN: usize, const HASH_LEN: usize>:
|
||||||
|
KeyedHashInstance<KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
fn keyed_hash_to(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
) -> impl To<[u8; HASH_LEN], Result<(), Self::Error>> {
|
||||||
|
with_destination(|out| self.keyed_hash(key, data, out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize, T: KeyedHashInstance<KEY_LEN, HASH_LEN>>
|
||||||
|
KeyedHashInstanceTo<KEY_LEN, HASH_LEN> for T
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -8,9 +8,22 @@ description = "Rosenpass internal ciphers and other cryptographic primitives use
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_libcrux = ["dep:libcrux"]
|
experiment_libcrux_all = [
|
||||||
|
"experiment_libcrux_blake2",
|
||||||
|
"experiment_libcrux_chachapoly",
|
||||||
|
"experiment_libcrux_chachapoly_test",
|
||||||
|
"experiment_libcrux_kyber",
|
||||||
|
]
|
||||||
|
experiment_libcrux_blake2 = ["dep:libcrux-blake2", "dep:thiserror"]
|
||||||
|
experiment_libcrux_chachapoly = ["dep:libcrux-chacha20poly1305"]
|
||||||
|
experiment_libcrux_chachapoly_test = [
|
||||||
|
"experiment_libcrux_chachapoly",
|
||||||
|
"dep:libcrux",
|
||||||
|
]
|
||||||
|
experiment_libcrux_kyber = ["dep:libcrux-ml-kem", "dep:rand"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
@@ -19,8 +32,21 @@ rosenpass-constant-time = { workspace = true }
|
|||||||
rosenpass-secret-memory = { workspace = true }
|
rosenpass-secret-memory = { workspace = true }
|
||||||
rosenpass-oqs = { workspace = true }
|
rosenpass-oqs = { workspace = true }
|
||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
|
rosenpass-cipher-traits = { workspace = true }
|
||||||
static_assertions = { workspace = true }
|
static_assertions = { workspace = true }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
chacha20poly1305 = { workspace = true }
|
chacha20poly1305 = { workspace = true }
|
||||||
blake2 = { workspace = true }
|
blake2 = { workspace = true }
|
||||||
|
sha3 = { workspace = true }
|
||||||
|
rand = { workspace = true, optional = true }
|
||||||
|
thiserror = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
libcrux-chacha20poly1305 = { workspace = true, optional = true }
|
||||||
|
libcrux-blake2 = { workspace = true, optional = true }
|
||||||
|
libcrux-ml-kem = { workspace = true, optional = true, features = ["kyber"] }
|
||||||
|
|
||||||
|
# this one is only used in testing, so it requires the `experiment_libcrux_chachapoly_test` feature.
|
||||||
libcrux = { workspace = true, optional = true }
|
libcrux = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = { workspace = true }
|
||||||
|
|||||||
@@ -1,74 +1,75 @@
|
|||||||
|
//!
|
||||||
|
//!```rust
|
||||||
|
//! # use rosenpass_ciphers::hash_domain::{HashDomain, HashDomainNamespace, SecretHashDomain, SecretHashDomainNamespace};
|
||||||
|
//! use rosenpass_ciphers::KeyedHash;
|
||||||
|
//! use rosenpass_secret_memory::Secret;
|
||||||
|
//! # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
|
||||||
|
//!
|
||||||
|
//! const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
|
||||||
|
//! // create use once hash domain for the protocol identifier
|
||||||
|
//! let mut hash_domain = HashDomain::zero(KeyedHash::keyed_shake256());
|
||||||
|
//! hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
|
||||||
|
//! // upgrade to reusable hash domain
|
||||||
|
//! let hash_domain_namespace: HashDomainNamespace = hash_domain.dup();
|
||||||
|
//! // derive new key
|
||||||
|
//! let key_identifier = "my_key_identifier";
|
||||||
|
//! let key = hash_domain_namespace.mix(key_identifier.as_bytes())?.into_value();
|
||||||
|
//! // derive a new key based on a secret
|
||||||
|
//! const MY_SECRET_LEN: usize = 21;
|
||||||
|
//! let my_secret_bytes = "my super duper secret".as_bytes();
|
||||||
|
//! let my_secret: Secret<21> = Secret::from_slice("my super duper secret".as_bytes());
|
||||||
|
//! let secret_hash_domain: SecretHashDomain = hash_domain_namespace.mix_secret(my_secret)?;
|
||||||
|
//! // derive a new key based on the secret key
|
||||||
|
//! let new_key_identifier = "my_new_key_identifier".as_bytes();
|
||||||
|
//! let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
|
||||||
|
//!
|
||||||
|
//! # Ok::<(), anyhow::Error>(())
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass_secret_memory::Secret;
|
use rosenpass_secret_memory::Secret;
|
||||||
use rosenpass_to::To;
|
use rosenpass_to::To as _;
|
||||||
|
|
||||||
use crate::keyed_hash as hash;
|
pub use crate::{KeyedHash, KEY_LEN};
|
||||||
|
|
||||||
pub use hash::KEY_LEN;
|
use rosenpass_cipher_traits::primitives::KeyedHashInstanceTo;
|
||||||
|
|
||||||
///
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::hash_domain::{HashDomain, HashDomainNamespace, SecretHashDomain, SecretHashDomainNamespace};
|
|
||||||
/// use rosenpass_secret_memory::Secret;
|
|
||||||
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
|
|
||||||
///
|
|
||||||
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
|
|
||||||
/// // create use once hash domain for the protocol identifier
|
|
||||||
/// let mut hash_domain = HashDomain::zero();
|
|
||||||
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
|
|
||||||
/// // upgrade to reusable hash domain
|
|
||||||
/// let hash_domain_namespace: HashDomainNamespace = hash_domain.dup();
|
|
||||||
/// // derive new key
|
|
||||||
/// let key_identifier = "my_key_identifier";
|
|
||||||
/// let key = hash_domain_namespace.mix(key_identifier.as_bytes())?.into_value();
|
|
||||||
/// // derive a new key based on a secret
|
|
||||||
/// const MY_SECRET_LEN: usize = 21;
|
|
||||||
/// let my_secret_bytes = "my super duper secret".as_bytes();
|
|
||||||
/// let my_secret: Secret<21> = Secret::from_slice("my super duper secret".as_bytes());
|
|
||||||
/// let secret_hash_domain: SecretHashDomain = hash_domain_namespace.mix_secret(my_secret)?;
|
|
||||||
/// // derive a new key based on the secret key
|
|
||||||
/// let new_key_identifier = "my_new_key_identifier".as_bytes();
|
|
||||||
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
|
|
||||||
///
|
|
||||||
/// # Ok::<(), anyhow::Error>(())
|
|
||||||
///```
|
|
||||||
///
|
|
||||||
|
|
||||||
// TODO Use a proper Dec interface
|
// TODO Use a proper Dec interface
|
||||||
/// A use-once hash domain for a specified key that can be used directly.
|
/// A use-once hash domain for a specified key that can be used directly.
|
||||||
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
|
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
|
||||||
/// use [SecretHashDomain] instead.
|
/// use [SecretHashDomain] instead.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HashDomain([u8; KEY_LEN]);
|
pub struct HashDomain([u8; KEY_LEN], KeyedHash);
|
||||||
/// A reusable hash domain for a namespace identified by the key.
|
/// A reusable hash domain for a namespace identified by the key.
|
||||||
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
|
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
|
||||||
/// use [SecretHashDomainNamespace] instead.
|
/// use [SecretHashDomainNamespace] instead.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HashDomainNamespace([u8; KEY_LEN]);
|
pub struct HashDomainNamespace([u8; KEY_LEN], KeyedHash);
|
||||||
/// A use-once hash domain for a specified key that can be used directly
|
/// A use-once hash domain for a specified key that can be used directly
|
||||||
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
|
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SecretHashDomain(Secret<KEY_LEN>);
|
pub struct SecretHashDomain(Secret<KEY_LEN>, KeyedHash);
|
||||||
/// A reusable secure hash domain for a namespace identified by the key and that keeps the key secure
|
/// A reusable secure hash domain for a namespace identified by the key and that keeps the key secure
|
||||||
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
|
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
|
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>, KeyedHash);
|
||||||
|
|
||||||
impl HashDomain {
|
impl HashDomain {
|
||||||
/// Creates a nw [HashDomain] initialized with a all-zeros key.
|
/// Creates a nw [HashDomain] initialized with a all-zeros key.
|
||||||
pub fn zero() -> Self {
|
pub fn zero(choice: KeyedHash) -> Self {
|
||||||
Self([0u8; KEY_LEN])
|
Self([0u8; KEY_LEN], choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this [HashDomain] into a [HashDomainNamespace], keeping the key.
|
/// Turns this [HashDomain] into a [HashDomainNamespace], keeping the key.
|
||||||
pub fn dup(self) -> HashDomainNamespace {
|
pub fn dup(self) -> HashDomainNamespace {
|
||||||
HashDomainNamespace(self.0)
|
HashDomainNamespace(self.0, self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this [HashDomain] into a [SecretHashDomain] by wrapping the key into a [Secret]
|
/// Turns this [HashDomain] into a [SecretHashDomain] by wrapping the key into a [Secret]
|
||||||
/// and creating a new [SecretHashDomain] from it.
|
/// and creating a new [SecretHashDomain] from it.
|
||||||
pub fn turn_secret(self) -> SecretHashDomain {
|
pub fn turn_secret(self) -> SecretHashDomain {
|
||||||
SecretHashDomain(Secret::from_slice(&self.0))
|
SecretHashDomain(Secret::from_slice(&self.0), self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Protocol! Use domain separation to ensure that
|
// TODO: Protocol! Use domain separation to ensure that
|
||||||
@@ -77,14 +78,16 @@ impl HashDomain {
|
|||||||
/// as the `data` and uses the result as the key for the new [HashDomain].
|
/// as the `data` and uses the result as the key for the new [HashDomain].
|
||||||
///
|
///
|
||||||
pub fn mix(self, v: &[u8]) -> Result<Self> {
|
pub fn mix(self, v: &[u8]) -> Result<Self> {
|
||||||
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
|
let mut new_key: [u8; KEY_LEN] = [0u8; KEY_LEN];
|
||||||
|
self.1.keyed_hash_to(&self.0, v).to(&mut new_key)?;
|
||||||
|
Ok(Self(new_key, self.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||||
/// by calling [SecretHashDomain::invoke_primitive] with this
|
/// by calling [SecretHashDomain::invoke_primitive] with this
|
||||||
/// [HashDomain]'s key as `k` and `v` as `d`.
|
/// [HashDomain]'s key as `k` and `v` as `d`.
|
||||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
SecretHashDomain::invoke_primitive(&self.0, v.secret(), self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the key of this [HashDomain].
|
/// Gets the key of this [HashDomain].
|
||||||
@@ -98,9 +101,9 @@ impl HashDomainNamespace {
|
|||||||
/// it evaluates [hash::hash] with the key of this HashDomainNamespace key as the key and `v`
|
/// it evaluates [hash::hash] with the key of this HashDomainNamespace key as the key and `v`
|
||||||
/// as the `data` and uses the result as the key for the new [HashDomain].
|
/// as the `data` and uses the result as the key for the new [HashDomain].
|
||||||
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
|
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
|
||||||
Ok(HashDomain(
|
let mut new_key: [u8; KEY_LEN] = [0u8; KEY_LEN];
|
||||||
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
|
self.1.keyed_hash_to(&self.0, v).to(&mut new_key)?;
|
||||||
))
|
Ok(HashDomain(new_key, self.1.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||||
@@ -109,7 +112,7 @@ impl HashDomainNamespace {
|
|||||||
///
|
///
|
||||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
SecretHashDomain::invoke_primitive(&self.0, v.secret(), self.1.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,27 +121,35 @@ impl SecretHashDomain {
|
|||||||
/// [hash::hash] with `k` as the `key` and `d` s the `data`, and using the result
|
/// [hash::hash] with `k` as the `key` and `d` s the `data`, and using the result
|
||||||
/// as the content for the new [SecretHashDomain].
|
/// as the content for the new [SecretHashDomain].
|
||||||
/// Both `k` and `d` have to be exactly [KEY_LEN] bytes in length.
|
/// Both `k` and `d` have to be exactly [KEY_LEN] bytes in length.
|
||||||
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
|
/// TODO: docu
|
||||||
let mut r = SecretHashDomain(Secret::zero());
|
pub fn invoke_primitive(
|
||||||
hash::hash(k, d).to(r.0.secret_mut())?;
|
k: &[u8],
|
||||||
|
d: &[u8],
|
||||||
|
hash_choice: KeyedHash,
|
||||||
|
) -> Result<SecretHashDomain> {
|
||||||
|
let mut new_secret_key = Secret::zero();
|
||||||
|
hash_choice
|
||||||
|
.keyed_hash_to(k.try_into()?, d)
|
||||||
|
.to(new_secret_key.secret_mut())?;
|
||||||
|
let r = SecretHashDomain(new_secret_key, hash_choice);
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] that is initialized with an all zeros key.
|
/// Creates a new [SecretHashDomain] that is initialized with an all zeros key.
|
||||||
pub fn zero() -> Self {
|
pub fn zero(hash_choice: KeyedHash) -> Self {
|
||||||
Self(Secret::zero())
|
Self(Secret::zero(), hash_choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this [SecretHashDomain] into a [SecretHashDomainNamespace].
|
/// Turns this [SecretHashDomain] into a [SecretHashDomainNamespace].
|
||||||
pub fn dup(self) -> SecretHashDomainNamespace {
|
pub fn dup(self) -> SecretHashDomainNamespace {
|
||||||
SecretHashDomainNamespace(self.0)
|
SecretHashDomainNamespace(self.0, self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] from a [Secret] `k`.
|
/// Creates a new [SecretHashDomain] from a [Secret] `k`.
|
||||||
///
|
///
|
||||||
/// It requires that `k` consist of exactly [KEY_LEN] bytes.
|
/// It requires that `k` consist of exactly [KEY_LEN] bytes.
|
||||||
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
|
pub fn danger_from_secret(k: Secret<KEY_LEN>, hash_choice: KeyedHash) -> Self {
|
||||||
Self(k)
|
Self(k, hash_choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
|
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
|
||||||
@@ -147,7 +158,7 @@ impl SecretHashDomain {
|
|||||||
///
|
///
|
||||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||||
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
|
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||||
Self::invoke_primitive(self.0.secret(), v)
|
Self::invoke_primitive(self.0.secret(), v, self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||||
@@ -156,21 +167,13 @@ impl SecretHashDomain {
|
|||||||
///
|
///
|
||||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
Self::invoke_primitive(self.0.secret(), v.secret())
|
Self::invoke_primitive(self.0.secret(), v.secret(), self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the secret key data from this [SecretHashDomain].
|
/// Get the secret key data from this [SecretHashDomain].
|
||||||
pub fn into_secret(self) -> Secret<KEY_LEN> {
|
pub fn into_secret(self) -> Secret<KEY_LEN> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate [hash::hash] with this [SecretHashDomain]'s data as the `key` and
|
|
||||||
/// `dst` as the `data` and stores the result as the new data for this [SecretHashDomain].
|
|
||||||
///
|
|
||||||
/// It requires that both `v` and `d` consist of exactly [KEY_LEN] many bytes.
|
|
||||||
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
|
|
||||||
hash::hash(v, dst).to(self.0.secret_mut())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SecretHashDomainNamespace {
|
impl SecretHashDomainNamespace {
|
||||||
@@ -180,7 +183,7 @@ impl SecretHashDomainNamespace {
|
|||||||
///
|
///
|
||||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||||
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
|
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(self.0.secret(), v)
|
SecretHashDomain::invoke_primitive(self.0.secret(), v, self.1.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
|
||||||
@@ -189,7 +192,7 @@ impl SecretHashDomainNamespace {
|
|||||||
///
|
///
|
||||||
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
|
||||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||||
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
|
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret(), self.1.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This entire API is not very nice; we need this for biscuits, but
|
// TODO: This entire API is not very nice; we need this for biscuits, but
|
||||||
@@ -199,4 +202,8 @@ impl SecretHashDomainNamespace {
|
|||||||
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
|
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn keyed_hash(&self) -> &KeyedHash {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
use rosenpass_cipher_traits::primitives::Aead as AeadTrait;
|
||||||
use static_assertions::const_assert;
|
use static_assertions::const_assert;
|
||||||
|
|
||||||
pub mod subtle;
|
pub mod subtle;
|
||||||
|
|
||||||
/// All keyed primitives in this crate use 32 byte keys
|
/// All keyed primitives in this crate use 32 byte keys
|
||||||
pub const KEY_LEN: usize = 32;
|
pub const KEY_LEN: usize = 32;
|
||||||
const_assert!(KEY_LEN == aead::KEY_LEN);
|
const_assert!(KEY_LEN == Aead::KEY_LEN);
|
||||||
const_assert!(KEY_LEN == xaead::KEY_LEN);
|
const_assert!(KEY_LEN == XAead::KEY_LEN);
|
||||||
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
||||||
|
|
||||||
/// Keyed hashing
|
/// Keyed hashing
|
||||||
@@ -13,41 +14,33 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
|||||||
/// This should only be used for implementation details; anything with relevance
|
/// This should only be used for implementation details; anything with relevance
|
||||||
/// to the cryptographic protocol should use the facilities in [hash_domain], (though
|
/// to the cryptographic protocol should use the facilities in [hash_domain], (though
|
||||||
/// hash domain uses this module internally)
|
/// hash domain uses this module internally)
|
||||||
pub mod keyed_hash {
|
pub use crate::subtle::keyed_hash::KeyedHash;
|
||||||
pub use crate::subtle::incorrect_hmac_blake2b::{
|
|
||||||
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Authenticated encryption with associated data
|
/// Authenticated encryption with associated data (AEAD)
|
||||||
/// Chacha20poly1305 is used.
|
/// Chacha20poly1305 is used.
|
||||||
pub mod aead {
|
#[cfg(feature = "experiment_libcrux_chachapoly")]
|
||||||
#[cfg(not(feature = "experiment_libcrux"))]
|
pub use subtle::libcrux::chacha20poly1305_ietf::ChaCha20Poly1305 as Aead;
|
||||||
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
|
|
||||||
#[cfg(feature = "experiment_libcrux")]
|
|
||||||
pub use crate::subtle::chacha20poly1305_ietf_libcrux::{
|
|
||||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Authenticated encryption with associated data with a constant nonce
|
/// Authenticated encryption with associated data (AEAD)
|
||||||
|
/// Chacha20poly1305 is used.
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_chachapoly"))]
|
||||||
|
pub use crate::subtle::rust_crypto::chacha20poly1305_ietf::ChaCha20Poly1305 as Aead;
|
||||||
|
|
||||||
|
/// Authenticated encryption with associated data with a extended-length nonce (XAEAD)
|
||||||
/// XChacha20poly1305 is used.
|
/// XChacha20poly1305 is used.
|
||||||
pub mod xaead {
|
pub use crate::subtle::rust_crypto::xchacha20poly1305_ietf::XChaCha20Poly1305 as XAead;
|
||||||
pub use crate::subtle::xchacha20poly1305_ietf::{
|
|
||||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
/// Use Classic-McEcliece-460986 as the Static KEM.
|
||||||
};
|
///
|
||||||
}
|
/// See [rosenpass_oqs::ClassicMceliece460896] for more details.
|
||||||
|
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
||||||
|
|
||||||
|
/// Use Kyber-512 as the Static KEM
|
||||||
|
///
|
||||||
|
/// See [rosenpass_oqs::Kyber512] for more details.
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_kyber"))]
|
||||||
|
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
||||||
|
#[cfg(feature = "experiment_libcrux_kyber")]
|
||||||
|
pub use subtle::libcrux::kyber512::Kyber512 as EphemeralKem;
|
||||||
|
|
||||||
pub mod hash_domain;
|
pub mod hash_domain;
|
||||||
|
|
||||||
/// This crate includes two key encapsulation mechanisms.
|
|
||||||
/// Namely ClassicMceliece460896 (also referred to as `StaticKem` sometimes) and
|
|
||||||
/// Kyber512 (also referred to as `EphemeralKem` sometimes).
|
|
||||||
///
|
|
||||||
/// See [rosenpass_oqs::ClassicMceliece460896]
|
|
||||||
/// and [rosenpass_oqs::Kyber512] for more details on the specific KEMS.
|
|
||||||
///
|
|
||||||
pub mod kem {
|
|
||||||
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
|
||||||
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
use blake2::digest::crypto_common::generic_array::GenericArray;
|
|
||||||
use blake2::digest::crypto_common::typenum::U32;
|
|
||||||
use blake2::digest::crypto_common::KeySizeUser;
|
|
||||||
use blake2::digest::{FixedOutput, Mac, OutputSizeUser};
|
|
||||||
use blake2::Blake2bMac;
|
|
||||||
|
|
||||||
use rosenpass_to::{ops::copy_slice, with_destination, To};
|
|
||||||
use rosenpass_util::typenum2const;
|
|
||||||
|
|
||||||
/// Specify that the used implementation of BLAKE2b is the MAC version of BLAKE2b
|
|
||||||
/// with output and key length of 32 bytes (see [Blake2bMac]).
|
|
||||||
type Impl = Blake2bMac<U32>;
|
|
||||||
|
|
||||||
type KeyLen = <Impl as KeySizeUser>::KeySize;
|
|
||||||
type OutLen = <Impl as OutputSizeUser>::OutputSize;
|
|
||||||
|
|
||||||
/// The key length for BLAKE2b supported by this API. Currently 32 Bytes.
|
|
||||||
const KEY_LEN: usize = typenum2const! { KeyLen };
|
|
||||||
/// The output length for BLAKE2b supported by this API. Currently 32 Bytes.
|
|
||||||
const OUT_LEN: usize = typenum2const! { OutLen };
|
|
||||||
|
|
||||||
/// Minimal key length supported by this API.
|
|
||||||
pub const KEY_MIN: usize = KEY_LEN;
|
|
||||||
/// maximal key length supported by this API.
|
|
||||||
pub const KEY_MAX: usize = KEY_LEN;
|
|
||||||
/// minimal output length supported by this API.
|
|
||||||
pub const OUT_MIN: usize = OUT_LEN;
|
|
||||||
/// maximal output length supported by this API.
|
|
||||||
pub const OUT_MAX: usize = OUT_LEN;
|
|
||||||
|
|
||||||
/// Hashes the given `data` with the [Blake2bMac] hash function under the given `key`.
|
|
||||||
/// The both the length of the output the length of the key 32 bytes (or 256 bits).
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::subtle::blake2b::hash;
|
|
||||||
/// use rosenpass_to::To;
|
|
||||||
/// let zero_key: [u8; 32] = [0; 32];
|
|
||||||
/// let data: [u8; 32] = [255; 32];
|
|
||||||
/// // buffer for the hash output
|
|
||||||
/// let mut hash_data: [u8; 32] = [0u8; 32];
|
|
||||||
///
|
|
||||||
/// assert!(hash(&zero_key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
|
|
||||||
///```
|
|
||||||
///
|
|
||||||
#[inline]
|
|
||||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
|
||||||
with_destination(|out: &mut [u8]| {
|
|
||||||
let mut h = Impl::new_from_slice(key)?;
|
|
||||||
h.update(data);
|
|
||||||
|
|
||||||
// Jesus christ, blake2 crate, your usage of GenericArray might be nice and fancy
|
|
||||||
// but it introduces a ton of complexity. This cost me half an hour just to figure
|
|
||||||
// out the right way to use the imports while allowing for zeroization.
|
|
||||||
// An API based on slices might actually be simpler.
|
|
||||||
let mut tmp = Zeroizing::new([0u8; OUT_LEN]);
|
|
||||||
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
|
||||||
h.finalize_into(tmp);
|
|
||||||
copy_slice(tmp.as_ref()).to(out);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
use rosenpass_to::ops::copy_slice;
|
|
||||||
use rosenpass_to::To;
|
|
||||||
use rosenpass_util::typenum2const;
|
|
||||||
|
|
||||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
|
||||||
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
|
|
||||||
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
|
|
||||||
|
|
||||||
/// The key length is 32 bytes or 256 bits.
|
|
||||||
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
|
|
||||||
/// The MAC tag length is 16 bytes or 128 bits.
|
|
||||||
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
|
|
||||||
/// The nonce length is 12 bytes or 96 bits.
|
|
||||||
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
|
|
||||||
|
|
||||||
/// Encrypts using ChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
|
||||||
/// `key` MUST be chosen (pseudo-)randomly and `nonce` MOST NOT be reused. The `key` slice MUST have
|
|
||||||
/// a length of [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
|
|
||||||
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
|
|
||||||
/// `plaintext.len()` + [TAG_LEN].
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
|
||||||
///
|
|
||||||
/// const PLAINTEXT_LEN: usize = 43;
|
|
||||||
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
|
||||||
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
|
||||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
|
||||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
|
||||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
|
||||||
/// let mut ciphertext_buffer = [0u8;PLAINTEXT_LEN + TAG_LEN];
|
|
||||||
///
|
|
||||||
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
|
|
||||||
/// assert!(res.is_ok());
|
|
||||||
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
|
||||||
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
|
||||||
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
|
||||||
/// # 8, 114, 85, 4, 25];
|
|
||||||
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
|
|
||||||
///```
|
|
||||||
#[inline]
|
|
||||||
pub fn encrypt(
|
|
||||||
ciphertext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
plaintext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let nonce = GenericArray::from_slice(nonce);
|
|
||||||
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
|
||||||
copy_slice(plaintext).to(ct);
|
|
||||||
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
|
|
||||||
copy_slice(&mac_value[..]).to(mac);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
|
||||||
/// `ad`. using ChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
|
||||||
///
|
|
||||||
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
|
|
||||||
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
|
||||||
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
|
||||||
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
|
||||||
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
|
||||||
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
|
|
||||||
/// const PLAINTEXT_LEN: usize = 43;
|
|
||||||
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
|
|
||||||
///
|
|
||||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
|
||||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
|
||||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
|
||||||
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
|
||||||
///
|
|
||||||
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
|
|
||||||
/// assert!(res.is_ok());
|
|
||||||
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
|
|
||||||
/// assert_eq!(expected_plaintext, plaintext_buffer);
|
|
||||||
///
|
|
||||||
///```
|
|
||||||
#[inline]
|
|
||||||
pub fn decrypt(
|
|
||||||
plaintext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
ciphertext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let nonce = GenericArray::from_slice(nonce);
|
|
||||||
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
|
||||||
let tag = GenericArray::from_slice(mac);
|
|
||||||
copy_slice(ct).to(plaintext);
|
|
||||||
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
use rosenpass_to::ops::copy_slice;
|
|
||||||
use rosenpass_to::To;
|
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
/// The key length is 32 bytes or 256 bits.
|
|
||||||
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
|
|
||||||
/// The MAC tag length is 16 bytes or 128 bits.
|
|
||||||
pub const TAG_LEN: usize = 16;
|
|
||||||
/// The nonce length is 12 bytes or 96 bits.
|
|
||||||
pub const NONCE_LEN: usize = 12;
|
|
||||||
|
|
||||||
/// Encrypts using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
|
|
||||||
/// Key and nonce MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
|
|
||||||
/// [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
|
|
||||||
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
|
|
||||||
/// `plaintext.len()` + [TAG_LEN].
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
|
||||||
///
|
|
||||||
/// const PLAINTEXT_LEN: usize = 43;
|
|
||||||
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
|
||||||
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
|
||||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
|
||||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
|
||||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
|
||||||
/// let mut ciphertext_buffer = [0u8; PLAINTEXT_LEN + TAG_LEN];
|
|
||||||
///
|
|
||||||
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
|
|
||||||
/// assert!(res.is_ok());
|
|
||||||
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
|
||||||
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
|
||||||
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
|
||||||
/// # 8, 114, 85, 4, 25];
|
|
||||||
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
|
|
||||||
///```
|
|
||||||
///
|
|
||||||
#[inline]
|
|
||||||
pub fn encrypt(
|
|
||||||
ciphertext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
plaintext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
|
||||||
|
|
||||||
use libcrux::aead as C;
|
|
||||||
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
|
||||||
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
|
||||||
|
|
||||||
copy_slice(plaintext).to(ciphertext);
|
|
||||||
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
|
|
||||||
copy_slice(crux_tag.as_ref()).to(mac);
|
|
||||||
|
|
||||||
match crux_key {
|
|
||||||
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
|
||||||
/// `ad`. using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
|
|
||||||
///
|
|
||||||
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
|
|
||||||
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
|
||||||
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
|
||||||
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
|
||||||
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
|
||||||
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
|
|
||||||
/// const PLAINTEXT_LEN: usize = 43;
|
|
||||||
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
|
|
||||||
///
|
|
||||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
|
||||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
|
||||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
|
||||||
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
|
||||||
///
|
|
||||||
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
|
|
||||||
/// assert!(res.is_ok());
|
|
||||||
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
|
|
||||||
/// assert_eq!(expected_plaintext, plaintext_buffer);
|
|
||||||
///
|
|
||||||
///```
|
|
||||||
#[inline]
|
|
||||||
pub fn decrypt(
|
|
||||||
plaintext: &mut [u8],
|
|
||||||
key: &[u8],
|
|
||||||
nonce: &[u8],
|
|
||||||
ad: &[u8],
|
|
||||||
ciphertext: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
|
||||||
|
|
||||||
use libcrux::aead as C;
|
|
||||||
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
|
||||||
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
|
||||||
let crux_tag = C::Tag::from_slice(mac).unwrap();
|
|
||||||
|
|
||||||
copy_slice(ciphertext).to(plaintext);
|
|
||||||
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
|
|
||||||
|
|
||||||
match crux_key {
|
|
||||||
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
79
ciphers/src/subtle/custom/incorrect_hmac_blake2b.rs
Normal file
79
ciphers/src/subtle/custom/incorrect_hmac_blake2b.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use rosenpass_cipher_traits::{
|
||||||
|
algorithms::KeyedHashIncorrectHmacBlake2b,
|
||||||
|
primitives::{InferKeyedHash, KeyedHash, KeyedHashTo},
|
||||||
|
};
|
||||||
|
use rosenpass_constant_time::xor;
|
||||||
|
use rosenpass_to::{ops::copy_slice, To};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_blake2"))]
|
||||||
|
use crate::subtle::rust_crypto::blake2b::Blake2b;
|
||||||
|
#[cfg(not(feature = "experiment_libcrux_blake2"))]
|
||||||
|
use anyhow::Error;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_blake2")]
|
||||||
|
use crate::subtle::libcrux::blake2b::{Blake2b, Error};
|
||||||
|
|
||||||
|
/// The key length, 32 bytes or 256 bits.
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// The hash length, 32 bytes or 256 bits.
|
||||||
|
pub const HASH_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// This is a woefully incorrect implementation of hmac_blake2b.
|
||||||
|
/// See <https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563612222>
|
||||||
|
///
|
||||||
|
/// It accepts 32 byte keys, exclusively.
|
||||||
|
///
|
||||||
|
/// This will be replaced, likely by Kekkac at some point soon.
|
||||||
|
/// <https://github.com/rosenpass/rosenpass/pull/145>
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::custom::incorrect_hmac_blake2b::IncorrectHmacBlake2bCore;
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHashTo;
|
||||||
|
/// use rosenpass_to::To;
|
||||||
|
/// let key: [u8; 32] = [0; 32];
|
||||||
|
/// let data: [u8; 32] = [255; 32];
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; 32];
|
||||||
|
///
|
||||||
|
/// assert!(IncorrectHmacBlake2bCore::keyed_hash_to(&key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[5, 152, 135, 141, 151, 106, 147, 8, 220, 95, 38, 66, 29, 33, 3,
|
||||||
|
/// 104, 250, 114, 131, 119, 27, 56, 59, 44, 11, 67, 230, 113, 112, 20, 80, 103];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
///```
|
||||||
|
///
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct IncorrectHmacBlake2bCore;
|
||||||
|
|
||||||
|
impl KeyedHash<KEY_LEN, HASH_LEN> for IncorrectHmacBlake2bCore {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];
|
||||||
|
const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN];
|
||||||
|
|
||||||
|
type Key = Zeroizing<[u8; KEY_LEN]>;
|
||||||
|
let mut tmp_key = Key::default();
|
||||||
|
|
||||||
|
copy_slice(key).to(tmp_key.as_mut());
|
||||||
|
xor(&IPAD).to(tmp_key.as_mut());
|
||||||
|
let mut outer_data = Key::default();
|
||||||
|
Blake2b::keyed_hash_to(&tmp_key, data).to(&mut outer_data)?;
|
||||||
|
|
||||||
|
copy_slice(key).to(tmp_key.as_mut());
|
||||||
|
xor(&OPAD).to(tmp_key.as_mut());
|
||||||
|
Blake2b::keyed_hash_to(&tmp_key, outer_data.as_ref()).to(out)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type IncorrectHmacBlake2b = InferKeyedHash<IncorrectHmacBlake2bCore, KEY_LEN, HASH_LEN>;
|
||||||
|
|
||||||
|
impl KeyedHashIncorrectHmacBlake2b for IncorrectHmacBlake2bCore {}
|
||||||
3
ciphers/src/subtle/custom/mod.rs
Normal file
3
ciphers/src/subtle/custom/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//! Own implementations of custom algorithms
|
||||||
|
|
||||||
|
pub mod incorrect_hmac_blake2b;
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
use anyhow::ensure;
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
use rosenpass_constant_time::xor;
|
|
||||||
use rosenpass_to::{ops::copy_slice, with_destination, To};
|
|
||||||
|
|
||||||
use crate::subtle::blake2b;
|
|
||||||
|
|
||||||
/// The key length, 32 bytes or 256 bits.
|
|
||||||
pub const KEY_LEN: usize = 32;
|
|
||||||
/// The minimal key length, identical to [KEY_LEN]
|
|
||||||
pub const KEY_MIN: usize = KEY_LEN;
|
|
||||||
/// The maximal key length, identical to [KEY_LEN]
|
|
||||||
pub const KEY_MAX: usize = KEY_LEN;
|
|
||||||
/// The minimal output length, see [blake2b::OUT_MIN]
|
|
||||||
pub const OUT_MIN: usize = blake2b::OUT_MIN;
|
|
||||||
/// The maximal output length, see [blake2b::OUT_MAX]
|
|
||||||
pub const OUT_MAX: usize = blake2b::OUT_MAX;
|
|
||||||
|
|
||||||
/// This is a woefully incorrect implementation of hmac_blake2b.
|
|
||||||
/// See <https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563612222>
|
|
||||||
///
|
|
||||||
/// It accepts 32 byte keys, exclusively.
|
|
||||||
///
|
|
||||||
/// This will be replaced, likely by Kekkac at some point soon.
|
|
||||||
/// <https://github.com/rosenpass/rosenpass/pull/145>
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///```rust
|
|
||||||
/// # use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::hash;
|
|
||||||
/// use rosenpass_to::To;
|
|
||||||
/// let key: [u8; 32] = [0; 32];
|
|
||||||
/// let data: [u8; 32] = [255; 32];
|
|
||||||
/// // buffer for the hash output
|
|
||||||
/// let mut hash_data: [u8; 32] = [0u8; 32];
|
|
||||||
///
|
|
||||||
/// assert!(hash(&key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
|
|
||||||
/// # let expected_hash: &[u8] = &[5, 152, 135, 141, 151, 106, 147, 8, 220, 95, 38, 66, 29, 33, 3,
|
|
||||||
/// 104, 250, 114, 131, 119, 27, 56, 59, 44, 11, 67, 230, 113, 112, 20, 80, 103];
|
|
||||||
/// # assert_eq!(hash_data, expected_hash);
|
|
||||||
///```
|
|
||||||
///
|
|
||||||
#[inline]
|
|
||||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
|
||||||
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];
|
|
||||||
const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN];
|
|
||||||
|
|
||||||
with_destination(|out: &mut [u8]| {
|
|
||||||
// Not bothering with padding; the implementation
|
|
||||||
// uses appropriately sized keys.
|
|
||||||
ensure!(key.len() == KEY_LEN);
|
|
||||||
|
|
||||||
type Key = Zeroizing<[u8; KEY_LEN]>;
|
|
||||||
let mut tmp_key = Key::default();
|
|
||||||
|
|
||||||
copy_slice(key).to(tmp_key.as_mut());
|
|
||||||
xor(&IPAD).to(tmp_key.as_mut());
|
|
||||||
let mut outer_data = Key::default();
|
|
||||||
blake2b::hash(tmp_key.as_ref(), data).to(outer_data.as_mut())?;
|
|
||||||
|
|
||||||
copy_slice(key).to(tmp_key.as_mut());
|
|
||||||
xor(&OPAD).to(tmp_key.as_mut());
|
|
||||||
blake2b::hash(tmp_key.as_ref(), outer_data.as_ref()).to(out)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
65
ciphers/src/subtle/keyed_hash.rs
Normal file
65
ciphers/src/subtle/keyed_hash.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//! This module provides types that enabling choosing the keyed hash building block to be used at
|
||||||
|
//! runtime (using enums) instead of at compile time (using generics).
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHashInstance;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::subtle::{
|
||||||
|
custom::incorrect_hmac_blake2b::IncorrectHmacBlake2b, rust_crypto::keyed_shake256::SHAKE256_32,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Length of symmetric key throughout Rosenpass.
|
||||||
|
pub const KEY_LEN: usize = 32;
|
||||||
|
|
||||||
|
/// The hash is used as a symmetric key and should have the same length.
|
||||||
|
pub const HASH_LEN: usize = KEY_LEN;
|
||||||
|
|
||||||
|
/// Provides a way to pick which keyed hash to use at runtime.
|
||||||
|
/// Implements [`KeyedHashInstance`] to allow hashing using the respective algorithm.
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum KeyedHash {
|
||||||
|
/// A hasher backed by [`SHAKE256_32`].
|
||||||
|
KeyedShake256(SHAKE256_32),
|
||||||
|
/// A hasher backed by [`IncorrectHmacBlake2b`].
|
||||||
|
IncorrectHmacBlake2b(IncorrectHmacBlake2b),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyedHash {
|
||||||
|
/// Creates an [`KeyedHash`] backed by SHAKE256.
|
||||||
|
pub fn keyed_shake256() -> Self {
|
||||||
|
Self::KeyedShake256(Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an [`KeyedHash`] backed by Blake2B.
|
||||||
|
pub fn incorrect_hmac_blake2b() -> Self {
|
||||||
|
Self::IncorrectHmacBlake2b(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyedHashInstance<KEY_LEN, HASH_LEN> for KeyedHash {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
&self,
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
Self::KeyedShake256(h) => h.keyed_hash(key, data, out)?,
|
||||||
|
Self::IncorrectHmacBlake2b(h) => h.keyed_hash(key, data, out)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for KeyedHash {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::KeyedShake256(_) => write!(f, "KeyedShake256_32"),
|
||||||
|
Self::IncorrectHmacBlake2b(_) => write!(f, "IncorrectHmacBlake2b"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
ciphers/src/subtle/libcrux/blake2b.rs
Normal file
88
ciphers/src/subtle/libcrux/blake2b.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//! Implementation of the [`KeyedHashBlake2b`] trait based on the [`libcrux_blake2`] crate.
|
||||||
|
|
||||||
|
use libcrux_blake2::Blake2bBuilder;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::KeyedHashBlake2b;
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHash;
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_blake2b::HASH_LEN;
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_blake2b::KEY_LEN;
|
||||||
|
|
||||||
|
/// Describles which error occurred
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An unexpected internal error occurred. Should never be returned and points to a bug in the
|
||||||
|
/// implementation.
|
||||||
|
#[error("internal error")]
|
||||||
|
InternalError,
|
||||||
|
|
||||||
|
/// Indicates that the provided data was too long.
|
||||||
|
#[error("data is too long")]
|
||||||
|
DataTooLong,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hasher for the given `data` with the Blake2b hash function.
|
||||||
|
pub struct Blake2b;
|
||||||
|
|
||||||
|
impl KeyedHash<KEY_LEN, HASH_LEN> for Blake2b {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut h = Blake2bBuilder::new_keyed_const(key)
|
||||||
|
// this may fail if the key length is invalid, but 32 is fine
|
||||||
|
.map_err(|_| Error::InternalError)?
|
||||||
|
.build_const_digest_len()
|
||||||
|
.map_err(|_|
|
||||||
|
// this can only fail if the output length is invalid, but 32 is fine.
|
||||||
|
Error::InternalError)?;
|
||||||
|
|
||||||
|
h.update(data).map_err(|_| Error::DataTooLong)?;
|
||||||
|
h.finalize(out);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyedHashBlake2b for Blake2b {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod equivalence_tests {
|
||||||
|
use super::*;
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzz_equivalence_libcrux_old_new() {
|
||||||
|
let datas: [&[u8]; 3] = [
|
||||||
|
b"".as_slice(),
|
||||||
|
b"test".as_slice(),
|
||||||
|
b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut key = [0; KEY_LEN];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut hash_left = [0; 32];
|
||||||
|
let mut hash_right = [0; 32];
|
||||||
|
|
||||||
|
for data in datas {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
rng.fill_bytes(&mut key);
|
||||||
|
|
||||||
|
crate::subtle::rust_crypto::blake2b::Blake2b::keyed_hash(
|
||||||
|
&key,
|
||||||
|
data,
|
||||||
|
&mut hash_left,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
crate::subtle::libcrux::blake2b::Blake2b::keyed_hash(&key, data, &mut hash_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(hash_left, hash_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
274
ciphers/src/subtle/libcrux/chacha20poly1305_ietf.rs
Normal file
274
ciphers/src/subtle/libcrux/chacha20poly1305_ietf.rs
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
//! Implementation of the [`AeadChaCha20Poly1305`] trait based on the [`libcrux_chacha20poly1305`] crate.
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::AeadChaCha20Poly1305;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Aead, AeadError};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::aead_chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||||
|
|
||||||
|
/// An implementation of the ChaCha20Poly1305 AEAD based on libcrux
|
||||||
|
pub struct ChaCha20Poly1305;
|
||||||
|
|
||||||
|
impl Aead<KEY_LEN, NONCE_LEN, TAG_LEN> for ChaCha20Poly1305 {
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
let (ctxt, tag) = libcrux_chacha20poly1305::encrypt(key, plaintext, ciphertext, ad, nonce)
|
||||||
|
.map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
// return an error of the destination buffer is longer than expected
|
||||||
|
// because the caller wouldn't know where the end is
|
||||||
|
if ctxt.len() + tag.len() != ciphertext.len() {
|
||||||
|
return Err(AeadError::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
let ptxt = libcrux_chacha20poly1305::decrypt(key, plaintext, ciphertext, ad, nonce)
|
||||||
|
.map_err(|_| AeadError::DecryptError)?;
|
||||||
|
|
||||||
|
// return an error of the destination buffer is longer than expected
|
||||||
|
// because the caller wouldn't know where the end is
|
||||||
|
if ptxt.len() != plaintext.len() {
|
||||||
|
return Err(AeadError::DecryptError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadChaCha20Poly1305 for ChaCha20Poly1305 {}
|
||||||
|
|
||||||
|
/// The idea of these tests is to check that the above implemenatation behaves, by and large, the
|
||||||
|
/// same as the one from the old libcrux and the one from RustCrypto. You can consider them janky,
|
||||||
|
/// self-rolled property-based tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
mod equivalence_tests {
|
||||||
|
use super::*;
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proptest_equivalence_libcrux_rustcrypto() {
|
||||||
|
use crate::subtle::rust_crypto::chacha20poly1305_ietf::ChaCha20Poly1305 as RustCryptoChaCha20Poly1305;
|
||||||
|
let ptxts: [&[u8]; 3] = [
|
||||||
|
b"".as_slice(),
|
||||||
|
b"test".as_slice(),
|
||||||
|
b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
];
|
||||||
|
let mut key = [0; KEY_LEN];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut ctxt_left = [0; 64 + TAG_LEN];
|
||||||
|
let mut ctxt_right = [0; 64 + TAG_LEN];
|
||||||
|
|
||||||
|
let mut ptxt_left = [0; 64];
|
||||||
|
let mut ptxt_right = [0; 64];
|
||||||
|
|
||||||
|
let nonce = [0; NONCE_LEN];
|
||||||
|
let ad = b"";
|
||||||
|
|
||||||
|
for ptxt in ptxts {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
rng.fill_bytes(&mut key);
|
||||||
|
let ctxt_left = &mut ctxt_left[..ptxt.len() + TAG_LEN];
|
||||||
|
let ctxt_right = &mut ctxt_right[..ptxt.len() + TAG_LEN];
|
||||||
|
|
||||||
|
let ptxt_left = &mut ptxt_left[..ptxt.len()];
|
||||||
|
let ptxt_right = &mut ptxt_right[..ptxt.len()];
|
||||||
|
|
||||||
|
RustCryptoChaCha20Poly1305
|
||||||
|
.encrypt(ctxt_left, &key, &nonce, ad, ptxt)
|
||||||
|
.unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.encrypt(ctxt_right, &key, &nonce, ad, ptxt)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ctxt_left, ctxt_right);
|
||||||
|
|
||||||
|
RustCryptoChaCha20Poly1305
|
||||||
|
.decrypt(ptxt_left, &key, &nonce, ad, ctxt_left)
|
||||||
|
.unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.decrypt(ptxt_right, &key, &nonce, ad, ctxt_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ptxt_left, ptxt);
|
||||||
|
assert_eq!(ptxt_right, ptxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "experiment_libcrux_chachapoly_test")]
|
||||||
|
fn proptest_equivalence_libcrux_old_new() {
|
||||||
|
let ptxts: [&[u8]; 3] = [
|
||||||
|
b"".as_slice(),
|
||||||
|
b"test".as_slice(),
|
||||||
|
b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
];
|
||||||
|
let mut key = [0; KEY_LEN];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut ctxt_left = [0; 64 + TAG_LEN];
|
||||||
|
let mut ctxt_right = [0; 64 + TAG_LEN];
|
||||||
|
|
||||||
|
let mut ptxt_left = [0; 64];
|
||||||
|
let mut ptxt_right = [0; 64];
|
||||||
|
|
||||||
|
let nonce = [0; NONCE_LEN];
|
||||||
|
let ad = b"";
|
||||||
|
|
||||||
|
for ptxt in ptxts {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
rng.fill_bytes(&mut key);
|
||||||
|
let ctxt_left = &mut ctxt_left[..ptxt.len() + TAG_LEN];
|
||||||
|
let ctxt_right = &mut ctxt_right[..ptxt.len() + TAG_LEN];
|
||||||
|
|
||||||
|
let ptxt_left = &mut ptxt_left[..ptxt.len()];
|
||||||
|
let ptxt_right = &mut ptxt_right[..ptxt.len()];
|
||||||
|
|
||||||
|
encrypt(ctxt_left, &key, &nonce, ad, ptxt).unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.encrypt(ctxt_right, &key, &nonce, ad, ptxt)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ctxt_left, ctxt_right);
|
||||||
|
|
||||||
|
decrypt(ptxt_left, &key, &nonce, ad, ctxt_left).unwrap();
|
||||||
|
ChaCha20Poly1305
|
||||||
|
.decrypt(ptxt_right, &key, &nonce, ad, ctxt_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ptxt_left, ptxt);
|
||||||
|
assert_eq!(ptxt_right, ptxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The old libcrux functions:
|
||||||
|
|
||||||
|
// The functions below are from the old libcrux backend. I am keeping them around so we can
|
||||||
|
// check if they behave the same.
|
||||||
|
use rosenpass_to::ops::copy_slice;
|
||||||
|
use rosenpass_to::To;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
/// Encrypts using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
|
||||||
|
/// Key and nonce MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
|
||||||
|
/// [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
|
||||||
|
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
|
||||||
|
/// `plaintext.len()` + [TAG_LEN].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||||
|
///
|
||||||
|
/// const PLAINTEXT_LEN: usize = 43;
|
||||||
|
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||||
|
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
||||||
|
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||||
|
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||||
|
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||||
|
/// let mut ciphertext_buffer = [0u8; PLAINTEXT_LEN + TAG_LEN];
|
||||||
|
///
|
||||||
|
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
|
||||||
|
/// assert!(res.is_ok());
|
||||||
|
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
||||||
|
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
||||||
|
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
||||||
|
/// # 8, 114, 85, 4, 25];
|
||||||
|
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
|
||||||
|
///```
|
||||||
|
///
|
||||||
|
#[inline]
|
||||||
|
pub fn encrypt(
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: &[u8],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (ciphertext, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
|
||||||
|
use libcrux::aead as C;
|
||||||
|
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||||
|
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||||
|
|
||||||
|
copy_slice(plaintext).to(ciphertext);
|
||||||
|
let crux_tag = libcrux::aead::encrypt(&crux_key, ciphertext, crux_iv, ad).unwrap();
|
||||||
|
copy_slice(crux_tag.as_ref()).to(mac);
|
||||||
|
|
||||||
|
match crux_key {
|
||||||
|
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
||||||
|
/// `ad`. using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
|
||||||
|
///
|
||||||
|
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
|
||||||
|
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||||
|
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
|
||||||
|
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
|
||||||
|
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
|
||||||
|
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
|
||||||
|
/// const PLAINTEXT_LEN: usize = 43;
|
||||||
|
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
|
||||||
|
///
|
||||||
|
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||||
|
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||||
|
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||||
|
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
||||||
|
///
|
||||||
|
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
|
||||||
|
/// assert!(res.is_ok());
|
||||||
|
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||||
|
/// assert_eq!(expected_plaintext, plaintext_buffer);
|
||||||
|
///
|
||||||
|
///```
|
||||||
|
#[inline]
|
||||||
|
pub fn decrypt(
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: &[u8],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let (ciphertext, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
|
||||||
|
use libcrux::aead as C;
|
||||||
|
let crux_key = C::Key::Chacha20Poly1305(C::Chacha20Key(key.try_into().unwrap()));
|
||||||
|
let crux_iv = C::Iv(nonce.try_into().unwrap());
|
||||||
|
let crux_tag = C::Tag::from_slice(mac).unwrap();
|
||||||
|
|
||||||
|
copy_slice(ciphertext).to(plaintext);
|
||||||
|
libcrux::aead::decrypt(&crux_key, plaintext, crux_iv, ad, &crux_tag).unwrap();
|
||||||
|
|
||||||
|
match crux_key {
|
||||||
|
C::Key::Chacha20Poly1305(mut k) => k.0.zeroize(),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
ciphers/src/subtle/libcrux/kyber512.rs
Normal file
133
ciphers/src/subtle/libcrux/kyber512.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//! Implementation of the [`KemKyber512`] trait based on the [`libcrux_ml_kem`] crate.
|
||||||
|
|
||||||
|
use libcrux_ml_kem::kyber512;
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::KemKyber512;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Kem, KemError};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::kem_kyber512::{CT_LEN, PK_LEN, SHK_LEN, SK_LEN};
|
||||||
|
|
||||||
|
/// An implementation of the Kyber512 KEM based on libcrux
|
||||||
|
pub struct Kyber512;
|
||||||
|
|
||||||
|
impl Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> for Kyber512 {
|
||||||
|
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), KemError> {
|
||||||
|
let mut randomness = [0u8; libcrux_ml_kem::KEY_GENERATION_SEED_SIZE];
|
||||||
|
rand::thread_rng().fill_bytes(&mut randomness);
|
||||||
|
|
||||||
|
let key_pair = kyber512::generate_key_pair(randomness);
|
||||||
|
|
||||||
|
let new_sk: &[u8; SK_LEN] = key_pair.sk();
|
||||||
|
let new_pk: &[u8; PK_LEN] = key_pair.pk();
|
||||||
|
|
||||||
|
sk.clone_from_slice(new_sk);
|
||||||
|
pk.clone_from_slice(new_pk);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
ct: &mut [u8; CT_LEN],
|
||||||
|
pk: &[u8; PK_LEN],
|
||||||
|
) -> Result<(), KemError> {
|
||||||
|
let mut randomness = [0u8; libcrux_ml_kem::SHARED_SECRET_SIZE];
|
||||||
|
rand::thread_rng().fill_bytes(&mut randomness);
|
||||||
|
|
||||||
|
let (new_ct, new_shk) = kyber512::encapsulate(&pk.into(), randomness);
|
||||||
|
let new_ct: &[u8; CT_LEN] = new_ct.as_slice();
|
||||||
|
|
||||||
|
shk.clone_from_slice(&new_shk);
|
||||||
|
ct.clone_from_slice(new_ct);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decaps(
|
||||||
|
&self,
|
||||||
|
shk: &mut [u8; SHK_LEN],
|
||||||
|
sk: &[u8; SK_LEN],
|
||||||
|
ct: &[u8; CT_LEN],
|
||||||
|
) -> Result<(), KemError> {
|
||||||
|
let new_shk: [u8; SHK_LEN] = kyber512::decapsulate(&sk.into(), &ct.into());
|
||||||
|
shk.clone_from(&new_shk);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Kyber512 {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KemKyber512 for Kyber512 {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod equivalence_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Test that libcrux and OQS produce the same results
|
||||||
|
#[test]
|
||||||
|
fn proptest_equivalence_libcrux_oqs() {
|
||||||
|
use rosenpass_oqs::Kyber512 as OqsKyber512;
|
||||||
|
|
||||||
|
let (mut sk1, mut pk1) = ([0; SK_LEN], [0; PK_LEN]);
|
||||||
|
let (mut sk2, mut pk2) = ([0; SK_LEN], [0; PK_LEN]);
|
||||||
|
|
||||||
|
let mut ct_left = [0; CT_LEN];
|
||||||
|
let mut ct_right = [0; CT_LEN];
|
||||||
|
|
||||||
|
let mut shk_enc_left = [0; SHK_LEN];
|
||||||
|
let mut shk_enc_right = [0; SHK_LEN];
|
||||||
|
|
||||||
|
// naming schema: shk_dec_{encapsing lib}_{decapsing lib}
|
||||||
|
// should be the same if the encapsing lib was the same.
|
||||||
|
let mut shk_dec_left_left = [0; SHK_LEN];
|
||||||
|
let mut shk_dec_left_right = [0; SHK_LEN];
|
||||||
|
let mut shk_dec_right_left = [0; SHK_LEN];
|
||||||
|
let mut shk_dec_right_right = [0; SHK_LEN];
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let sk1 = &mut sk1;
|
||||||
|
let pk1 = &mut pk1;
|
||||||
|
let sk2 = &mut sk2;
|
||||||
|
let pk2 = &mut pk2;
|
||||||
|
|
||||||
|
let ct_left = &mut ct_left;
|
||||||
|
let ct_right = &mut ct_right;
|
||||||
|
|
||||||
|
let shk_enc_left = &mut shk_enc_left;
|
||||||
|
let shk_enc_right = &mut shk_enc_right;
|
||||||
|
|
||||||
|
let shk_dec_left_left = &mut shk_dec_left_left;
|
||||||
|
let shk_dec_left_right = &mut shk_dec_left_right;
|
||||||
|
let shk_dec_right_left = &mut shk_dec_right_left;
|
||||||
|
let shk_dec_right_right = &mut shk_dec_right_right;
|
||||||
|
|
||||||
|
Kyber512.keygen(sk1, pk1).unwrap();
|
||||||
|
Kyber512.keygen(sk2, pk2).unwrap();
|
||||||
|
|
||||||
|
Kyber512.encaps(shk_enc_left, ct_left, pk2).unwrap();
|
||||||
|
OqsKyber512.encaps(shk_enc_right, ct_right, pk2).unwrap();
|
||||||
|
|
||||||
|
Kyber512.decaps(shk_dec_left_left, sk2, ct_left).unwrap();
|
||||||
|
Kyber512.decaps(shk_dec_right_left, sk2, ct_right).unwrap();
|
||||||
|
|
||||||
|
OqsKyber512
|
||||||
|
.decaps(shk_dec_left_right, sk2, ct_left)
|
||||||
|
.unwrap();
|
||||||
|
OqsKyber512
|
||||||
|
.decaps(shk_dec_right_right, sk2, ct_right)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(shk_enc_left, shk_dec_left_left);
|
||||||
|
assert_eq!(shk_enc_left, shk_dec_left_right);
|
||||||
|
|
||||||
|
assert_eq!(shk_enc_right, shk_dec_right_left);
|
||||||
|
assert_eq!(shk_enc_right, shk_dec_right_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
ciphers/src/subtle/libcrux/mod.rs
Normal file
14
ciphers/src/subtle/libcrux/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//! Implementations backed by libcrux, a verified crypto library.
|
||||||
|
//!
|
||||||
|
//! [Website](https://cryspen.com/libcrux/)
|
||||||
|
//!
|
||||||
|
//! [Github](https://github.com/cryspen/libcrux)
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_blake2")]
|
||||||
|
pub mod blake2b;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_chachapoly")]
|
||||||
|
pub mod chacha20poly1305_ietf;
|
||||||
|
|
||||||
|
#[cfg(feature = "experiment_libcrux_kyber")]
|
||||||
|
pub mod kyber512;
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
/// This module provides the following cryptographic schemes:
|
//! Contains the implementations of the crypto algorithms used throughout Rosenpass.
|
||||||
/// - [blake2b]: The blake2b hash function
|
|
||||||
/// - [chacha20poly1305_ietf]: The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305) (only used when the feature `experiment_libcrux` is disabled).
|
pub mod keyed_hash;
|
||||||
/// - [chacha20poly1305_ietf_libcrux]: The Chacha20Poly1305 AEAD as implemented in [libcrux](https://github.com/cryspen/libcrux) (only used when the feature `experiment_libcrux` is enabled).
|
|
||||||
/// - [incorrect_hmac_blake2b]: An (incorrect) hmac based on [blake2b].
|
pub use custom::incorrect_hmac_blake2b;
|
||||||
/// - [xchacha20poly1305_ietf] The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305)
|
pub use rust_crypto::{blake2b, keyed_shake256};
|
||||||
pub mod blake2b;
|
|
||||||
#[cfg(not(feature = "experiment_libcrux"))]
|
pub mod custom;
|
||||||
pub mod chacha20poly1305_ietf;
|
pub mod rust_crypto;
|
||||||
#[cfg(feature = "experiment_libcrux")]
|
|
||||||
pub mod chacha20poly1305_ietf_libcrux;
|
#[cfg(any(
|
||||||
pub mod incorrect_hmac_blake2b;
|
feature = "experiment_libcrux_blake2",
|
||||||
pub mod xchacha20poly1305_ietf;
|
feature = "experiment_libcrux_chachapoly",
|
||||||
|
feature = "experiment_libcrux_kyber"
|
||||||
|
))]
|
||||||
|
pub mod libcrux;
|
||||||
|
|||||||
44
ciphers/src/subtle/rust_crypto/blake2b.rs
Normal file
44
ciphers/src/subtle/rust_crypto/blake2b.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
use blake2::digest::crypto_common::generic_array::GenericArray;
|
||||||
|
use blake2::digest::crypto_common::typenum::U32;
|
||||||
|
use blake2::digest::{FixedOutput, Mac};
|
||||||
|
use blake2::Blake2bMac;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHash;
|
||||||
|
use rosenpass_to::{ops::copy_slice, To};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_blake2b::{HASH_LEN, KEY_LEN};
|
||||||
|
|
||||||
|
/// Specify that the used implementation of BLAKE2b is the MAC version of BLAKE2b
|
||||||
|
/// with output and key length of 32 bytes (see [Blake2bMac]).
|
||||||
|
type Impl = Blake2bMac<U32>;
|
||||||
|
|
||||||
|
/// Hashes the given `data` with the [Blake2bMac] hash function under the given `key`.
|
||||||
|
/// The both the length of the output the length of the key 32 bytes (or 256 bits).
|
||||||
|
pub struct Blake2b;
|
||||||
|
|
||||||
|
impl KeyedHash<KEY_LEN, HASH_LEN> for Blake2b {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let mut h = Impl::new_from_slice(key)?;
|
||||||
|
h.update(data);
|
||||||
|
|
||||||
|
// Jesus christ, blake2 crate, your usage of GenericArray might be nice and fancy,
|
||||||
|
// but it introduces a ton of complexity. This cost me half an hour just to figure
|
||||||
|
// out the right way to use the imports while allowing for zeroization.
|
||||||
|
// An API based on slices might actually be simpler.
|
||||||
|
let mut tmp = Zeroizing::new([0u8; HASH_LEN]);
|
||||||
|
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
|
||||||
|
h.finalize_into(tmp);
|
||||||
|
copy_slice(tmp.as_ref()).to(out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rosenpass_cipher_traits::algorithms::KeyedHashBlake2b for Blake2b {}
|
||||||
79
ciphers/src/subtle/rust_crypto/chacha20poly1305_ietf.rs
Normal file
79
ciphers/src/subtle/rust_crypto/chacha20poly1305_ietf.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use rosenpass_to::ops::copy_slice;
|
||||||
|
use rosenpass_to::To;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::AeadChaCha20Poly1305;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Aead, AeadError};
|
||||||
|
|
||||||
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
|
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
|
||||||
|
use chacha20poly1305::{AeadInPlace, KeyInit};
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::aead_chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
|
||||||
|
|
||||||
|
/// Implements the [`Aead`] and [`AeadChaCha20Poly1305`] traits backed by the RustCrypto
|
||||||
|
/// implementation.
|
||||||
|
pub struct ChaCha20Poly1305;
|
||||||
|
|
||||||
|
impl Aead<KEY_LEN, NONCE_LEN, TAG_LEN> for ChaCha20Poly1305 {
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
copy_slice(plaintext).to(ct);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let encrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
let mac_value = encrypter
|
||||||
|
.encrypt_in_place_detached(nonce, ad, ct)
|
||||||
|
.map_err(|_| AeadError::InternalError)?;
|
||||||
|
copy_slice(&mac_value[..]).to(mac);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
let tag = GenericArray::from_slice(mac);
|
||||||
|
copy_slice(ct).to(plaintext);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let decrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
decrypter
|
||||||
|
.decrypt_in_place_detached(nonce, ad, plaintext, tag)
|
||||||
|
.map_err(|_| AeadError::DecryptError)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadChaCha20Poly1305 for ChaCha20Poly1305 {}
|
||||||
117
ciphers/src/subtle/rust_crypto/keyed_shake256.rs
Normal file
117
ciphers/src/subtle/rust_crypto/keyed_shake256.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use anyhow::ensure;
|
||||||
|
use rosenpass_cipher_traits::primitives::{InferKeyedHash, KeyedHash};
|
||||||
|
use sha3::digest::{ExtendableOutput, Update, XofReader};
|
||||||
|
use sha3::Shake256;
|
||||||
|
|
||||||
|
pub use rosenpass_cipher_traits::algorithms::keyed_hash_shake256::{HASH_LEN, KEY_LEN};
|
||||||
|
|
||||||
|
/// An implementation of the [`KeyedHash`] trait backed by the RustCrypto implementation of SHAKE256.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SHAKE256Core<const KEY_LEN: usize, const HASH_LEN: usize>;
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize> KeyedHash<KEY_LEN, HASH_LEN>
|
||||||
|
for SHAKE256Core<KEY_LEN, HASH_LEN>
|
||||||
|
{
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
/// Provides a keyed hash function based on SHAKE256. To work for the protocol, the output length
|
||||||
|
/// and key length are fixed to 32 bytes (also see [KEY_LEN] and [HASH_LEN]).
|
||||||
|
///
|
||||||
|
/// Note that the SHAKE256 is designed for 64 bytes output length, which we truncate to 32 bytes
|
||||||
|
/// to work well with the overall protocol. Referring to Table 4 of FIPS 202, this offers the
|
||||||
|
/// same collision resistance as SHAKE128, but 256 bits of preimage resistance. We therefore
|
||||||
|
/// prefer a truncated SHAKE256 over SHAKE128.
|
||||||
|
///
|
||||||
|
/// #Examples
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::rust_crypto::keyed_shake256::SHAKE256Core;
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHash;
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const HASH_LEN: usize = 32;
|
||||||
|
/// let key: [u8; 32] = [0; KEY_LEN];
|
||||||
|
/// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; HASH_LEN];
|
||||||
|
///
|
||||||
|
/// assert!(SHAKE256Core::<32, 32>::keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[174, 4, 47, 188, 1, 228, 179, 246, 67, 43, 255, 94, 155, 11,
|
||||||
|
/// 187, 161, 38, 110, 217, 23, 4, 62, 172, 30, 218, 187, 249, 80, 171, 21, 145, 238];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
/// ```
|
||||||
|
fn keyed_hash(
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
data: &[u8],
|
||||||
|
out: &mut [u8; HASH_LEN],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
// Since SHAKE256 is a XOF, we fix the output length manually to what is required for the
|
||||||
|
// protocol.
|
||||||
|
ensure!(out.len() == HASH_LEN);
|
||||||
|
// Not bothering with padding; the implementation
|
||||||
|
// uses appropriately sized keys.
|
||||||
|
ensure!(key.len() == KEY_LEN);
|
||||||
|
let mut shake256 = Shake256::default();
|
||||||
|
shake256.update(key);
|
||||||
|
shake256.update(data);
|
||||||
|
|
||||||
|
// Since we use domain separation extensively, related outputs of the truncated XOF
|
||||||
|
// are not a concern. This follows the NIST recommendations in Section A.2 of the FIPS 202
|
||||||
|
// standard, (pages 24/25, i.e., 32/33 in the PDF).
|
||||||
|
shake256.finalize_xof().read(out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize> SHAKE256Core<KEY_LEN, HASH_LEN> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const KEY_LEN: usize, const HASH_LEN: usize> Default for SHAKE256Core<KEY_LEN, HASH_LEN> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This type provides the same functionality as [SHAKE256Core], but bound to an instance.
|
||||||
|
/// In contrast to [SHAKE256Core], this allows for type interference and thus allows the user of the
|
||||||
|
/// type to omit explicit type parameters when instantiating the type or using it.
|
||||||
|
///
|
||||||
|
/// The instantiation is based on the [InferKeyedHash] trait.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::rust_crypto::keyed_shake256::{SHAKE256};
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHashInstance;
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const HASH_LEN: usize = 32;
|
||||||
|
/// let key: [u8; KEY_LEN] = [0; KEY_LEN];
|
||||||
|
/// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; HASH_LEN];
|
||||||
|
/// assert!(SHAKE256::new().keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[174, 4, 47, 188, 1, 228, 179, 246, 67, 43, 255, 94, 155, 11, 187,
|
||||||
|
/// 161, 38, 110, 217, 23, 4, 62, 172, 30, 218, 187, 249, 80, 171, 21, 145, 238];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
/// ```
|
||||||
|
pub type SHAKE256<const KEY_LEN: usize, const HASH_LEN: usize> =
|
||||||
|
InferKeyedHash<SHAKE256Core<KEY_LEN, HASH_LEN>, KEY_LEN, HASH_LEN>;
|
||||||
|
|
||||||
|
/// The SHAKE256_32 type is a specific instance of the [SHAKE256] type with the key length and hash
|
||||||
|
/// length fixed to 32 bytes.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_ciphers::subtle::keyed_shake256::{SHAKE256_32};
|
||||||
|
/// use rosenpass_cipher_traits::primitives::KeyedHashInstance;
|
||||||
|
/// const KEY_LEN: usize = 32;
|
||||||
|
/// const HASH_LEN: usize = 32;
|
||||||
|
/// let key: [u8; 32] = [0; KEY_LEN];
|
||||||
|
/// let data: [u8; 32] = [255; 32]; // arbitrary data, could also be longer
|
||||||
|
/// // buffer for the hash output
|
||||||
|
/// let mut hash_data: [u8; 32] = [0u8; HASH_LEN];
|
||||||
|
///
|
||||||
|
/// assert!(SHAKE256_32::new().keyed_hash(&key, &data, &mut hash_data).is_ok(), "Hashing has to return OK result");
|
||||||
|
/// # let expected_hash: &[u8] = &[174, 4, 47, 188, 1, 228, 179, 246, 67, 43, 255, 94, 155, 11, 187,
|
||||||
|
/// 161, 38, 110, 217, 23, 4, 62, 172, 30, 218, 187, 249, 80, 171, 21, 145, 238];
|
||||||
|
/// # assert_eq!(hash_data, expected_hash);
|
||||||
|
/// ```
|
||||||
|
pub type SHAKE256_32 = SHAKE256<32, 32>;
|
||||||
7
ciphers/src/subtle/rust_crypto/mod.rs
Normal file
7
ciphers/src/subtle/rust_crypto/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! Implementations backed by RustCrypto
|
||||||
|
|
||||||
|
pub mod blake2b;
|
||||||
|
pub mod keyed_shake256;
|
||||||
|
|
||||||
|
pub mod chacha20poly1305_ietf;
|
||||||
|
pub mod xchacha20poly1305_ietf;
|
||||||
@@ -1,17 +1,82 @@
|
|||||||
use rosenpass_to::ops::copy_slice;
|
use rosenpass_to::ops::copy_slice;
|
||||||
use rosenpass_to::To;
|
use rosenpass_to::To;
|
||||||
use rosenpass_util::typenum2const;
|
|
||||||
|
use rosenpass_cipher_traits::algorithms::aead_xchacha20poly1305::AeadXChaCha20Poly1305;
|
||||||
|
use rosenpass_cipher_traits::primitives::{Aead, AeadError, AeadWithNonceInCiphertext};
|
||||||
|
|
||||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
|
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
|
||||||
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
|
use chacha20poly1305::{AeadInPlace, KeyInit};
|
||||||
|
|
||||||
/// The key length is 32 bytes or 256 bits.
|
pub use rosenpass_cipher_traits::algorithms::aead_xchacha20poly1305::{
|
||||||
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
|
KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||||
/// The MAC tag length is 16 bytes or 128 bits.
|
};
|
||||||
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
|
/// Implements the [`Aead`] and [`AeadXChaCha20Poly1305`] traits backed by the RustCrypto
|
||||||
/// The nonce length is 24 bytes or 192 bits.
|
/// implementation.
|
||||||
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
|
pub struct XChaCha20Poly1305;
|
||||||
|
|
||||||
|
impl Aead<KEY_LEN, NONCE_LEN, TAG_LEN> for XChaCha20Poly1305 {
|
||||||
|
fn encrypt(
|
||||||
|
&self,
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
plaintext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ct, mac) = ciphertext.split_at_mut(ciphertext.len() - TAG_LEN);
|
||||||
|
copy_slice(plaintext).to(ct);
|
||||||
|
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let encrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
let mac_value = encrypter
|
||||||
|
.encrypt_in_place_detached(nonce, ad, ct)
|
||||||
|
.map_err(|_| AeadError::InternalError)?;
|
||||||
|
copy_slice(&mac_value[..]).to(mac);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(
|
||||||
|
&self,
|
||||||
|
plaintext: &mut [u8],
|
||||||
|
key: &[u8; KEY_LEN],
|
||||||
|
nonce: &[u8; NONCE_LEN],
|
||||||
|
ad: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), AeadError> {
|
||||||
|
// The comparison looks complicated, but we need to do it this way to prevent
|
||||||
|
// over/underflows.
|
||||||
|
if ciphertext.len() < TAG_LEN || ciphertext.len() - TAG_LEN < plaintext.len() {
|
||||||
|
return Err(AeadError::InvalidLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ct, mac) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
|
||||||
|
let nonce = GenericArray::from_slice(nonce);
|
||||||
|
let tag = GenericArray::from_slice(mac);
|
||||||
|
copy_slice(ct).to(plaintext);
|
||||||
|
|
||||||
|
// This only fails if the length is wrong, which really shouldn't happen and would
|
||||||
|
// constitute an internal error.
|
||||||
|
let decrypter = AeadImpl::new_from_slice(key).map_err(|_| AeadError::InternalError)?;
|
||||||
|
|
||||||
|
decrypter
|
||||||
|
.decrypt_in_place_detached(nonce, ad, plaintext, tag)
|
||||||
|
.map_err(|_| AeadError::DecryptError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadXChaCha20Poly1305 for XChaCha20Poly1305 {}
|
||||||
|
|
||||||
/// Encrypts using XChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
/// Encrypts using XChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
|
||||||
/// `key` and `nonce` MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
|
/// `key` and `nonce` MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
|
||||||
@@ -23,12 +88,12 @@ pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///```rust
|
///```rust
|
||||||
/// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
/// # use rosenpass_ciphers::subtle::rust_crypto::xchacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||||
/// const PLAINTEXT_LEN: usize = 43;
|
/// const PLAINTEXT_LEN: usize = 43;
|
||||||
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
|
||||||
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
|
||||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
/// let key: &[u8; KEY_LEN] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
/// let nonce: &[u8; NONCE_LEN] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||||
/// let mut ciphertext_buffer = [0u8; NONCE_LEN + PLAINTEXT_LEN + TAG_LEN];
|
/// let mut ciphertext_buffer = [0u8; NONCE_LEN + PLAINTEXT_LEN + TAG_LEN];
|
||||||
///
|
///
|
||||||
@@ -44,19 +109,14 @@ pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn encrypt(
|
pub fn encrypt(
|
||||||
ciphertext: &mut [u8],
|
ciphertext: &mut [u8],
|
||||||
key: &[u8],
|
key: &[u8; KEY_LEN],
|
||||||
nonce: &[u8],
|
nonce: &[u8; NONCE_LEN],
|
||||||
ad: &[u8],
|
ad: &[u8],
|
||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let nonce = GenericArray::from_slice(nonce);
|
XChaCha20Poly1305
|
||||||
let (n, ct_mac) = ciphertext.split_at_mut(NONCE_LEN);
|
.encrypt_with_nonce_in_ctxt(ciphertext, key, nonce, ad, plaintext)
|
||||||
let (ct, mac) = ct_mac.split_at_mut(ct_mac.len() - TAG_LEN);
|
.map_err(anyhow::Error::from)
|
||||||
copy_slice(nonce).to(n);
|
|
||||||
copy_slice(plaintext).to(ct);
|
|
||||||
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
|
|
||||||
copy_slice(&mac_value[..]).to(mac);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
|
||||||
@@ -71,7 +131,7 @@ pub fn encrypt(
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///```rust
|
///```rust
|
||||||
/// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
/// # use rosenpass_ciphers::subtle::rust_crypto::xchacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
|
||||||
/// let ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
/// let ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
/// # 0, 0, 0, 0, 8, 241, 229, 253, 200, 81, 248, 30, 183, 149, 134, 168, 149, 87, 109, 49, 159, 108,
|
/// # 0, 0, 0, 0, 8, 241, 229, 253, 200, 81, 248, 30, 183, 149, 134, 168, 149, 87, 109, 49, 159, 108,
|
||||||
/// # 206, 89, 51, 232, 232, 197, 163, 253, 254, 208, 73, 76, 253, 13, 247, 162, 133, 184, 177, 44,
|
/// # 206, 89, 51, 232, 232, 197, 163, 253, 254, 208, 73, 76, 253, 13, 247, 162, 133, 184, 177, 44,
|
||||||
@@ -80,8 +140,8 @@ pub fn encrypt(
|
|||||||
/// const PLAINTEXT_LEN: usize = 43;
|
/// const PLAINTEXT_LEN: usize = 43;
|
||||||
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN + NONCE_LEN, ciphertext.len());
|
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN + NONCE_LEN, ciphertext.len());
|
||||||
///
|
///
|
||||||
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
/// let key: &[u8; KEY_LEN] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
|
||||||
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
/// let nonce: &[u8; NONCE_LEN] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
|
||||||
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
|
||||||
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
|
||||||
///
|
///
|
||||||
@@ -94,15 +154,11 @@ pub fn encrypt(
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn decrypt(
|
pub fn decrypt(
|
||||||
plaintext: &mut [u8],
|
plaintext: &mut [u8],
|
||||||
key: &[u8],
|
key: &[u8; KEY_LEN],
|
||||||
ad: &[u8],
|
ad: &[u8],
|
||||||
ciphertext: &[u8],
|
ciphertext: &[u8],
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let (n, ct_mac) = ciphertext.split_at(NONCE_LEN);
|
XChaCha20Poly1305
|
||||||
let (ct, mac) = ct_mac.split_at(ct_mac.len() - TAG_LEN);
|
.decrypt_with_nonce_in_ctxt(plaintext, key, ad, ciphertext)
|
||||||
let nonce = GenericArray::from_slice(n);
|
.map_err(anyhow::Error::from)
|
||||||
let tag = GenericArray::from_slice(mac);
|
|
||||||
copy_slice(ct).to(plaintext);
|
|
||||||
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ description = "Rosenpass internal utilities for constant time crypto implementat
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -19,4 +20,7 @@ rosenpass-to = { workspace = true }
|
|||||||
memsec = { workspace = true }
|
memsec = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8.5"
|
rand = { workspace = true }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
|||||||
/// For discussion on how to (further) ensure the constant-time execution of this function,
|
/// For discussion on how to (further) ensure the constant-time execution of this function,
|
||||||
/// see <https://github.com/rosenpass/rosenpass/issues/232>
|
/// see <https://github.com/rosenpass/rosenpass/issues/232>
|
||||||
#[cfg(all(test, feature = "constant_time_tests"))]
|
#[cfg(all(test, feature = "constant_time_tests"))]
|
||||||
|
// Stopgap measure against https://github.com/rosenpass/rosenpass/issues/634
|
||||||
|
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use core::hint::black_box;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -50,14 +53,12 @@ mod tests {
|
|||||||
fn memcmp_runs_in_constant_time() {
|
fn memcmp_runs_in_constant_time() {
|
||||||
// prepare data to compare
|
// prepare data to compare
|
||||||
let n: usize = 1E6 as usize; // number of comparisons to run
|
let n: usize = 1E6 as usize; // number of comparisons to run
|
||||||
let len = 1024; // length of each slice passed as parameters to the tested comparison function
|
const LEN: usize = 1024; // length of each slice passed as parameters to the tested comparison function
|
||||||
let a1 = "a".repeat(len);
|
|
||||||
let a2 = a1.clone();
|
|
||||||
let b = "b".repeat(len);
|
|
||||||
|
|
||||||
let a1 = a1.as_bytes();
|
let a = [b'a'; LEN];
|
||||||
let a2 = a2.as_bytes();
|
let b = [b'b'; LEN];
|
||||||
let b = b.as_bytes();
|
|
||||||
|
let mut tmp = [0u8; LEN];
|
||||||
|
|
||||||
// vector representing all timing tests
|
// vector representing all timing tests
|
||||||
//
|
//
|
||||||
@@ -71,12 +72,14 @@ mod tests {
|
|||||||
|
|
||||||
// run comparisons / call function to test
|
// run comparisons / call function to test
|
||||||
for test in tests.iter_mut() {
|
for test in tests.iter_mut() {
|
||||||
|
let src = match test.0 {
|
||||||
|
true => a,
|
||||||
|
false => b,
|
||||||
|
};
|
||||||
|
tmp.copy_from_slice(&src);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if test.0 {
|
memcmp(black_box(&a), black_box(&tmp));
|
||||||
memcmp(a1, a2);
|
|
||||||
} else {
|
|
||||||
memcmp(a1, b);
|
|
||||||
}
|
|
||||||
test.1 = now.elapsed();
|
test.1 = now.elapsed();
|
||||||
// println!("eq: {}, elapsed: {:.2?}", test.0, test.1);
|
// println!("eq: {}, elapsed: {:.2?}", test.0, test.1);
|
||||||
}
|
}
|
||||||
@@ -113,6 +116,7 @@ mod tests {
|
|||||||
// Pearson correlation
|
// Pearson correlation
|
||||||
let correlation = cv / (sd_x * sd_y);
|
let correlation = cv / (sd_x * sd_y);
|
||||||
println!("correlation: {:.6?}", correlation);
|
println!("correlation: {:.6?}", correlation);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
correlation.abs() < 0.01,
|
correlation.abs() < 0.01,
|
||||||
"execution time correlates with result"
|
"execution time correlates with result"
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ main() {
|
|||||||
|
|
||||||
exc cargo llvm-cov --all-features --workspace --doctests --branch
|
exc cargo llvm-cov --all-features --workspace --doctests --branch
|
||||||
|
|
||||||
exc cp -rv target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/doctestbins
|
exc rm -rf target/llvm-cov-target/debug/deps/doctestbins
|
||||||
|
exc mv -v target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/
|
||||||
exc rm -rf "${OUTPUT_DIR}"
|
exc rm -rf "${OUTPUT_DIR}"
|
||||||
exc mkdir -p "${OUTPUT_DIR}"
|
exc mkdir -p "${OUTPUT_DIR}"
|
||||||
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
|
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
|
||||||
|
|||||||
129
deny.toml
Normal file
129
deny.toml
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# The graph table configures how the dependency graph is constructed and thus
|
||||||
|
# which crates the checks are performed against
|
||||||
|
[graph]
|
||||||
|
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||||
|
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||||
|
# is recommended to pass `--all-features` on the cmd line instead
|
||||||
|
all-features = true
|
||||||
|
# If true, metadata will be collected with `--no-default-features`. The same
|
||||||
|
# caveat with `all-features` applies
|
||||||
|
no-default-features = false
|
||||||
|
|
||||||
|
# The output table provides options for how/if diagnostics are outputted
|
||||||
|
[output]
|
||||||
|
# When outputting inclusion graphs in diagnostics that include features, this
|
||||||
|
# option can be used to specify the depth at which feature edges will be added.
|
||||||
|
# This option is included since the graphs can be quite large and the addition
|
||||||
|
# of features from the crate(s) to all of the graph roots can be far too verbose.
|
||||||
|
# This option can be overridden via `--feature-depth` on the cmd line
|
||||||
|
feature-depth = 1
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check advisories`
|
||||||
|
# More documentation for the advisories section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||||
|
[advisories]
|
||||||
|
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||||
|
# output a note when they are encountered.
|
||||||
|
ignore = [
|
||||||
|
"RUSTSEC-2024-0370",
|
||||||
|
"RUSTSEC-2024-0436",
|
||||||
|
"RUSTSEC-2023-0089",
|
||||||
|
]
|
||||||
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
|
# If this is false, then it uses a built-in git library.
|
||||||
|
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||||
|
# See Git Authentication for more information about setting up git authentication.
|
||||||
|
#git-fetch-with-cli = true
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check #licenses`
|
||||||
|
# More documentation for the licenses section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||||
|
[licenses]
|
||||||
|
# List of explicitly allowed licenses
|
||||||
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
|
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||||
|
allow = [
|
||||||
|
"MIT",
|
||||||
|
"Apache-2.0",
|
||||||
|
"Apache-2.0 WITH LLVM-exception",
|
||||||
|
"BSD-3-Clause",
|
||||||
|
"ISC",
|
||||||
|
]
|
||||||
|
# The confidence threshold for detecting a license from license text.
|
||||||
|
# The higher the value, the more closely the license text must be to the
|
||||||
|
# canonical license text of a valid SPDX license file.
|
||||||
|
# [possible values: any between 0.0 and 1.0].
|
||||||
|
confidence-threshold = 0.8
|
||||||
|
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||||
|
# aren't accepted for every possible crate as with the normal allow list
|
||||||
|
exceptions = [
|
||||||
|
# Each entry is the crate and version constraint, and its specific allow
|
||||||
|
# list
|
||||||
|
{ allow = ["Unicode-DFS-2016", "Unicode-3.0"], crate = "unicode-ident" },
|
||||||
|
{ allow = ["NCSA"], crate = "libfuzzer-sys" },
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
[licenses.private]
|
||||||
|
# If true, ignores workspace crates that aren't published, or are only
|
||||||
|
# published to private registries.
|
||||||
|
# To see how to mark a crate as unpublished (to the official registry),
|
||||||
|
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||||
|
ignore = true
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check bans`.
|
||||||
|
# More documentation about the 'bans' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||||
|
[bans]
|
||||||
|
# Lint level for when multiple versions of the same crate are detected
|
||||||
|
multiple-versions = "warn"
|
||||||
|
# Lint level for when a crate version requirement is `*`
|
||||||
|
wildcards = "allow"
|
||||||
|
# The graph highlighting used when creating dotgraphs for crates
|
||||||
|
# with multiple versions
|
||||||
|
# * lowest-version - The path to the lowest versioned duplicate is highlighted
|
||||||
|
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||||
|
# * all - Both lowest-version and simplest-path are used
|
||||||
|
highlight = "all"
|
||||||
|
# The default lint level for `default` features for crates that are members of
|
||||||
|
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||||
|
# `default` on a crate-by-crate basis if desired.
|
||||||
|
workspace-default-features = "allow"
|
||||||
|
# The default lint level for `default` features for external crates that are not
|
||||||
|
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||||
|
# on a crate-by-crate basis if desired.
|
||||||
|
external-default-features = "allow"
|
||||||
|
# List of crates that are allowed. Use with care!
|
||||||
|
allow = [
|
||||||
|
]
|
||||||
|
# List of crates to deny
|
||||||
|
deny = [
|
||||||
|
]
|
||||||
|
|
||||||
|
skip-tree = [
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check sources`.
|
||||||
|
# More documentation about the 'sources' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||||
|
[sources]
|
||||||
|
# Lint level for what to happen when a crate from a crate registry that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-registry = "warn"
|
||||||
|
# Lint level for what to happen when a crate from a git repository that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-git = "warn"
|
||||||
|
# List of URLs for allowed crate registries. Defaults to the crates.io index
|
||||||
|
# if not specified. If it is specified but empty, no registries are allowed.
|
||||||
|
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||||
|
# List of URLs for allowed Git repositories
|
||||||
|
allow-git = ["git+https://github.com/rosenpass/memsec.git?branch=master"]
|
||||||
|
|
||||||
|
[sources.allow-org]
|
||||||
|
# github.com organizations to allow git sources for
|
||||||
|
github = []
|
||||||
|
# gitlab.com organizations to allow git sources for
|
||||||
|
gitlab = []
|
||||||
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
bitbucket = []
|
||||||
45
docker/Dockerfile
Normal file
45
docker/Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
ARG BASE_IMAGE=debian:bookworm-slim
|
||||||
|
|
||||||
|
# Stage 1: Base image with cargo-chef installed
|
||||||
|
FROM rust:latest AS chef
|
||||||
|
RUN cargo install cargo-chef
|
||||||
|
# install software required for liboqs-rust
|
||||||
|
RUN apt-get update && apt-get install -y clang cmake && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Stage 2: Prepare the cargo-chef recipe
|
||||||
|
FROM chef AS planner
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
|
# Stage 3: Cache dependencies using the recipe
|
||||||
|
FROM chef AS cacher
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=planner /app/recipe.json recipe.json
|
||||||
|
RUN cargo chef cook --release --recipe-path recipe.json
|
||||||
|
|
||||||
|
# Stage 4: Build the application
|
||||||
|
FROM cacher AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# Stage 5: Install runtime-dependencies in the base image
|
||||||
|
FROM ${BASE_IMAGE} AS base_image_with_dependencies
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y iproute2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Final Stage (rosenpass): Copy the rosenpass binary
|
||||||
|
FROM base_image_with_dependencies AS rosenpass
|
||||||
|
COPY --from=builder /app/target/release/rosenpass /usr/local/bin/rosenpass
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/rosenpass" ]
|
||||||
|
|
||||||
|
# Final Stage (rp): Copy the rp binary
|
||||||
|
FROM base_image_with_dependencies AS rp
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y wireguard && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /app/target/release/rp /usr/local/bin/rp
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/rp" ]
|
||||||
203
docker/USAGE.md
Normal file
203
docker/USAGE.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# Rosenpass in Docker
|
||||||
|
|
||||||
|
Rosenpass provides post-quantum-secure key exchange for VPNs. It generates symmetric keys used by [WireGuard](https://www.wireguard.com/papers/wireguard.pdf) or other applications. The protocol enhances "Post-Quantum WireGuard" ([PQWG](https://eprint.iacr.org/2020/379)) with a cookie mechanism for better security against state disruption attacks.
|
||||||
|
|
||||||
|
Prebuilt Docker images are available for easy deployment:
|
||||||
|
|
||||||
|
- [`ghcr.io/rosenpass/rosenpass`](https://github.com/rosenpass/rosenpass/pkgs/container/rosenpass) – the core key exchange tool
|
||||||
|
- [`ghcr.io/rosenpass/rp`](https://github.com/rosenpass/rosenpass/pkgs/container/rp) – a frontend for setting up WireGuard VPNs
|
||||||
|
|
||||||
|
The entrypoint of the `rosenpass` image is the `rosenpass` executable, whose documentation can be found [here](https://rosenpass.eu/docs/rosenpass-tool/manuals/rp_manual/).
|
||||||
|
Similarly, the entrypoint of the `rp` image is the `rp` executable, with its documentation available [here](https://rosenpass.eu/docs/rosenpass-tool/manuals/rp1/).
|
||||||
|
|
||||||
|
## Usage - Standalone Key Exchange
|
||||||
|
|
||||||
|
The `ghcr.io/rosenpass/rosenpass` image can be used in a server-client setup to exchange quantum-secure shared keys.
|
||||||
|
This setup uses rosenpass as a standalone application, without using any other component such as wireguard.
|
||||||
|
What follows, is a simple setup for illustrative purposes.
|
||||||
|
|
||||||
|
Create a docker network that is used to connect the containers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create -d bridge rp
|
||||||
|
export NET=rp
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the server and client key pairs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir ./workdir-client ./workdir-server
|
||||||
|
docker run -it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
gen-keys --public-key=workdir/server-public --secret-key=workdir/server-secret
|
||||||
|
docker run -it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
gen-keys --public-key=workdir/client-public --secret-key=workdir/client-secret
|
||||||
|
# share the public keys between client and server
|
||||||
|
cp workdir-client/client-public workdir-server/client-public
|
||||||
|
cp workdir-server/server-public workdir-client/server-public
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name "rpserver" --network ${NET} \
|
||||||
|
-it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
exchange \
|
||||||
|
private-key workdir/server-secret \
|
||||||
|
public-key workdir/server-public \
|
||||||
|
listen 0.0.0.0:9999 \
|
||||||
|
peer public-key workdir/client-public \
|
||||||
|
outfile workdir/server-sharedkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Find out the ip address of the server container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EP="rpserver"
|
||||||
|
EP=$(docker inspect --format '{{ .NetworkSettings.Networks.rp.IPAddress }}' $EP)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the client container and perform the key exchange:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name "rpclient" --network ${NET} \
|
||||||
|
-it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rosenpass \
|
||||||
|
exchange \
|
||||||
|
private-key workdir/client-secret \
|
||||||
|
public-key workdir/client-public \
|
||||||
|
peer public-key workdir/server-public endpoint ${EP}:9999 \
|
||||||
|
outfile workdir/client-sharedkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the containers will exchange shared keys and each put them into their respective outfile.
|
||||||
|
|
||||||
|
Comparing the outfiles shows that these shared keys equal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmp workdir-server/server-sharedkey workdir-client/client-sharedkey
|
||||||
|
```
|
||||||
|
|
||||||
|
It is now possible to set add these keys as pre-shared keys within a wireguard interface.
|
||||||
|
For example as the server,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PREKEY=$(cat workdir-server/server-sharedkey)
|
||||||
|
wg set <server-interface> peer <client-peer-public-key> preshared-key <(echo "$PREKEY")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage - Combined with wireguard
|
||||||
|
|
||||||
|
The `ghcr.io/rosenpass/rp` image can be used to build a VPN with WireGuard and Rosenpass.
|
||||||
|
In this example, we run two containers on the same system and connect them with a bridge network within the docker overlay network.
|
||||||
|
|
||||||
|
Create the named docker network, to be able to connect the containers.
|
||||||
|
|
||||||
|
Create a docker network that is used to connect the containers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create -d bridge rp
|
||||||
|
export NET=rp
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the server and client secret keys and extract public keys.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ./workdir-server ./workdir-client
|
||||||
|
|
||||||
|
# server
|
||||||
|
docker run -it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
genkey workdir/server.rosenpass-secret
|
||||||
|
docker run -it --rm -v ./workdir-server:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
pubkey workdir/server.rosenpass-secret workdir/server.rosenpass-public
|
||||||
|
|
||||||
|
# client
|
||||||
|
docker run -it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
genkey workdir/client.rosenpass-secret
|
||||||
|
docker run -it --rm -v ./workdir-client:/workdir ghcr.io/rosenpass/rp \
|
||||||
|
pubkey workdir/client.rosenpass-secret workdir/client.rosenpass-public
|
||||||
|
|
||||||
|
# share the public keys between client and server
|
||||||
|
cp -r workdir-client/client.rosenpass-public workdir-server/client.rosenpass-public
|
||||||
|
cp -r workdir-server/server.rosenpass-public workdir-client/server.rosenpass-public
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server container.
|
||||||
|
Note that the `NET_ADMIN` capability is neccessary, the rp command will create and manage wireguard interfaces.
|
||||||
|
Also make sure the `wireguard` kernel module is loaded by the host. (`lsmod | grep wireguard`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name "rpserver" --network ${NET} -it -d --rm -v ./workdir-server:/workdir \
|
||||||
|
--cap-add=NET_ADMIN \
|
||||||
|
ghcr.io/rosenpass/rp \
|
||||||
|
exchange workdir/server.rosenpass-secret dev rosenpass0 \
|
||||||
|
listen 0.0.0.0:9999 peer workdir/client.rosenpass-public allowed-ips 10.0.0.0/8
|
||||||
|
```
|
||||||
|
|
||||||
|
Now find out the ip-address of the server container and then start the client container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EP="rpserver"
|
||||||
|
EP=$(docker inspect --format '{{ .NetworkSettings.Networks.rp.IPAddress }}' $EP)
|
||||||
|
docker run --name "rpclient" --network ${NET} -it -d --rm -v ./workdir-client:/workdir \
|
||||||
|
--cap-add=NET_ADMIN \
|
||||||
|
ghcr.io/rosenpass/rp \
|
||||||
|
exchange workdir/client.rosenpass-secret dev rosenpass1 \
|
||||||
|
peer workdir/server.rosenpass-public endpoint ${EP}:9999 allowed-ips 10.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the docker containers assign the IP addresses:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# server
|
||||||
|
docker exec -it rpserver ip a add 10.0.0.1/24 dev rosenpass0
|
||||||
|
|
||||||
|
# client
|
||||||
|
docker exec -it rpclient ip a add 10.0.0.2/24 dev rosenpass1
|
||||||
|
```
|
||||||
|
|
||||||
|
Done! The two containers should now be connected through a wireguard VPN (Port 1000) with pre-shared keys exchanged by rosenpass (Port 9999).
|
||||||
|
|
||||||
|
Now, test the connection by starting a shell inside the client container, and ping the server through the VPN:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# client
|
||||||
|
docker exec -it rpclient bash
|
||||||
|
apt update; apt install iputils-ping
|
||||||
|
ping 10.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
The ping command should continuously show ping-logs:
|
||||||
|
|
||||||
|
```
|
||||||
|
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
|
||||||
|
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.119 ms
|
||||||
|
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.132 ms
|
||||||
|
64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.394 ms
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
While the ping is running, you may stop the server container, and verify that the ping-log halts. In another terminal do:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker stop -t 1 rpserver
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building the Docker Images Locally
|
||||||
|
|
||||||
|
Clone the Rosenpass repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/rosenpass/rosenpass
|
||||||
|
cd rosenpass
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the rp image from the root of the repository as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -f docker/Dockerfile -t ghcr.io/rosenpass/rp --target rp .
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the rosenpass image from the root of the repostiry with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -f docker/Dockerfile -t ghcr.io/rosenpass/rosenpass --target rosenpass .
|
||||||
|
```
|
||||||
21
flake.lock
generated
21
flake.lock
generated
@@ -39,6 +39,26 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix-vm-test": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1734355073,
|
||||||
|
"narHash": "sha256-FfdPOGy1zElTwKzjgIMp5K2D3gfPn6VWjVa4MJ9L1Tc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-vm-test",
|
||||||
|
"rev": "5948de39a616f2261dbbf4b6f25cbe1cbefd788c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-vm-test",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728193676,
|
"lastModified": 1728193676,
|
||||||
@@ -59,6 +79,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
|
"nix-vm-test": "nix-vm-test",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
25
flake.nix
25
flake.nix
@@ -6,9 +6,13 @@
|
|||||||
# for rust nightly with llvm-tools-preview
|
# for rust nightly with llvm-tools-preview
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
nix-vm-test.url = "github:numtide/nix-vm-test";
|
||||||
|
nix-vm-test.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
nix-vm-test.inputs.flake-utils.follows = "flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
outputs = { self, nixpkgs, flake-utils, nix-vm-test, ... }@inputs:
|
||||||
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
||||||
|
|
||||||
|
|
||||||
@@ -77,10 +81,19 @@
|
|||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||||
overlays = [ self.overlays.default ];
|
overlays = [
|
||||||
|
self.overlays.default
|
||||||
|
nix-vm-test.overlays.default
|
||||||
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
packages.package-deb = pkgs.callPackage ./pkgs/package-deb.nix {
|
||||||
|
rosenpass = pkgs.pkgsStatic.rosenpass;
|
||||||
|
};
|
||||||
|
packages.package-rpm = pkgs.callPackage ./pkgs/package-rpm.nix {
|
||||||
|
rosenpass = pkgs.pkgsStatic.rosenpass;
|
||||||
|
};
|
||||||
|
|
||||||
#
|
#
|
||||||
### Reading materials ###
|
### Reading materials ###
|
||||||
@@ -114,7 +127,9 @@
|
|||||||
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||||
inputsFrom = [ pkgs.rosenpass ];
|
inputsFrom = [ pkgs.rosenpass ];
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
|
cargo-audit
|
||||||
cargo-release
|
cargo-release
|
||||||
|
cargo-msrv
|
||||||
rustfmt
|
rustfmt
|
||||||
nodePackages.prettier
|
nodePackages.prettier
|
||||||
nushell # for the .ci/gen-workflow-files.nu script
|
nushell # for the .ci/gen-workflow-files.nu script
|
||||||
@@ -150,7 +165,11 @@
|
|||||||
{ nativeBuildInputs = [ pkgs.nodePackages.prettier ]; } ''
|
{ nativeBuildInputs = [ pkgs.nodePackages.prettier ]; } ''
|
||||||
cd ${./.} && prettier --check . && touch $out
|
cd ${./.} && prettier --check . && touch $out
|
||||||
'';
|
'';
|
||||||
};
|
} // pkgs.lib.optionalAttrs (system == "x86_64-linux") (import ./tests/legacy-distro-packaging.nix {
|
||||||
|
inherit pkgs;
|
||||||
|
rosenpass-deb = self.packages.${system}.package-deb;
|
||||||
|
rosenpass-rpm = self.packages.${system}.package-rpm;
|
||||||
|
});
|
||||||
|
|
||||||
formatter = pkgs.nixpkgs-fmt;
|
formatter = pkgs.nixpkgs-fmt;
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ name = "rosenpass-fuzzing"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
publish = false
|
publish = false
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux_all"]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass_ciphers::aead;
|
use rosenpass_cipher_traits::primitives::Aead as _;
|
||||||
|
use rosenpass_ciphers::Aead;
|
||||||
|
|
||||||
#[derive(arbitrary::Arbitrary, Debug)]
|
#[derive(arbitrary::Arbitrary, Debug)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
@@ -17,7 +18,7 @@ pub struct Input {
|
|||||||
fuzz_target!(|input: Input| {
|
fuzz_target!(|input: Input| {
|
||||||
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
|
let mut ciphertext = vec![0u8; input.plaintext.len() + 16];
|
||||||
|
|
||||||
aead::encrypt(
|
Aead.encrypt(
|
||||||
ciphertext.as_mut_slice(),
|
ciphertext.as_mut_slice(),
|
||||||
&input.key,
|
&input.key,
|
||||||
&input.nonce,
|
&input.nonce,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use rosenpass_cipher_traits::primitives::KeyedHashTo;
|
||||||
use rosenpass_ciphers::subtle::blake2b;
|
use rosenpass_ciphers::subtle::blake2b;
|
||||||
use rosenpass_to::To;
|
use rosenpass_to::To;
|
||||||
|
|
||||||
@@ -16,5 +17,7 @@ pub struct Blake2b {
|
|||||||
fuzz_target!(|input: Blake2b| {
|
fuzz_target!(|input: Blake2b| {
|
||||||
let mut out = [0u8; 32];
|
let mut out = [0u8; 32];
|
||||||
|
|
||||||
blake2b::hash(&input.key, &input.data).to(&mut out).unwrap();
|
blake2b::Blake2b::keyed_hash_to(&input.key, &input.data)
|
||||||
|
.to(&mut out)
|
||||||
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ extern crate rosenpass;
|
|||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass::protocol::CryptoServer;
|
use rosenpass::protocol::CryptoServer;
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
use rosenpass_secret_memory::policy::*;
|
use rosenpass_secret_memory::policy::*;
|
||||||
use rosenpass_secret_memory::{PublicBox, Secret};
|
use rosenpass_secret_memory::{PublicBox, Secret};
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::EphemeralKem;
|
use rosenpass_ciphers::EphemeralKem;
|
||||||
|
|
||||||
#[derive(arbitrary::Arbitrary, Debug)]
|
#[derive(arbitrary::Arbitrary, Debug)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
@@ -16,5 +16,7 @@ fuzz_target!(|input: Input| {
|
|||||||
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
let mut ciphertext = [0u8; EphemeralKem::CT_LEN];
|
||||||
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
|
let mut shared_secret = [0u8; EphemeralKem::SHK_LEN];
|
||||||
|
|
||||||
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
EphemeralKem
|
||||||
|
.encaps(&mut shared_secret, &mut ciphertext, &input.pk)
|
||||||
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ extern crate rosenpass;
|
|||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
|
|
||||||
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
|
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
|
||||||
let mut ciphertext = [0u8; StaticKem::CT_LEN];
|
let mut ciphertext = [0u8; StaticKem::CT_LEN];
|
||||||
let mut shared_secret = [0u8; StaticKem::SHK_LEN];
|
let mut shared_secret = [0u8; StaticKem::SHK_LEN];
|
||||||
|
|
||||||
// We expect errors while fuzzing therefore we do not check the result.
|
// We expect errors while fuzzing therefore we do not check the result.
|
||||||
let _ = StaticKem::encaps(&mut shared_secret, &mut ciphertext, &input);
|
let _ = StaticKem.encaps(&mut shared_secret, &mut ciphertext, &input);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ description = "Rosenpass internal bindings to liboqs"
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rosenpass-cipher-traits = { workspace = true }
|
rosenpass-cipher-traits = { workspace = true }
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
/// Generate bindings to a liboqs-provided KEM
|
/// Generate bindings to a liboqs-provided KEM
|
||||||
macro_rules! oqs_kem {
|
macro_rules! oqs_kem {
|
||||||
($name:ident) => { ::paste::paste!{
|
($name:ident, $algo_trait:path) => { ::paste::paste!{
|
||||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
mod [< $name:snake >] {
|
mod [< $name:snake >] {
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::{Kem, KemError};
|
||||||
use rosenpass_util::result::Guaranteed;
|
|
||||||
|
|
||||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
@@ -14,7 +13,7 @@ macro_rules! oqs_kem {
|
|||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "```rust"]
|
#[doc = "```rust"]
|
||||||
#[doc = "use std::borrow::{Borrow, BorrowMut};"]
|
#[doc = "use std::borrow::{Borrow, BorrowMut};"]
|
||||||
#[doc = "use rosenpass_cipher_traits::Kem;"]
|
#[doc = "use rosenpass_cipher_traits::primitives::Kem;"]
|
||||||
#[doc = "use rosenpass_oqs::" $name:camel " as MyKem;"]
|
#[doc = "use rosenpass_oqs::" $name:camel " as MyKem;"]
|
||||||
#[doc = "use rosenpass_secret_memory::{Secret, Public};"]
|
#[doc = "use rosenpass_secret_memory::{Secret, Public};"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
@@ -23,21 +22,26 @@ macro_rules! oqs_kem {
|
|||||||
#[doc = "// Recipient generates secret key, transfers pk to sender"]
|
#[doc = "// Recipient generates secret key, transfers pk to sender"]
|
||||||
#[doc = "let mut sk = Secret::<{ MyKem::SK_LEN }>::zero();"]
|
#[doc = "let mut sk = Secret::<{ MyKem::SK_LEN }>::zero();"]
|
||||||
#[doc = "let mut pk = Public::<{ MyKem::PK_LEN }>::zero();"]
|
#[doc = "let mut pk = Public::<{ MyKem::PK_LEN }>::zero();"]
|
||||||
#[doc = "MyKem::keygen(sk.secret_mut(), pk.borrow_mut());"]
|
#[doc = "MyKem.keygen(sk.secret_mut(), &mut pk);"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "// Sender generates ciphertext and local shared key, sends ciphertext to recipient"]
|
#[doc = "// Sender generates ciphertext and local shared key, sends ciphertext to recipient"]
|
||||||
#[doc = "let mut shk_enc = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
#[doc = "let mut shk_enc = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||||
#[doc = "let mut ct = Public::<{ MyKem::CT_LEN }>::zero();"]
|
#[doc = "let mut ct = Public::<{ MyKem::CT_LEN }>::zero();"]
|
||||||
#[doc = "MyKem::encaps(shk_enc.secret_mut(), ct.borrow_mut(), pk.borrow());"]
|
#[doc = "MyKem.encaps(shk_enc.secret_mut(), &mut ct, &pk);"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "// Recipient decapsulates ciphertext"]
|
#[doc = "// Recipient decapsulates ciphertext"]
|
||||||
#[doc = "let mut shk_dec = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
#[doc = "let mut shk_dec = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||||
#[doc = "MyKem::decaps(shk_dec.secret_mut(), sk.secret(), ct.borrow());"]
|
#[doc = "MyKem.decaps(shk_dec.secret_mut(), sk.secret_mut(), &ct);"]
|
||||||
#[doc = ""]
|
#[doc = ""]
|
||||||
#[doc = "// Both parties end up with the same shared key"]
|
#[doc = "// Both parties end up with the same shared key"]
|
||||||
#[doc = "assert!(rosenpass_constant_time::compare(shk_enc.secret_mut(), shk_dec.secret_mut()) == 0);"]
|
#[doc = "assert!(rosenpass_constant_time::compare(shk_enc.secret(), shk_dec.secret()) == 0);"]
|
||||||
#[doc = "```"]
|
#[doc = "```"]
|
||||||
pub enum [< $name:camel >] {}
|
pub struct [< $name:camel >];
|
||||||
|
|
||||||
|
pub const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
|
||||||
|
pub const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
|
||||||
|
pub const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
|
||||||
|
pub const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
|
||||||
|
|
||||||
/// # Panic & Safety
|
/// # Panic & Safety
|
||||||
///
|
///
|
||||||
@@ -51,17 +55,8 @@ macro_rules! oqs_kem {
|
|||||||
/// to only check that the buffers are big enough, allowing them to be even
|
/// to only check that the buffers are big enough, allowing them to be even
|
||||||
/// bigger. However, from a correctness point of view it does not make sense to
|
/// bigger. However, from a correctness point of view it does not make sense to
|
||||||
/// allow bigger buffers.
|
/// allow bigger buffers.
|
||||||
impl Kem for [< $name:camel >] {
|
impl Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> for [< $name:camel >] {
|
||||||
type Error = ::std::convert::Infallible;
|
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), KemError> {
|
||||||
|
|
||||||
const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
|
|
||||||
const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
|
|
||||||
const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
|
|
||||||
const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
|
|
||||||
|
|
||||||
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Guaranteed<()> {
|
|
||||||
assert_eq!(sk.len(), Self::SK_LEN);
|
|
||||||
assert_eq!(pk.len(), Self::PK_LEN);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
oqs_call!(
|
oqs_call!(
|
||||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
|
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
|
||||||
@@ -73,10 +68,7 @@ macro_rules! oqs_kem {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
|
fn encaps(&self, shk: &mut [u8; SHK_LEN], ct: &mut [u8; CT_LEN], pk: &[u8; PK_LEN]) -> Result<(), KemError> {
|
||||||
assert_eq!(shk.len(), Self::SHK_LEN);
|
|
||||||
assert_eq!(ct.len(), Self::CT_LEN);
|
|
||||||
assert_eq!(pk.len(), Self::PK_LEN);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
oqs_call!(
|
oqs_call!(
|
||||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
|
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
|
||||||
@@ -89,10 +81,7 @@ macro_rules! oqs_kem {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
|
fn decaps(&self, shk: &mut [u8; SHK_LEN], sk: &[u8; SK_LEN], ct: &[u8; CT_LEN]) -> Result<(), KemError> {
|
||||||
assert_eq!(shk.len(), Self::SHK_LEN);
|
|
||||||
assert_eq!(sk.len(), Self::SK_LEN);
|
|
||||||
assert_eq!(ct.len(), Self::CT_LEN);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
oqs_call!(
|
oqs_call!(
|
||||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
|
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
|
||||||
@@ -105,9 +94,16 @@ macro_rules! oqs_kem {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for [< $name:camel >] {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $algo_trait for [< $name:camel >] {}
|
||||||
|
|
||||||
pub use [< $name:snake >] :: [< $name:camel >];
|
pub use [< $name:snake >] :: [< $name:camel >];
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,5 +22,8 @@ macro_rules! oqs_call {
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod kem_macro;
|
mod kem_macro;
|
||||||
oqs_kem!(kyber_512);
|
oqs_kem!(kyber_512, rosenpass_cipher_traits::algorithms::KemKyber512);
|
||||||
oqs_kem!(classic_mceliece_460896);
|
oqs_kem!(
|
||||||
|
classic_mceliece_460896,
|
||||||
|
rosenpass_cipher_traits::algorithms::KemClassicMceliece460896
|
||||||
|
);
|
||||||
|
|||||||
9
pkgs/example.toml
Normal file
9
pkgs/example.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
dev = "rp-example"
|
||||||
|
ip = "fc00::1/64"
|
||||||
|
listen = "[::]:51821"
|
||||||
|
private_keys_dir = "/run/credentials/rp@example.service"
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_keys_dir = "/etc/rosenpass/example/peers/client"
|
||||||
|
allowed_ips = "fc00::2"
|
||||||
30
pkgs/package-deb.nix
Normal file
30
pkgs/package-deb.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{ runCommand, dpkg, rosenpass }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (rosenpass) version;
|
||||||
|
in
|
||||||
|
|
||||||
|
runCommand "rosenpass-${version}.deb" { } ''
|
||||||
|
mkdir -p packageroot/DEBIAN
|
||||||
|
|
||||||
|
cat << EOF > packageroot/DEBIAN/control
|
||||||
|
Package: rosenpass
|
||||||
|
Version: ${version}
|
||||||
|
Architecture: all
|
||||||
|
Maintainer: Jacek Galowicz <jacek@galowicz.de>
|
||||||
|
Depends:
|
||||||
|
Description: Post-quantum-secure VPN tool Rosenpass
|
||||||
|
Rosenpass is a post-quantum-secure VPN
|
||||||
|
that uses WireGuard to transport the actual data.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
mkdir -p packageroot/usr/bin
|
||||||
|
install -m755 -t packageroot/usr/bin ${rosenpass}/bin/*
|
||||||
|
|
||||||
|
mkdir -p packageroot/etc/rosenpass
|
||||||
|
cp -r ${rosenpass}/lib/systemd packageroot/etc/
|
||||||
|
cp ${./example.toml} packageroot/etc/rosenpass/example.toml
|
||||||
|
|
||||||
|
${dpkg}/bin/dpkg --build packageroot $out
|
||||||
|
''
|
||||||
57
pkgs/package-rpm.nix
Normal file
57
pkgs/package-rpm.nix
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{ lib, system, runCommand, rosenpass, rpm }:
|
||||||
|
|
||||||
|
let
|
||||||
|
splitVersion = lib.strings.splitString "-" rosenpass.version;
|
||||||
|
version = builtins.head splitVersion;
|
||||||
|
release =
|
||||||
|
if builtins.length splitVersion != 2
|
||||||
|
then "release"
|
||||||
|
else builtins.elemAt splitVersion 1;
|
||||||
|
arch = builtins.head (builtins.split "-" system);
|
||||||
|
in
|
||||||
|
|
||||||
|
runCommand "rosenpass-${version}.rpm" { } ''
|
||||||
|
mkdir -p rpmbuild/SPECS
|
||||||
|
|
||||||
|
cat << EOF > rpmbuild/SPECS/rosenpass.spec
|
||||||
|
Name: rosenpass
|
||||||
|
Release: ${release}
|
||||||
|
Version: ${version}
|
||||||
|
Summary: Post-quantum-secure VPN key exchange
|
||||||
|
License: Apache-2.0
|
||||||
|
|
||||||
|
%description
|
||||||
|
Post-quantum-secure VPN tool Rosenpass
|
||||||
|
Rosenpass is a post-quantum-secure VPN
|
||||||
|
that uses WireGuard to transport the actual data.
|
||||||
|
|
||||||
|
%files
|
||||||
|
/usr/bin/rosenpass
|
||||||
|
/usr/bin/rp
|
||||||
|
/etc/systemd/system/rosenpass.target
|
||||||
|
/etc/systemd/system/rosenpass@.service
|
||||||
|
/etc/systemd/system/rp@.service
|
||||||
|
/etc/rosenpass/example.toml
|
||||||
|
EOF
|
||||||
|
|
||||||
|
buildroot=rpmbuild/BUILDROOT/rosenpass-${version}-${release}.${arch}
|
||||||
|
mkdir -p $buildroot/usr/bin
|
||||||
|
install -m755 -t $buildroot/usr/bin ${rosenpass}/bin/*
|
||||||
|
|
||||||
|
mkdir -p $buildroot/etc/rosenpass
|
||||||
|
cp -r ${rosenpass}/lib/systemd $buildroot/etc/
|
||||||
|
chmod -R 744 $buildroot/etc/systemd
|
||||||
|
cp ${./example.toml} $buildroot/etc/rosenpass/example.toml
|
||||||
|
|
||||||
|
export HOME=/build
|
||||||
|
mkdir -p /build/tmp
|
||||||
|
ls -R rpmbuild
|
||||||
|
|
||||||
|
${rpm}/bin/rpmbuild \
|
||||||
|
-bb \
|
||||||
|
--dbpath=$HOME \
|
||||||
|
--define "_tmppath /build/tmp" \
|
||||||
|
rpmbuild/SPECS/rosenpass.spec
|
||||||
|
|
||||||
|
cp rpmbuild/RPMS/${arch}/rosenpass*.rpm $out
|
||||||
|
''
|
||||||
@@ -57,6 +57,7 @@ rustPlatform.buildRustPackage {
|
|||||||
outputHashes = {
|
outputHashes = {
|
||||||
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
||||||
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
||||||
|
"libcrux-blake2-0.0.3-pre" = "sha256-0CLjuzwJqGooiODOHf5D8Hc8ClcG/XcGvVGyOVnLmJY=";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ Rosenpass is packaged for more and more distributions, maybe also for the distri
|
|||||||
|
|
||||||
[](https://repology.org/project/rosenpass/versions)
|
[](https://repology.org/project/rosenpass/versions)
|
||||||
|
|
||||||
|
## Docker Images
|
||||||
|
|
||||||
|
Rosenpass is also available as prebuilt Docker images:
|
||||||
|
|
||||||
|
- [`ghcr.io/rosenpass/rosenpass`](https://github.com/rosenpass/rosenpass/pkgs/container/rosenpass)
|
||||||
|
- [`ghcr.io/rosenpass/rp`](https://github.com/rosenpass/rosenpass/pkgs/container/rp)
|
||||||
|
|
||||||
|
For details on how to use these images, refer to the [Docker usage guide](docker/USAGE.md).
|
||||||
|
|
||||||
# Mirrors
|
# Mirrors
|
||||||
|
|
||||||
Don't want to use GitHub or only have an IPv6 connection? Rosenpass has set up two mirrors for this:
|
Don't want to use GitHub or only have an IPv6 connection? Rosenpass has set up two mirrors for this:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ description = "Build post-quantum-secure VPNs with WireGuard!"
|
|||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
readme = "readme.md"
|
readme = "readme.md"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rosenpass"
|
name = "rosenpass"
|
||||||
@@ -26,6 +27,14 @@ required-features = ["experiment_api", "internal_testing"]
|
|||||||
name = "api-integration-tests-api-setup"
|
name = "api-integration-tests-api-setup"
|
||||||
required-features = ["experiment_api", "internal_testing"]
|
required-features = ["experiment_api", "internal_testing"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "gen-ipc-msg-types"
|
||||||
|
required-features = [
|
||||||
|
"experiment_api",
|
||||||
|
"internal_testing",
|
||||||
|
"internal_bin_gen_ipc_msg_types",
|
||||||
|
]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "handshake"
|
name = "handshake"
|
||||||
harness = false
|
harness = false
|
||||||
@@ -77,9 +86,15 @@ tempfile = { workspace = true }
|
|||||||
rustix = { workspace = true }
|
rustix = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
#default = ["experiment_libcrux_all"]
|
||||||
|
experiment_cookie_dos_mitigation = []
|
||||||
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
||||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
experiment_libcrux_all = ["rosenpass-ciphers/experiment_libcrux_all"]
|
||||||
|
experiment_libcrux_blake2 = ["rosenpass-ciphers/experiment_libcrux_blake2"]
|
||||||
|
experiment_libcrux_chachapoly = [
|
||||||
|
"rosenpass-ciphers/experiment_libcrux_chachapoly",
|
||||||
|
]
|
||||||
|
experiment_libcrux_kyber = ["rosenpass-ciphers/experiment_libcrux_kyber"]
|
||||||
experiment_api = [
|
experiment_api = [
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"uds",
|
"uds",
|
||||||
@@ -91,3 +106,6 @@ experiment_api = [
|
|||||||
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
||||||
internal_testing = []
|
internal_testing = []
|
||||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
|
use rosenpass::protocol::{
|
||||||
|
CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, ProtocolVersion, SPk, SSk, SymKey,
|
||||||
|
};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
|
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
|
||||||
@@ -41,25 +43,33 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
|
|||||||
|
|
||||||
fn keygen() -> Result<(SSk, SPk)> {
|
fn keygen() -> Result<(SSk, SPk)> {
|
||||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
StaticKem.keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||||
Ok((sk, pk))
|
Ok((sk, pk))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
|
fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> {
|
||||||
let psk = SymKey::random();
|
let psk = SymKey::random();
|
||||||
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
|
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
|
||||||
let (mut a, mut b) = (
|
let (mut a, mut b) = (
|
||||||
CryptoServer::new(ska, pka.clone()),
|
CryptoServer::new(ska, pka.clone()),
|
||||||
CryptoServer::new(skb, pkb.clone()),
|
CryptoServer::new(skb, pkb.clone()),
|
||||||
);
|
);
|
||||||
a.add_peer(Some(psk.clone()), pkb)?;
|
a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?;
|
||||||
b.add_peer(Some(psk), pka)?;
|
b.add_peer(Some(psk), pka, protocol_version)?;
|
||||||
Ok((a, b))
|
Ok((a, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
fn criterion_benchmark_v02(c: &mut Criterion) {
|
||||||
|
criterion_benchmark(c, ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn criterion_benchmark_v03(c: &mut Criterion) {
|
||||||
|
criterion_benchmark(c, ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion, protocol_version: ProtocolVersion) {
|
||||||
secret_policy_try_use_memfd_secrets();
|
secret_policy_try_use_memfd_secrets();
|
||||||
let (mut a, mut b) = make_server_pair().unwrap();
|
let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
|
||||||
c.bench_function("cca_secret_alloc", |bench| {
|
c.bench_function("cca_secret_alloc", |bench| {
|
||||||
bench.iter(|| {
|
bench.iter(|| {
|
||||||
SSk::zero();
|
SSk::zero();
|
||||||
@@ -82,5 +92,6 @@ fn criterion_benchmark(c: &mut Criterion) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, criterion_benchmark);
|
criterion_group!(benches_v02, criterion_benchmark_v02);
|
||||||
criterion_main!(benches);
|
criterion_group!(benches_v03, criterion_benchmark_v03);
|
||||||
|
criterion_main!(benches_v02, benches_v03);
|
||||||
|
|||||||
@@ -8,210 +8,250 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub trait ByteSliceRefExt: ByteSlice {
|
pub trait ByteSliceRefExt: ByteSlice {
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and
|
||||||
self.zk_parse()
|
/// [RefMakerRawMsgTypeExt::parse_request_msg_type]
|
||||||
}
|
|
||||||
|
|
||||||
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
|
||||||
self.zk_parse_prefix()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
|
||||||
self.zk_parse_suffix()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||||
self.msg_type_maker().parse_request_msg_type()
|
self.msg_type_maker().parse_request_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||||
|
/// [RefMaker::from_prefix], and
|
||||||
|
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
|
||||||
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_prefix()?
|
.from_prefix()?
|
||||||
.parse_request_msg_type()
|
.parse_request_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||||
|
/// [RefMaker::from_suffix], and
|
||||||
|
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
|
||||||
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_suffix()?
|
.from_suffix()?
|
||||||
.parse_request_msg_type()
|
.parse_request_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||||
|
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||||
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
self.msg_type_maker().parse_response_msg_type()
|
self.msg_type_maker().parse_response_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||||
|
/// [RefMaker::from_prefix], and
|
||||||
|
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||||
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_prefix()?
|
.from_prefix()?
|
||||||
.parse_response_msg_type()
|
.parse_response_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||||
|
/// [RefMaker::from_suffix], and
|
||||||
|
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||||
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
||||||
self.msg_type_maker()
|
self.msg_type_maker()
|
||||||
.from_suffix()?
|
.from_suffix()?
|
||||||
.parse_response_msg_type()
|
.parse_response_msg_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [RequestRef::parse] in chaining.
|
||||||
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
RequestRef::parse(self)
|
RequestRef::parse(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [RequestRef::parse_from_prefix] in chaining.
|
||||||
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
RequestRef::parse_from_prefix(self)
|
RequestRef::parse_from_prefix(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [RequestRef::parse_from_suffix] in chaining.
|
||||||
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||||
RequestRef::parse_from_suffix(self)
|
RequestRef::parse_from_suffix(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [ResponseRef::parse] in chaining.
|
||||||
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
ResponseRef::parse(self)
|
ResponseRef::parse(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [ResponseRef::parse_from_prefix] in chaining.
|
||||||
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
ResponseRef::parse_from_prefix(self)
|
ResponseRef::parse_from_prefix(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the use of [ResponseRef::parse_from_suffix] in chaining.
|
||||||
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||||
ResponseRef::parse_from_suffix(self)
|
ResponseRef::parse_from_suffix(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn supply_keypair_response_from_prefix(
|
fn supply_keypair_response_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn supply_keypair_response_from_suffix(
|
fn supply_keypair_response_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_listen_socket_request_from_prefix(
|
fn add_listen_socket_request_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_listen_socket_request_from_suffix(
|
fn add_listen_socket_request_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn add_listen_socket_response(
|
fn add_listen_socket_response(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_listen_socket_response_from_prefix(
|
fn add_listen_socket_response_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_listen_socket_response_from_suffix(
|
fn add_listen_socket_response_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_psk_broker_request_from_prefix(
|
fn add_psk_broker_request_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_psk_broker_request_from_suffix(
|
fn add_psk_broker_request_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||||
self.zk_parse_suffix()
|
self.zk_parse_suffix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||||
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
||||||
self.zk_ref_maker()
|
self.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||||
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
self.zk_parse()
|
self.zk_parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||||
fn add_psk_broker_response_from_prefix(
|
fn add_psk_broker_response_from_prefix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
self.zk_parse_prefix()
|
self.zk_parse_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||||
fn add_psk_broker_response_from_suffix(
|
fn add_psk_broker_response_from_suffix(
|
||||||
self,
|
self,
|
||||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||||
|
|||||||
@@ -4,16 +4,41 @@ use rosenpass_util::zerocopy::RefMaker;
|
|||||||
|
|
||||||
use super::RawMsgType;
|
use super::RawMsgType;
|
||||||
|
|
||||||
|
/// Trait implemented by all the Rosenpass API message types.
|
||||||
|
///
|
||||||
|
/// Implemented by the message as including the message envelope; e.g.
|
||||||
|
/// [crate::api::PingRequest] but not by [crate::api::PingRequestPayload].
|
||||||
pub trait Message {
|
pub trait Message {
|
||||||
|
/// The payload this API message contains. E.g. this is [crate::api::PingRequestPayload] for [[crate::api::PingRequest].
|
||||||
type Payload;
|
type Payload;
|
||||||
|
/// Either [crate::api::RequestMsgType] or [crate::api::ResponseMsgType]
|
||||||
type MessageClass: Into<RawMsgType>;
|
type MessageClass: Into<RawMsgType>;
|
||||||
|
/// The specific message type in the [Self::MessageClass].
|
||||||
|
/// E.g. this is [crate::api::RequestMsgType::Ping] for [crate::api::PingRequest]
|
||||||
const MESSAGE_TYPE: Self::MessageClass;
|
const MESSAGE_TYPE: Self::MessageClass;
|
||||||
|
|
||||||
|
/// Wraps the payload into the envelope
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [crate::api::PingRequest::from_payload]
|
||||||
fn from_payload(payload: Self::Payload) -> Self;
|
fn from_payload(payload: Self::Payload) -> Self;
|
||||||
|
/// Initialize the message;
|
||||||
|
/// just sets the message type [crate::api::Envelope::msg_type].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [crate::api::PingRequest::init]
|
||||||
fn init(&mut self);
|
fn init(&mut self);
|
||||||
|
/// Initialize the message from a raw buffer: Zeroize the buffer and then call [Self::init].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [crate::api::PingRequest::setup]
|
||||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
|
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional convenience functions for working with [rosenpass_util::zerocopy::RefMaker]
|
||||||
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
||||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
||||||
}
|
}
|
||||||
@@ -23,6 +48,27 @@ where
|
|||||||
B: ByteSliceMut,
|
B: ByteSliceMut,
|
||||||
T: Message,
|
T: Message,
|
||||||
{
|
{
|
||||||
|
/// Initialize the message using [Message::setup].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::api::{
|
||||||
|
/// PingRequest, ZerocopyResponseMakerSetupMessageExt, PING_REQUEST,
|
||||||
|
/// };
|
||||||
|
/// use rosenpass_util::zerocopy::RefMaker;
|
||||||
|
/// use std::mem::size_of;
|
||||||
|
///
|
||||||
|
/// let mut buf = [0u8; { size_of::<PingRequest>() }];
|
||||||
|
///
|
||||||
|
/// let rm = RefMaker::<&mut [u8], PingRequest>::new(&mut buf);
|
||||||
|
/// let msg: zerocopy::Ref<_, PingRequest> = rm.setup_msg()?;
|
||||||
|
///
|
||||||
|
/// let t = msg.msg_type; // Deal with unaligned read
|
||||||
|
/// assert_eq!(t, PING_REQUEST);
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
||||||
T::setup(self.into_buf())
|
T::setup(self.into_buf())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,15 @@ const ADD_PSK_BROKER_REQUEST: RawMsgType =
|
|||||||
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
||||||
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
||||||
|
|
||||||
|
/// Message properties global to the message type
|
||||||
pub trait MessageAttributes {
|
pub trait MessageAttributes {
|
||||||
|
/// Get the size of the message
|
||||||
|
///
|
||||||
|
/// # Exampleds
|
||||||
fn message_size(&self) -> usize;
|
fn message_size(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API request message types as an enum
|
||||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
pub enum RequestMsgType {
|
pub enum RequestMsgType {
|
||||||
Ping,
|
Ping,
|
||||||
@@ -47,6 +52,7 @@ pub enum RequestMsgType {
|
|||||||
AddPskBroker,
|
AddPskBroker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API response messages types as an enum
|
||||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
pub enum ResponseMsgType {
|
pub enum ResponseMsgType {
|
||||||
Ping,
|
Ping,
|
||||||
@@ -131,8 +137,17 @@ impl From<ResponseMsgType> for RawMsgType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [RawMsgType].
|
||||||
|
///
|
||||||
|
/// We are using an extension trait rather than just using methods
|
||||||
|
/// because [RawMsgType] is a type alias, so we can not define methods
|
||||||
|
/// on it.
|
||||||
pub trait RawMsgTypeExt {
|
pub trait RawMsgTypeExt {
|
||||||
|
/// Try to convert this to a [RequestMsgType]; alias for the appropriate [TryFrom]
|
||||||
|
/// implementation
|
||||||
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
|
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
|
||||||
|
/// Try to convert this to a [ResponseMsgType]; alias for the appropriate [TryFrom]
|
||||||
|
/// implementation
|
||||||
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +161,11 @@ impl RawMsgTypeExt for RawMsgType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
|
||||||
pub trait RefMakerRawMsgTypeExt {
|
pub trait RefMakerRawMsgTypeExt {
|
||||||
|
/// Parse a request message type from bytes
|
||||||
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
||||||
|
/// Parse a response message type from bytes
|
||||||
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
|
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
//! Boring, repetitive code related to message parsing for the API.
|
||||||
|
//!
|
||||||
|
//! Most of this should be automatically generated though some derive macro at some point.
|
||||||
|
|
||||||
mod byte_slice_ext;
|
mod byte_slice_ext;
|
||||||
mod message_trait;
|
mod message_trait;
|
||||||
mod message_type;
|
mod message_type;
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
|
|||||||
|
|
||||||
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
||||||
|
|
||||||
/// Size required to fit any message in binary form
|
/// Size required to fit any request message in binary form
|
||||||
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
|
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
|
||||||
|
/// Size required to fit any response message in binary form
|
||||||
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
|
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
|
||||||
|
/// Maximum number of file descriptors that can be sent in a request.
|
||||||
pub const MAX_REQUEST_FDS: usize = 2;
|
pub const MAX_REQUEST_FDS: usize = 2;
|
||||||
|
|
||||||
|
/// Message envelope for API messages
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||||
@@ -17,9 +20,12 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
|||||||
pub payload: M,
|
pub payload: M,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Message envelope for API requests
|
||||||
pub type RequestEnvelope<M> = Envelope<M>;
|
pub type RequestEnvelope<M> = Envelope<M>;
|
||||||
|
/// Message envelope for API responses
|
||||||
pub type ResponseEnvelope<M> = Envelope<M>;
|
pub type ResponseEnvelope<M> = Envelope<M>;
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct PingRequestPayload {
|
pub struct PingRequestPayload {
|
||||||
@@ -27,9 +33,11 @@ pub struct PingRequestPayload {
|
|||||||
pub echo: [u8; 256],
|
pub echo: [u8; 256],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
||||||
|
|
||||||
impl PingRequest {
|
impl PingRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(echo: [u8; 256]) -> Self {
|
pub fn new(echo: [u8; 256]) -> Self {
|
||||||
Self::from_payload(PingRequestPayload { echo })
|
Self::from_payload(PingRequestPayload { echo })
|
||||||
}
|
}
|
||||||
@@ -58,6 +66,7 @@ impl Message for PingRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct PingResponsePayload {
|
pub struct PingResponsePayload {
|
||||||
@@ -65,9 +74,11 @@ pub struct PingResponsePayload {
|
|||||||
pub echo: [u8; 256],
|
pub echo: [u8; 256],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
||||||
|
|
||||||
impl PingResponse {
|
impl PingResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(echo: [u8; 256]) -> Self {
|
pub fn new(echo: [u8; 256]) -> Self {
|
||||||
Self::from_payload(PingResponsePayload { echo })
|
Self::from_payload(PingResponsePayload { echo })
|
||||||
}
|
}
|
||||||
@@ -96,10 +107,12 @@ impl Message for PingResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct SupplyKeypairRequestPayload {}
|
pub struct SupplyKeypairRequestPayload {}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
||||||
|
|
||||||
impl Default for SupplyKeypairRequest {
|
impl Default for SupplyKeypairRequest {
|
||||||
@@ -109,6 +122,7 @@ impl Default for SupplyKeypairRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SupplyKeypairRequest {
|
impl SupplyKeypairRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_payload(SupplyKeypairRequestPayload {})
|
Self::from_payload(SupplyKeypairRequestPayload {})
|
||||||
}
|
}
|
||||||
@@ -137,25 +151,35 @@ impl Message for SupplyKeypairRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub mod supply_keypair_response_status {
|
pub mod supply_keypair_response_status {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
||||||
// TODO: This is not actually part of the API. Remove.
|
/// TODO: This is not actually part of the API. Remove.
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INTERNAL_ERROR: u128 = 2;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INVALID_REQUEST: u128 = 3;
|
pub const INVALID_REQUEST: u128 = 3;
|
||||||
/// TODO: Deprectaed, remove
|
/// TODO: Deprectaed, remove
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const IO_ERROR: u128 = 4;
|
pub const IO_ERROR: u128 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct SupplyKeypairResponsePayload {
|
pub struct SupplyKeypairResponsePayload {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub status: u128,
|
pub status: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
||||||
|
|
||||||
impl SupplyKeypairResponse {
|
impl SupplyKeypairResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(status: u128) -> Self {
|
pub fn new(status: u128) -> Self {
|
||||||
Self::from_payload(SupplyKeypairResponsePayload { status })
|
Self::from_payload(SupplyKeypairResponsePayload { status })
|
||||||
}
|
}
|
||||||
@@ -184,10 +208,12 @@ impl Message for SupplyKeypairResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddListenSocketRequestPayload {}
|
pub struct AddListenSocketRequestPayload {}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
||||||
|
|
||||||
impl Default for AddListenSocketRequest {
|
impl Default for AddListenSocketRequest {
|
||||||
@@ -197,6 +223,7 @@ impl Default for AddListenSocketRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AddListenSocketRequest {
|
impl AddListenSocketRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_payload(AddListenSocketRequestPayload {})
|
Self::from_payload(AddListenSocketRequestPayload {})
|
||||||
}
|
}
|
||||||
@@ -225,21 +252,28 @@ impl Message for AddListenSocketRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub mod add_listen_socket_response_status {
|
pub mod add_listen_socket_response_status {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INVALID_REQUEST: u128 = 1;
|
pub const INVALID_REQUEST: u128 = 1;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INTERNAL_ERROR: u128 = 2;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddListenSocketResponsePayload {
|
pub struct AddListenSocketResponsePayload {
|
||||||
pub status: u128,
|
pub status: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
||||||
|
|
||||||
impl AddListenSocketResponse {
|
impl AddListenSocketResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(status: u128) -> Self {
|
pub fn new(status: u128) -> Self {
|
||||||
Self::from_payload(AddListenSocketResponsePayload { status })
|
Self::from_payload(AddListenSocketResponsePayload { status })
|
||||||
}
|
}
|
||||||
@@ -268,19 +302,23 @@ impl Message for AddListenSocketResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddPskBrokerRequestPayload {}
|
pub struct AddPskBrokerRequestPayload {}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
||||||
|
|
||||||
impl Default for AddPskBrokerRequest {
|
impl Default for AddPskBrokerRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddPskBrokerRequest {
|
impl AddPskBrokerRequest {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_payload(AddPskBrokerRequestPayload {})
|
Self::from_payload(AddPskBrokerRequestPayload {})
|
||||||
}
|
}
|
||||||
@@ -309,21 +347,28 @@ impl Message for AddPskBrokerRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub mod add_psk_broker_response_status {
|
pub mod add_psk_broker_response_status {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INVALID_REQUEST: u128 = 1;
|
pub const INVALID_REQUEST: u128 = 1;
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub const INTERNAL_ERROR: u128 = 2;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||||
pub struct AddPskBrokerResponsePayload {
|
pub struct AddPskBrokerResponsePayload {
|
||||||
pub status: u128,
|
pub status: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
||||||
|
|
||||||
impl AddPskBrokerResponse {
|
impl AddPskBrokerResponse {
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn new(status: u128) -> Self {
|
pub fn new(status: u128) -> Self {
|
||||||
Self::from_payload(AddPskBrokerResponsePayload { status })
|
Self::from_payload(AddPskBrokerResponsePayload { status })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,63 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
|
|
||||||
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
||||||
|
|
||||||
|
/// Helper for producing API message request references, [RequestRef].
|
||||||
|
///
|
||||||
|
/// This is to [RequestRef] as [rosenpass_util::zerocopy::RefMaker] is to
|
||||||
|
/// [zerocopy::Ref].
|
||||||
struct RequestRefMaker<B> {
|
struct RequestRefMaker<B> {
|
||||||
buf: B,
|
buf: B,
|
||||||
msg_type: RequestMsgType,
|
msg_type: RequestMsgType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: ByteSlice> RequestRef<B> {
|
impl<B: ByteSlice> RequestRef<B> {
|
||||||
|
/// Produce a [RequestRef] from a raw message buffer,
|
||||||
|
/// reading the type from the buffer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use zerocopy::AsBytes;
|
||||||
|
///
|
||||||
|
/// use rosenpass::api::{PingRequest, RequestRef, RequestMsgType};
|
||||||
|
///
|
||||||
|
/// let msg = PingRequest::new([0u8; 256]);
|
||||||
|
///
|
||||||
|
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
|
||||||
|
/// let typ = msg.msg_type;
|
||||||
|
/// assert_eq!(typ, rosenpass::api::PING_REQUEST);
|
||||||
|
///
|
||||||
|
/// let buf = msg.as_bytes();
|
||||||
|
/// let msg_ref = RequestRef::parse(buf)?;
|
||||||
|
/// assert!(matches!(msg_ref, RequestRef::Ping(_)));
|
||||||
|
///
|
||||||
|
/// assert_eq!(msg_ref.message_type(), RequestMsgType::Ping);
|
||||||
|
///
|
||||||
|
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||||
RequestRefMaker::new(buf)?.parse()
|
RequestRefMaker::new(buf)?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||||
|
/// reading the type from the buffer.
|
||||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||||
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||||
|
/// reading the type from the buffer.
|
||||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||||
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the message type [Self] contains
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse]
|
||||||
pub fn message_type(&self) -> RequestMsgType {
|
pub fn message_type(&self) -> RequestMsgType {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(_) => RequestMsgType::Ping,
|
Self::Ping(_) => RequestMsgType::Ping,
|
||||||
@@ -110,6 +149,7 @@ impl<B: ByteSlice> RequestRefMaker<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a API message response, typed as an enum.
|
||||||
pub enum RequestRef<B> {
|
pub enum RequestRef<B> {
|
||||||
Ping(Ref<B, PingRequest>),
|
Ping(Ref<B, PingRequest>),
|
||||||
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
||||||
@@ -121,6 +161,11 @@ impl<B> RequestRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSlice,
|
B: ByteSlice,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse].
|
||||||
pub fn bytes(&self) -> &[u8] {
|
pub fn bytes(&self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes(),
|
Self::Ping(r) => r.bytes(),
|
||||||
@@ -135,6 +180,7 @@ impl<B> RequestRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSliceMut,
|
B: ByteSliceMut,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference; mutably
|
||||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes_mut(),
|
Self::Ping(r) => r.bytes_mut(),
|
||||||
|
|||||||
@@ -6,23 +6,29 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
use super::{Message, PingRequest, PingResponse};
|
use super::{Message, PingRequest, PingResponse};
|
||||||
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
||||||
|
|
||||||
|
/// Extension trait for [Message]s that are requests messages
|
||||||
pub trait RequestMsg: Sized + Message {
|
pub trait RequestMsg: Sized + Message {
|
||||||
|
/// The response message belonging to this request message
|
||||||
type ResponseMsg: ResponseMsg;
|
type ResponseMsg: ResponseMsg;
|
||||||
|
|
||||||
|
/// Construct a response make for this particular message
|
||||||
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
||||||
buf.zk_ref_maker()
|
buf.zk_ref_maker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setup a response maker (through [Message::setup]) for this request message type
|
||||||
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
Self::zk_response_maker(buf).setup_msg()
|
Self::zk_response_maker(buf).setup_msg()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setup a response maker from a buffer prefix (through [Message::setup]) for this request message type
|
||||||
fn setup_response_from_prefix<B: ByteSliceMut>(
|
fn setup_response_from_prefix<B: ByteSliceMut>(
|
||||||
buf: B,
|
buf: B,
|
||||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setup a response maker from a buffer suffix (through [Message::setup]) for this request message type
|
||||||
fn setup_response_from_suffix<B: ByteSliceMut>(
|
fn setup_response_from_suffix<B: ByteSliceMut>(
|
||||||
buf: B,
|
buf: B,
|
||||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||||
@@ -30,6 +36,7 @@ pub trait RequestMsg: Sized + Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [Message]s that are response messages
|
||||||
pub trait ResponseMsg: Message {
|
pub trait ResponseMsg: Message {
|
||||||
type RequestMsg: RequestMsg;
|
type RequestMsg: RequestMsg;
|
||||||
}
|
}
|
||||||
@@ -66,20 +73,25 @@ impl ResponseMsg for super::AddPskBrokerResponse {
|
|||||||
type RequestMsg = super::AddPskBrokerRequest;
|
type RequestMsg = super::AddPskBrokerRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request and response for the [crate::api::RequestMsgType::Ping] message type
|
||||||
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
||||||
|
/// Request and response for the [crate::api::RequestMsgType::SupplyKeypair] message type
|
||||||
pub type SupplyKeypairPair<B1, B2> = (
|
pub type SupplyKeypairPair<B1, B2> = (
|
||||||
Ref<B1, super::SupplyKeypairRequest>,
|
Ref<B1, super::SupplyKeypairRequest>,
|
||||||
Ref<B2, super::SupplyKeypairResponse>,
|
Ref<B2, super::SupplyKeypairResponse>,
|
||||||
);
|
);
|
||||||
|
/// Request and response for the [crate::api::RequestMsgType::AddListenSocket] message type
|
||||||
pub type AddListenSocketPair<B1, B2> = (
|
pub type AddListenSocketPair<B1, B2> = (
|
||||||
Ref<B1, super::AddListenSocketRequest>,
|
Ref<B1, super::AddListenSocketRequest>,
|
||||||
Ref<B2, super::AddListenSocketResponse>,
|
Ref<B2, super::AddListenSocketResponse>,
|
||||||
);
|
);
|
||||||
|
/// Request and response for the [crate::api::RequestMsgType::AddPskBroker] message type
|
||||||
pub type AddPskBrokerPair<B1, B2> = (
|
pub type AddPskBrokerPair<B1, B2> = (
|
||||||
Ref<B1, super::AddPskBrokerRequest>,
|
Ref<B1, super::AddPskBrokerRequest>,
|
||||||
Ref<B2, super::AddPskBrokerResponse>,
|
Ref<B2, super::AddPskBrokerResponse>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// A pair of references to messages; request and response each.
|
||||||
pub enum RequestResponsePair<B1, B2> {
|
pub enum RequestResponsePair<B1, B2> {
|
||||||
Ping(PingPair<B1, B2>),
|
Ping(PingPair<B1, B2>),
|
||||||
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
||||||
@@ -116,6 +128,7 @@ where
|
|||||||
B1: ByteSlice,
|
B1: ByteSlice,
|
||||||
B2: ByteSlice,
|
B2: ByteSlice,
|
||||||
{
|
{
|
||||||
|
/// Returns a tuple to both the request and the response message
|
||||||
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping((req, res)) => {
|
Self::Ping((req, res)) => {
|
||||||
@@ -141,10 +154,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the request message
|
||||||
pub fn request(&self) -> RequestRef<&[u8]> {
|
pub fn request(&self) -> RequestRef<&[u8]> {
|
||||||
self.both().0
|
self.both().0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the response message
|
||||||
pub fn response(&self) -> ResponseRef<&[u8]> {
|
pub fn response(&self) -> ResponseRef<&[u8]> {
|
||||||
self.both().1
|
self.both().1
|
||||||
}
|
}
|
||||||
@@ -155,6 +170,7 @@ where
|
|||||||
B1: ByteSliceMut,
|
B1: ByteSliceMut,
|
||||||
B2: ByteSliceMut,
|
B2: ByteSliceMut,
|
||||||
{
|
{
|
||||||
|
/// Returns a mutable tuple to both the request and the response message
|
||||||
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping((req, res)) => {
|
Self::Ping((req, res)) => {
|
||||||
@@ -180,10 +196,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the request message, mutably
|
||||||
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
||||||
self.both_mut().0
|
self.both_mut().0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the response message, mutably
|
||||||
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
||||||
self.both_mut().1
|
self.both_mut().1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,66 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
|
|
||||||
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
||||||
|
|
||||||
|
/// Helper for producing API message response references, [ResponseRef].
|
||||||
|
///
|
||||||
|
/// This is to [ResponseRef] as [rosenpass_util::zerocopy::RefMaker] is to
|
||||||
|
/// [zerocopy::Ref].
|
||||||
struct ResponseRefMaker<B> {
|
struct ResponseRefMaker<B> {
|
||||||
|
/// Buffer we are referencing
|
||||||
buf: B,
|
buf: B,
|
||||||
|
/// Message type we are producing
|
||||||
msg_type: ResponseMsgType,
|
msg_type: ResponseMsgType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: ByteSlice> ResponseRef<B> {
|
impl<B: ByteSlice> ResponseRef<B> {
|
||||||
|
/// Produce a [ResponseRef] from a raw message buffer,
|
||||||
|
/// reading the type from the buffer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use zerocopy::AsBytes;
|
||||||
|
///
|
||||||
|
/// use rosenpass::api::{PingResponse, ResponseRef, ResponseMsgType};
|
||||||
|
/// // Produce the original PingResponse
|
||||||
|
/// let msg = PingResponse::new([0u8; 256]);
|
||||||
|
///
|
||||||
|
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
|
||||||
|
/// let typ = msg.msg_type;
|
||||||
|
/// assert_eq!(typ, rosenpass::api::PING_RESPONSE);
|
||||||
|
///
|
||||||
|
/// // Parse as a message type
|
||||||
|
/// let buf = msg.as_bytes();
|
||||||
|
/// let msg_ref = ResponseRef::parse(buf)?;
|
||||||
|
/// assert!(matches!(msg_ref, ResponseRef::Ping(_)));
|
||||||
|
///
|
||||||
|
/// // Buffers and message types of course match what we expect
|
||||||
|
/// assert_eq!(msg_ref.message_type(), ResponseMsgType::Ping);
|
||||||
|
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||||
ResponseRefMaker::new(buf)?.parse()
|
ResponseRefMaker::new(buf)?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||||
|
/// reading the type from the buffer.
|
||||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||||
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||||
|
/// reading the type from the buffer.
|
||||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||||
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the message type [Self] contains
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse]
|
||||||
pub fn message_type(&self) -> ResponseMsgType {
|
pub fn message_type(&self) -> ResponseMsgType {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(_) => ResponseMsgType::Ping,
|
Self::Ping(_) => ResponseMsgType::Ping,
|
||||||
@@ -111,6 +153,7 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a API message response, typed.
|
||||||
pub enum ResponseRef<B> {
|
pub enum ResponseRef<B> {
|
||||||
Ping(Ref<B, PingResponse>),
|
Ping(Ref<B, PingResponse>),
|
||||||
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
||||||
@@ -122,6 +165,11 @@ impl<B> ResponseRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSlice,
|
B: ByteSlice,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::parse].
|
||||||
pub fn bytes(&self) -> &[u8] {
|
pub fn bytes(&self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes(),
|
Self::Ping(r) => r.bytes(),
|
||||||
@@ -136,6 +184,7 @@ impl<B> ResponseRef<B>
|
|||||||
where
|
where
|
||||||
B: ByteSliceMut,
|
B: ByteSliceMut,
|
||||||
{
|
{
|
||||||
|
/// Access the byte data of this reference; mutably
|
||||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Ping(r) => r.bytes_mut(),
|
Self::Ping(r) => r.bytes_mut(),
|
||||||
|
|||||||
@@ -2,10 +2,21 @@ use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, Req
|
|||||||
use std::{collections::VecDeque, os::fd::OwnedFd};
|
use std::{collections::VecDeque, os::fd::OwnedFd};
|
||||||
use zerocopy::{ByteSlice, ByteSliceMut};
|
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||||
|
|
||||||
|
/// The rosenpass API implementation functions.
|
||||||
|
///
|
||||||
|
/// Implemented by [crate::api::ApiHandler].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the example of how to use the API in [crate::api].
|
||||||
pub trait Server {
|
pub trait Server {
|
||||||
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
||||||
///
|
///
|
||||||
/// It merely takes a buffer and returns that same buffer.
|
/// It merely takes a buffer and returns that same buffer.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the example of how to use the API in [crate::api].
|
||||||
fn ping(
|
fn ping(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &PingRequest,
|
req: &PingRequest,
|
||||||
@@ -54,6 +65,10 @@ pub trait Server {
|
|||||||
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
|
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
|
||||||
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
|
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
|
||||||
/// backed file descriptor if the server keys are not backed by a file system file.
|
/// backed file descriptor if the server keys are not backed by a file system file.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the example of how to use the API in [crate::api].
|
||||||
fn supply_keypair(
|
fn supply_keypair(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::SupplyKeypairRequest,
|
req: &super::SupplyKeypairRequest,
|
||||||
@@ -80,8 +95,13 @@ pub trait Server {
|
|||||||
///
|
///
|
||||||
/// # Description
|
/// # Description
|
||||||
///
|
///
|
||||||
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform
|
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform key
|
||||||
|
/// key exchanges using the Rosenpass protocol.
|
||||||
/// cryptographic key exchanges via the Rosenpass protocol.
|
/// cryptographic key exchanges via the Rosenpass protocol.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the example of how to use the API in [crate::api].
|
||||||
fn add_listen_socket(
|
fn add_listen_socket(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::AddListenSocketRequest,
|
req: &super::AddListenSocketRequest,
|
||||||
@@ -89,6 +109,31 @@ pub trait Server {
|
|||||||
res: &mut super::AddListenSocketResponse,
|
res: &mut super::AddListenSocketResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// Supply a new PSK broker listen socket through file descriptor passing via the API
|
||||||
|
///
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::AddPskBroker] API message.
|
||||||
|
///
|
||||||
|
/// # File descriptors
|
||||||
|
///
|
||||||
|
/// 1. The listen socket; must be backed by a unix domain stream socket
|
||||||
|
///
|
||||||
|
/// # API Return Status
|
||||||
|
///
|
||||||
|
/// 1. [crate::api::add_psk_broker_response_status::OK] - Indicates success
|
||||||
|
/// 2. [crate::api::add_psk_broker_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||||
|
/// - Missing file descriptors for public key
|
||||||
|
/// - Invalid file descriptor type
|
||||||
|
/// 3. [crate::api::add_psk_broker_response_status::INTERNAL_ERROR] – Some other, non-fatal error
|
||||||
|
/// occured. Check the logs on log
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// This endpoint allows you to supply a UDP listen socket; it will be used to transmit
|
||||||
|
/// cryptographic keys exchanged to WireGuard.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the example of how to use the API in [crate::api].
|
||||||
fn add_psk_broker(
|
fn add_psk_broker(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::AddPskBrokerRequest,
|
req: &super::AddPskBrokerRequest,
|
||||||
@@ -96,6 +141,11 @@ pub trait Server {
|
|||||||
res: &mut super::AddPskBrokerResponse,
|
res: &mut super::AddPskBrokerResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// Similar to [Self::handle_message], but takes a [RequestResponsePair]
|
||||||
|
/// instead of taking to separate byte buffers.
|
||||||
|
///
|
||||||
|
/// I.e. this function uses the explicit type tag encoded in [RequestResponsePair]
|
||||||
|
/// rather than reading the type tag from the request buffer.
|
||||||
fn dispatch<ReqBuf, ResBuf>(
|
fn dispatch<ReqBuf, ResBuf>(
|
||||||
&mut self,
|
&mut self,
|
||||||
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
||||||
@@ -117,6 +167,14 @@ pub trait Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [crate::api::mio::MioConnection] when a new API request was received.
|
||||||
|
///
|
||||||
|
/// The parameters are:
|
||||||
|
///
|
||||||
|
/// - `req` – A buffer containing the request
|
||||||
|
/// - `res_fds` – A list of file descriptors received during the API call (i.e. this is used
|
||||||
|
/// with unix socket file descriptor passing)
|
||||||
|
/// - `res` – The buffer to store the response in.
|
||||||
fn handle_message<ReqBuf, ResBuf>(
|
fn handle_message<ReqBuf, ResBuf>(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: ReqBuf,
|
req: ReqBuf,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::config::Rosenpass as RosenpassConfig;
|
|||||||
|
|
||||||
use super::config::ApiConfig;
|
use super::config::ApiConfig;
|
||||||
|
|
||||||
|
/// Additional command line arguments for the API
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct ApiCli {
|
pub struct ApiCli {
|
||||||
@@ -27,10 +28,14 @@ pub struct ApiCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ApiCli {
|
impl ApiCli {
|
||||||
|
/// Copy the parameters set here into the [RosenpassConfig].
|
||||||
|
/// Forwards to [Self::apply_to_api_config]:
|
||||||
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
||||||
self.apply_to_api_config(&mut cfg.api)
|
self.apply_to_api_config(&mut cfg.api)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fills the values from [ApiConfig::listen_path], [ApiConfig::listen_fd], and
|
||||||
|
/// [ApiConfig::stream_fd] with the values from [Self]
|
||||||
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
|
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
|
||||||
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
||||||
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::app_server::AppServer;
|
use crate::app_server::AppServer;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
/// Configuration options for the Rosenpass API
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct ApiConfig {
|
pub struct ApiConfig {
|
||||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||||
/// connections on
|
/// connections on
|
||||||
@@ -23,6 +24,10 @@ pub struct ApiConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ApiConfig {
|
impl ApiConfig {
|
||||||
|
/// Construct appropriate [UnixListener]s for each of the API
|
||||||
|
/// listeners and connections configured in [Self] and invoke
|
||||||
|
/// [AppServer::add_api_listener] for each to add them to the
|
||||||
|
/// [AppServer].
|
||||||
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
|
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
for path in self.listen_path.iter() {
|
for path in self.listen_path.iter() {
|
||||||
srv.add_api_listener(UnixListener::bind(path)?)?;
|
srv.add_api_listener(UnixListener::bind(path)?)?;
|
||||||
@@ -39,10 +44,12 @@ impl ApiConfig {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sum of all the API sources configured in here
|
||||||
pub fn count_api_sources(&self) -> usize {
|
pub fn count_api_sources(&self) -> usize {
|
||||||
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if [Self::count_api_sources] is greater than zero
|
||||||
pub fn has_api_sources(&self) -> bool {
|
pub fn has_api_sources(&self) -> bool {
|
||||||
self.count_api_sources() > 0
|
self.count_api_sources() > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ struct MioConnectionBuffers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// Represents a single connection with an API client.
|
||||||
|
/// Includes the necessary buffers, the [ApiHandler],
|
||||||
|
/// and the [UnixStream] that is used for communication.
|
||||||
pub struct MioConnection {
|
pub struct MioConnection {
|
||||||
io: UnixStream,
|
io: UnixStream,
|
||||||
mio_token: mio::Token,
|
mio_token: mio::Token,
|
||||||
@@ -62,6 +65,8 @@ pub struct MioConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MioConnection {
|
impl MioConnection {
|
||||||
|
/// Construct a new [Self] for the given app server from the unix socket stream
|
||||||
|
/// to communicate on.
|
||||||
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
|
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
|
||||||
let mio_token = app_server.mio_token_dispenser.dispense();
|
let mio_token = app_server.mio_token_dispenser.dispense();
|
||||||
app_server
|
app_server
|
||||||
@@ -88,6 +93,8 @@ impl MioConnection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if this unix stream should be closed by the enclosing
|
||||||
|
/// structure
|
||||||
pub fn should_close(&self) -> bool {
|
pub fn should_close(&self) -> bool {
|
||||||
let exhausted = self
|
let exhausted = self
|
||||||
.buffers
|
.buffers
|
||||||
@@ -97,22 +104,30 @@ impl MioConnection {
|
|||||||
self.invalid_read && exhausted
|
self.invalid_read && exhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close and deregister this particular API connection
|
||||||
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
||||||
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the mio token
|
||||||
pub fn mio_token(&self) -> mio::Token {
|
pub fn mio_token(&self) -> mio::Token {
|
||||||
self.mio_token
|
self.mio_token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We require references to both [MioConnection] and to the [AppServer] that contains it.
|
||||||
pub trait MioConnectionContext {
|
pub trait MioConnectionContext {
|
||||||
|
/// Reference to the [MioConnection] we are focusing on
|
||||||
fn mio_connection(&self) -> &MioConnection;
|
fn mio_connection(&self) -> &MioConnection;
|
||||||
|
/// Reference to the [AppServer] that contains the [Self::mio_connection]
|
||||||
fn app_server(&self) -> &AppServer;
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Mutable reference to the [MioConnection] we are focusing on
|
||||||
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
||||||
|
/// Mutable reference to the [AppServer] that contains the [Self::mio_connection]
|
||||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||||
|
|
||||||
|
/// Called by [AppServer::poll] regularly to process any incoming (and outgoing) API messages
|
||||||
fn poll(&mut self) -> anyhow::Result<()> {
|
fn poll(&mut self) -> anyhow::Result<()> {
|
||||||
macro_rules! short {
|
macro_rules! short {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
@@ -133,6 +148,7 @@ pub trait MioConnectionContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [Self::poll] to process incoming messages
|
||||||
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
self.with_buffers_stolen(|this, bufs| {
|
self.with_buffers_stolen(|this, bufs| {
|
||||||
// Acquire request & response. Caller is responsible to make sure
|
// Acquire request & response. Caller is responsible to make sure
|
||||||
@@ -156,6 +172,7 @@ pub trait MioConnectionContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [Self::poll] to write data in the send buffer to the unix stream
|
||||||
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
if self.write_buf_mut().exhausted() {
|
if self.write_buf_mut().exhausted() {
|
||||||
return Ok(Some(()));
|
return Ok(Some(()));
|
||||||
@@ -194,6 +211,7 @@ pub trait MioConnectionContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by [Self::poll] to check for messages to receive
|
||||||
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
||||||
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -257,10 +275,12 @@ pub trait MioConnectionContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forwards to [MioConnection::mio_token]
|
||||||
fn mio_token(&self) -> mio::Token {
|
fn mio_token(&self) -> mio::Token {
|
||||||
self.mio_connection().mio_token()
|
self.mio_connection().mio_token()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forwards to [MioConnection::should_close]
|
||||||
fn should_close(&self) -> bool {
|
fn should_close(&self) -> bool {
|
||||||
self.mio_connection().should_close()
|
self.mio_connection().should_close()
|
||||||
}
|
}
|
||||||
@@ -299,6 +319,7 @@ trait MioConnectionContextPrivate: MioConnectionContext {
|
|||||||
|
|
||||||
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
||||||
|
|
||||||
|
/// Every [MioConnectionContext] is also a [ApiHandlerContext]
|
||||||
impl<T> ApiHandlerContext for T
|
impl<T> ApiHandlerContext for T
|
||||||
where
|
where
|
||||||
T: ?Sized + MioConnectionContext,
|
T: ?Sized + MioConnectionContext,
|
||||||
|
|||||||
@@ -10,41 +10,59 @@ use crate::app_server::{AppServer, AppServerIoSource};
|
|||||||
|
|
||||||
use super::{MioConnection, MioConnectionContext};
|
use super::{MioConnection, MioConnectionContext};
|
||||||
|
|
||||||
|
/// This is in essence a unix listener for API connections.
|
||||||
|
///
|
||||||
|
/// It contains a number of [UnixListener]s and the associated [MioConnection]s encapsulating [mio::net::UnixListener]s.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct MioManager {
|
pub struct MioManager {
|
||||||
listeners: Vec<UnixListener>,
|
listeners: Vec<UnixListener>,
|
||||||
connections: Vec<Option<MioConnection>>,
|
connections: Vec<Option<MioConnection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Points at a particular source of IO events inside [MioManager]
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum MioManagerIoSource {
|
pub enum MioManagerIoSource {
|
||||||
|
// Source of IO events is the Nth unix socket listener (see [MioManager::listeners])
|
||||||
Listener(usize),
|
Listener(usize),
|
||||||
|
// Source of IO events is the Nth unix socket listener (see [MioManager::connections])
|
||||||
Connection(usize),
|
Connection(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MioManager {
|
impl MioManager {
|
||||||
|
/// Construct an empty [Self]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Focus in on a particular [MioConnection] inside a [MioManager]
|
||||||
|
///
|
||||||
|
/// This is mainly used to implement [MioConnectionContext].
|
||||||
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
||||||
|
/// [MioConnectionContext] to access the [MioManager] instance and [AppServer]
|
||||||
ctx: &'a mut T,
|
ctx: &'a mut T,
|
||||||
|
/// Index of the connection referenced to by [Self]
|
||||||
conn_idx: usize,
|
conn_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
||||||
|
/// Produce a MioConnectionContext from the [MioConnectionContext] and the connection index
|
||||||
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
||||||
Self { ctx, conn_idx }
|
Self { ctx, conn_idx }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MioManagerContext {
|
pub trait MioManagerContext {
|
||||||
|
/// Reference to the [MioManager]
|
||||||
fn mio_manager(&self) -> &MioManager;
|
fn mio_manager(&self) -> &MioManager;
|
||||||
|
/// Reference to the [MioManager], mutably
|
||||||
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
||||||
|
/// Reference to the [AppServer] this [MioManager] is associated with
|
||||||
fn app_server(&self) -> &AppServer;
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Mutable reference to the [AppServer] this [MioManager] is associated with
|
||||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||||
|
|
||||||
|
/// Add a new [UnixListener] to listen for API connections on
|
||||||
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
|
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
|
||||||
let srv = self.app_server_mut();
|
let srv = self.app_server_mut();
|
||||||
let mio_token = srv.mio_token_dispenser.dispense();
|
let mio_token = srv.mio_token_dispenser.dispense();
|
||||||
@@ -64,6 +82,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new connection to an API client
|
||||||
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
||||||
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
||||||
let mio_token = connection.mio_token();
|
let mio_token = connection.mio_token();
|
||||||
@@ -84,6 +103,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Poll a particular [MioManagerIoSource] in this [MioManager]
|
||||||
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
||||||
use MioManagerIoSource as S;
|
use MioManagerIoSource as S;
|
||||||
match io_source {
|
match io_source {
|
||||||
@@ -93,12 +113,14 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check for new connections and poll all the [MioConnectionContext]s managed by [Self]
|
||||||
fn poll(&mut self) -> anyhow::Result<()> {
|
fn poll(&mut self) -> anyhow::Result<()> {
|
||||||
self.accept_connections()?;
|
self.accept_connections()?;
|
||||||
self.poll_connections()?;
|
self.poll_connections()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check all the [UnixListener]s managed by this [MioManager] for new connections
|
||||||
fn accept_connections(&mut self) -> io::Result<()> {
|
fn accept_connections(&mut self) -> io::Result<()> {
|
||||||
for idx in 0..self.mio_manager_mut().listeners.len() {
|
for idx in 0..self.mio_manager_mut().listeners.len() {
|
||||||
self.accept_from(idx)?;
|
self.accept_from(idx)?;
|
||||||
@@ -106,6 +128,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check a particular [UnixListener] managed by this for new connections.
|
||||||
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
||||||
// Accept connection until the socket would block or returns another error
|
// Accept connection until the socket would block or returns another error
|
||||||
// TODO: This currently only adds connections--we eventually need the ability to remove
|
// TODO: This currently only adds connections--we eventually need the ability to remove
|
||||||
@@ -122,6 +145,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call [MioConnectionContext::poll] on all the [MioConnection]s in This
|
||||||
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
||||||
for idx in 0..self.mio_manager().connections.len() {
|
for idx in 0..self.mio_manager().connections.len() {
|
||||||
self.poll_particular_connection(idx)?;
|
self.poll_particular_connection(idx)?;
|
||||||
@@ -129,6 +153,7 @@ pub trait MioManagerContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call [MioConnectionContext::poll] on a particular connection
|
||||||
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
||||||
if self.mio_manager().connections[idx].is_none() {
|
if self.mio_manager().connections[idx].is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
//! The bulk code relating to the Rosenpass unix socket API
|
//! The bulk code relating to the Rosenpass unix socket API
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
#![doc = "```ignore"]
|
||||||
|
#![doc = include_str!("../../tests/api-integration-tests-api-setup.rs")]
|
||||||
|
#![doc = "```"]
|
||||||
|
|
||||||
mod api_handler;
|
mod api_handler;
|
||||||
mod boilerplate;
|
mod boilerplate;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/// This contains the bulk of the rosenpass server IO handling code whereas
|
||||||
|
/// the actual cryptographic code lives in the [crate::protocol] module
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -40,6 +42,7 @@ use std::slice;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use crate::config::ProtocolVersion;
|
||||||
use crate::protocol::BuildCryptoServer;
|
use crate::protocol::BuildCryptoServer;
|
||||||
use crate::protocol::HostIdentification;
|
use crate::protocol::HostIdentification;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -49,33 +52,78 @@ use crate::{
|
|||||||
use rosenpass_util::attempt;
|
use rosenpass_util::attempt;
|
||||||
use rosenpass_util::b64::B64Display;
|
use rosenpass_util::b64::B64Display;
|
||||||
|
|
||||||
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
/// The maximum size of a base64 encoded symmetric key (estimate)
|
||||||
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
||||||
|
/// The maximum size of a base64 peer ID (estimate)
|
||||||
|
pub const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
||||||
|
|
||||||
|
/// The zero IPv4 address; this is generally used to tell network servers to choose any interface
|
||||||
|
/// when listening
|
||||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||||
|
/// The zero IPv6 address; this is generally used to tell network servers to choose any interface
|
||||||
|
/// when listening
|
||||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
/// Ratio of blocking epoll(7) polls to non-blocking polls at which the rosenpass server
|
||||||
|
/// assumes it is under load (i.e. a DOS attack may be happening)
|
||||||
const UNDER_LOAD_RATIO: f64 = 0.5;
|
const UNDER_LOAD_RATIO: f64 = 0.5;
|
||||||
|
/// Period at which the DOS detection code updates whether there is an "under load" status
|
||||||
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
|
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
const BROKER_ID_BYTES: usize = 8;
|
pub const BROKER_ID_BYTES: usize = 8;
|
||||||
|
|
||||||
fn ipv4_any_binding() -> SocketAddr {
|
/// IPv4 address that tells the network layer to listen on any interface
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [AppServer::new].
|
||||||
|
pub fn ipv4_any_binding() -> SocketAddr {
|
||||||
// addr, port
|
// addr, port
|
||||||
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ipv6_any_binding() -> SocketAddr {
|
/// IPv6 address that tells the network layer to listen on any interface
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [AppServer::new].
|
||||||
|
pub fn ipv6_any_binding() -> SocketAddr {
|
||||||
// addr, port, flowinfo, scope_id
|
// addr, port, flowinfo, scope_id
|
||||||
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is used to assign indices to MIO (epoll) event sources
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MioTokenDispenser {
|
pub struct MioTokenDispenser {
|
||||||
counter: usize,
|
pub counter: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MioTokenDispenser {
|
impl MioTokenDispenser {
|
||||||
|
/// Produces a single IO event source ID
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Use is quite straightforward:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::app_server::MioTokenDispenser;
|
||||||
|
/// use mio::Token;
|
||||||
|
///
|
||||||
|
/// let mut dispenser = MioTokenDispenser {
|
||||||
|
/// counter: 0
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let t1 = dispenser.dispense();
|
||||||
|
/// let t2 = dispenser.dispense();
|
||||||
|
///
|
||||||
|
/// assert_ne!(t1, t2);
|
||||||
|
///
|
||||||
|
/// // If you inspected the output, you would find that the dispenser is really just a counter.
|
||||||
|
/// // Though this is an implementation detail
|
||||||
|
/// assert_eq!(t1, Token(0));
|
||||||
|
/// assert_eq!(t2, Token(1));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
pub fn dispense(&mut self) -> Token {
|
pub fn dispense(&mut self) -> Token {
|
||||||
let r = self.counter;
|
let r = self.counter;
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
@@ -83,42 +131,132 @@ impl MioTokenDispenser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of WireGuard brokers
|
||||||
|
///
|
||||||
|
/// Each WireGuard peer ([AppPeer]) is assigned a broker ([AppPeer::broker_peer]).
|
||||||
|
///
|
||||||
|
/// When a new has been exchanged, then its associated broker is called to transmit the key
|
||||||
|
/// to WireGuard (or to do something else).
|
||||||
|
///
|
||||||
|
/// Brokers live in [AppServer::brokers]. They are added/removed from the AppServer via
|
||||||
|
/// [AppServer::register_broker] and [AppServer::unregister_broker]. PSKs are distributed
|
||||||
|
/// to their respective brokers via [AppPeerPtr::set_psk].
|
||||||
|
///
|
||||||
|
/// Note that the entire broker system is an experimental feature; it is not up to the
|
||||||
|
/// same quality standards as the rest of the code. In particular, the use of [BROKER_ID_BYTES]
|
||||||
|
/// and a hash map is simultaneously overengineered and not really that useful for what it is
|
||||||
|
/// doing.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct BrokerStore {
|
pub struct BrokerStore {
|
||||||
|
/// The collection of WireGuard brokers. See [Self].
|
||||||
pub store: HashMap<
|
pub store: HashMap<
|
||||||
Public<BROKER_ID_BYTES>,
|
Public<BROKER_ID_BYTES>,
|
||||||
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to a broker imbued with utility methods
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
|
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
|
||||||
|
|
||||||
|
/// This is the broker configuration for a particular broker peer
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BrokerPeer {
|
pub struct BrokerPeer {
|
||||||
|
/// Reference to the broker used for this particular peer
|
||||||
ptr: BrokerStorePtr,
|
ptr: BrokerStorePtr,
|
||||||
|
/// Configuration for a WireGuard broker.
|
||||||
|
///
|
||||||
|
/// This is woefully overengineered and there is very little reason why the broker
|
||||||
|
/// configuration should not live in the particular WireGuard broker.
|
||||||
peer_cfg: Box<dyn WireguardBrokerCfg>,
|
peer_cfg: Box<dyn WireguardBrokerCfg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrokerPeer {
|
impl BrokerPeer {
|
||||||
|
/// Create a broker peer
|
||||||
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
|
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
|
||||||
Self { ptr, peer_cfg }
|
Self { ptr, peer_cfg }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the pointer to WireGuard PSK broker used with this peer
|
||||||
pub fn ptr(&self) -> &BrokerStorePtr {
|
pub fn ptr(&self) -> &BrokerStorePtr {
|
||||||
&self.ptr
|
&self.ptr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// IO/BusinessLogic information for a particular protocol peer.
|
||||||
|
///
|
||||||
|
/// There is a one-to-one correspondence between this struct and [crate::protocol::Peer];
|
||||||
|
/// whereas the struct in the protocol module stores information specific to the cryptographic layer,
|
||||||
|
/// this struct stores information IO information.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct AppPeer {
|
pub struct AppPeer {
|
||||||
|
/// If set, then [AppServer::output_key] will write generated output keys
|
||||||
|
/// to a file configured here and produce information on standard out to
|
||||||
|
/// notify the calling process that
|
||||||
pub outfile: Option<PathBuf>,
|
pub outfile: Option<PathBuf>,
|
||||||
|
/// If this option is set, then [AppServer::output_key] will send generated output
|
||||||
|
/// keys to the broker configured here
|
||||||
pub broker_peer: Option<BrokerPeer>,
|
pub broker_peer: Option<BrokerPeer>,
|
||||||
|
/// This is the network address configured for a particular peer at program start.
|
||||||
|
///
|
||||||
|
/// I.e. this is the address the rosenpass program will send [crate::msgs::InitHello]
|
||||||
|
/// packets to, trying to exchange a key.
|
||||||
|
///
|
||||||
|
/// Note that the remote peer may connect with another address. See [Self::current_endpoint].
|
||||||
pub initial_endpoint: Option<Endpoint>,
|
pub initial_endpoint: Option<Endpoint>,
|
||||||
|
/// The network address currently used for a particular peer.
|
||||||
|
///
|
||||||
|
/// This is not necessarily the address that was configured at program start (see [Self::initial_endpoint]),
|
||||||
|
/// because the remote peer can initiate handshakes from an arbitrary network address.
|
||||||
|
///
|
||||||
|
/// If another peer successfully connects to this one from any address, then this field will
|
||||||
|
/// be updated to reflect which address this was.
|
||||||
pub current_endpoint: Option<Endpoint>,
|
pub current_endpoint: Option<Endpoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppPeer {
|
impl AppPeer {
|
||||||
|
/// Retrieve the [Endpoint] associated with this peer.
|
||||||
|
///
|
||||||
|
/// I.e. the [Self::current_endpoint] if set and [Self::initial_endpoint] otherwise.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::app_server::{Endpoint, AppPeer};
|
||||||
|
/// use rosenpass_util::functional::run;
|
||||||
|
///
|
||||||
|
/// let mut peer = AppPeer {
|
||||||
|
/// outfile: None,
|
||||||
|
/// broker_peer: None,
|
||||||
|
/// initial_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:0".to_string())?),
|
||||||
|
/// current_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:1".to_string())?),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// fn same(a: Option<&Endpoint>, b: Option<&Endpoint>) -> bool {
|
||||||
|
/// if a.is_none() && b.is_none() {
|
||||||
|
/// return true;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// run(|| Some(std::ptr::eq(a?, b?)) )
|
||||||
|
/// .unwrap_or(a.is_some() == b.is_some())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
|
||||||
|
///
|
||||||
|
/// let mut tmp = None;
|
||||||
|
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
|
||||||
|
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
|
||||||
|
///
|
||||||
|
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
|
||||||
|
/// std::mem::swap(&mut tmp, &mut peer.current_endpoint);
|
||||||
|
/// assert!(same(peer.endpoint(), peer.initial_endpoint.as_ref()));
|
||||||
|
///
|
||||||
|
/// peer.initial_endpoint = None;
|
||||||
|
/// peer.current_endpoint = None;
|
||||||
|
/// assert!(peer.endpoint().is_none());
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn endpoint(&self) -> Option<&Endpoint> {
|
pub fn endpoint(&self) -> Option<&Endpoint> {
|
||||||
self.current_endpoint
|
self.current_endpoint
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -126,6 +264,8 @@ impl AppPeer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// No longer in use since we have the broker system (see [BrokerPeer])
|
||||||
|
/// TODO: Remove
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct WireguardOut {
|
pub struct WireguardOut {
|
||||||
// impl KeyOutput
|
// impl KeyOutput
|
||||||
@@ -134,12 +274,20 @@ pub struct WireguardOut {
|
|||||||
pub extra_params: Vec<String>,
|
pub extra_params: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to indicate whether the rosenpass server is in normal operating
|
||||||
|
/// conditions or under load (i.e. a DOS attack is happening)
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum DoSOperation {
|
pub enum DoSOperation {
|
||||||
UnderLoad,
|
UnderLoad,
|
||||||
Normal,
|
Normal,
|
||||||
}
|
}
|
||||||
/// Integration test helpers for AppServer
|
/// Integration test helpers for AppServer
|
||||||
|
///
|
||||||
|
/// TODO: Remove; this is no way to write integration tests
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [AppServer]
|
||||||
#[derive(Debug, Builder)]
|
#[derive(Debug, Builder)]
|
||||||
#[builder(pattern = "owned")]
|
#[builder(pattern = "owned")]
|
||||||
pub struct AppServerTest {
|
pub struct AppServerTest {
|
||||||
@@ -151,43 +299,100 @@ pub struct AppServerTest {
|
|||||||
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This represents a some source of IO operations in the context of the Rosenpass server
|
||||||
|
///
|
||||||
|
/// I.e. this identifies some structure that could be marked as "ready for IO" by [mio]
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum AppServerIoSource {
|
pub enum AppServerIoSource {
|
||||||
|
/// IO source refers to a socket in [AppServer::sockets]
|
||||||
Socket(usize),
|
Socket(usize),
|
||||||
|
/// IO source refers to a PSK broker in [AppServer::brokers]
|
||||||
PskBroker(Public<BROKER_ID_BYTES>),
|
PskBroker(Public<BROKER_ID_BYTES>),
|
||||||
|
/// IO source refers to some IO sources used in the API;
|
||||||
|
/// see [AppServer::api_manager]
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
MioManager(crate::api::mio::MioManagerIoSource),
|
MioManager(crate::api::mio::MioManagerIoSource),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of epoll(7) events Rosenpass can receive at a time
|
||||||
const EVENT_CAPACITY: usize = 20;
|
const EVENT_CAPACITY: usize = 20;
|
||||||
|
|
||||||
/// Holds the state of the application, namely the external IO
|
/// This holds pretty much all of the state of the Rosenpass application
|
||||||
|
/// including the cryptographic state in [Self::crypto_site]
|
||||||
///
|
///
|
||||||
/// Responsible for file IO, network IO
|
/// Responsible for file IO, network IO, etc…
|
||||||
// TODO add user control via unix domain socket and stdin/stdout
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/app_server_example.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppServer {
|
pub struct AppServer {
|
||||||
|
/// Contains the actual cryptographic implementation.
|
||||||
|
///
|
||||||
|
/// Because the API supports initializing the server with a keypair
|
||||||
|
/// and CryptoServer needs to be initialized with a keypair, the struct
|
||||||
|
/// is wrapped in a ConstructionSite
|
||||||
|
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
|
||||||
|
/// The UDP sockets used to send and receive protocol messages
|
||||||
|
pub sockets: Vec<mio::net::UdpSocket>,
|
||||||
|
/// Buffer for [mio] (epoll(7), async IO handling) IO events
|
||||||
|
pub events: mio::Events,
|
||||||
|
/// Supplemental buffer for [mio] events. See the inline documentation of [AppServer::try_recv]
|
||||||
|
/// for details.
|
||||||
|
pub short_poll_queue: VecDeque<mio::event::Event>,
|
||||||
|
/// We have two different polling modes; long polling (legacy) and short polling (new and
|
||||||
|
/// somewhat experimental). See [AppServer::try_recv] for details
|
||||||
|
pub performed_long_poll: bool,
|
||||||
|
/// Events produced by [mio] refer to IO sources by a numeric token assigned to them
|
||||||
|
/// (see [MioTokenDispenser]). This index associateds mio token with the specific IO
|
||||||
|
/// source stored in this object
|
||||||
|
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
|
||||||
|
/// Asynchronous IO source
|
||||||
|
pub mio_poll: mio::Poll,
|
||||||
|
/// MIO associates IO sources with numeric tokens. This struct takes care of generating these
|
||||||
|
/// tokens
|
||||||
|
pub mio_token_dispenser: MioTokenDispenser,
|
||||||
|
/// Helpers handling communication with WireGuard; these take a generated key and forward it to
|
||||||
|
/// WireGuard
|
||||||
|
pub brokers: BrokerStore,
|
||||||
|
/// This is our view of the peers; generally every peer in here is associated with one peer in
|
||||||
|
/// CryptoServer
|
||||||
|
pub peers: Vec<AppPeer>,
|
||||||
|
/// If set to [Verbosity::Verbose], then some extra information will be printed
|
||||||
|
/// at the info log level
|
||||||
|
pub verbosity: Verbosity,
|
||||||
|
/// Used by [AppServer::try_recv] to ensure that all packages have been read
|
||||||
|
/// from the UDP sockets
|
||||||
|
pub all_sockets_drained: bool,
|
||||||
|
/// Whether network message handling determined that a Denial of Service attack is happening
|
||||||
|
pub under_load: DoSOperation,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
|
pub blocking_polls_count: usize,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
|
pub non_blocking_polls_count: usize,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
|
pub unpolled_count: usize,
|
||||||
|
/// State kept by the [AppServer::try_recv] for polling
|
||||||
|
pub last_update_time: Instant,
|
||||||
|
/// Used by integration tests to force [Self] into DoS condition
|
||||||
|
/// and to terminate the AppServer after the test is complete
|
||||||
|
pub test_helpers: Option<AppServerTest>,
|
||||||
|
/// Helper for integration tests running rosenpass as a subprocess
|
||||||
|
/// to terminate properly upon receiving an appropriate system signal.
|
||||||
|
///
|
||||||
|
/// This is primarily needed for coverage testing, since llvm-cov does not
|
||||||
|
/// write coverage reports to disk when a process is stopped by the default
|
||||||
|
/// signal handler.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rosenpass/rosenpass/issues/385>
|
||||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||||
pub term_signal: terminate::TerminateRequested,
|
pub term_signal: terminate::TerminateRequested,
|
||||||
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")]
|
#[cfg(feature = "experiment_api")]
|
||||||
|
/// The Rosenpass unix socket API handler; this is an experimental
|
||||||
|
/// feature that can be used to embed Rosenpass in external applications
|
||||||
|
/// via communication by unix socket
|
||||||
pub api_manager: crate::api::mio::MioManager,
|
pub api_manager: crate::api::mio::MioManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,14 +406,19 @@ pub struct AppServer {
|
|||||||
pub struct SocketPtr(pub usize);
|
pub struct SocketPtr(pub usize);
|
||||||
|
|
||||||
impl SocketPtr {
|
impl SocketPtr {
|
||||||
|
/// Retrieve the concrete udp socket associated with the pointer
|
||||||
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
|
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
|
||||||
&srv.sockets[self.0]
|
&srv.sockets[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the concrete udp socket associated with the pointer, mutably
|
||||||
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
|
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
|
||||||
&mut srv.sockets[self.0]
|
&mut srv.sockets[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a UDP packet to another address.
|
||||||
|
///
|
||||||
|
/// Merely forwards to [mio::net::UdpSocket::send_to]
|
||||||
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
|
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
|
||||||
self.get(srv).send_to(buf, addr)?;
|
self.get(srv).send_to(buf, addr)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -216,28 +426,41 @@ impl SocketPtr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Index based pointer to a Peer
|
/// Index based pointer to a Peer
|
||||||
|
///
|
||||||
|
/// This allows retrieving both the io-oriented and the cryptographic information
|
||||||
|
/// about a peer.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct AppPeerPtr(pub usize);
|
pub struct AppPeerPtr(pub usize);
|
||||||
|
|
||||||
impl AppPeerPtr {
|
impl AppPeerPtr {
|
||||||
/// Takes an index based handle and returns the actual peer
|
/// Takes an pointer from the cryptography subsystem
|
||||||
|
/// in [AppServer::crypto_site] and derives the associated AppPeerPtr
|
||||||
|
/// in [AppServer]
|
||||||
pub fn lift(p: PeerPtr) -> Self {
|
pub fn lift(p: PeerPtr) -> Self {
|
||||||
Self(p.0)
|
Self(p.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an index based handle to one Peer
|
/// Turns this pointer into a cryptographic peer pointer for [CryptoServer]
|
||||||
|
/// in [AppServer::crypto_site]
|
||||||
pub fn lower(&self) -> PeerPtr {
|
pub fn lower(&self) -> PeerPtr {
|
||||||
PeerPtr(self.0)
|
PeerPtr(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the [AppPeer] pointed to by [Self]
|
||||||
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
||||||
&srv.peers[self.0]
|
&srv.peers[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the [AppPeer] pointed to by [Self], mutably
|
||||||
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
||||||
&mut srv.peers[self.0]
|
&mut srv.peers[self.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the associated WireGuard PSK broker via [BrokerStorePtr]
|
||||||
|
/// to upload a new PSK.
|
||||||
|
///
|
||||||
|
/// If no PSK broker is set and [AppPeer::outfile] is none, then
|
||||||
|
/// this prints a warning
|
||||||
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
|
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
|
||||||
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
|
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
|
||||||
let config = broker.peer_cfg.create_config(psk);
|
let config = broker.peer_cfg.create_config(psk);
|
||||||
@@ -250,17 +473,34 @@ impl AppPeerPtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of [AppServer::poll].
|
||||||
|
///
|
||||||
|
/// Instructs [AppServer::event_loop_without_error_handling] on how to proceed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AppPollResult {
|
pub enum AppPollResult {
|
||||||
|
/// Erase the key for a given peer. Corresponds to [crate::protocol::PollResult::DeleteKey]
|
||||||
DeleteKey(AppPeerPtr),
|
DeleteKey(AppPeerPtr),
|
||||||
|
/// Send an initiation to the given peer. Corresponds to [crate::protocol::PollResult::SendInitiation]
|
||||||
SendInitiation(AppPeerPtr),
|
SendInitiation(AppPeerPtr),
|
||||||
|
/// Send a retransmission to the given peer. Corresponds to
|
||||||
|
/// [crate::protocol::PollResult::SendRetransmission]
|
||||||
SendRetransmission(AppPeerPtr),
|
SendRetransmission(AppPeerPtr),
|
||||||
|
/// Received a network message.
|
||||||
|
///
|
||||||
|
/// This is the only case without a correspondence in [crate::protocol::PollResult]
|
||||||
ReceivedMessage(usize, Endpoint),
|
ReceivedMessage(usize, Endpoint),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The reason why we are outputting a key
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum KeyOutputReason {
|
pub enum KeyOutputReason {
|
||||||
|
/// The reason is that a new key for the given peer was successfully exchanged
|
||||||
Exchanged,
|
Exchanged,
|
||||||
|
/// The reason is, that no key could be exchanged with the peer before the output
|
||||||
|
/// key lifetime was reached; a [AppPollResult::DeleteKey] event was issued.
|
||||||
|
///
|
||||||
|
/// The key we output in this case is chosen randomly and serves to securely
|
||||||
|
/// erase whatever key is currently stored.
|
||||||
Stale,
|
Stale,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,14 +539,21 @@ impl std::fmt::Display for Endpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A network address bound to a particular socket.
|
||||||
|
///
|
||||||
|
/// We need the information which socket is used because different listen sockets
|
||||||
|
/// might be on different networks.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SocketBoundEndpoint {
|
pub struct SocketBoundEndpoint {
|
||||||
/// The socket the address can be reached under; this is generally
|
/// The socket the address can be reached under; this is generally
|
||||||
/// determined when we actually receive an RespHello message
|
/// determined when we actually receive an RespHello message
|
||||||
socket: SocketPtr,
|
socket: SocketPtr,
|
||||||
/// Just the address
|
/// The network address
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
/// identifier
|
/// Byte representation of this socket bound network address.
|
||||||
|
/// Generated through [SocketBoundEndpoint::to_bytes].
|
||||||
|
///
|
||||||
|
/// Read through [HostIdentification::encode]
|
||||||
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
|
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,16 +564,22 @@ impl std::fmt::Display for SocketBoundEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SocketBoundEndpoint {
|
impl SocketBoundEndpoint {
|
||||||
|
/// Length in bytes of the serialized socket index
|
||||||
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
|
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
|
||||||
|
/// Length in bytes of the serialized ipv6 address
|
||||||
const IPV6_SIZE: usize = 16;
|
const IPV6_SIZE: usize = 16;
|
||||||
|
/// Length in bytes of the serialized port
|
||||||
const PORT_SIZE: usize = 2;
|
const PORT_SIZE: usize = 2;
|
||||||
|
/// Length in bytes of the serialized ipv6 address scope (see [SocketAddrV6::scope_id])
|
||||||
const SCOPE_ID_SIZE: usize = 4;
|
const SCOPE_ID_SIZE: usize = 4;
|
||||||
|
|
||||||
|
/// Length in size of
|
||||||
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
|
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
|
||||||
+ SocketBoundEndpoint::IPV6_SIZE
|
+ SocketBoundEndpoint::IPV6_SIZE
|
||||||
+ SocketBoundEndpoint::PORT_SIZE
|
+ SocketBoundEndpoint::PORT_SIZE
|
||||||
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
|
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
|
||||||
|
|
||||||
|
/// Produce a new [Self]
|
||||||
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
|
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
|
||||||
let bytes = Self::to_bytes(&socket, &addr);
|
let bytes = Self::to_bytes(&socket, &addr);
|
||||||
Self {
|
Self {
|
||||||
@@ -336,6 +589,7 @@ impl SocketBoundEndpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes [HostIdentification::encode] for [Self]. Value cached in [Self::bytes].
|
||||||
fn to_bytes(
|
fn to_bytes(
|
||||||
socket: &SocketPtr,
|
socket: &SocketPtr,
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
@@ -370,12 +624,18 @@ impl HostIdentification for SocketBoundEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
/// Start discovery from some addresses
|
/// Given a list of potential network addresses, start peer discovery.
|
||||||
|
///
|
||||||
|
/// Will send initiations to different addresses given here on each [crate::msgs::InitHello]
|
||||||
|
/// retransmission during the peer discovery phase.
|
||||||
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||||
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
|
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start endpoint discovery from a hostname
|
/// Given a hostname, start peer discovery.
|
||||||
|
///
|
||||||
|
/// Will send initiations to different addresses assigned to the host name
|
||||||
|
/// on each [crate::msgs::InitHello] retransmission during the peer discovery phase.
|
||||||
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
|
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
|
||||||
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
|
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
|
||||||
Ok(Endpoint::Discovery(host))
|
Ok(Endpoint::Discovery(host))
|
||||||
@@ -406,6 +666,8 @@ impl Endpoint {
|
|||||||
Some(Self::discovery_from_addresses(addrs))
|
Some(Self::discovery_from_addresses(addrs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a message to the address referenced by this endpoint or to one of
|
||||||
|
/// the endpoints if we are in the peer discovery phase for this endpoint
|
||||||
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
|
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
|
||||||
use Endpoint::*;
|
use Endpoint::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -414,6 +676,9 @@ impl Endpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of addresses this endpoint may be associated with.
|
||||||
|
///
|
||||||
|
/// During peer discovery, this can be multiple addresses.
|
||||||
fn addresses(&self) -> &[SocketAddr] {
|
fn addresses(&self) -> &[SocketAddr] {
|
||||||
use Endpoint::*;
|
use Endpoint::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -451,7 +716,14 @@ impl Endpoint {
|
|||||||
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
|
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HostPathDiscoveryEndpoint {
|
pub struct HostPathDiscoveryEndpoint {
|
||||||
scouting_state: Cell<(usize, usize)>, // addr_off, sock_off
|
/// Round robin index the next [Self::send_scouting] call should send packets to
|
||||||
|
///
|
||||||
|
/// (address offset, socket offset)
|
||||||
|
///
|
||||||
|
/// Including the socket here accounts for the fact that some network addresses may be
|
||||||
|
/// reachable only through particular UDP sockets
|
||||||
|
scouting_state: Cell<(usize, usize)>,
|
||||||
|
/// List of addresses fir oeer discovery
|
||||||
addresses: Vec<SocketAddr>,
|
addresses: Vec<SocketAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,6 +734,7 @@ impl std::fmt::Display for HostPathDiscoveryEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HostPathDiscoveryEndpoint {
|
impl HostPathDiscoveryEndpoint {
|
||||||
|
/// Initiate a peer discovery process through a list of potential addresses
|
||||||
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||||
let scouting_state = Cell::new((0, 0));
|
let scouting_state = Cell::new((0, 0));
|
||||||
Self {
|
Self {
|
||||||
@@ -470,7 +743,7 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup a hostname
|
/// Initiate a peer discovery process through hostname lookup
|
||||||
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
|
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
|
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
|
||||||
@@ -478,10 +751,14 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of address candidates for the peer
|
||||||
pub fn addresses(&self) -> &Vec<SocketAddr> {
|
pub fn addresses(&self) -> &Vec<SocketAddr> {
|
||||||
&self.addresses
|
&self.addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates and stores the next value for [Self::scouting_state]
|
||||||
|
/// given the address and socket we just sent a scouting [crate::msgs::InitHello] message
|
||||||
|
/// to
|
||||||
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
|
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
|
||||||
self.scouting_state.set((
|
self.scouting_state.set((
|
||||||
(addr_no + 1) % self.addresses.len(),
|
(addr_no + 1) % self.addresses.len(),
|
||||||
@@ -536,6 +813,11 @@ impl HostPathDiscoveryEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppServer {
|
impl AppServer {
|
||||||
|
/// Construct a new AppServer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
keypair: Option<(SSk, SPk)>,
|
keypair: Option<(SSk, SPk)>,
|
||||||
addrs: Vec<SocketAddr>,
|
addrs: Vec<SocketAddr>,
|
||||||
@@ -660,22 +942,37 @@ impl AppServer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the cryptographic protocol server
|
||||||
|
///
|
||||||
|
/// This may return an error if [Self] was initialized without a keypair
|
||||||
|
/// and no keypair has been supplied since then.
|
||||||
|
///
|
||||||
|
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
|
||||||
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
||||||
self.crypto_site
|
self.crypto_site
|
||||||
.product_ref()
|
.product_ref()
|
||||||
.context("Cryptography handler not initialized")
|
.context("Cryptography handler not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the cryptographic protocol server, mutably
|
||||||
|
///
|
||||||
|
/// This may return an error if [Self] was initialized without a keypair
|
||||||
|
/// and no keypair has been supplied since then.
|
||||||
|
///
|
||||||
|
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
|
||||||
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
||||||
self.crypto_site
|
self.crypto_site
|
||||||
.product_mut()
|
.product_mut()
|
||||||
.context("Cryptography handler not initialized")
|
.context("Cryptography handler not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If set to [Verbosity::Verbose], then some extra information will be printed
|
||||||
|
/// at the info log level
|
||||||
pub fn verbose(&self) -> bool {
|
pub fn verbose(&self) -> bool {
|
||||||
matches!(self.verbosity, Verbosity::Verbose)
|
matches!(self.verbosity, Verbosity::Verbose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by [Self::new] to register a new udp listen source
|
||||||
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
||||||
let mio_token = self.mio_token_dispenser.dispense();
|
let mio_token = self.mio_token_dispenser.dispense();
|
||||||
self.mio_poll
|
self.mio_poll
|
||||||
@@ -687,16 +984,19 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to register a source of IO such as a listen socket with [Self::io_source_index]
|
||||||
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
||||||
let prev = self.io_source_index.insert(token, io_source);
|
let prev = self.io_source_index.insert(token, io_source);
|
||||||
assert!(prev.is_none());
|
assert!(prev.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unregister an IO source registered with [Self::register_io_source]
|
||||||
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
||||||
let value = self.io_source_index.remove(&token);
|
let value = self.io_source_index.remove(&token);
|
||||||
assert!(value.is_some(), "Removed IO source that does not exist");
|
assert!(value.is_some(), "Removed IO source that does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new WireGuard PSK broker
|
||||||
pub fn register_broker(
|
pub fn register_broker(
|
||||||
&mut self,
|
&mut self,
|
||||||
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||||
@@ -719,6 +1019,7 @@ impl AppServer {
|
|||||||
Ok(BrokerStorePtr(ptr))
|
Ok(BrokerStorePtr(ptr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unregister a WireGuard PSK broker registered with [Self::register_broker]
|
||||||
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
||||||
let mut broker = self
|
let mut broker = self
|
||||||
.brokers
|
.brokers
|
||||||
@@ -730,6 +1031,11 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new protocol peer
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::new].
|
||||||
pub fn add_peer(
|
pub fn add_peer(
|
||||||
&mut self,
|
&mut self,
|
||||||
psk: Option<SymKey>,
|
psk: Option<SymKey>,
|
||||||
@@ -737,11 +1043,12 @@ impl AppServer {
|
|||||||
outfile: Option<PathBuf>,
|
outfile: Option<PathBuf>,
|
||||||
broker_peer: Option<BrokerPeer>,
|
broker_peer: Option<BrokerPeer>,
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
|
protocol_version: ProtocolVersion,
|
||||||
) -> anyhow::Result<AppPeerPtr> {
|
) -> anyhow::Result<AppPeerPtr> {
|
||||||
let PeerPtr(pn) = match &mut self.crypto_site {
|
let PeerPtr(pn) = match &mut self.crypto_site {
|
||||||
ConstructionSite::Void => bail!("Crypto server construction site is void"),
|
ConstructionSite::Void => bail!("Crypto server construction site is void"),
|
||||||
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk),
|
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk, protocol_version),
|
||||||
ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?,
|
ConstructionSite::Product(srv) => srv.add_peer(psk, pk, protocol_version.into())?,
|
||||||
};
|
};
|
||||||
assert!(pn == self.peers.len());
|
assert!(pn == self.peers.len());
|
||||||
|
|
||||||
@@ -758,6 +1065,11 @@ impl AppServer {
|
|||||||
Ok(AppPeerPtr(pn))
|
Ok(AppPeerPtr(pn))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Main IO handler; this generally does not terminate
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self::new].
|
||||||
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||||
const INIT_SLEEP: f64 = 0.01;
|
const INIT_SLEEP: f64 = 0.01;
|
||||||
const MAX_FAILURES: i32 = 10;
|
const MAX_FAILURES: i32 = 10;
|
||||||
@@ -811,6 +1123,9 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// IO handler without proactive restarts after errors.
|
||||||
|
///
|
||||||
|
/// This is used internally in [Self::event_loop].
|
||||||
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
|
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
|
||||||
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
||||||
|
|
||||||
@@ -930,6 +1245,8 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for [Self::event_loop_without_error_handling] to handle network messages
|
||||||
|
/// under DoS condition
|
||||||
fn handle_msg_under_load(
|
fn handle_msg_under_load(
|
||||||
&mut self,
|
&mut self,
|
||||||
endpoint: &Endpoint,
|
endpoint: &Endpoint,
|
||||||
@@ -946,6 +1263,8 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used as a helper by [Self::event_loop_without_error_handling] when
|
||||||
|
/// a new output key has been exchanged
|
||||||
pub fn output_key(
|
pub fn output_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
peer: AppPeerPtr,
|
peer: AppPeerPtr,
|
||||||
@@ -995,6 +1314,10 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Poll for events from the cryptographic server ([Self::crypto_server()])
|
||||||
|
/// and for IO events through [Self::poll].
|
||||||
|
///
|
||||||
|
/// Used internally in [Self::event_loop_without_error_handling]
|
||||||
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
||||||
use crate::protocol::PollResult as C;
|
use crate::protocol::PollResult as C;
|
||||||
use AppPollResult as A;
|
use AppPollResult as A;
|
||||||
@@ -1026,7 +1349,9 @@ impl AppServer {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to receive a new message
|
/// Tries to receive a new message from the network sockets.
|
||||||
|
///
|
||||||
|
/// Used internally in [Self::poll]
|
||||||
///
|
///
|
||||||
/// - might wait for an duration up to `timeout`
|
/// - might wait for an duration up to `timeout`
|
||||||
/// - returns immediately if an error occurs
|
/// - returns immediately if an error occurs
|
||||||
@@ -1198,6 +1523,7 @@ impl AppServer {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
||||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||||
// Fill the short poll buffer with the acquired events
|
// Fill the short poll buffer with the acquired events
|
||||||
@@ -1208,6 +1534,7 @@ impl AppServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn try_recv_from_mio_token(
|
fn try_recv_from_mio_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -1224,6 +1551,7 @@ impl AppServer {
|
|||||||
self.try_recv_from_io_source(buf, io_source)
|
self.try_recv_from_io_source(buf, io_source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn try_recv_from_io_source(
|
fn try_recv_from_io_source(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -1254,6 +1582,7 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal helper for [Self::try_recv]
|
||||||
fn try_recv_from_listen_socket(
|
fn try_recv_from_listen_socket(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -1289,6 +1618,20 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
|
/// This is a wrapper around a reference to [AppServer] that
|
||||||
|
/// dishes out references to [AppServer::api_manager] and
|
||||||
|
/// to [AppServer] itself.
|
||||||
|
///
|
||||||
|
/// It really just implements [crate::api::mio::MioManagerContext] which
|
||||||
|
/// provides the methods operating on [crate::api::mio::MioManager] provided
|
||||||
|
/// through a trait.
|
||||||
|
///
|
||||||
|
/// This is a rather complicated way of providing just a few functions. The entire
|
||||||
|
/// point of this exercise is to decouple the code in the API from [AppServer] and
|
||||||
|
/// this file a bit, despite those functions all needing access to [AppServer].
|
||||||
|
///
|
||||||
|
/// We want the code to live in its own module instead of expanding and expanding the source
|
||||||
|
/// file with [AppServer] more and more.
|
||||||
struct MioManagerFocus<'a>(&'a mut AppServer);
|
struct MioManagerFocus<'a>(&'a mut AppServer);
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use heck::ToShoutySnakeCase;
|
use heck::ToShoutySnakeCase;
|
||||||
|
|
||||||
|
use rosenpass_ciphers::subtle::keyed_hash::KeyedHash;
|
||||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||||
|
|
||||||
/// Recursively calculate a concrete hash value for an API message type
|
/// Recursively calculate a concrete hash value for an API message type
|
||||||
@@ -12,12 +13,12 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Print a hash literal for pasting into the Rosenpass source code
|
/// Print a hash literal for pasting into the Rosenpass source code
|
||||||
fn print_literal(path: &[&str]) -> Result<()> {
|
fn print_literal(path: &[&str], shake_or_blake: KeyedHash) -> Result<()> {
|
||||||
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
let val = calculate_hash_value(HashDomain::zero(shake_or_blake.clone()), path)?;
|
||||||
let (last, prefix) = path.split_last().context("developer error!")?;
|
let (last, prefix) = path.split_last().context("developer error!")?;
|
||||||
let var_name = last.to_shouty_snake_case();
|
let var_name = last.to_shouty_snake_case();
|
||||||
|
|
||||||
print!("// hash domain hash of: ");
|
print!("// hash domain hash with hash {} of: ", shake_or_blake);
|
||||||
for n in prefix.iter() {
|
for n in prefix.iter() {
|
||||||
print!("{n} -> ");
|
print!("{n} -> ");
|
||||||
}
|
}
|
||||||
@@ -51,24 +52,24 @@ impl Tree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> {
|
fn gen_code_inner(&self, prefix: &[&str], shake_or_blake: KeyedHash) -> Result<()> {
|
||||||
let mut path = prefix.to_owned();
|
let mut path = prefix.to_owned();
|
||||||
path.push(self.name());
|
path.push(self.name());
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Branch(_, ref children) => {
|
Self::Branch(_, ref children) => {
|
||||||
for c in children.iter() {
|
for c in children.iter() {
|
||||||
c.gen_code_inner(&path)?
|
c.gen_code_inner(&path, shake_or_blake.clone())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Leaf(_) => print_literal(&path)?,
|
Self::Leaf(_) => print_literal(&path, shake_or_blake)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_code(&self) -> Result<()> {
|
fn gen_code(&self, shake_or_blake: KeyedHash) -> Result<()> {
|
||||||
self.gen_code_inner(&[])
|
self.gen_code_inner(&[], shake_or_blake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,5 +94,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
println!("type RawMsgType = u128;");
|
println!("type RawMsgType = u128;");
|
||||||
println!();
|
println!();
|
||||||
tree.gen_code()
|
tree.gen_code(KeyedHash::keyed_shake256())?;
|
||||||
|
println!();
|
||||||
|
tree.gen_code(KeyedHash::incorrect_hmac_blake2b())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
//! Contains the code used to parse command line parameters for rosenpass.
|
||||||
|
//!
|
||||||
|
//! [CliArgs::run] is called by the rosenpass main function and contains the
|
||||||
|
//! bulk of our boostrapping code while the main function just sets up the basic environment
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Context};
|
use anyhow::{bail, ensure, Context};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
use rosenpass_secret_memory::file::StoreSecret;
|
use rosenpass_secret_memory::file::StoreSecret;
|
||||||
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
|
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};
|
||||||
use rosenpass_wireguard_broker::brokers::native_unix::{
|
use rosenpass_wireguard_broker::brokers::native_unix::{
|
||||||
@@ -31,15 +36,25 @@ use {
|
|||||||
std::thread,
|
std::thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// enum representing a choice of interface to a WireGuard broker
|
/// How to reach a WireGuard PSK Broker
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BrokerInterface {
|
pub enum BrokerInterface {
|
||||||
|
/// The PSK Broker is listening on a unix socket at the given path
|
||||||
Socket(PathBuf),
|
Socket(PathBuf),
|
||||||
|
/// The PSK Broker broker is already connected to this process; a
|
||||||
|
/// unix socket stream can be reached at the given file descriptor.
|
||||||
|
///
|
||||||
|
/// This is generally used with file descriptor passing.
|
||||||
FileDescriptor(i32),
|
FileDescriptor(i32),
|
||||||
|
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
|
||||||
|
/// and hand one end of the socketpair to the broker process via file
|
||||||
|
/// descriptor passing to the subprocess
|
||||||
SocketPair,
|
SocketPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// struct holding all CLI arguments for `clap` crate to parse
|
/// Command line arguments to the Rosenpass binary.
|
||||||
|
///
|
||||||
|
/// Used for parsing with [clap].
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
@@ -80,6 +95,7 @@ pub struct CliArgs {
|
|||||||
#[arg(short, long, group = "psk-broker-specs")]
|
#[arg(short, long, group = "psk-broker-specs")]
|
||||||
psk_broker_spawn: bool,
|
psk_broker_spawn: bool,
|
||||||
|
|
||||||
|
/// The subcommand to be invoked
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<CliCommand>,
|
pub command: Option<CliCommand>,
|
||||||
|
|
||||||
@@ -98,6 +114,10 @@ pub struct CliArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
|
/// Apply the command line parameters to the Rosenpass configuration struct
|
||||||
|
///
|
||||||
|
/// Generally the flow of control here is that all the command line parameters
|
||||||
|
/// are merged into the configuration file to avoid much code duplication.
|
||||||
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
self.api.apply_to_config(_cfg)?;
|
self.api.apply_to_config(_cfg)?;
|
||||||
@@ -123,9 +143,11 @@ impl CliArgs {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the WireGuard PSK broker interface configured.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
/// returns the broker interface set by CLI args
|
|
||||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
|
||||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||||
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
||||||
@@ -138,9 +160,10 @@ impl CliArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the WireGuard PSK broker interface configured.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||||
#[cfg(not(feature = "experiment_api"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
/// returns the broker interface set by CLI args
|
|
||||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
|
||||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -244,15 +267,17 @@ pub enum CliCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
/// Runs the command specified via CLI
|
/// Run Rosenpass with the given command line parameters
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// This contains the bulk of our startup logic with
|
||||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
/// the main function just setting up the basic environment
|
||||||
|
/// and then calling this function.
|
||||||
pub fn run(
|
pub fn run(
|
||||||
self,
|
self,
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
test_helpers: Option<AppServerTest>,
|
test_helpers: Option<AppServerTest>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||||
use CliCommand::*;
|
use CliCommand::*;
|
||||||
match &self.command {
|
match &self.command {
|
||||||
Some(GenConfig { config_file, force }) => {
|
Some(GenConfig { config_file, force }) => {
|
||||||
@@ -403,6 +428,7 @@ impl CliArgs {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by [Self::run] to start the Rosenpass key exchange server
|
||||||
fn event_loop(
|
fn event_loop(
|
||||||
config: config::Rosenpass,
|
config: config::Rosenpass,
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
@@ -464,12 +490,26 @@ impl CliArgs {
|
|||||||
cfg_peer.key_out,
|
cfg_peer.key_out,
|
||||||
broker_peer,
|
broker_peer,
|
||||||
cfg_peer.endpoint.clone(),
|
cfg_peer.endpoint.clone(),
|
||||||
|
cfg_peer.protocol_version.into(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.event_loop()
|
srv.event_loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the WireGuard PSK broker to be used by
|
||||||
|
/// [crate::app_server::AppServer].
|
||||||
|
///
|
||||||
|
/// If the `experiment_api`
|
||||||
|
/// feature flag is set, then this communicates with a PSK broker
|
||||||
|
/// running in a different process as configured via
|
||||||
|
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||||
|
/// fields.
|
||||||
|
///
|
||||||
|
/// If the `experiment_api`
|
||||||
|
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||||
|
/// sending pre-shared keys directly to WireGuard from within this
|
||||||
|
/// process.
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
fn create_broker(
|
fn create_broker(
|
||||||
broker_interface: Option<BrokerInterface>,
|
broker_interface: Option<BrokerInterface>,
|
||||||
@@ -485,6 +525,19 @@ impl CliArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the WireGuard PSK broker to be used by
|
||||||
|
/// [crate::app_server::AppServer].
|
||||||
|
///
|
||||||
|
/// If the `experiment_api`
|
||||||
|
/// feature flag is set, then this communicates with a PSK broker
|
||||||
|
/// running in a different process as configured via
|
||||||
|
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||||
|
/// fields.
|
||||||
|
///
|
||||||
|
/// If the `experiment_api`
|
||||||
|
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||||
|
/// sending pre-shared keys directly to WireGuard from within this
|
||||||
|
/// process.
|
||||||
#[cfg(not(feature = "experiment_api"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
fn create_broker(
|
fn create_broker(
|
||||||
_broker_interface: Option<BrokerInterface>,
|
_broker_interface: Option<BrokerInterface>,
|
||||||
@@ -492,6 +545,10 @@ impl CliArgs {
|
|||||||
Ok(Box::new(NativeUnixBroker::new()))
|
Ok(Box::new(NativeUnixBroker::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by [Self::create_broker] if the `experiment_api` is configured
|
||||||
|
/// to set up the connection with the PSK broker process as configured
|
||||||
|
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||||
|
/// fields.
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||||
// Connect to the psk broker unix socket if one was specified
|
// Connect to the psk broker unix socket if one was specified
|
||||||
@@ -549,10 +606,10 @@ impl CliArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||||
let mut ssk = crate::protocol::SSk::random();
|
let mut ssk = crate::protocol::SSk::random();
|
||||||
let mut spk = crate::protocol::SPk::random();
|
let mut spk = crate::protocol::SPk::random();
|
||||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
StaticKem.keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||||
ssk.store_secret(secret_key)?;
|
ssk.store_secret(secret_key)?;
|
||||||
spk.store(public_key)
|
spk.store(public_key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
//! [`Rosenpass`] which holds such a configuration.
|
//! [`Rosenpass`] which holds such a configuration.
|
||||||
//!
|
//!
|
||||||
//! ## TODO
|
//! ## TODO
|
||||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||||
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||||
|
|
||||||
use crate::protocol::{SPk, SSk};
|
use crate::protocol::{SPk, SSk};
|
||||||
use rosenpass_util::file::LoadValue;
|
use rosenpass_util::file::LoadValue;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Configuration for the Rosenpass key exchange
|
||||||
|
///
|
||||||
|
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct Rosenpass {
|
pub struct Rosenpass {
|
||||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||||
@@ -46,7 +50,10 @@ pub struct Rosenpass {
|
|||||||
/// list of [`SocketAddr`] to listen on
|
/// list of [`SocketAddr`] to listen on
|
||||||
///
|
///
|
||||||
/// Examples:
|
/// Examples:
|
||||||
/// - `0.0.0.0:123`
|
///
|
||||||
|
/// - `0.0.0.0:123` – Listen on any interface using IPv4, port 123
|
||||||
|
/// - `[::1]:1234` – Listen on IPv6 localhost, port 1234
|
||||||
|
/// - `[::]:4476` – Listen on any IPv4 or IPv6 interface, port 4476
|
||||||
pub listen: Vec<SocketAddr>,
|
pub listen: Vec<SocketAddr>,
|
||||||
|
|
||||||
/// log verbosity
|
/// log verbosity
|
||||||
@@ -68,6 +75,7 @@ pub struct Rosenpass {
|
|||||||
pub config_file_path: PathBuf,
|
pub config_file_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public key and secret key locations.
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||||
pub struct Keypair {
|
pub struct Keypair {
|
||||||
/// path to the public key file
|
/// path to the public key file
|
||||||
@@ -78,6 +86,7 @@ pub struct Keypair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Keypair {
|
impl Keypair {
|
||||||
|
/// Construct a keypair from its fields
|
||||||
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
||||||
let public_key = public_key.as_ref().to_path_buf();
|
let public_key = public_key.as_ref().to_path_buf();
|
||||||
let secret_key = secret_key.as_ref().to_path_buf();
|
let secret_key = secret_key.as_ref().to_path_buf();
|
||||||
@@ -88,62 +97,84 @@ impl Keypair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Level of verbosity for [crate::app_server::AppServer]
|
||||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
///
|
||||||
|
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
|
||||||
|
/// for details.
|
||||||
|
///
|
||||||
|
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
Quiet,
|
Quiet,
|
||||||
Verbose,
|
Verbose,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// The protocol version to be used by a peer.
|
||||||
/// - examples
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone, Default)]
|
||||||
/// - documentation
|
pub enum ProtocolVersion {
|
||||||
|
#[default]
|
||||||
|
V02,
|
||||||
|
V03,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration data for a single Rosenpass peer
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RosenpassPeer {
|
pub struct RosenpassPeer {
|
||||||
/// path to the public key of the peer
|
/// path to the public key of the peer
|
||||||
pub public_key: PathBuf,
|
pub public_key: PathBuf,
|
||||||
|
|
||||||
/// ## TODO
|
/// The hostname and port to connect to
|
||||||
/// - documentation
|
///
|
||||||
|
/// Can be a
|
||||||
|
///
|
||||||
|
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
|
||||||
|
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
|
||||||
|
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
|
||||||
pub endpoint: Option<String>,
|
pub endpoint: Option<String>,
|
||||||
|
|
||||||
/// path to the pre-shared key with the peer
|
/// path to the pre-shared key shared with the peer
|
||||||
///
|
///
|
||||||
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
|
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
|
||||||
pub pre_shared_key: Option<PathBuf>,
|
pub pre_shared_key: Option<PathBuf>,
|
||||||
|
|
||||||
/// ## TODO
|
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
|
||||||
/// - documentation
|
/// to the given file and write a notification to standard out to let the calling application
|
||||||
|
/// know that a new key was exchanged
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub key_out: Option<PathBuf>,
|
pub key_out: Option<PathBuf>,
|
||||||
|
|
||||||
/// ## TODO
|
/// Information for supplying exchanged keys directly to WireGuard
|
||||||
/// - documentation
|
|
||||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub wg: Option<WireGuard>,
|
pub wg: Option<WireGuard>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
/// The protocol version to use for the exchange
|
||||||
|
pub protocol_version: ProtocolVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## TODO
|
/// Information for supplying exchanged keys directly to WireGuard
|
||||||
/// - documentation
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WireGuard {
|
pub struct WireGuard {
|
||||||
/// ## TODO
|
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
|
||||||
/// - documentation
|
/// key exchange
|
||||||
pub device: String,
|
pub device: String,
|
||||||
|
|
||||||
/// ## TODO
|
/// WireGuard public key of the peer to supply with pre-shared keys
|
||||||
/// - documentation
|
|
||||||
pub peer: String,
|
pub peer: String,
|
||||||
|
|
||||||
/// ## TODO
|
/// Extra parameters passed to the `wg` command
|
||||||
/// - documentation
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub extra_params: Vec<String>,
|
pub extra_params: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rosenpass {
|
impl Default for Rosenpass {
|
||||||
|
/// Generate an empty configuration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::empty()
|
Self::empty()
|
||||||
}
|
}
|
||||||
@@ -156,8 +187,15 @@ impl Rosenpass {
|
|||||||
/// checked whether they even exist.
|
/// checked whether they even exist.
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// ## TODO
|
||||||
|
///
|
||||||
/// - consider using a different algorithm to determine home directory – the below one may
|
/// - consider using a different algorithm to determine home directory – the below one may
|
||||||
/// behave unexpectedly on Windows
|
/// behave unexpectedly on Windows
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||||
// read file and deserialize
|
// read file and deserialize
|
||||||
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||||
@@ -185,7 +223,13 @@ impl Rosenpass {
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a config to a file
|
/// Encode a configuration object as toml and write it to a file
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||||
let serialized_config =
|
let serialized_config =
|
||||||
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
||||||
@@ -194,6 +238,12 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Commit the configuration to where it came from, overwriting the original file
|
/// Commit the configuration to where it came from, overwriting the original file
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn commit(&self) -> anyhow::Result<()> {
|
pub fn commit(&self) -> anyhow::Result<()> {
|
||||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||||
@@ -201,13 +251,21 @@ impl Rosenpass {
|
|||||||
self.store(&self.config_file_path)
|
self.store(&self.config_file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
|
||||||
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
self.api.apply_to_app_server(_srv)?;
|
self.api.apply_to_app_server(_srv)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate a configuration
|
/// Check that the configuration is sound, ensuring
|
||||||
|
/// for instance that the referenced files exist
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn validate(&self) -> anyhow::Result<()> {
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
if let Some(ref keypair) = self.keypair {
|
if let Some(ref keypair) = self.keypair {
|
||||||
// check the public key file exists
|
// check the public key file exists
|
||||||
@@ -284,6 +342,21 @@ impl Rosenpass {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
|
||||||
|
/// and the configuration values.
|
||||||
|
///
|
||||||
|
/// This was introduced when we introduced a unix-socket API feature allowing the server
|
||||||
|
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
|
||||||
|
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
|
||||||
|
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
|
||||||
|
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
|
||||||
|
/// enabled in the configuration.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||||
#[cfg(not(feature = "experiment_api"))]
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||||
@@ -299,15 +372,38 @@ impl Rosenpass {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce an empty confuguration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::new(None)
|
Self::new(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce configuration from the keypair
|
||||||
|
///
|
||||||
|
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||||
Self::new(Some(Keypair::new(pk, sk)))
|
Self::new(Some(Keypair::new(pk, sk)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new configuration
|
/// Initialize a minimal configuration with the [Self::keypair] field supplied
|
||||||
|
/// as a parameter
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn new(keypair: Option<Keypair>) -> Self {
|
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
keypair,
|
keypair,
|
||||||
@@ -321,6 +417,14 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
||||||
|
///
|
||||||
|
/// I.e. listen on any interface.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn add_if_any(&mut self, port: u16) {
|
pub fn add_if_any(&mut self, port: u16) {
|
||||||
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||||
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||||
@@ -333,8 +437,20 @@ impl Rosenpass {
|
|||||||
self.listen.push(ipv6_any);
|
self.listen.push(ipv6_any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// from chaotic args
|
/// Parser for the old, IP style grammar.
|
||||||
/// Quest: the grammar is undecideable, what do we do here?
|
///
|
||||||
|
/// See out manual page rosenpass-exchange(1) on details about the grammar.
|
||||||
|
///
|
||||||
|
/// This function parses the grammar and turns it into an instance of the configuration
|
||||||
|
/// struct.
|
||||||
|
///
|
||||||
|
/// TODO: the grammar is undecidable, what do we do here?
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
#[doc = "```ignore"]
|
||||||
|
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
|
||||||
|
#[doc = "```"]
|
||||||
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||||
let mut config = Self::new(Some(Keypair::new("", "")));
|
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||||
|
|
||||||
@@ -525,11 +641,13 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Verbosity {
|
impl Default for Verbosity {
|
||||||
|
/// Self::Quiet
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Quiet
|
Self::Quiet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
|
||||||
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
||||||
secret_key = "/path/to/rp-secret-key"
|
secret_key = "/path/to/rp-secret-key"
|
||||||
listen = []
|
listen = []
|
||||||
@@ -553,7 +671,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
|
|||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::{borrow::Borrow, net::IpAddr};
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||||
toml::from_str(s.borrow())
|
toml::from_str(s.borrow())
|
||||||
@@ -665,34 +783,50 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_cli_parse() {
|
fn test_protocol_version() {
|
||||||
let args = split_str(
|
let mut rosenpass = Rosenpass::empty();
|
||||||
"public-key /my/public-key secret-key /my/secret-key verbose \
|
let mut peer_v_02 = RosenpassPeer::default();
|
||||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
peer_v_02.protocol_version = ProtocolVersion::V02;
|
||||||
peer.test:9999 outfile /peer/rp-out",
|
rosenpass.peers.push(peer_v_02);
|
||||||
);
|
let mut peer_v_03 = RosenpassPeer::default();
|
||||||
|
peer_v_03.protocol_version = ProtocolVersion::V03;
|
||||||
|
rosenpass.peers.push(peer_v_03);
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
{
|
||||||
|
rosenpass.api.listen_fd = vec![];
|
||||||
|
rosenpass.api.listen_path = vec![];
|
||||||
|
rosenpass.api.stream_fd = vec![];
|
||||||
|
}
|
||||||
|
#[cfg(feature = "experiment_api")]
|
||||||
|
let expected_toml = r#"listen = []
|
||||||
|
verbosity = "Quiet"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_fd = []
|
||||||
|
listen_path = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
let config = Rosenpass::parse_args(args).unwrap();
|
[[peers]]
|
||||||
|
protocol_version = "V02"
|
||||||
|
public_key = ""
|
||||||
|
|
||||||
assert_eq!(
|
[[peers]]
|
||||||
config.keypair,
|
protocol_version = "V03"
|
||||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
public_key = ""
|
||||||
);
|
"#;
|
||||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
#[cfg(not(feature = "experiment_api"))]
|
||||||
assert_eq!(
|
let expected_toml = r#"listen = []
|
||||||
&config.listen,
|
verbosity = "Quiet"
|
||||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
|
||||||
);
|
[[peers]]
|
||||||
assert_eq!(
|
protocol_version = "V02"
|
||||||
config.peers,
|
public_key = ""
|
||||||
vec![RosenpassPeer {
|
|
||||||
public_key: PathBuf::from("/peer/public-key"),
|
[[peers]]
|
||||||
endpoint: Some("peer.test:9999".into()),
|
protocol_version = "V03"
|
||||||
pre_shared_key: None,
|
public_key = ""
|
||||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
"#;
|
||||||
..Default::default()
|
assert_toml_round(rosenpass, expected_toml).unwrap()
|
||||||
}]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
//! use rosenpass::{hash_domain, hash_domain_ns};
|
//! use rosenpass::{hash_domain, hash_domain_ns};
|
||||||
//! use rosenpass::hash_domains::protocol;
|
//! use rosenpass::hash_domains::protocol;
|
||||||
//!
|
//!
|
||||||
|
//! use rosenpass_ciphers::subtle::keyed_hash::KeyedHash;
|
||||||
|
//!
|
||||||
//! // Declaring a custom hash domain
|
//! // Declaring a custom hash domain
|
||||||
//! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label");
|
//! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label");
|
||||||
//!
|
//!
|
||||||
@@ -26,15 +28,18 @@
|
|||||||
//! hash_domain!(domain_separators, sep1, "1");
|
//! hash_domain!(domain_separators, sep1, "1");
|
||||||
//! hash_domain!(domain_separators, sep2, "2");
|
//! hash_domain!(domain_separators, sep2, "2");
|
||||||
//!
|
//!
|
||||||
|
//! // We use the SHAKE256 hash function for this example
|
||||||
|
//! let hash_choice = KeyedHash::keyed_shake256();
|
||||||
|
//!
|
||||||
//! // Generating values under hasher1 with both domain separators
|
//! // Generating values under hasher1 with both domain separators
|
||||||
//! let h1 = hasher1()?.mix(b"some data")?.dup();
|
//! let h1 = hasher1(hash_choice.clone())?.mix(b"some data")?.dup();
|
||||||
//! let h1v1 = h1.mix(&sep1()?)?.mix(b"More data")?.into_value();
|
//! let h1v1 = h1.mix(&sep1(hash_choice.clone())?)?.mix(b"More data")?.into_value();
|
||||||
//! let h1v2 = h1.mix(&sep2()?)?.mix(b"More data")?.into_value();
|
//! let h1v2 = h1.mix(&sep2(hash_choice.clone())?)?.mix(b"More data")?.into_value();
|
||||||
//!
|
//!
|
||||||
//! // Generating values under hasher2 with both domain separators
|
//! // Generating values under hasher2 with both domain separators
|
||||||
//! let h2 = hasher2()?.mix(b"some data")?.dup();
|
//! let h2 = hasher2(hash_choice.clone())?.mix(b"some data")?.dup();
|
||||||
//! let h2v1 = h2.mix(&sep1()?)?.mix(b"More data")?.into_value();
|
//! let h2v1 = h2.mix(&sep1(hash_choice.clone())?)?.mix(b"More data")?.into_value();
|
||||||
//! let h2v2 = h2.mix(&sep2()?)?.mix(b"More data")?.into_value();
|
//! let h2v2 = h2.mix(&sep2(hash_choice.clone())?)?.mix(b"More data")?.into_value();
|
||||||
//!
|
//!
|
||||||
//! // All of the domain separators are now different, random strings
|
//! // All of the domain separators are now different, random strings
|
||||||
//! let values = [h1v1, h1v2, h2v1, h2v2];
|
//! let values = [h1v1, h1v2, h2v1, h2v2];
|
||||||
@@ -49,6 +54,7 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass_ciphers::hash_domain::HashDomain;
|
use rosenpass_ciphers::hash_domain::HashDomain;
|
||||||
|
use rosenpass_ciphers::subtle::keyed_hash::KeyedHash;
|
||||||
|
|
||||||
/// Declare a hash function
|
/// Declare a hash function
|
||||||
///
|
///
|
||||||
@@ -62,8 +68,8 @@ use rosenpass_ciphers::hash_domain::HashDomain;
|
|||||||
macro_rules! hash_domain_ns {
|
macro_rules! hash_domain_ns {
|
||||||
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
||||||
$(#[$($attrss)*])*
|
$(#[$($attrss)*])*
|
||||||
pub fn $name() -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> {
|
pub fn $name(hash_choice: KeyedHash) -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> {
|
||||||
let t = $base()?;
|
let t = $base(hash_choice)?;
|
||||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||||
Ok(t)
|
Ok(t)
|
||||||
}
|
}
|
||||||
@@ -81,8 +87,8 @@ macro_rules! hash_domain_ns {
|
|||||||
macro_rules! hash_domain {
|
macro_rules! hash_domain {
|
||||||
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
||||||
$(#[$($attrss)*])*
|
$(#[$($attrss)*])*
|
||||||
pub fn $name() -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> {
|
pub fn $name(hash_choice: KeyedHash) -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> {
|
||||||
let t = $base()?;
|
let t = $base(hash_choice)?;
|
||||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||||
Ok(t.into_value())
|
Ok(t.into_value())
|
||||||
}
|
}
|
||||||
@@ -94,15 +100,22 @@ macro_rules! hash_domain {
|
|||||||
/// This serves as a global [domain separator](https://en.wikipedia.org/wiki/Domain_separation)
|
/// This serves as a global [domain separator](https://en.wikipedia.org/wiki/Domain_separation)
|
||||||
/// used in various places in the rosenpass protocol.
|
/// used in various places in the rosenpass protocol.
|
||||||
///
|
///
|
||||||
/// This is generally used to create further hash-domains for specific purposes. See
|
/// This is generally used to create further hash-domains for specific purposes. Depending on
|
||||||
|
/// the used hash function, the protocol string is different.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// See the source file for details about how this is used concretely.
|
/// See the source file for details about how this is used concretely.
|
||||||
///
|
///
|
||||||
/// See the [module](self) documentation on how to use the hash domains in general
|
/// See the [module](self) documentation on how to use the hash domains in general
|
||||||
pub fn protocol() -> Result<HashDomain> {
|
pub fn protocol(hash_choice: KeyedHash) -> Result<HashDomain> {
|
||||||
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
|
// Depending on the hash function, we use different protocol strings
|
||||||
|
match hash_choice {
|
||||||
|
KeyedHash::KeyedShake256(_) => HashDomain::zero(hash_choice)
|
||||||
|
.mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 SHAKE256".as_bytes()),
|
||||||
|
KeyedHash::IncorrectHmacBlake2b(_) => HashDomain::zero(hash_choice)
|
||||||
|
.mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hash_domain_ns!(
|
hash_domain_ns!(
|
||||||
|
|||||||
@@ -9,13 +9,12 @@
|
|||||||
//! To achieve this we utilize the zerocopy library.
|
//! To achieve this we utilize the zerocopy library.
|
||||||
//!
|
//!
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::u8;
|
|
||||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||||
|
|
||||||
use super::RosenpassError;
|
use super::RosenpassError;
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::primitives::{Aead as _, Kem};
|
||||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
use rosenpass_ciphers::{Aead, XAead, KEY_LEN};
|
||||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
use rosenpass_ciphers::{EphemeralKem, StaticKem};
|
||||||
|
|
||||||
/// Length of a session ID such as [InitHello::sidi]
|
/// Length of a session ID such as [InitHello::sidi]
|
||||||
pub const SESSION_ID_LEN: usize = 4;
|
pub const SESSION_ID_LEN: usize = 4;
|
||||||
@@ -32,7 +31,7 @@ pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
|
|||||||
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
||||||
|
|
||||||
/// Length in bytes of an encrypted Biscuit (cipher text)
|
/// Length in bytes of an encrypted Biscuit (cipher text)
|
||||||
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
|
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + XAead::NONCE_LEN + XAead::TAG_LEN;
|
||||||
|
|
||||||
/// Size of the field [Envelope::mac]
|
/// Size of the field [Envelope::mac]
|
||||||
pub const MAC_SIZE: usize = 16;
|
pub const MAC_SIZE: usize = 16;
|
||||||
@@ -136,9 +135,9 @@ pub struct InitHello {
|
|||||||
/// Classic McEliece Ciphertext
|
/// Classic McEliece Ciphertext
|
||||||
pub sctr: [u8; StaticKem::CT_LEN],
|
pub sctr: [u8; StaticKem::CT_LEN],
|
||||||
/// Encryped: 16 byte hash of McEliece initiator static key
|
/// Encryped: 16 byte hash of McEliece initiator static key
|
||||||
pub pidic: [u8; aead::TAG_LEN + 32],
|
pub pidic: [u8; Aead::TAG_LEN + 32],
|
||||||
/// Encrypted TAI64N Time Stamp (against replay attacks)
|
/// Encrypted TAI64N Time Stamp (against replay attacks)
|
||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; Aead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is the second message sent by the responder to the initiator
|
/// This is the second message sent by the responder to the initiator
|
||||||
@@ -187,7 +186,7 @@ pub struct RespHello {
|
|||||||
/// Classic McEliece Ciphertext
|
/// Classic McEliece Ciphertext
|
||||||
pub scti: [u8; StaticKem::CT_LEN],
|
pub scti: [u8; StaticKem::CT_LEN],
|
||||||
/// Empty encrypted message (just an auth tag)
|
/// Empty encrypted message (just an auth tag)
|
||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; Aead::TAG_LEN],
|
||||||
/// Responders handshake state in encrypted form
|
/// Responders handshake state in encrypted form
|
||||||
pub biscuit: [u8; BISCUIT_CT_LEN],
|
pub biscuit: [u8; BISCUIT_CT_LEN],
|
||||||
}
|
}
|
||||||
@@ -236,7 +235,7 @@ pub struct InitConf {
|
|||||||
/// Responders handshake state in encrypted form
|
/// Responders handshake state in encrypted form
|
||||||
pub biscuit: [u8; BISCUIT_CT_LEN],
|
pub biscuit: [u8; BISCUIT_CT_LEN],
|
||||||
/// Empty encrypted message (just an auth tag)
|
/// Empty encrypted message (just an auth tag)
|
||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; Aead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is the fourth message sent by the initiator to the responder
|
/// This is the fourth message sent by the initiator to the responder
|
||||||
@@ -292,7 +291,7 @@ pub struct EmptyData {
|
|||||||
/// Nonce
|
/// Nonce
|
||||||
pub ctr: [u8; 8],
|
pub ctr: [u8; 8],
|
||||||
/// Empty encrypted message (just an auth tag)
|
/// Empty encrypted message (just an auth tag)
|
||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; Aead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cookie encrypted and sent to the initiator by the responder in [RespHello]
|
/// Cookie encrypted and sent to the initiator by the responder in [RespHello]
|
||||||
@@ -346,7 +345,7 @@ pub struct CookieReplyInner {
|
|||||||
/// Session ID of the sender (initiator)
|
/// Session ID of the sender (initiator)
|
||||||
pub sid: [u8; 4],
|
pub sid: [u8; 4],
|
||||||
/// Encrypted cookie with authenticated initiator `mac`
|
/// Encrypted cookie with authenticated initiator `mac`
|
||||||
pub cookie_encrypted: [u8; xaead::NONCE_LEN + COOKIE_SIZE + xaead::TAG_LEN],
|
pub cookie_encrypted: [u8; XAead::NONCE_LEN + COOKIE_SIZE + XAead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specialized message for use in the cookie mechanism.
|
/// Specialized message for use in the cookie mechanism.
|
||||||
@@ -437,7 +436,8 @@ impl From<MsgType> for u8 {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_constants {
|
mod test_constants {
|
||||||
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
|
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
|
||||||
use rosenpass_ciphers::{xaead, KEY_LEN};
|
use rosenpass_cipher_traits::primitives::Aead as _;
|
||||||
|
use rosenpass_ciphers::{XAead, KEY_LEN};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sodium_keysize() {
|
fn sodium_keysize() {
|
||||||
@@ -453,7 +453,7 @@ mod test_constants {
|
|||||||
fn biscuit_ct_len() {
|
fn biscuit_ct_len() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BISCUIT_CT_LEN,
|
BISCUIT_CT_LEN,
|
||||||
BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN
|
BISCUIT_PT_LEN + XAead::NONCE_LEN + XAead::TAG_LEN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
|
||||||
|
use crate::config::ProtocolVersion;
|
||||||
use rosenpass_util::{
|
use rosenpass_util::{
|
||||||
build::Build,
|
build::Build,
|
||||||
mem::{DiscardResultExt, SwapWithDefaultExt},
|
mem::{DiscardResultExt, SwapWithDefaultExt},
|
||||||
@@ -5,32 +7,100 @@ use rosenpass_util::{
|
|||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// A pair of matching public/secret keys used to launch the crypto server.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Decomposing a key pair into its individual components, then recreating it:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rosenpass::protocol::Keypair;
|
||||||
|
///
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// let random_pair = Keypair::random();
|
||||||
|
/// let random_copy = random_pair.clone();
|
||||||
|
/// let (sk_copy, pk_copy) = random_copy.into_parts();
|
||||||
|
///
|
||||||
|
/// // Re-assemble the key pair from the original secret/public key
|
||||||
|
/// // Note that it doesn't have to be the exact same keys;
|
||||||
|
/// // you could just as easily use a completely different pair here
|
||||||
|
/// let reconstructed_pair = Keypair::from_parts((sk_copy, pk_copy));
|
||||||
|
///
|
||||||
|
/// assert_eq!(random_pair.sk.secret(), reconstructed_pair.sk.secret());
|
||||||
|
/// assert_eq!(random_pair.pk, reconstructed_pair.pk);
|
||||||
|
/// ```
|
||||||
pub struct Keypair {
|
pub struct Keypair {
|
||||||
|
/// Secret key matching the crypto server's public key.
|
||||||
pub sk: SSk,
|
pub sk: SSk,
|
||||||
|
/// Public key identifying the crypto server instance.
|
||||||
pub pk: SPk,
|
pub pk: SPk,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We need a named tuple derive
|
// TODO: We need a named tuple derive
|
||||||
impl Keypair {
|
impl Keypair {
|
||||||
|
/// Creates a new key pair from the given secret/public key components.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rosenpass::protocol::{Keypair, SSk, SPk};
|
||||||
|
///
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// let random_sk = SSk::random();
|
||||||
|
/// let random_pk = SPk::random();
|
||||||
|
/// let random_pair = Keypair::new(random_sk.clone(), random_pk.clone());
|
||||||
|
///
|
||||||
|
/// assert_eq!(random_sk.secret(), random_pair.sk.secret());
|
||||||
|
/// assert_eq!(random_pk, random_pair.pk);
|
||||||
|
/// ```
|
||||||
pub fn new(sk: SSk, pk: SPk) -> Self {
|
pub fn new(sk: SSk, pk: SPk) -> Self {
|
||||||
Self { sk, pk }
|
Self { sk, pk }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new "empty" key pair. All bytes are initialized to zero.
|
||||||
|
///
|
||||||
|
/// See [SSk:zero()][crate::protocol::SSk::zero] and [SPk:zero()][crate::protocol::SPk::zero], respectively.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rosenpass::protocol::{Keypair, SSk, SPk};
|
||||||
|
///
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// let zero_sk = SSk::zero();
|
||||||
|
/// let zero_pk = SPk::zero();
|
||||||
|
/// let zero_pair = Keypair::zero();
|
||||||
|
///
|
||||||
|
/// assert_eq!(zero_sk.secret(), zero_pair.sk.secret());
|
||||||
|
/// assert_eq!(zero_pk, zero_pair.pk);
|
||||||
|
/// ```
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self::new(SSk::zero(), SPk::zero())
|
Self::new(SSk::zero(), SPk::zero())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new (securely-)random key pair. The mechanism is described in [rosenpass_secret_memory::Secret].
|
||||||
|
///
|
||||||
|
/// See [SSk:random()][crate::protocol::SSk::random] and [SPk:random()][crate::protocol::SPk::random], respectively.
|
||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
Self::new(SSk::random(), SPk::random())
|
Self::new(SSk::random(), SPk::random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new key pair from the given public/secret key components.
|
||||||
pub fn from_parts(parts: (SSk, SPk)) -> Self {
|
pub fn from_parts(parts: (SSk, SPk)) -> Self {
|
||||||
Self::new(parts.0, parts.1)
|
Self::new(parts.0, parts.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deconstructs the key pair, yielding the individual public/secret key components.
|
||||||
pub fn into_parts(self) -> (SSk, SPk) {
|
pub fn into_parts(self) -> (SSk, SPk) {
|
||||||
(self.sk, self.pk)
|
(self.sk, self.pk)
|
||||||
}
|
}
|
||||||
@@ -38,25 +108,78 @@ impl Keypair {
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("PSK already set in BuildCryptoServer")]
|
#[error("PSK already set in BuildCryptoServer")]
|
||||||
|
/// Error indicating that the PSK is already set.
|
||||||
|
/// Unused in the current version of the protocol.
|
||||||
pub struct PskAlreadySet;
|
pub struct PskAlreadySet;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Keypair already set in BuildCryptoServer")]
|
#[error("Keypair already set in BuildCryptoServer")]
|
||||||
|
/// Error type indicating that the public/secret key pair has already been set.
|
||||||
pub struct KeypairAlreadySet;
|
pub struct KeypairAlreadySet;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Can not construct CryptoServer: Missing keypair")]
|
#[error("Can not construct CryptoServer: Missing keypair")]
|
||||||
|
/// Error type indicating that no public/secret key pair has been provided.
|
||||||
pub struct MissingKeypair;
|
pub struct MissingKeypair;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
/// Builder for setting up a [CryptoServer] (with deferred initialization).
|
||||||
|
///
|
||||||
|
/// There are multiple ways of creating a crypto server:
|
||||||
|
///
|
||||||
|
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][crate::protocol::CryptoServer::new])
|
||||||
|
/// 2. Provide the key pair at a later time (using [BuildCryptoServer::empty])
|
||||||
|
///
|
||||||
|
/// With BuildCryptoServer, you can gradually configure parameters as they become available.
|
||||||
|
/// This may be useful when they depend on runtime conditions or have to be fetched asynchronously.
|
||||||
|
/// It's possible to use the builder multiple times; it then serves as a "blueprint" for new
|
||||||
|
/// instances, several of which may be spawned with the same base configuration (or variations thereof).
|
||||||
|
///
|
||||||
|
/// Note that the server won't actually launch without a key pair (expect a [MissingKeypair] error).
|
||||||
|
/// The setup will be much simplified if one is provided, at the cost of some flexibility.
|
||||||
|
/// It's however possible to defer this step in case your application requires it.
|
||||||
|
///
|
||||||
|
/// For additional details or examples, see [AppServer::crypto_site][crate::app_server::AppServer::crypto_site] and [ConstructionSite][rosenpass_util::build::ConstructionSite].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rosenpass_util::build::Build;
|
||||||
|
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
|
||||||
|
/// use rosenpass::config::ProtocolVersion;
|
||||||
|
///
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// let keypair = Keypair::random();
|
||||||
|
/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random(), protocol_version: ProtocolVersion::V02 };
|
||||||
|
/// let peer2 = PeerParams { psk: None, pk: SPk::random(), protocol_version: ProtocolVersion::V02 };
|
||||||
|
///
|
||||||
|
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]);
|
||||||
|
/// builder.add_peer(peer2.psk.clone(), peer2.pk, ProtocolVersion::V02);
|
||||||
|
///
|
||||||
|
/// let server = builder.build().expect("build failed");
|
||||||
|
/// assert_eq!(server.peers.len(), 2);
|
||||||
|
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
|
||||||
|
/// assert_eq!(server.spkm, keypair.pk);
|
||||||
|
/// ```
|
||||||
pub struct BuildCryptoServer {
|
pub struct BuildCryptoServer {
|
||||||
|
/// The key pair (secret/public key) identifying the crypto server instance.
|
||||||
pub keypair: Option<Keypair>,
|
pub keypair: Option<Keypair>,
|
||||||
|
/// A list of network peers that should be registered when launching the server.
|
||||||
pub peers: Vec<PeerParams>,
|
pub peers: Vec<PeerParams>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Build<CryptoServer> for BuildCryptoServer {
|
impl Build<CryptoServer> for BuildCryptoServer {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
/// Creates a crypto server, adding all peers that have previously been registered.
|
||||||
|
///
|
||||||
|
/// You must provide a key pair at the time of instantiation.
|
||||||
|
/// If the list of peers is outdated, building the server will fail.
|
||||||
|
///
|
||||||
|
/// In this case, make sure to remove or re-add any peers that may have changed.
|
||||||
fn build(self) -> Result<CryptoServer, Self::Error> {
|
fn build(self) -> Result<CryptoServer, Self::Error> {
|
||||||
let Some(Keypair { sk, pk }) = self.keypair else {
|
let Some(Keypair { sk, pk }) = self.keypair else {
|
||||||
return Err(MissingKeypair)?;
|
return Err(MissingKeypair)?;
|
||||||
@@ -64,8 +187,16 @@ impl Build<CryptoServer> for BuildCryptoServer {
|
|||||||
|
|
||||||
let mut srv = CryptoServer::new(sk, pk);
|
let mut srv = CryptoServer::new(sk, pk);
|
||||||
|
|
||||||
for (idx, PeerParams { psk, pk }) in self.peers.into_iter().enumerate() {
|
for (
|
||||||
let PeerPtr(idx2) = srv.add_peer(psk, pk)?;
|
idx,
|
||||||
|
PeerParams {
|
||||||
|
psk,
|
||||||
|
pk,
|
||||||
|
protocol_version,
|
||||||
|
},
|
||||||
|
) in self.peers.into_iter().enumerate()
|
||||||
|
{
|
||||||
|
let PeerPtr(idx2) = srv.add_peer(psk, pk, protocol_version.into())?;
|
||||||
assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.")
|
assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,20 +205,34 @@ impl Build<CryptoServer> for BuildCryptoServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// Cryptographic key(s) identifying the connected [peer][crate::protocol::Peer] ("client")
|
||||||
|
/// for a given session that is being managed by the crypto server.
|
||||||
|
///
|
||||||
|
/// Each peer must be identified by a [public key (SPk)][crate::protocol::SPk].
|
||||||
|
/// Optionally, a [symmetric key (SymKey)][crate::protocol::SymKey]
|
||||||
|
/// can be provided when setting up the connection.
|
||||||
|
/// For more information on the intended usage and security considerations, see [Peer::psk][crate::protocol::Peer::psk] and [Peer::spkt][crate::protocol::Peer::spkt].
|
||||||
pub struct PeerParams {
|
pub struct PeerParams {
|
||||||
|
/// Pre-shared (symmetric) encryption keys that should be used with this peer.
|
||||||
pub psk: Option<SymKey>,
|
pub psk: Option<SymKey>,
|
||||||
|
/// Public key identifying the peer.
|
||||||
pub pk: SPk,
|
pub pk: SPk,
|
||||||
|
/// The used protocol version.
|
||||||
|
pub protocol_version: ProtocolVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildCryptoServer {
|
impl BuildCryptoServer {
|
||||||
|
/// Creates a new builder instance using the given key pair and peer list.
|
||||||
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
|
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
|
||||||
Self { keypair, peers }
|
Self { keypair, peers }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an "incomplete" builder instance, without assigning a key pair.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::new(None, Vec::new())
|
Self::new(None, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a builder instance from the given key pair and peer list components.
|
||||||
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
|
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
keypair: parts.0,
|
keypair: parts.0,
|
||||||
@@ -95,32 +240,182 @@ impl BuildCryptoServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deconstructs the current builder instance, taking ownership of its key pair and peer list.
|
||||||
|
///
|
||||||
|
/// Replaces all parameters with their default values, which allows extracting them
|
||||||
|
/// while leaving the builder in a reusable state.
|
||||||
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||||
(self.keypair.take(), self.peers.swap_with_default())
|
(self.keypair.take(), self.peers.swap_with_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deconstructs the builder instance, yielding the assigned key pair and peer list.
|
||||||
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||||
self.take_parts()
|
self.take_parts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new builder instance, assigning the given keypair to it.
|
||||||
|
///
|
||||||
|
/// Note that only one key pair can be assigned (expect [KeypairAlreadySet] on failure).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ## Adding key pairs to an existing builder
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// use rosenpass_util::build::Build;
|
||||||
|
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
|
||||||
|
///
|
||||||
|
/// // Deferred initialization: Create builder first, add the key pair later
|
||||||
|
/// let mut builder = BuildCryptoServer::empty();
|
||||||
|
/// // Do something with the builder ...
|
||||||
|
///
|
||||||
|
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
|
||||||
|
/// // Now we've got a key pair that should be added to the configuration
|
||||||
|
/// let keypair = Keypair::random();
|
||||||
|
/// builder.with_keypair(keypair.clone()).expect("build with key pair failed");
|
||||||
|
///
|
||||||
|
/// // New server instances can now make use of the assigned key pair
|
||||||
|
/// let server = builder.build().expect("build failed");
|
||||||
|
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
|
||||||
|
/// assert_eq!(server.spkm, keypair.pk);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Basic error handling: Re-assigning key pairs
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// use rosenpass_util::build::Build;
|
||||||
|
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, KeypairAlreadySet};
|
||||||
|
///
|
||||||
|
/// // In this case, we'll create a functional builder from its various components
|
||||||
|
/// // These could be salvaged from another builder, or obtained from disk/network (etc.)
|
||||||
|
/// let keypair = Keypair::random();
|
||||||
|
/// let mut builder = BuildCryptoServer::from_parts((Some(keypair.clone()), Vec::new()));
|
||||||
|
///
|
||||||
|
/// // The builder has already been assigned a key pair, so this won't work
|
||||||
|
/// let err = builder.with_keypair(keypair).expect_err("should fail to reassign key pair");
|
||||||
|
/// assert!(matches!(err, KeypairAlreadySet));
|
||||||
|
/// ```
|
||||||
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
|
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
|
||||||
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
|
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
|
||||||
self.keypair.insert(keypair).discard_result();
|
self.keypair.insert(keypair).discard_result();
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self {
|
/// Creates a new builder instance, adding a new entry to the list of registered peers.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Adding peers to an existing builder:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rosenpass::config::ProtocolVersion;
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// use rosenpass_util::build::Build;
|
||||||
|
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
|
||||||
|
///
|
||||||
|
/// // Deferred initialization: Create builder first, add some peers later
|
||||||
|
/// let keypair_option = Some(Keypair::random());
|
||||||
|
/// let mut builder = BuildCryptoServer::new(keypair_option, Vec::new());
|
||||||
|
/// assert!(builder.peers.is_empty());
|
||||||
|
///
|
||||||
|
/// // Do something with the builder ...
|
||||||
|
///
|
||||||
|
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
|
||||||
|
/// // Now we've found a peer that should be added to the configuration
|
||||||
|
/// let pre_shared_key = SymKey::random();
|
||||||
|
/// let public_key = SPk::random();
|
||||||
|
/// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone(), ProtocolVersion::V02);
|
||||||
|
///
|
||||||
|
/// // New server instances will then start with the peer being registered already
|
||||||
|
/// let server = builder.build().expect("build failed");
|
||||||
|
/// assert_eq!(server.peers.len(), 1);
|
||||||
|
/// let peer = &server.peers[0];
|
||||||
|
/// let peer_psk = Some(peer.psk.clone()).expect("PSK is None");
|
||||||
|
/// assert_eq!(peer.spkt, public_key);
|
||||||
|
/// assert_eq!(peer_psk.secret(), pre_shared_key.secret());
|
||||||
|
/// ```
|
||||||
|
pub fn with_added_peer(
|
||||||
|
&mut self,
|
||||||
|
psk: Option<SymKey>,
|
||||||
|
pk: SPk,
|
||||||
|
protocol_version: ProtocolVersion,
|
||||||
|
) -> &mut Self {
|
||||||
// TODO: Check here already whether peer was already added
|
// TODO: Check here already whether peer was already added
|
||||||
self.peers.push(PeerParams { psk, pk });
|
self.peers.push(PeerParams {
|
||||||
|
psk,
|
||||||
|
pk,
|
||||||
|
protocol_version,
|
||||||
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr {
|
/// Add a new entry to the list of registered peers, with or without a pre-shared key.
|
||||||
|
pub fn add_peer(
|
||||||
|
&mut self,
|
||||||
|
psk: Option<SymKey>,
|
||||||
|
pk: SPk,
|
||||||
|
protocol_version: ProtocolVersion,
|
||||||
|
) -> PeerPtr {
|
||||||
let id = PeerPtr(self.peers.len());
|
let id = PeerPtr(self.peers.len());
|
||||||
self.with_added_peer(psk, pk);
|
self.with_added_peer(psk, pk, protocol_version);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new builder, taking ownership of another instance's key pair and peer list.
|
||||||
|
/// Allows duplicating the current set of launch parameters, which can then be used to
|
||||||
|
/// start multiple servers with the exact same configuration (or variants using it as a base).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Extracting the server configuration from a builder:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // We have to define the security policy before using Secrets.
|
||||||
|
/// use rosenpass::config::ProtocolVersion;
|
||||||
|
/// use rosenpass::hash_domains::protocol;
|
||||||
|
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||||
|
/// secret_policy_use_only_malloc_secrets();
|
||||||
|
///
|
||||||
|
/// use rosenpass_util::build::Build;
|
||||||
|
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
|
||||||
|
///
|
||||||
|
/// let keypair = Keypair::random();
|
||||||
|
/// let peer_pk = SPk::random();
|
||||||
|
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]);
|
||||||
|
/// builder.add_peer(None, peer_pk, ProtocolVersion::V02);
|
||||||
|
///
|
||||||
|
/// // Extract configuration parameters from the decomissioned builder
|
||||||
|
/// let (keypair_option, peers) = builder.take_parts();
|
||||||
|
/// let extracted_keypair = keypair_option.unwrap();
|
||||||
|
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
|
||||||
|
/// assert_eq!(extracted_keypair.pk, keypair.pk);
|
||||||
|
/// assert_eq!(peers.len(), 1);
|
||||||
|
///
|
||||||
|
/// // Now we can create a new builder with the same configuration
|
||||||
|
/// let parts = (Some(extracted_keypair), peers);
|
||||||
|
/// let mut reassembled_builder = BuildCryptoServer::from_parts(parts);
|
||||||
|
/// let new_builder = reassembled_builder.emancipate();
|
||||||
|
///
|
||||||
|
/// // Do something with the new builder ...
|
||||||
|
///
|
||||||
|
/// // ... and now, deconstruct this one as well - still using the same parts
|
||||||
|
/// let (keypair_option, peers) = new_builder.into_parts();
|
||||||
|
/// let extracted_keypair = keypair_option.unwrap();
|
||||||
|
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
|
||||||
|
/// assert_eq!(extracted_keypair.pk, keypair.pk);
|
||||||
|
/// assert_eq!(peers.len(), 1);
|
||||||
|
/// ```
|
||||||
pub fn emancipate(&mut self) -> Self {
|
pub fn emancipate(&mut self) -> Self {
|
||||||
Self::from_parts(self.take_parts())
|
Self::from_parts(self.take_parts())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,23 +25,24 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//! use std::ops::DerefMut;
|
//! use std::ops::DerefMut;
|
||||||
//! use rosenpass_secret_memory::policy::*;
|
//! use rosenpass_secret_memory::policy::*;
|
||||||
//! use rosenpass_cipher_traits::Kem;
|
//! use rosenpass_cipher_traits::primitives::Kem;
|
||||||
//! use rosenpass_ciphers::kem::StaticKem;
|
//! use rosenpass_ciphers::StaticKem;
|
||||||
//! use rosenpass::{
|
//! use rosenpass::{
|
||||||
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
|
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
|
||||||
//! };
|
//! };
|
||||||
//! # fn main() -> anyhow::Result<()> {
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
//! // Set security policy for storing secrets
|
//! // Set security policy for storing secrets
|
||||||
//!
|
//!
|
||||||
|
//! use rosenpass::protocol::ProtocolVersion;
|
||||||
//! secret_policy_try_use_memfd_secrets();
|
//! secret_policy_try_use_memfd_secrets();
|
||||||
//!
|
//!
|
||||||
//! // initialize secret and public key for peer a ...
|
//! // initialize secret and public key for peer a ...
|
||||||
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||||
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
//! StaticKem.keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||||
//!
|
//!
|
||||||
//! // ... and for peer b
|
//! // ... and for peer b
|
||||||
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||||
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
//! StaticKem.keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||||
//!
|
//!
|
||||||
//! // initialize server and a pre-shared key
|
//! // initialize server and a pre-shared key
|
||||||
//! let psk = SymKey::random();
|
//! let psk = SymKey::random();
|
||||||
@@ -49,8 +50,8 @@
|
|||||||
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
||||||
//!
|
//!
|
||||||
//! // introduce peers to each other
|
//! // introduce peers to each other
|
||||||
//! a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
//! a.add_peer(Some(psk.clone()), peer_b_pk, ProtocolVersion::V03)?;
|
||||||
//! b.add_peer(Some(psk), peer_a_pk)?;
|
//! b.add_peer(Some(psk), peer_a_pk, ProtocolVersion::V03)?;
|
||||||
//!
|
//!
|
||||||
//! // declare buffers for message exchange
|
//! // declare buffers for message exchange
|
||||||
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
|
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@ use rosenpass::api::{
|
|||||||
self, add_listen_socket_response_status, add_psk_broker_response_status,
|
self, add_listen_socket_response_status, add_psk_broker_response_status,
|
||||||
supply_keypair_response_status,
|
supply_keypair_response_status,
|
||||||
};
|
};
|
||||||
|
use rosenpass::config::ProtocolVersion;
|
||||||
|
use rosenpass::protocol::SymKey;
|
||||||
use rosenpass_util::{
|
use rosenpass_util::{
|
||||||
b64::B64Display,
|
b64::B64Display,
|
||||||
file::LoadValueB64,
|
file::LoadValueB64,
|
||||||
@@ -27,8 +29,6 @@ use std::os::fd::{AsFd, AsRawFd};
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use zerocopy::AsBytes;
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
use rosenpass::protocol::SymKey;
|
|
||||||
|
|
||||||
struct KillChild(std::process::Child);
|
struct KillChild(std::process::Child);
|
||||||
|
|
||||||
impl Drop for KillChild {
|
impl Drop for KillChild {
|
||||||
@@ -39,7 +39,7 @@ impl Drop for KillChild {
|
|||||||
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||||
// so the signal is absorbed
|
// so the signal is absorbed
|
||||||
loop {
|
loop {
|
||||||
rustix::process::kill_process(pid, Term).discard_result();
|
kill_process(pid, Term).discard_result();
|
||||||
if self.0.try_wait().unwrap().is_some() {
|
if self.0.try_wait().unwrap().is_some() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,16 @@ impl Drop for KillChild {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn api_integration_api_setup() -> anyhow::Result<()> {
|
fn api_integration_api_setup_v02() -> anyhow::Result<()> {
|
||||||
|
api_integration_api_setup(ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn api_integration_api_setup_v03() -> anyhow::Result<()> {
|
||||||
|
api_integration_api_setup(ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_integration_api_setup(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
|
||||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
||||||
@@ -96,6 +105,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> {
|
|||||||
peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()),
|
peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()),
|
||||||
extra_params: vec![],
|
extra_params: vec![],
|
||||||
}),
|
}),
|
||||||
|
protocol_version: protocol_version.clone(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,6 +126,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> {
|
|||||||
endpoint: Some(peer_a_endpoint.to_owned()),
|
endpoint: Some(peer_a_endpoint.to_owned()),
|
||||||
pre_shared_key: None,
|
pre_shared_key: None,
|
||||||
wg: None,
|
wg: None,
|
||||||
|
protocol_version: protocol_version.clone(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,19 +16,37 @@ use rosenpass_util::{mem::DiscardResultExt, zerocopy::ZerocopySliceExt};
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use zerocopy::AsBytes;
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
|
use rosenpass::config::ProtocolVersion;
|
||||||
use rosenpass::protocol::SymKey;
|
use rosenpass::protocol::SymKey;
|
||||||
|
|
||||||
struct KillChild(std::process::Child);
|
struct KillChild(std::process::Child);
|
||||||
|
|
||||||
impl Drop for KillChild {
|
impl Drop for KillChild {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.0.kill().discard_result();
|
use rustix::process::{kill_process, Pid, Signal::Term};
|
||||||
self.0.wait().discard_result()
|
let pid = Pid::from_child(&self.0);
|
||||||
|
// We seriously need to start handling signals with signalfd, our current signal handling
|
||||||
|
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||||
|
// so the signal is absorbed
|
||||||
|
loop {
|
||||||
|
kill_process(pid, Term).discard_result();
|
||||||
|
if self.0.try_wait().unwrap().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn api_integration_test() -> anyhow::Result<()> {
|
fn api_integration_test_v02() -> anyhow::Result<()> {
|
||||||
|
api_integration_test(ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_integration_test_v03() -> anyhow::Result<()> {
|
||||||
|
api_integration_test(ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_integration_test(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
|
||||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
|
||||||
@@ -64,6 +82,7 @@ fn api_integration_test() -> anyhow::Result<()> {
|
|||||||
endpoint: None,
|
endpoint: None,
|
||||||
pre_shared_key: None,
|
pre_shared_key: None,
|
||||||
wg: None,
|
wg: None,
|
||||||
|
protocol_version: protocol_version.clone(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,6 +103,7 @@ fn api_integration_test() -> anyhow::Result<()> {
|
|||||||
endpoint: Some(peer_a_endpoint.to_owned()),
|
endpoint: Some(peer_a_endpoint.to_owned()),
|
||||||
pre_shared_key: None,
|
pre_shared_key: None,
|
||||||
wg: None,
|
wg: None,
|
||||||
|
protocol_version: protocol_version.clone(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
143
rosenpass/tests/app_server_example.rs
Normal file
143
rosenpass/tests/app_server_example.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
ops::DerefMut,
|
||||||
|
str::FromStr,
|
||||||
|
sync::mpsc,
|
||||||
|
thread::{self, sleep},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rosenpass::config::ProtocolVersion;
|
||||||
|
use rosenpass::{
|
||||||
|
app_server::{AppServer, AppServerTest, MAX_B64_KEY_SIZE},
|
||||||
|
protocol::{SPk, SSk, SymKey},
|
||||||
|
};
|
||||||
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
|
use rosenpass_ciphers::StaticKem;
|
||||||
|
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_exchange_with_app_server_v02() -> anyhow::Result<()> {
|
||||||
|
key_exchange_with_app_server(ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_exchange_with_app_server_v03() -> anyhow::Result<()> {
|
||||||
|
key_exchange_with_app_server(ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_exchange_with_app_server(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
let outfile_a = tmpdir.path().join("osk_a");
|
||||||
|
let outfile_b = tmpdir.path().join("osk_b");
|
||||||
|
|
||||||
|
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||||
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
|
// Introduce the servers to each other
|
||||||
|
let psk_a = SymKey::random();
|
||||||
|
let psk_b = psk_a.clone();
|
||||||
|
|
||||||
|
let (tx_a, rx_b) = mpsc::sync_channel(1);
|
||||||
|
let (tx_b, rx_a) = mpsc::sync_channel(1);
|
||||||
|
|
||||||
|
let (tx_term_a, rx_term_a) = mpsc::channel();
|
||||||
|
let (tx_term_b, rx_term_b) = mpsc::channel();
|
||||||
|
|
||||||
|
let configs = [
|
||||||
|
(false, outfile_a.clone(), psk_a, tx_a, rx_a, rx_term_a),
|
||||||
|
(true, outfile_b.clone(), psk_b, tx_b, rx_b, rx_term_b),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (is_client, osk, psk, tx, rx, rx_term) in configs {
|
||||||
|
thread::spawn(move || {
|
||||||
|
run(move || -> anyhow::Result<()> {
|
||||||
|
let mut srv = TestServer::new(rx_term)?;
|
||||||
|
|
||||||
|
tx.send((srv.loopback_port()?, srv.public_key()?.clone()))?;
|
||||||
|
let (otr_port, otr_pk) = rx.recv()?;
|
||||||
|
|
||||||
|
let psk = Some(psk);
|
||||||
|
let broker_peer = None;
|
||||||
|
let pk = otr_pk;
|
||||||
|
let outfile = Some(osk);
|
||||||
|
let port = otr_port;
|
||||||
|
let hostname = is_client.then(|| format!("[::1]:{port}"));
|
||||||
|
srv.app_srv.add_peer(
|
||||||
|
psk,
|
||||||
|
pk,
|
||||||
|
outfile,
|
||||||
|
broker_peer,
|
||||||
|
hostname,
|
||||||
|
protocol_version.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
srv.app_srv.event_loop()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busy wait for both keys to be exchanged
|
||||||
|
let mut successful_exchange = false;
|
||||||
|
for _ in 0..2000 {
|
||||||
|
// 40s
|
||||||
|
sleep(Duration::from_millis(20));
|
||||||
|
run(|| -> anyhow::Result<()> {
|
||||||
|
let osk_a = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_a)?;
|
||||||
|
let osk_b = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_b)?;
|
||||||
|
successful_exchange = rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret());
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.discard_result();
|
||||||
|
if successful_exchange {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the parties to terminate
|
||||||
|
tx_term_a.send(())?;
|
||||||
|
tx_term_b.send(())?;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
successful_exchange,
|
||||||
|
"Test did not complete successfully within the deadline"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestServer {
|
||||||
|
app_srv: AppServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestServer {
|
||||||
|
fn new(termination_queue: mpsc::Receiver<()>) -> anyhow::Result<Self> {
|
||||||
|
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||||
|
StaticKem.keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||||
|
|
||||||
|
let keypair = Some((sk, pk));
|
||||||
|
let addrs = vec![
|
||||||
|
SocketAddr::from_str("[::1]:0")?, // Localhost, any port. For connecting to the test server.
|
||||||
|
// ipv4_any_binding(), // any IPv4 interface
|
||||||
|
// ipv6_any_binding(), // any IPv6 interface
|
||||||
|
];
|
||||||
|
let verbosity = rosenpass::config::Verbosity::Verbose;
|
||||||
|
let test_helpers = Some(AppServerTest {
|
||||||
|
enable_dos_permanently: false,
|
||||||
|
termination_handler: Some(termination_queue),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app_srv = AppServer::new(keypair, addrs, verbosity, test_helpers)?;
|
||||||
|
|
||||||
|
Self { app_srv }.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loopback_port(&self) -> anyhow::Result<u16> {
|
||||||
|
self.app_srv.sockets[0].local_addr()?.port().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_key(&self) -> anyhow::Result<&SPk> {
|
||||||
|
Ok(&self.app_srv.crypto_server()?.spkm)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use rosenpass::config::Rosenpass;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_Rosenpass_add_if_any_example() {
|
||||||
|
let mut v = Rosenpass::empty();
|
||||||
|
v.add_if_any(4000);
|
||||||
|
|
||||||
|
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
|
||||||
|
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
|
||||||
|
}
|
||||||
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use rosenpass::config::{Keypair, Rosenpass};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_config_rosenpass_new() {
|
||||||
|
let (sk, pk) = ("./example.sk", "./example.pk");
|
||||||
|
|
||||||
|
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
|
||||||
|
assert_eq!(Rosenpass::empty(), Rosenpass::default());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Rosenpass::from_sk_pk(sk, pk),
|
||||||
|
Rosenpass::new(Some(Keypair::new(pk, sk)))
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut v = Rosenpass::empty();
|
||||||
|
v.keypair = Some(Keypair::new(pk, sk));
|
||||||
|
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
|
||||||
|
}
|
||||||
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use std::{
|
||||||
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_simple() {
|
||||||
|
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
|
||||||
|
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||||
|
peer.test:9999 outfile /peer/rp-out";
|
||||||
|
let argv = argv.split(' ').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
let config = Rosenpass::parse_args(argv).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.keypair,
|
||||||
|
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||||
|
);
|
||||||
|
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||||
|
assert_eq!(
|
||||||
|
&config.listen,
|
||||||
|
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.peers,
|
||||||
|
vec![RosenpassPeer {
|
||||||
|
public_key: PathBuf::from("/peer/public-key"),
|
||||||
|
endpoint: Some("peer.test:9999".into()),
|
||||||
|
pre_shared_key: None,
|
||||||
|
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||||
|
..Default::default()
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use rosenpass::config::{Rosenpass, Verbosity};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_config_rosenpass_store() -> anyhow::Result<()> {
|
||||||
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let sk = tmpdir.path().join("example.sk");
|
||||||
|
let pk = tmpdir.path().join("example.pk");
|
||||||
|
let cfg = tmpdir.path().join("config.toml");
|
||||||
|
|
||||||
|
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
|
||||||
|
|
||||||
|
// Can not commit config, path not known
|
||||||
|
assert!(c.commit().is_err());
|
||||||
|
|
||||||
|
// We can store it to an explicit path though
|
||||||
|
c.store(&cfg)?;
|
||||||
|
|
||||||
|
// Storing does not set commitment path
|
||||||
|
assert!(c.commit().is_err());
|
||||||
|
|
||||||
|
// We can reload the config now and the configurations
|
||||||
|
// are equal if we adjust the commitment path
|
||||||
|
let mut c2 = Rosenpass::load(&cfg)?;
|
||||||
|
c.config_file_path = PathBuf::from(&cfg);
|
||||||
|
assert_eq!(c, c2);
|
||||||
|
|
||||||
|
// And this loaded config can now be committed
|
||||||
|
c2.verbosity = Verbosity::Verbose;
|
||||||
|
c2.commit()?;
|
||||||
|
|
||||||
|
// And the changes actually made it to disk
|
||||||
|
let c3 = Rosenpass::load(cfg)?;
|
||||||
|
assert_eq!(c2, c3);
|
||||||
|
assert_ne!(c, c3);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
|
||||||
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
// Empty validates OK
|
||||||
|
assert!(Rosenpass::empty().validate().is_ok());
|
||||||
|
|
||||||
|
// Missing secret key does not pass usefulness
|
||||||
|
assert!(Rosenpass::empty().check_usefullness().is_err());
|
||||||
|
|
||||||
|
let sk = tmpdir.path().join("example.sk");
|
||||||
|
let pk = tmpdir.path().join("example.pk");
|
||||||
|
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
|
||||||
|
|
||||||
|
// Missing secret key does not validate
|
||||||
|
assert!(cfg.validate().is_err());
|
||||||
|
|
||||||
|
// But passes usefulness (the configuration is useful but invalid)
|
||||||
|
assert!(cfg.check_usefullness().is_ok());
|
||||||
|
|
||||||
|
// Providing empty key files does not help
|
||||||
|
fs::write(&sk, b"")?;
|
||||||
|
fs::write(&pk, b"")?;
|
||||||
|
assert!(cfg.validate().is_err());
|
||||||
|
|
||||||
|
// But after providing proper key files, the configuration validates
|
||||||
|
generate_and_save_keypair(sk, pk)?;
|
||||||
|
assert!(cfg.validate().is_ok());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
19
rosenpass/tests/gen-ipc-msg-types.rs
Normal file
19
rosenpass/tests/gen-ipc-msg-types.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gen_ipc_msg_types() -> anyhow::Result<()> {
|
||||||
|
let out = Command::new(env!("CARGO_BIN_EXE_rosenpass-gen-ipc-msg-types")).output()?;
|
||||||
|
assert!(out.status.success());
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(out.stdout)?;
|
||||||
|
|
||||||
|
// Smoke tests only
|
||||||
|
assert!(stdout.contains("type RawMsgType = u128;"));
|
||||||
|
// For Blake2b:
|
||||||
|
assert!(stdout.contains("const SUPPLY_KEYPAIR_RESPONSE : RawMsgType = RawMsgType::from_le_bytes(hex!(\"f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9\"));"));
|
||||||
|
// For SHAKE256:
|
||||||
|
assert!(stdout.contains("const SUPPLY_KEYPAIR_RESPONSE : RawMsgType = RawMsgType::from_le_bytes(hex!(\"ff80 3886 68a4 47ce 2ae6 0915 0972 682f\"))"));
|
||||||
|
|
||||||
|
// TODO: Also test SHAKE256 here
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@ fn run_server_client_exchange(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// give them some time to do the key exchange under load
|
// give them some time to do the key exchange under load
|
||||||
std::thread::sleep(Duration::from_secs(10));
|
std::thread::sleep(Duration::from_secs(30));
|
||||||
|
|
||||||
// time's up, kill the childs
|
// time's up, kill the childs
|
||||||
server_terminate.send(()).unwrap();
|
server_terminate.send(()).unwrap();
|
||||||
@@ -251,7 +251,7 @@ fn check_exchange_under_normal() {
|
|||||||
fs::remove_dir_all(&tmpdir).unwrap();
|
fs::remove_dir_all(&tmpdir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that we can trigger a DoS condition and we can exchange keys under DoS
|
// 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.
|
// 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]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ fn main_fn_generates_manpages() -> anyhow::Result<()> {
|
|||||||
"rosenpass-exchange-config.1",
|
"rosenpass-exchange-config.1",
|
||||||
"rosenpass-gen-config.1",
|
"rosenpass-gen-config.1",
|
||||||
"rosenpass-gen-keys.1",
|
"rosenpass-gen-keys.1",
|
||||||
"rosenpass-keygen.1",
|
|
||||||
"rosenpass-validate.1",
|
"rosenpass-validate.1",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -56,7 +55,10 @@ fn main_fn_generates_manpages() -> anyhow::Result<()> {
|
|||||||
.map(|name| (name, dir.path().join(name)))
|
.map(|name| (name, dir.path().join(name)))
|
||||||
.map(|(name, path)| {
|
.map(|(name, path)| {
|
||||||
let res = std::process::Command::new("man").arg(path).output()?;
|
let res = std::process::Command::new("man").arg(path).output()?;
|
||||||
assert!(res.status.success());
|
assert!(
|
||||||
|
res.status.success(),
|
||||||
|
"Error rendering manpage {name} using man"
|
||||||
|
);
|
||||||
let body = res
|
let body = res
|
||||||
.stdout
|
.stdout
|
||||||
.apply(String::from_utf8)?
|
.apply(String::from_utf8)?
|
||||||
@@ -64,7 +66,6 @@ fn main_fn_generates_manpages() -> anyhow::Result<()> {
|
|||||||
Ok((name, body))
|
Ok((name, body))
|
||||||
})
|
})
|
||||||
.collect::<anyhow::Result<_>>()?;
|
.collect::<anyhow::Result<_>>()?;
|
||||||
|
|
||||||
for (name, body) in man_texts.iter() {
|
for (name, body) in man_texts.iter() {
|
||||||
expect_sections(body, &["NAME", "SYNOPSIS", "OPTIONS"])?;
|
expect_sections(body, &["NAME", "SYNOPSIS", "OPTIONS"])?;
|
||||||
|
|
||||||
|
|||||||
@@ -2,86 +2,109 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt::{Debug, Write},
|
ops::DerefMut,
|
||||||
ops::{DerefMut, RangeBounds},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::distributions::uniform::SampleBorrow;
|
use rosenpass_cipher_traits::primitives::Kem;
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_ciphers::StaticKem;
|
||||||
use rosenpass_ciphers::kem::StaticKem;
|
|
||||||
use rosenpass_util::result::OkExt;
|
use rosenpass_util::result::OkExt;
|
||||||
|
|
||||||
use rosenpass::protocol::{
|
use rosenpass::protocol::{
|
||||||
testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult,
|
testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult,
|
||||||
SPk, SSk, SymKey, Timing, UNENDING,
|
ProtocolVersion, SPk, SSk, SymKey, Timing, UNENDING,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Most of the utility functions in here should probably be moved to
|
// TODO: Most of the utility functions in here should probably be moved to
|
||||||
// rosenpass::protocol::testutils;
|
// rosenpass::protocol::testutils;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
|
fn test_successful_exchange_with_poll_v02() -> anyhow::Result<()> {
|
||||||
|
test_successful_exchange_with_poll(ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_successful_exchange_with_poll_v03() -> anyhow::Result<()> {
|
||||||
|
test_successful_exchange_with_poll(ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_successful_exchange_with_poll(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
|
||||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
let mut sim = RosenpassSimulator::new()?;
|
let mut sim = RosenpassSimulator::new(protocol_version)?;
|
||||||
sim.poll_loop(150)?; // Poll 75 times
|
sim.poll_loop(150)?; // Poll 75 times
|
||||||
let transcript = sim.transcript;
|
let transcript = sim.transcript;
|
||||||
|
|
||||||
let completions: Vec<_> = transcript
|
let _completions: Vec<_> = transcript
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
!completions.is_empty(),
|
!_completions.is_empty(),
|
||||||
"\
|
"\
|
||||||
Should have performed a successful key exchanged!\n\
|
Should have performed a successful key exchanged!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions[0].0 < 20.0,
|
_completions[0].0 < 60.0,
|
||||||
"\
|
"\
|
||||||
First key exchange should happen in under twenty seconds!\n\
|
First key exchange should happen in under 60 seconds!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions.len() >= 3,
|
_completions.len() >= 3,
|
||||||
"\
|
"\
|
||||||
Should have at least two renegotiations!\n\
|
Should have at least two renegotiations!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
(110.0..175.0).contains(&completions[1].0),
|
(110.0..175.0).contains(&_completions[1].0),
|
||||||
"\
|
"\
|
||||||
First renegotiation should happen in between two and three minutes!\n\
|
First renegotiation should happen in between two and three minutes!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
|
#[cfg(not(coverage))]
|
||||||
|
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
|
||||||
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
");
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
fn test_successful_exchange_under_packet_loss_v02() -> anyhow::Result<()> {
|
||||||
|
test_successful_exchange_under_packet_loss(ProtocolVersion::V02)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_successful_exchange_under_packet_loss_v03() -> anyhow::Result<()> {
|
||||||
|
test_successful_exchange_under_packet_loss(ProtocolVersion::V03)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_successful_exchange_under_packet_loss(
|
||||||
|
protocol_version: ProtocolVersion,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||||
|
|
||||||
// Create the simulator
|
// Create the simulator
|
||||||
let mut sim = RosenpassSimulator::new()?;
|
let mut sim = RosenpassSimulator::new(protocol_version)?;
|
||||||
|
|
||||||
// Make sure the servers are set to under load condition
|
// Make sure the servers are set to under load condition
|
||||||
sim.srv_a.under_load = true;
|
sim.srv_a.under_load = true;
|
||||||
@@ -96,7 +119,7 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
|||||||
event: ServerEvent::Transmit(_, _),
|
event: ServerEvent::Transmit(_, _),
|
||||||
} = ev
|
} = ev
|
||||||
{
|
{
|
||||||
// Drop every fifth package
|
// Drop every tenth package
|
||||||
if pkg_counter % 10 == 0 {
|
if pkg_counter % 10 == 0 {
|
||||||
source.drop_outgoing_packet(&mut sim);
|
source.drop_outgoing_packet(&mut sim);
|
||||||
}
|
}
|
||||||
@@ -106,48 +129,53 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let transcript = sim.transcript;
|
let transcript = sim.transcript;
|
||||||
let completions: Vec<_> = transcript
|
let _completions: Vec<_> = transcript
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
!completions.is_empty(),
|
!_completions.is_empty(),
|
||||||
"\
|
"\
|
||||||
Should have performed a successful key exchanged!\n\
|
Should have performed a successful key exchanged!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions[0].0 < 10.0,
|
_completions[0].0 < 60.0,
|
||||||
"\
|
"\
|
||||||
First key exchange should happen in under twenty seconds!\n\
|
First key exchange should happen in under 60 seconds!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
completions.len() >= 3,
|
_completions.len() >= 3,
|
||||||
"\
|
"\
|
||||||
Should have at least two renegotiations!\n\
|
Should have at least two renegotiations!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
#[cfg(not(coverage))]
|
||||||
assert!(
|
assert!(
|
||||||
(110.0..175.0).contains(&completions[1].0),
|
(110.0..175.0).contains(&_completions[1].0),
|
||||||
"\
|
"\
|
||||||
First renegotiation should happen in between two and three minutes!\n\
|
First renegotiation should happen in between two and three minutes!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
|
#[cfg(not(coverage))]
|
||||||
|
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
|
||||||
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||||
Transcript: {transcript:?}\n\
|
Transcript: {transcript:?}\n\
|
||||||
Completions: {completions:?}\
|
Completions: {_completions:?}\
|
||||||
");
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -264,21 +292,21 @@ struct SimulatorServer {
|
|||||||
|
|
||||||
impl RosenpassSimulator {
|
impl RosenpassSimulator {
|
||||||
/// Set up the simulator
|
/// Set up the simulator
|
||||||
fn new() -> anyhow::Result<Self> {
|
fn new(protocol_version: ProtocolVersion) -> anyhow::Result<Self> {
|
||||||
// Set up the first server
|
// Set up the first server
|
||||||
let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
StaticKem.keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||||
let mut srv_a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
let mut srv_a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
||||||
|
|
||||||
// …and the second server.
|
// …and the second server.
|
||||||
let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
StaticKem.keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||||
let mut srv_b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
let mut srv_b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
||||||
|
|
||||||
// Generate a PSK and introduce the Peers to each other.
|
// Generate a PSK and introduce the Peers to each other.
|
||||||
let psk = SymKey::random();
|
let psk = SymKey::random();
|
||||||
let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk, protocol_version.clone())?;
|
||||||
let peer_b = srv_b.add_peer(Some(psk), peer_a_pk)?;
|
let peer_b = srv_b.add_peer(Some(psk), peer_a_pk, protocol_version.clone())?;
|
||||||
|
|
||||||
// Set up the individual server data structures
|
// Set up the individual server data structures
|
||||||
let srv_a = SimulatorServer::new(srv_a, peer_b);
|
let srv_a = SimulatorServer::new(srv_a, peer_b);
|
||||||
@@ -306,8 +334,8 @@ impl RosenpassSimulator {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every call to poll produces one [TranscriptEvent] and
|
/// Every call to poll produces one [TranscriptEvent]
|
||||||
/// and implicitly adds it to [Self:::transcript]
|
/// and implicitly adds it to [Self::transcript]
|
||||||
fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> {
|
fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> {
|
||||||
let ev = TranscriptEvent::begin_poll()
|
let ev = TranscriptEvent::begin_poll()
|
||||||
.try_fold_with(|| self.poll_focus.poll(self))?
|
.try_fold_with(|| self.poll_focus.poll(self))?
|
||||||
@@ -482,8 +510,11 @@ impl ServerPtr {
|
|||||||
// Let the crypto server handle the message now
|
// Let the crypto server handle the message now
|
||||||
let mut tx_buf = MsgBuf::zero();
|
let mut tx_buf = MsgBuf::zero();
|
||||||
let handle_msg_result = if self.get(sim).under_load {
|
let handle_msg_result = if self.get(sim).under_load {
|
||||||
self.srv_mut(sim)
|
self.srv_mut(sim).handle_msg_under_load(
|
||||||
.handle_msg_under_load(rx_msg.borrow(), tx_buf.borrow_mut(), &self)
|
rx_msg.borrow(),
|
||||||
|
tx_buf.borrow_mut(),
|
||||||
|
&self.other(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
self.srv_mut(sim)
|
self.srv_mut(sim)
|
||||||
.handle_msg(rx_msg.borrow(), tx_buf.borrow_mut())
|
.handle_msg(rx_msg.borrow(), tx_buf.borrow_mut())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
description = "Build post-quantum-secure VPNs with WireGuard!"
|
description = "Build post-quantum-secure VPNs with WireGuard!"
|
||||||
homepage = "https://rosenpass.eu/"
|
homepage = "https://rosenpass.eu/"
|
||||||
repository = "https://github.com/rosenpass/rosenpass"
|
repository = "https://github.com/rosenpass/rosenpass"
|
||||||
|
rust-version = "1.77"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ anyhow = { workspace = true }
|
|||||||
base64ct = { workspace = true }
|
base64ct = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
x25519-dalek = { workspace = true, features = ["static_secrets"] }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
rosenpass = { workspace = true }
|
rosenpass = { workspace = true }
|
||||||
@@ -26,8 +27,8 @@ rosenpass-wireguard-broker = { workspace = true }
|
|||||||
|
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
futures-util = "0.3"
|
futures-util = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||||
ctrlc-async = "3.2"
|
ctrlc-async = "3.2"
|
||||||
@@ -43,4 +44,4 @@ stacker = { workspace = true }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_memfd_secret = []
|
experiment_memfd_secret = []
|
||||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux_all"]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user