Compare commits

..

4 Commits

Author SHA1 Message Date
Karolin Varner
caf91c84f0 chore: Remove unused warning in api integration test 2024-12-18 13:45:38 +01:00
Karolin Varner
f5b4c17011 fix: Disable asserts that rely on timing characteristics during coverage testing 2024-12-18 13:45:38 +01:00
Karolin Varner
12506e5f95 chore: Final improvements on the to crate API doc 2024-12-18 13:45:38 +01:00
David Niehues
965600212d docs+doctest(to): Add tests, examples and documentation to the to-crate 2024-12-18 13:45:38 +01:00
191 changed files with 2976 additions and 19011 deletions

View File

@@ -3,6 +3,11 @@ secret_key = "rp-a-secret-key"
listen = ["127.0.0.1:9999"] listen = ["127.0.0.1:9999"]
verbosity = "Verbose" verbosity = "Verbose"
[api]
listen_path = []
listen_fd = []
stream_fd = []
[[peers]] [[peers]]
public_key = "rp-b-public-key" public_key = "rp-b-public-key"
endpoint = "127.0.0.1:9998" endpoint = "127.0.0.1:9998"

View File

@@ -3,6 +3,11 @@ secret_key = "rp-b-secret-key"
listen = ["127.0.0.1:9998"] listen = ["127.0.0.1:9998"]
verbosity = "Verbose" verbosity = "Verbose"
[api]
listen_path = []
listen_fd = []
stream_fd = []
[[peers]] [[peers]]
public_key = "rp-a-public-key" public_key = "rp-a-public-key"
endpoint = "127.0.0.1:9999" endpoint = "127.0.0.1:9999"

View File

@@ -32,9 +32,9 @@ let systems_map = {
# aarch64-darwin # aarch64-darwin
# aarch64-linux # aarch64-linux
i686-linux: ubicloud-standard-2-ubuntu-2204, i686-linux: ubuntu-latest,
x86_64-darwin: macos-13, x86_64-darwin: macos-13,
x86_64-linux: ubicloud-standard-2-ubuntu-2204 x86_64-linux: ubuntu-latest
} }
let targets = (get-attr-names ".#packages" let targets = (get-attr-names ".#packages"
@@ -61,13 +61,14 @@ mut release_workflow = {
let runner_setup = [ let runner_setup = [
{ {
uses: "actions/checkout@v4" uses: "actions/checkout@v3"
} }
{ {
uses: "cachix/install-nix-action@v30", uses: "cachix/install-nix-action@v22",
with: { nix_path: "nixpkgs=channel:nixos-unstable" }
} }
{ {
uses: "cachix/cachix-action@v15", uses: "cachix/cachix-action@v12",
with: { with: {
name: rosenpass, name: rosenpass,
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
@@ -153,7 +154,7 @@ for system in ($targets | columns) {
} }
{ {
name: Release, name: Release,
uses: "softprops/action-gh-release@v2", uses: "softprops/action-gh-release@v1",
with: { with: {
draft: "${{ contains(github.ref_name, 'rc') }}", draft: "${{ contains(github.ref_name, 'rc') }}",
prerelease: "${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}", prerelease: "${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}",
@@ -181,7 +182,7 @@ $cachix_workflow.jobs = ($cachix_workflow.jobs | insert $"($system)---whitepaper
} }
{ {
name: "Deploy PDF artifacts", name: "Deploy PDF artifacts",
uses: "peaceiris/actions-gh-pages@v4", uses: "peaceiris/actions-gh-pages@v3",
with: { with: {
github_token: "${{ secrets.GITHUB_TOKEN }}", github_token: "${{ secrets.GITHUB_TOKEN }}",
publish_dir: result/, publish_dir: result/,

View File

@@ -1 +0,0 @@
.gitignore

View File

@@ -1,103 +0,0 @@
name: rosenpass-ciphers - primitives - benchmark
permissions:
contents: write
on:
#pull_request:
push:
env:
CARGO_TERM_COLOR: always
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
prim-benchmark:
strategy:
fail-fast: true
matrix:
system: ["x86_64-linux", "i686-linux"]
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
# Install nix
- name: Install Nix
uses: cachix/install-nix-action@v27 # A popular action for installing Nix
with:
extra_nix_config: |
experimental-features = nix-command flakes
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
# Set up environment
- name: 🛠️ Prepare Benchmark Path
env:
EVENT_NAME: ${{ github.event_name }}
BRANCH_NAME: ${{ github.ref_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
case "$EVENT_NAME" in
"push")
echo "BENCH_PATH=branch/$BRANCH_NAME" >> $GITHUB_ENV
;;
"pull_request")
echo "BENCH_PATH=pull/$PR_NUMBER" >> $GITHUB_ENV
;;
*)
echo "don't know benchmark path for event of type $EVENT_NAME, aborting"
exit 1
esac
# Benchmarks ...
- name: 🏃🏻‍♀️ Benchmarks (using Nix as shell)
working-directory: ciphers
run: nix develop ".#devShells.${{ matrix.system }}.benchmarks" --command cargo bench -F bench --bench primitives --verbose -- --output-format bencher | tee ../bench-primitives.txt
- name: Extract benchmarks
uses: cryspen/benchmark-data-extract-transform@v2
with:
name: rosenpass-ciphers primitives benchmarks
tool: "cargo"
os: ${{ matrix.system }}
output-file-path: bench-primitives.txt
data-out-path: bench-primitives-os.json
- name: Fix up 'os' label in benchmark data
run: jq 'map(with_entries(.key |= if . == "os" then "operating system" else . end))' <bench-primitives-os.json >bench-primitives.json
- name: Upload benchmarks
uses: cryspen/benchmark-upload-and-plot-action@v3
with:
name: Crypto Primitives Benchmarks
group-by: "operating system,primitive,algorithm"
schema: "operating system,primitive,algorithm,implementation,operation,length"
input-data-path: bench-primitives.json
github-token: ${{ secrets.GITHUB_TOKEN }}
# NOTE: pushes to current repository
gh-repository: github.com/${{ github.repository }}
auto-push: true
fail-on-alert: true
base-path: benchmarks/
ciphers-primitives-bench-status:
if: ${{ always() }}
needs: [prim-benchmark]
runs-on: ubuntu-latest
steps:
- name: Successful
if: ${{ !(contains(needs.*.result, 'failure')) }}
run: exit 0
- name: Failing
if: ${{ (contains(needs.*.result, 'failure')) }}
run: exit 1

View File

@@ -1,90 +0,0 @@
name: rosenpass - protocol - benchmark
permissions:
contents: write
on:
#pull_request:
push:
env:
CARGO_TERM_COLOR: always
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
proto-benchmark:
strategy:
fail-fast: true
matrix:
system: ["x86_64-linux", "i686-linux"]
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
# Install nix
- name: Install Nix
uses: cachix/install-nix-action@v27 # A popular action for installing Nix
with:
extra_nix_config: |
experimental-features = nix-command flakes
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
# Set up environment
- name: 🛠️ Prepare Benchmark Path
env:
EVENT_NAME: ${{ github.event_name }}
BRANCH_NAME: ${{ github.ref_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
case "$EVENT_NAME" in
"push")
echo "BENCH_PATH=branch/$BRANCH_NAME" >> $GITHUB_ENV
;;
"pull_request")
echo "BENCH_PATH=pull/$PR_NUMBER" >> $GITHUB_ENV
;;
*)
echo "don't know benchmark path for event of type $EVENT_NAME, aborting"
exit 1
esac
# Benchmarks ...
- name: 🏃🏻‍♀️ Benchmarks
run: nix develop ".#devShells.${{ matrix.system }}.benchmarks" --command cargo bench -p rosenpass --bench trace_handshake -F trace_bench --verbose >bench-protocol.json
- name: Upload benchmarks
uses: cryspen/benchmark-upload-and-plot-action@v3
with:
name: Protocol Benchmarks
group-by: "operating system,architecture,protocol version,run time"
schema: "operating system,architecture,protocol version,run time,name"
input-data-path: bench-protocol.json
github-token: ${{ secrets.GITHUB_TOKEN }}
# NOTE: pushes to current repository
gh-repository: github.com/${{ github.repository }}
auto-push: true
fail-on-alert: true
base-path: benchmarks/
ciphers-protocol-bench-status:
if: ${{ always() }}
needs: [proto-benchmark]
runs-on: ubuntu-latest
steps:
- name: Successful
if: ${{ !(contains(needs.*.result, 'failure')) }}
run: exit 0
- name: Failing
if: ${{ (contains(needs.*.result, 'failure')) }}
run: exit 1

View File

@@ -1,288 +0,0 @@
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 }}

View File

@@ -1,19 +0,0 @@
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

View File

@@ -1,114 +0,0 @@
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

View File

@@ -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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: needs:
- i686-linux---rosenpass - i686-linux---rosenpass
steps: steps:
@@ -65,7 +65,107 @@ 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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
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
@@ -80,7 +180,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: needs:
- x86_64-linux---rosenpass - x86_64-linux---rosenpass
steps: steps:
@@ -97,7 +197,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: needs:
- x86_64-linux---proverif-patched - x86_64-linux---proverif-patched
steps: steps:
@@ -114,7 +214,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -130,7 +230,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
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
@@ -149,7 +249,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:
# - ubicloud-standard-2-arm-ubuntu-2204 # - ubuntu-latest
# needs: # needs:
# - aarch64-linux---rosenpass-oci-image # - aarch64-linux---rosenpass-oci-image
# - aarch64-linux---rosenpass # - aarch64-linux---rosenpass
@@ -173,7 +273,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -189,12 +289,12 @@ jobs:
aarch64-linux---rosenpass: aarch64-linux---rosenpass:
name: Build aarch64-linux.rosenpass name: Build aarch64-linux.rosenpass
runs-on: runs-on:
- ubicloud-standard-2-arm-ubuntu-2204 - ubuntu-latest
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-aarch64 binfmt-support qemu-user-static sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30 - uses: cachix/install-nix-action@v30
with: with:
@@ -210,12 +310,12 @@ jobs:
aarch64-linux---rp: aarch64-linux---rp:
name: Build aarch64-linux.rp name: Build aarch64-linux.rp
runs-on: runs-on:
- ubicloud-standard-2-arm-ubuntu-2204 - ubuntu-latest
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-aarch64 binfmt-support qemu-user-static sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30 - uses: cachix/install-nix-action@v30
with: with:
@@ -231,7 +331,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: needs:
- x86_64-linux---rosenpass - x86_64-linux---rosenpass
steps: steps:
@@ -248,13 +348,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:
- ubicloud-standard-2-arm-ubuntu-2204 - ubuntu-latest
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-aarch64 binfmt-support qemu-user-static sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30 - uses: cachix/install-nix-action@v30
with: with:
@@ -270,7 +370,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -286,7 +386,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -302,7 +402,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: needs:
- x86_64-linux---rosenpass-static - x86_64-linux---rosenpass-static
steps: steps:
@@ -319,7 +419,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -335,7 +435,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:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30 - uses: cachix/install-nix-action@v30
@@ -349,7 +449,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: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' }} if: ${{ github.ref == 'refs/heads/main' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -1,32 +0,0 @@
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

View File

@@ -14,7 +14,7 @@ permissions:
jobs: jobs:
prettier: prettier:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actionsx/prettier@v3 - uses: actionsx/prettier@v3
@@ -23,38 +23,22 @@ jobs:
shellcheck: shellcheck:
name: Shellcheck name: Shellcheck
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
rustfmt: rustfmt:
name: Rust code formatting name: Rust Format
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - name: Run Rust Formatting Script
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install nightly toolchain
run: |
rustup toolchain install nightly
rustup override set nightly
- run: rustup component add rustfmt
- name: Run Cargo Fmt
run: cargo fmt --all --check
- name: Run Rust Markdown code block Formatting Script
run: bash format_rust_code.sh --mode check run: bash format_rust_code.sh --mode check
cargo-bench: cargo-bench:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - uses: actions/cache@v4
@@ -73,7 +57,7 @@ jobs:
mandoc: mandoc:
name: mandoc name: mandoc
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- name: Install mandoc - name: Install mandoc
run: sudo apt-get install -y mandoc run: sudo apt-get install -y mandoc
@@ -82,7 +66,7 @@ jobs:
run: doc/check.sh doc/rp.1 run: doc/check.sh doc/rp.1
cargo-audit: cargo-audit:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions-rs/audit-check@v1 - uses: actions-rs/audit-check@v1
@@ -90,7 +74,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
cargo-clippy: cargo-clippy:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - uses: actions/cache@v4
@@ -109,7 +93,7 @@ jobs:
args: --all-features args: --all-features
cargo-doc: cargo-doc:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - uses: actions/cache@v4
@@ -128,7 +112,12 @@ jobs:
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items - run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
cargo-test: cargo-test:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-13]
# - ubuntu is x86-64
# - macos-13 is also x86-64 architecture
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - uses: actions/cache@v4
@@ -147,7 +136,7 @@ jobs:
cargo-test-nix-devshell-x86_64-linux: cargo-test-nix-devshell-x86_64-linux:
runs-on: runs-on:
- ubicloud-standard-2-ubuntu-2204 - ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - uses: actions/cache@v4
@@ -169,7 +158,7 @@ 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: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - uses: actions/cache@v4
@@ -184,7 +173,7 @@ jobs:
- name: Install nightly toolchain - name: Install nightly toolchain
run: | run: |
rustup toolchain install nightly rustup toolchain install nightly
rustup override set nightly rustup default 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
@@ -202,24 +191,10 @@ 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: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: nightly
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/cache@v4 - run: rustup default nightly
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install nightly toolchain
run: |
rustup toolchain install nightly
rustup override set nightly
- run: rustup component add llvm-tools-preview - run: rustup component add llvm-tools-preview
- run: | - run: |
cargo install cargo-llvm-cov || true cargo install cargo-llvm-cov || true
@@ -233,4 +208,5 @@ jobs:
with: with:
files: ./target/grcov/lcov files: ./target/grcov/lcov
verbose: true verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -14,7 +14,7 @@ permissions:
jobs: jobs:
multi-peer: multi-peer:
runs-on: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
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: ubicloud-standard-2-ubuntu-2204 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: cargo build --bin rosenpass --release - run: cargo build --bin rosenpass --release

View File

@@ -13,6 +13,8 @@ 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
@@ -32,6 +34,8 @@ 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
@@ -65,24 +69,3 @@ 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/*

View File

@@ -1,177 +0,0 @@
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
- name: Install nightly toolchain
run: |
rustup toolchain install nightly
rustup override set nightly
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
key: cargo-vet-cache
- name: Install nightly toolchain
run: |
rustup toolchain install nightly
rustup override set nightly
- 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: Check which event triggered this CI run, a push or a pull request.
run: |
EVENT_NAME="${{ github.event_name }}"
IS_PR="false"
IS_PUSH="false"
if [[ "$EVENT_NAME" == "pull_request" ]]; then
echo "This CI run was triggered in the context of a pull request."
IS_PR="true"
elif [[ "$EVENT_NAME" == "push" ]]; then
echo "This CI run was triggered in the context of a push."
IS_PUSH="true"
else
echo "ERROR: This CI run was not triggered in the context of a pull request or a push. Exiting with error."
exit 1
fi
echo "IS_PR=$IS_PR" >> $GITHUB_ENV
echo "IS_PUSH=$IS_PUSH" >> $GITHUB_ENV
shell: bash
- name: Check if last commit was by Dependabot
run: |
# Depending on the trigger for, the relevant commit has to be deduced differently.
if [[ "$IS_PR" == true ]]; then
# This is the commit ID for the last commit to the head branch of the pull request.
# If we used github.sha here instead, it would point to a merge commit between the PR and the main branch, which is only created for the CI run.
SHA="${{ github.event.pull_request.head.sha }}"
REF="${{ github.head_ref }}"
elif [[ "$IS_PUSH" == "true" ]]; then
SHA="${{ github.sha }}" # This is the last commit to the branch.
REF=${GITHUB_REF#refs/heads/}
else
echo "ERROR: This action only supports pull requests and push events as triggers. Exiting with error."
exit 1
fi
echo "Commit SHA is $SHA"
echo "Branch is $REF"
echo "REF=$REF" >> $GITHUB_ENV
COMMIT_AUTHOR=$(gh api repos/${{ github.repository }}/commits/$SHA --jq .author.login) # .author.login might be null, but for dependabot it will always be there and cannot be spoofed in contrast to .commit.author.name
echo "The author of the last commit is $COMMIT_AUTHOR"
if [[ "$COMMIT_AUTHOR" == "dependabot[bot]" ]]; then
echo "The last commit was made by dependabot"
LAST_COMMIT_IS_BY_DEPENDABOT=true
else
echo "The last commit was made by $COMMIT_AUTHOR not by dependabot"
LAST_COMMIT_IS_BY_DEPENDABOT=false
fi
echo "LAST_COMMIT_IS_BY_DEPENDABOT=$LAST_COMMIT_IS_BY_DEPENDABOT" >> $GITHUB_ENV
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
- name: Check if the last commit's message ends in "--regenerate-exemptions"
run: |
# Get commit message
COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")
if [[ "$COMMIT_MESSAGE" == *"--regenerate-exemptions" ]]; then
echo "The last commit message ends in --regenerate-exemptions"
REGEN_EXEMP=true
else
echo "The last commit message does not end in --regenerate-exemptions"
REGEN_EXEMP=false
fi
echo "REGEN_EXEMP=$REGEN_EXEMP" >> $GITHUB_ENV
shell: bash
- name: Check if the CI run happens in the context of a dependabot PR # Even if a PR is created by dependabot, the last commit can, and often should be, the regeneration of the cargo vet exemptions. It could also be from an individual making manual changes.
run: |
IN_DEPENDABOT_PR_CONTEXT="false"
if [[ $IS_PR == "true" && "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then
IN_DEPENDABOT_PR_CONTEXT="true"
echo "This CI run is in the context of PR by dependabot."
else
echo "This CI run is NOT in the context of PR by dependabot."
IN_DEPENDABOT_PR_CONTEXT="false"
fi
echo "IN_DEPENDABOT_PR_CONTEXT=$IN_DEPENDABOT_PR_CONTEXT" >> $GITHUB_ENV
shell: bash
- uses: actions/checkout@v4
if: env.IN_DEPENDABOT_PR_CONTEXT == 'true'
with:
token: ${{ secrets.CI_BOT_PAT }}
- name: In case of a dependabot PR, ensure that we are not in a detached HEAD state
if: env.IN_DEPENDABOT_PR_CONTEXT == 'true'
run: |
git fetch origin $REF # ensure that we are up to date.
git switch $REF # ensure that we are NOT in a detached HEAD state. This is important for the commit action in the end
shell: bash
- name: Regenerate cargo vet exemptions if we are in the context of a PR created by dependabot and the last commit is by dependabot or a regeneration of cargo vet exemptions was explicitly requested.
if: env.IN_DEPENDABOT_PR_CONTEXT == 'true' && (env.LAST_COMMIT_IS_BY_DEPENDABOT == 'true' || env.REGEN_EXEMP=='true') # Run only for Dependabot PRs or if specifically requested
run: cargo vet regenerate exemptions
- name: Commit and push changes if we are in the context of a PR created by dependabot and the last commit is by dependabot or a regeneration of cargo vet exemptions was explicitly requested.
if: env.IN_DEPENDABOT_PR_CONTEXT == 'true' && (env.LAST_COMMIT_IS_BY_DEPENDABOT == 'true' || env.REGEN_EXEMP=='true')
uses: stefanzweifel/git-auto-commit-action@v6
with:
commit_message: Regenerate cargo vet exemptions
commit_user_name: rosenpass-ci-bot[bot]
commit_user_email: noreply@rosenpass.eu
commit_author: Rosenpass CI Bot <noreply@rosenpass.eu>
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_PAT }}
- name: Invoke cargo-vet
run: cargo vet --locked

1
.gitignore vendored
View File

@@ -25,4 +25,3 @@ _markdown_*
.vscode .vscode
/output /output
.nixos-test-history

886
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,17 @@
resolver = "2" resolver = "2"
members = [ members = [
"rosenpass", "rosenpass",
"cipher-traits", "cipher-traits",
"ciphers", "ciphers",
"util", "util",
"constant-time", "constant-time",
"oqs", "oqs",
"to", "to",
"fuzz", "fuzz",
"secret-memory", "secret-memory",
"rp", "rp",
"wireguard-broker", "wireguard-broker",
] ]
default-members = ["rosenpass", "rp", "wireguard-broker"] default-members = ["rosenpass", "rp", "wireguard-broker"]
@@ -42,45 +42,39 @@ toml = "0.7.8"
static_assertions = "1.1.0" static_assertions = "1.1.0"
allocator-api2 = "0.2.14" allocator-api2 = "0.2.14"
memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec6844125bd6612f92e9a281373df", features = [ memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec6844125bd6612f92e9a281373df", features = [
"alloc_ext", "alloc_ext",
] } ] }
rand = "0.8.5" rand = "0.8.5"
typenum = "1.17.0" typenum = "1.17.0"
log = { version = "0.4.22" } log = { version = "0.4.22" }
clap = { version = "4.5.23", features = ["derive"] } clap = { version = "4.5.23", features = ["derive"] }
clap_mangen = "0.2.29" clap_mangen = "0.2.24"
clap_complete = "4.5.40" clap_complete = "4.5.40"
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.216", features = ["derive"] }
arbitrary = { version = "1.4.1", features = ["derive"] } arbitrary = { version = "1.4.1", features = ["derive"] }
anyhow = { version = "1.0.95", features = ["backtrace", "std"] } anyhow = { version = "1.0.94", 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" # 5.11 requires rustc 1.81 home = "0.5.9"
derive_builder = "0.20.1" derive_builder = "0.20.1"
tokio = { version = "1.46", 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" }
libcrux-test-utils = { git = "https://github.com/cryspen/libcrux.git", rev = "0ab6d2dd9c1f" }
hex-literal = { version = "0.4.1" } hex-literal = { version = "0.4.1" }
hex = { version = "0.4.3" } hex = { version = "0.4.3" }
heck = { version = "0.5.0" } heck = { version = "0.5.0" }
libc = { version = "0.2" } libc = { version = "0.2" }
uds = { git = "https://github.com/rosenpass/uds" } uds = { git = "https://github.com/rosenpass/uds" }
signal-hook = "0.3.17" signal-hook = "0.3.17"
lazy_static = "1.5"
#Dev dependencies #Dev dependencies
serial_test = "3.2.0" serial_test = "3.2.0"
@@ -88,14 +82,12 @@ 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.5.1" criterion = "0.4.0"
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"] }
#Broker dependencies (might need cleanup or changes) #Broker dependencies (might need cleanup or changes)
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] } wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
command-fds = "0.2.3" command-fds = "0.2.3"
rustix = { version = "0.38.42", features = ["net", "fs", "process"] } rustix = { version = "0.38.42", features = ["net", "fs", "process"] }
futures = "0.3"
futures-util = "0.3"
x25519-dalek = "2"

View File

@@ -88,18 +88,6 @@ set verboseCompleted=VERBOSE.
#define SES_EV(...) #define SES_EV(...)
#endif #endif
#if COOKIE_EVENTS
#define COOKIE_EV(...) __VA_ARGS__
#else
#define COOKIE_EV(...)
#endif
#if KEM_EVENTS
#define KEM_EV(...) __VA_ARGS__
#else
#define KEM_EV(...)
#endif
(* TODO: Authentication timing properties *) (* TODO: Authentication timing properties *)
(* TODO: Proof that every adversary submitted package is equivalent to one generated by the proper algorithm using different coins. This probably requires introducing an oracle that extracts the coins used and explicitly adding the notion of coins used for Packet->Packet steps and an inductive RNG notion. *) (* TODO: Proof that every adversary submitted package is equivalent to one generated by the proper algorithm using different coins. This probably requires introducing an oracle that extracts the coins used and explicitly adding the notion of coins used for Packet->Packet steps and an inductive RNG notion. *)

View File

@@ -8,13 +8,10 @@ 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.0"
[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}

View File

@@ -1,137 +0,0 @@
//! 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;

View File

@@ -1,5 +1,2 @@
//! This trait contains traits, constants and wrappers that provid= the interface between Rosenpass mod kem;
//! as a consumer of cryptographic libraries and the implementations of cryptographic algorithms. pub use kem::Kem;
pub mod algorithms;
pub mod primitives;

View File

@@ -1,10 +0,0 @@
//! 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::*;

View File

@@ -1,175 +0,0 @@
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,
}

View File

@@ -1,212 +0,0 @@
//! 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,
}

View File

@@ -1,159 +0,0 @@
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
{
}

View File

@@ -8,42 +8,9 @@ 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.0"
[features] [features]
# whether the types should be defined experiment_libcrux = ["dep:libcrux"]
experiment_libcrux_define_blake2 = ["dep:libcrux-blake2", "dep:thiserror"]
experiment_libcrux_define_kyber = ["dep:libcrux-ml-kem", "dep:rand"]
experiment_libcrux_define_chachapoly = ["dep:libcrux-chacha20poly1305"]
# whether the types should be used by default
experiment_libcrux_blake2 = ["experiment_libcrux_define_blake2"]
experiment_libcrux_kyber = ["experiment_libcrux_define_kyber"]
experiment_libcrux_chachapoly = ["experiment_libcrux_define_chachapoly"]
experiment_libcrux_chachapoly_test = [
"experiment_libcrux_define_chachapoly",
"dep:libcrux",
]
# shorthands
experiment_libcrux_define_all = [
"experiment_libcrux_define_blake2",
"experiment_libcrux_define_chachapoly",
"experiment_libcrux_define_kyber",
]
experiment_libcrux_all = [
"experiment_libcrux_blake2",
"experiment_libcrux_chachapoly",
"experiment_libcrux_chachapoly_test",
"experiment_libcrux_kyber",
]
bench = ["experiment_libcrux_define_all"]
[[bench]]
name = "primitives"
harness = false
required-features = ["bench"]
[dependencies] [dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
@@ -52,22 +19,8 @@ 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 }
criterion = { workspace = true }

View File

@@ -1,378 +0,0 @@
criterion::criterion_main!(keyed_hash::benches, aead::benches, kem::benches);
fn benchid(base: KvPairs, last: KvPairs) -> String {
format!("{base},{last}")
}
#[derive(Clone, Copy, Debug)]
struct KvPair<'a>(&'a str, &'a str);
impl std::fmt::Display for KvPair<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{k}={v}", k = self.0, v = self.1)
}
}
#[derive(Clone, Copy, Debug)]
struct KvPairs<'a>(&'a [KvPair<'a>]);
impl std::fmt::Display for KvPairs<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0.len() {
0 => Ok(()),
1 => write!(f, "{}", &self.0[0]),
_ => {
let mut delim = "";
for pair in self.0 {
write!(f, "{delim}{pair}")?;
delim = ",";
}
Ok(())
}
}
}
}
mod kem {
criterion::criterion_group!(
benches,
bench_kyber512_libcrux,
bench_kyber512_oqs,
bench_classicmceliece460896_oqs
);
use criterion::Criterion;
fn bench_classicmceliece460896_oqs(c: &mut Criterion) {
template(
c,
"classicmceliece460896",
"oqs",
rosenpass_oqs::ClassicMceliece460896,
);
}
fn bench_kyber512_libcrux(c: &mut Criterion) {
template(
c,
"kyber512",
"libcrux",
rosenpass_ciphers::subtle::libcrux::kyber512::Kyber512,
);
}
fn bench_kyber512_oqs(c: &mut Criterion) {
template(c, "kyber512", "oqs", rosenpass_oqs::Kyber512);
}
use rosenpass_cipher_traits::primitives::Kem;
fn template<
const SK_LEN: usize,
const PK_LEN: usize,
const CT_LEN: usize,
const SHK_LEN: usize,
T: Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN>,
>(
c: &mut Criterion,
alg_name: &str,
impl_name: &str,
scheme: T,
) {
use super::{benchid, KvPair, KvPairs};
let base = [
KvPair("primitive", "kem"),
KvPair("algorithm", alg_name),
KvPair("implementation", impl_name),
KvPair("length", "-1"),
];
let kem_benchid = |op| benchid(KvPairs(&base), KvPairs(&[KvPair("operation", op)]));
c.bench_function(&kem_benchid("keygen"), |bench| {
let mut sk = [0; SK_LEN];
let mut pk = [0; PK_LEN];
bench.iter(|| {
scheme.keygen(&mut sk, &mut pk).unwrap();
});
});
c.bench_function(&kem_benchid("encaps"), |bench| {
let mut sk = [0; SK_LEN];
let mut pk = [0; PK_LEN];
let mut ct = [0; CT_LEN];
let mut shk = [0; SHK_LEN];
scheme.keygen(&mut sk, &mut pk).unwrap();
bench.iter(|| {
scheme.encaps(&mut shk, &mut ct, &pk).unwrap();
});
});
c.bench_function(&kem_benchid("decaps"), |bench| {
let mut sk = [0; SK_LEN];
let mut pk = [0; PK_LEN];
let mut ct = [0; CT_LEN];
let mut shk = [0; SHK_LEN];
let mut shk2 = [0; SHK_LEN];
scheme.keygen(&mut sk, &mut pk).unwrap();
scheme.encaps(&mut shk, &mut ct, &pk).unwrap();
bench.iter(|| {
scheme.decaps(&mut shk2, &sk, &ct).unwrap();
});
});
}
}
mod aead {
criterion::criterion_group!(
benches,
bench_chachapoly_libcrux,
bench_chachapoly_rustcrypto,
bench_xchachapoly_rustcrypto,
);
use criterion::Criterion;
const KEY_LEN: usize = rosenpass_ciphers::Aead::KEY_LEN;
const TAG_LEN: usize = rosenpass_ciphers::Aead::TAG_LEN;
fn bench_xchachapoly_rustcrypto(c: &mut Criterion) {
template(
c,
"xchacha20poly1305",
"rustcrypto",
rosenpass_ciphers::subtle::rust_crypto::xchacha20poly1305_ietf::XChaCha20Poly1305,
);
}
fn bench_chachapoly_rustcrypto(c: &mut Criterion) {
template(
c,
"chacha20poly1305",
"rustcrypto",
rosenpass_ciphers::subtle::rust_crypto::chacha20poly1305_ietf::ChaCha20Poly1305,
);
}
fn bench_chachapoly_libcrux(c: &mut Criterion) {
template(
c,
"chacha20poly1305",
"libcrux",
rosenpass_ciphers::subtle::libcrux::chacha20poly1305_ietf::ChaCha20Poly1305,
);
}
use rosenpass_cipher_traits::primitives::Aead;
fn template<const NONCE_LEN: usize, T: Aead<KEY_LEN, NONCE_LEN, TAG_LEN>>(
c: &mut Criterion,
alg_name: &str,
impl_name: &str,
scheme: T,
) {
use crate::{benchid, KvPair, KvPairs};
let base = [
KvPair("primitive", "aead"),
KvPair("algorithm", alg_name),
KvPair("implementation", impl_name),
];
let aead_benchid = |op, len| {
benchid(
KvPairs(&base),
KvPairs(&[KvPair("operation", op), KvPair("length", len)]),
)
};
let key = [12; KEY_LEN];
let nonce = [23; NONCE_LEN];
let ad = [];
c.bench_function(&aead_benchid("encrypt", "0byte"), |bench| {
const DATA_LEN: usize = 0;
let ptxt = [];
let mut ctxt = [0; DATA_LEN + TAG_LEN];
bench.iter(|| {
scheme.encrypt(&mut ctxt, &key, &nonce, &ad, &ptxt).unwrap();
});
});
c.bench_function(&aead_benchid("decrypt", "0byte"), |bench| {
const DATA_LEN: usize = 0;
let ptxt = [];
let mut ctxt = [0; DATA_LEN + TAG_LEN];
let mut ptxt_out = [0u8; DATA_LEN];
scheme.encrypt(&mut ctxt, &key, &nonce, &ad, &ptxt).unwrap();
bench.iter(|| {
scheme
.decrypt(&mut ptxt_out, &key, &nonce, &ad, &mut ctxt)
.unwrap()
})
});
c.bench_function(&aead_benchid("encrypt", "32byte"), |bench| {
const DATA_LEN: usize = 32;
let ptxt = [34u8; DATA_LEN];
let mut ctxt = [0; DATA_LEN + TAG_LEN];
bench.iter(|| {
scheme.encrypt(&mut ctxt, &key, &nonce, &ad, &ptxt).unwrap();
});
});
c.bench_function(&aead_benchid("decrypt", "32byte"), |bench| {
const DATA_LEN: usize = 32;
let ptxt = [34u8; DATA_LEN];
let mut ctxt = [0; DATA_LEN + TAG_LEN];
let mut ptxt_out = [0u8; DATA_LEN];
scheme.encrypt(&mut ctxt, &key, &nonce, &ad, &ptxt).unwrap();
bench.iter(|| {
scheme
.decrypt(&mut ptxt_out, &key, &nonce, &ad, &mut ctxt)
.unwrap()
})
});
c.bench_function(&aead_benchid("encrypt", "1024byte"), |bench| {
const DATA_LEN: usize = 1024;
let ptxt = [34u8; DATA_LEN];
let mut ctxt = [0; DATA_LEN + TAG_LEN];
bench.iter(|| {
scheme.encrypt(&mut ctxt, &key, &nonce, &ad, &ptxt).unwrap();
});
});
c.bench_function(&aead_benchid("decrypt", "1024byte"), |bench| {
const DATA_LEN: usize = 1024;
let ptxt = [34u8; DATA_LEN];
let mut ctxt = [0; DATA_LEN + TAG_LEN];
let mut ptxt_out = [0u8; DATA_LEN];
scheme.encrypt(&mut ctxt, &key, &nonce, &ad, &ptxt).unwrap();
bench.iter(|| {
scheme
.decrypt(&mut ptxt_out, &key, &nonce, &ad, &mut ctxt)
.unwrap()
})
});
}
}
mod keyed_hash {
criterion::criterion_group!(
benches,
bench_blake2b_rustcrypto,
bench_blake2b_libcrux,
bench_shake256_rustcrypto,
);
const KEY_LEN: usize = 32;
const HASH_LEN: usize = 32;
use criterion::Criterion;
fn bench_shake256_rustcrypto(c: &mut Criterion) {
template(
c,
"shake256",
"rustcrypto",
&rosenpass_ciphers::subtle::rust_crypto::keyed_shake256::SHAKE256Core,
);
}
fn bench_blake2b_rustcrypto(c: &mut Criterion) {
template(
c,
"blake2b",
"rustcrypto",
&rosenpass_ciphers::subtle::rust_crypto::blake2b::Blake2b,
);
}
fn bench_blake2b_libcrux(c: &mut Criterion) {
template(
c,
"blake2b",
"libcrux",
&rosenpass_ciphers::subtle::libcrux::blake2b::Blake2b,
);
}
use rosenpass_cipher_traits::primitives::KeyedHash;
fn template<H: KeyedHash<KEY_LEN, HASH_LEN>>(
c: &mut Criterion,
alg_name: &str,
impl_name: &str,
_: &H,
) where
H::Error: std::fmt::Debug,
{
use crate::{benchid, KvPair, KvPairs};
let key = [12u8; KEY_LEN];
let mut out = [0u8; HASH_LEN];
let base = [
KvPair("primitive", "keyedhash"),
KvPair("algorithm", alg_name),
KvPair("implementation", impl_name),
KvPair("operation", "hash"),
];
let keyedhash_benchid = |len| benchid(KvPairs(&base), KvPairs(&[KvPair("length", len)]));
c.bench_function(&keyedhash_benchid("0byte"), |bench| {
let bytes = [];
bench.iter(|| {
H::keyed_hash(&key, &bytes, &mut out).unwrap();
})
})
.bench_function(&keyedhash_benchid("32byte"), |bench| {
let bytes = [34u8; 32];
bench.iter(|| {
H::keyed_hash(&key, &bytes, &mut out).unwrap();
})
})
.bench_function(&keyedhash_benchid("64byte"), |bench| {
let bytes = [34u8; 64];
bench.iter(|| {
H::keyed_hash(&key, &bytes, &mut out).unwrap();
})
})
.bench_function(&keyedhash_benchid("128byte"), |bench| {
let bytes = [34u8; 128];
bench.iter(|| {
H::keyed_hash(&key, &bytes, &mut out).unwrap();
})
})
.bench_function(&keyedhash_benchid("1024byte"), |bench| {
let bytes = [34u8; 1024];
bench.iter(|| {
H::keyed_hash(&key, &bytes, &mut out).unwrap();
})
});
}
}

View File

@@ -1,75 +1,74 @@
//!
//!```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 as _; use rosenpass_to::To;
pub use crate::{KeyedHash, KEY_LEN}; use crate::keyed_hash as hash;
use rosenpass_cipher_traits::primitives::KeyedHashInstanceTo; pub use hash::KEY_LEN;
///
///```rust
/// # use rosenpass_ciphers::hash_domain::{HashDomain, HashDomainNamespace, SecretHashDomain, SecretHashDomainNamespace};
/// use rosenpass_secret_memory::Secret;
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
///
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
/// // create use once hash domain for the protocol identifier
/// let mut hash_domain = HashDomain::zero();
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
/// // upgrade to reusable hash domain
/// let hash_domain_namespace: HashDomainNamespace = hash_domain.dup();
/// // derive new key
/// let key_identifier = "my_key_identifier";
/// let key = hash_domain_namespace.mix(key_identifier.as_bytes())?.into_value();
/// // derive a new key based on a secret
/// const MY_SECRET_LEN: usize = 21;
/// let my_secret_bytes = "my super duper secret".as_bytes();
/// let my_secret: Secret<21> = Secret::from_slice("my super duper secret".as_bytes());
/// let secret_hash_domain: SecretHashDomain = hash_domain_namespace.mix_secret(my_secret)?;
/// // derive a new key based on the secret key
/// let new_key_identifier = "my_new_key_identifier".as_bytes();
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
///
/// # Ok::<(), anyhow::Error>(())
///```
///
// TODO Use a proper Dec interface // 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], KeyedHash); pub struct HashDomain([u8; KEY_LEN]);
/// 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], KeyedHash); pub struct HashDomainNamespace([u8; KEY_LEN]);
/// 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>, KeyedHash); pub struct SecretHashDomain(Secret<KEY_LEN>);
/// 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>, KeyedHash); pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
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(choice: KeyedHash) -> Self { pub fn zero() -> Self {
Self([0u8; KEY_LEN], choice) Self([0u8; KEY_LEN])
} }
/// 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, self.1) HashDomainNamespace(self.0)
} }
/// 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), self.1) SecretHashDomain(Secret::from_slice(&self.0))
} }
// TODO: Protocol! Use domain separation to ensure that // TODO: Protocol! Use domain separation to ensure that
@@ -78,43 +77,14 @@ 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> {
let mut new_key: [u8; KEY_LEN] = [0u8; KEY_LEN]; Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
self.1.keyed_hash_to(&self.0, v).to(&mut new_key)?;
Ok(Self(new_key, self.1))
}
/// Version of [Self::mix] that accepts an iterator and mixes all values from the iterator into
/// this hash domain.
///
/// # Examples
///
/// ```rust
/// use rosenpass_ciphers::{hash_domain::HashDomain, KeyedHash};
///
/// let hasher = HashDomain::zero(KeyedHash::keyed_shake256());
/// assert_eq!(
/// hasher.clone().mix(b"Hello")?.mix(b"World")?.into_value(),
/// hasher.clone().mix_many([b"Hello", b"World"])?.into_value()
/// );
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn mix_many<I, T>(mut self, it: I) -> Result<Self>
where
I: IntoIterator<Item = T>,
T: AsRef<[u8]>,
{
for e in it {
self = self.mix(e.as_ref())?;
}
Ok(self)
} }
/// 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(), self.1) SecretHashDomain::invoke_primitive(&self.0, v.secret())
} }
/// Gets the key of this [HashDomain]. /// Gets the key of this [HashDomain].
@@ -128,9 +98,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> {
let mut new_key: [u8; KEY_LEN] = [0u8; KEY_LEN]; Ok(HashDomain(
self.1.keyed_hash_to(&self.0, v).to(&mut new_key)?; hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
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`
@@ -139,7 +109,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(), self.1.clone()) SecretHashDomain::invoke_primitive(&self.0, v.secret())
} }
} }
@@ -148,35 +118,27 @@ 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.
/// TODO: docu pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
pub fn invoke_primitive( let mut r = SecretHashDomain(Secret::zero());
k: &[u8], hash::hash(k, d).to(r.0.secret_mut())?;
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(hash_choice: KeyedHash) -> Self { pub fn zero() -> Self {
Self(Secret::zero(), hash_choice) Self(Secret::zero())
} }
/// 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, self.1) SecretHashDomainNamespace(self.0)
} }
/// 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>, hash_choice: KeyedHash) -> Self { pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
Self(k, hash_choice) Self(k)
} }
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically, /// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
@@ -185,47 +147,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.1) Self::invoke_primitive(self.0.secret(), v)
}
/// Version of [Self::mix] that accepts an iterator and mixes all values from the iterator into
/// this hash domain.
///
/// # Examples
///
/// ```rust
/// use rosenpass_ciphers::{hash_domain::HashDomain, KeyedHash};
///
/// rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
///
/// let hasher = HashDomain::zero(KeyedHash::keyed_shake256());
/// assert_eq!(
/// hasher
/// .clone()
/// .turn_secret()
/// .mix(b"Hello")?
/// .mix(b"World")?
/// .into_secret()
/// .secret(),
/// hasher
/// .clone()
/// .turn_secret()
/// .mix_many([b"Hello", b"World"])?
/// .into_secret()
/// .secret(),
/// );
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn mix_many<I, T>(mut self, it: I) -> Result<Self>
where
I: IntoIterator<Item = T>,
T: AsRef<[u8]>,
{
for e in it {
self = self.mix(e.as_ref())?;
}
Ok(self)
} }
/// Creates a new [SecretHashDomain] by mixing in a new key `v` /// Creates a new [SecretHashDomain] by mixing in a new key `v`
@@ -234,13 +156,21 @@ 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.1) Self::invoke_primitive(self.0.secret(), v.secret())
} }
/// 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 {
@@ -250,7 +180,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, self.1.clone()) SecretHashDomain::invoke_primitive(self.0.secret(), v)
} }
/// Creates a new [SecretHashDomain] by mixing in a new key `v` /// Creates a new [SecretHashDomain] by mixing in a new key `v`
@@ -259,7 +189,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(), self.1.clone()) SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
} }
// 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
@@ -269,8 +199,4 @@ 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
}
} }

View File

@@ -1,12 +1,11 @@
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
@@ -14,33 +13,41 @@ 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 use crate::subtle::keyed_hash::KeyedHash; pub mod keyed_hash {
pub use crate::subtle::incorrect_hmac_blake2b::{
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
};
}
/// Authenticated encryption with associated data (AEAD) /// Authenticated encryption with associated data
/// Chacha20poly1305 is used. /// Chacha20poly1305 is used.
#[cfg(feature = "experiment_libcrux_chachapoly")] pub mod aead {
pub use subtle::libcrux::chacha20poly1305_ietf::ChaCha20Poly1305 as Aead; #[cfg(not(feature = "experiment_libcrux"))]
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
#[cfg(feature = "experiment_libcrux")]
pub use crate::subtle::chacha20poly1305_ietf_libcrux::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
}
/// Authenticated encryption with associated data (AEAD) /// Authenticated encryption with associated data with a constant nonce
/// 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 use crate::subtle::rust_crypto::xchacha20poly1305_ietf::XChaCha20Poly1305 as XAead; pub mod xaead {
pub use crate::subtle::xchacha20poly1305_ietf::{
/// Use Classic-McEcliece-460986 as the Static KEM. decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
/// };
/// 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;
}

View File

@@ -0,0 +1,65 @@
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(())
})
}

View File

@@ -0,0 +1,99 @@
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(())
}

View File

@@ -0,0 +1,117 @@
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(())
}

View File

@@ -1,79 +0,0 @@
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 {}

View File

@@ -1,3 +0,0 @@
//! Own implementations of custom algorithms
pub mod incorrect_hmac_blake2b;

View File

@@ -0,0 +1,67 @@
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(())
})
}

View File

@@ -1,65 +0,0 @@
//! 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"),
}
}
}

View File

@@ -1,88 +0,0 @@
//! 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);
}
}
}
}

View File

@@ -1,274 +0,0 @@
//! 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(())
}
}
}

View File

@@ -1,133 +0,0 @@
//! 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);
}
}
}

View File

@@ -1,14 +0,0 @@
//! Implementations backed by libcrux, a verified crypto library.
//!
//! [Website](https://cryspen.com/libcrux/)
//!
//! [Github](https://github.com/cryspen/libcrux)
#[cfg(feature = "experiment_libcrux_define_blake2")]
pub mod blake2b;
#[cfg(feature = "experiment_libcrux_define_chachapoly")]
pub mod chacha20poly1305_ietf;
#[cfg(feature = "experiment_libcrux_define_kyber")]
pub mod kyber512;

View File

@@ -1,16 +1,13 @@
//! Contains the implementations of the crypto algorithms used throughout Rosenpass. /// This module provides the following cryptographic schemes:
/// - [blake2b]: The blake2b hash function
pub mod keyed_hash; /// - [chacha20poly1305_ietf]: The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305) (only used when the feature `experiment_libcrux` is disabled).
/// - [chacha20poly1305_ietf_libcrux]: The Chacha20Poly1305 AEAD as implemented in [libcrux](https://github.com/cryspen/libcrux) (only used when the feature `experiment_libcrux` is enabled).
pub use custom::incorrect_hmac_blake2b; /// - [incorrect_hmac_blake2b]: An (incorrect) hmac based on [blake2b].
pub use rust_crypto::{blake2b, keyed_shake256}; /// - [xchacha20poly1305_ietf] The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305)
pub mod blake2b;
pub mod custom; #[cfg(not(feature = "experiment_libcrux"))]
pub mod rust_crypto; pub mod chacha20poly1305_ietf;
#[cfg(feature = "experiment_libcrux")]
#[cfg(any( pub mod chacha20poly1305_ietf_libcrux;
feature = "experiment_libcrux_define_blake2", pub mod incorrect_hmac_blake2b;
feature = "experiment_libcrux_define_chachapoly", pub mod xchacha20poly1305_ietf;
feature = "experiment_libcrux_define_kyber",
))]
pub mod libcrux;

View File

@@ -1,44 +0,0 @@
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 {}

View File

@@ -1,79 +0,0 @@
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 {}

View File

@@ -1,117 +0,0 @@
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>;

View File

@@ -1,7 +0,0 @@
//! Implementations backed by RustCrypto
pub mod blake2b;
pub mod keyed_shake256;
pub mod chacha20poly1305_ietf;
pub mod xchacha20poly1305_ietf;

View File

@@ -1,82 +1,17 @@
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::{AeadInPlace, KeyInit}; use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
pub use rosenpass_cipher_traits::algorithms::aead_xchacha20poly1305::{ /// The key length is 32 bytes or 256 bits.
KEY_LEN, NONCE_LEN, TAG_LEN, pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
}; /// The MAC tag length is 16 bytes or 128 bits.
/// Implements the [`Aead`] and [`AeadXChaCha20Poly1305`] traits backed by the RustCrypto pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
/// implementation. /// The nonce length is 24 bytes or 192 bits.
pub struct XChaCha20Poly1305; pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
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
@@ -88,12 +23,12 @@ impl AeadXChaCha20Poly1305 for XChaCha20Poly1305 {}
/// ///
/// # Examples /// # Examples
///```rust ///```rust
/// # use rosenpass_ciphers::subtle::rust_crypto::xchacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN}; /// # use rosenpass_ciphers::subtle::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; KEY_LEN] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY /// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8; NONCE_LEN] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE /// 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 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];
/// ///
@@ -109,14 +44,19 @@ impl AeadXChaCha20Poly1305 for XChaCha20Poly1305 {}
#[inline] #[inline]
pub fn encrypt( pub fn encrypt(
ciphertext: &mut [u8], ciphertext: &mut [u8],
key: &[u8; KEY_LEN], key: &[u8],
nonce: &[u8; NONCE_LEN], nonce: &[u8],
ad: &[u8], ad: &[u8],
plaintext: &[u8], plaintext: &[u8],
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
XChaCha20Poly1305 let nonce = GenericArray::from_slice(nonce);
.encrypt_with_nonce_in_ctxt(ciphertext, key, nonce, ad, plaintext) let (n, ct_mac) = ciphertext.split_at_mut(NONCE_LEN);
.map_err(anyhow::Error::from) let (ct, mac) = ct_mac.split_at_mut(ct_mac.len() - TAG_LEN);
copy_slice(nonce).to(n);
copy_slice(plaintext).to(ct);
let mac_value = AeadImpl::new_from_slice(key)?.encrypt_in_place_detached(nonce, ad, ct)?;
copy_slice(&mac_value[..]).to(mac);
Ok(())
} }
/// 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
@@ -131,7 +71,7 @@ pub fn encrypt(
/// ///
/// # Examples /// # Examples
///```rust ///```rust
/// # use rosenpass_ciphers::subtle::rust_crypto::xchacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN}; /// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
/// let ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /// 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,
@@ -140,8 +80,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; KEY_LEN] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY /// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8; NONCE_LEN] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE /// 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 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];
/// ///
@@ -154,11 +94,15 @@ pub fn encrypt(
#[inline] #[inline]
pub fn decrypt( pub fn decrypt(
plaintext: &mut [u8], plaintext: &mut [u8],
key: &[u8; KEY_LEN], key: &[u8],
ad: &[u8], ad: &[u8],
ciphertext: &[u8], ciphertext: &[u8],
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
XChaCha20Poly1305 let (n, ct_mac) = ciphertext.split_at(NONCE_LEN);
.decrypt_with_nonce_in_ctxt(plaintext, key, ad, ciphertext) let (ct, mac) = ct_mac.split_at(ct_mac.len() - TAG_LEN);
.map_err(anyhow::Error::from) let nonce = GenericArray::from_slice(n);
let tag = GenericArray::from_slice(mac);
copy_slice(ct).to(plaintext);
AeadImpl::new_from_slice(key)?.decrypt_in_place_detached(nonce, ad, plaintext, tag)?;
Ok(())
} }

View File

@@ -8,7 +8,6 @@ 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.0"
# 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
@@ -20,7 +19,7 @@ rosenpass-to = { workspace = true }
memsec = { workspace = true } memsec = { workspace = true }
[dev-dependencies] [dev-dependencies]
rand = { workspace = true } rand = "0.8.5"
[lints.rust] [lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] } unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -32,11 +32,8 @@ 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;
@@ -53,12 +50,14 @@ 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
const LEN: usize = 1024; // length of each slice passed as parameters to the tested comparison function let len = 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 a = [b'a'; LEN]; let a1 = a1.as_bytes();
let b = [b'b'; LEN]; let a2 = a2.as_bytes();
let b = b.as_bytes();
let mut tmp = [0u8; LEN];
// vector representing all timing tests // vector representing all timing tests
// //
@@ -72,14 +71,12 @@ 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();
memcmp(black_box(&a), black_box(&tmp)); if test.0 {
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);
} }
@@ -120,6 +117,6 @@ mod tests {
assert!( assert!(
correlation.abs() < 0.01, correlation.abs() < 0.01,
"execution time correlates with result" "execution time correlates with result"
) );
} }
} }

View File

@@ -23,8 +23,7 @@ main() {
exc cargo llvm-cov --all-features --workspace --doctests --branch exc cargo llvm-cov --all-features --workspace --doctests --branch
exc rm -rf target/llvm-cov-target/debug/deps/doctestbins exc cp -rv target/llvm-cov-target/doctestbins 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 \

121
deny.toml
View File

@@ -1,121 +0,0 @@
# 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 = []

View File

@@ -1,45 +0,0 @@
# 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" ]

View File

@@ -1,203 +0,0 @@
# 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 .
```

90
flake.lock generated
View File

@@ -1,5 +1,26 @@
{ {
"nodes": { "nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1728282832,
"narHash": "sha256-I7AbcwGggf+CHqpyd/9PiAjpIBGTGx5woYHqtwxaV7I=",
"owner": "nix-community",
"repo": "fenix",
"rev": "1ec71be1f4b8f3105c5d38da339cb061fefc43f4",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
@@ -18,26 +39,6 @@
"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,
@@ -56,30 +57,25 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nix-vm-test": "nix-vm-test", "nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix"
} }
}, },
"rust-overlay": { "rust-analyzer-src": {
"inputs": { "flake": false,
"nixpkgs": [
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1744513456, "lastModified": 1728249780,
"narHash": "sha256-NLVluTmK8d01Iz+WyarQhwFcXpHEwU7m5hH3YQQFJS0=", "narHash": "sha256-J269DvCI5dzBmPrXhAAtj566qt0b22TJtF3TIK+tMsI=",
"owner": "oxalica", "owner": "rust-lang",
"repo": "rust-overlay", "repo": "rust-analyzer",
"rev": "730fd8e82799219754418483fabe1844262fd1e2", "rev": "2b750da1a1a2c1d2c70896108d7096089842d877",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "oxalica", "owner": "rust-lang",
"repo": "rust-overlay", "ref": "nightly",
"repo": "rust-analyzer",
"type": "github" "type": "github"
} }
}, },
@@ -97,26 +93,6 @@
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1743748085,
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

282
flake.nix
View File

@@ -3,51 +3,37 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
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";
# for rust nightly with llvm-tools-preview # for rust nightly with llvm-tools-preview
rust-overlay.url = "github:oxalica/rust-overlay"; fenix.url = "github:nix-community/fenix";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; fenix.inputs.nixpkgs.follows = "nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = outputs = { self, nixpkgs, flake-utils, ... }@inputs:
{
self,
nixpkgs,
flake-utils,
nix-vm-test,
rust-overlay,
treefmt-nix,
...
}@inputs:
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [ nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
# #
### Export the overlay.nix from this flake ### ### Export the overlay.nix from this flake ###
# #
{ overlays.default = import ./overlay.nix; } {
overlays.default = import ./overlay.nix;
}
# #
### Actual Rosenpass Package and Docker Container Images ### ### Actual Rosenpass Package and Docker Container Images ###
# #
(flake-utils.lib.eachSystem (flake-utils.lib.eachSystem [
[ "x86_64-linux"
"x86_64-linux" "aarch64-linux"
"aarch64-linux"
# unsuported best-effort # unsuported best-effort
"i686-linux" "i686-linux"
"x86_64-darwin" "x86_64-darwin"
"aarch64-darwin" "aarch64-darwin"
# "x86_64-windows" # "x86_64-windows"
] ]
( (system:
system:
let let
# normal nixpkgs # normal nixpkgs
pkgs = import nixpkgs { pkgs = import nixpkgs {
@@ -58,159 +44,115 @@
}; };
in in
{ {
packages = packages = {
{ default = pkgs.rosenpass;
default = pkgs.rosenpass; rosenpass = pkgs.rosenpass;
rosenpass = pkgs.rosenpass; rosenpass-oci-image = pkgs.rosenpass-oci-image;
rosenpass-oci-image = pkgs.rosenpass-oci-image; rp = pkgs.rp;
rp = pkgs.rp;
release-package = pkgs.release-package; release-package = pkgs.release-package;
# for good measure, we also offer to cross compile to Linux on Arm # for good measure, we also offer to cross compile to Linux on Arm
aarch64-linux-rosenpass-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass; aarch64-linux-rosenpass-static =
aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp; pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass;
} aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp;
// }
# We only offer static builds for linux, as this is not supported on OS X //
(nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux { # We only offer static builds for linux, as this is not supported on OS X
rosenpass-static = pkgs.pkgsStatic.rosenpass; (nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux {
rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image; rosenpass-static = pkgs.pkgsStatic.rosenpass;
rp-static = pkgs.pkgsStatic.rp; rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image;
}); rp-static = pkgs.pkgsStatic.rp;
});
} }
) ))
)
# #
### Linux specifics ### ### Linux specifics ###
# #
(flake-utils.lib.eachSystem (flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
[ let
"x86_64-linux" pkgs = import nixpkgs {
"aarch64-linux" inherit system;
"i686-linux"
]
(
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ # 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 ];
self.overlays.default };
in
{
nix-vm-test.overlays.default #
### Reading materials ###
#
packages.whitepaper = pkgs.whitepaper;
# apply rust-overlay to get specific versions of the rust toolchain for a MSRV check #
(import rust-overlay) ### Proof and Proof Tools ###
]; #
}; packages.proverif-patched = pkgs.proverif-patched;
packages.proof-proverif = pkgs.proof-proverif;
treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix;
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 ### ### Devshells ###
# #
packages.whitepaper = pkgs.whitepaper; devShells.default = pkgs.mkShell {
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = with pkgs; [
cargo-release
clippy
rustfmt
nodePackages.prettier
nushell # for the .ci/gen-workflow-files.nu script
proverif-patched
];
};
# TODO: Write this as a patched version of the default environment
devShells.fullEnv = pkgs.mkShell {
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = with pkgs; [
cargo-release
rustfmt
nodePackages.prettier
nushell # for the .ci/gen-workflow-files.nu script
proverif-patched
inputs.fenix.packages.${system}.complete.toolchain
pkgs.cargo-llvm-cov
pkgs.grcov
];
};
devShells.coverage = pkgs.mkShell {
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = [
inputs.fenix.packages.${system}.complete.toolchain
pkgs.cargo-llvm-cov
pkgs.grcov
];
};
#
### Proof and Proof Tools ###
#
packages.proverif-patched = pkgs.proverif-patched;
packages.proof-proverif = pkgs.proof-proverif;
# checks = {
### Devshells ### systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix;
# systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix;
devShells.default = pkgs.mkShell {
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = with pkgs; [
cargo-release
clippy
rustfmt
nodePackages.prettier
nushell # for the .ci/gen-workflow-files.nu script
proverif-patched
];
};
# TODO: Write this as a patched version of the default environment
devShells.fullEnv = pkgs.mkShell {
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = with pkgs; [
cargo-audit
cargo-msrv
cargo-release
cargo-vet
rustfmt
nodePackages.prettier
nushell # for the .ci/gen-workflow-files.nu script
proverif-patched
pkgs.cargo-llvm-cov
pkgs.grcov
pkgs.rust-bin.stable.latest.complete
];
};
devShells.coverage = pkgs.mkShell {
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = [
pkgs.cargo-llvm-cov
pkgs.grcov
pkgs.rustc.llvmPackages.llvm
];
env = {
inherit (pkgs.cargo-llvm-cov) LLVM_COV LLVM_PROFDATA;
};
};
devShells.benchmarks = pkgs.mkShell {
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = with pkgs; [
cargo-release
clippy
rustfmt
];
};
checks = cargo-fmt = pkgs.runCommand "check-cargo-fmt"
{ { inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix; cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix; '';
formatting = treefmtEval.config.build.check self; nixpkgs-fmt = pkgs.runCommand "check-nixpkgs-fmt"
rosenpass-msrv-check = { nativeBuildInputs = [ pkgs.nixpkgs-fmt ]; } ''
let nixpkgs-fmt --check ${./.} && touch $out
rosenpassCargoToml = pkgs.lib.trivial.importTOML ./rosenpass/Cargo.toml; '';
prettier-check = pkgs.runCommand "check-with-prettier"
{ nativeBuildInputs = [ pkgs.nodePackages.prettier ]; } ''
cd ${./.} && prettier --check . && touch $out
'';
};
rustToolchain = pkgs.rust-bin.stable.${rosenpassCargoToml.package.rust-version}.default; formatter = pkgs.nixpkgs-fmt;
rustPlatform = pkgs.makeRustPlatform { }))
cargo = rustToolchain;
rustc = rustToolchain;
};
in
pkgs.rosenpass.override { inherit rustPlatform; };
}
// 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;
}
);
# for `nix fmt`
formatter = treefmtEval.config.build.wrapper;
}
)
)
]; ];
} }

View File

@@ -3,10 +3,9 @@ name = "rosenpass-fuzzing"
version = "0.0.1" version = "0.0.1"
publish = false publish = false
edition = "2021" edition = "2021"
rust-version = "1.77.0"
[features] [features]
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux_all"] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
[package.metadata] [package.metadata]
cargo-fuzz = true cargo-fuzz = true

View File

@@ -4,8 +4,7 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::primitives::Aead as _; use rosenpass_ciphers::aead;
use rosenpass_ciphers::Aead;
#[derive(arbitrary::Arbitrary, Debug)] #[derive(arbitrary::Arbitrary, Debug)]
pub struct Input { pub struct Input {
@@ -18,7 +17,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,

View File

@@ -4,7 +4,6 @@ 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;
@@ -17,7 +16,5 @@ pub struct Blake2b {
fuzz_target!(|input: Blake2b| { fuzz_target!(|input: Blake2b| {
let mut out = [0u8; 32]; let mut out = [0u8; 32];
blake2b::Blake2b::keyed_hash_to(&input.key, &input.data) blake2b::hash(&input.key, &input.data).to(&mut out).unwrap();
.to(&mut out)
.unwrap();
}); });

View File

@@ -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::primitives::Kem; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::StaticKem; use rosenpass_ciphers::kem::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;

View File

@@ -4,8 +4,8 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::primitives::Kem; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::EphemeralKem; use rosenpass_ciphers::kem::EphemeralKem;
#[derive(arbitrary::Arbitrary, Debug)] #[derive(arbitrary::Arbitrary, Debug)]
pub struct Input { pub struct Input {
@@ -16,7 +16,5 @@ 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 EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
.encaps(&mut shared_secret, &mut ciphertext, &input.pk)
.unwrap();
}); });

View File

@@ -3,13 +3,13 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::primitives::Kem; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::StaticKem; use rosenpass_ciphers::kem::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);
}); });

View File

@@ -1,8 +0,0 @@
# Rewriting analyze.sh in Python
* `../analyze.sh` is the old script
* `src/__init__.py` is the new script
* call the old script from the Rosenpass repository's root directory with `./analyze.sh`
* call the new script from the marzipan directory:
* `nix run .# -- analyze $repo` where `$repo` is the absolute(?) path to the root directory of the Rosenpass repository.

View File

@@ -1,66 +0,0 @@
# TODO for the project of rewriting Marzipan
## Done
* ~~figure out why ProVerif is started on the non-processed mpv file~~
* ~~rework rebound warnings (`clean_warnings` Bash function)~~
```bash
rosenpass$ rosenpass-marzipan run-proverif target/proverif/03_identity_hiding_responder.entry.o.pv target/proverif/03_identity_hiding_responder.entry.log
```
* ~~provide log parameter to `rosenpass-marzipan`-call~~ (no, it was intentionally not used)
* ~~cpp pre-processing stuff~~
* ~~awk pre-processing stuff~~
* ~~`pretty_output` Bash function~~
* ~~pretty_output_line~~
* ~~click function intervention weirdness~~
* ~~why is everything red in the pretty output? (see line 96 in __init__.py)~~
* ~~awk RESULT flush in marzipan()~~
* ~~move the whole metaverif function to Python~~
* ~move the whole analyze function to Python~
* ~find the files~
* ~start subprocesses in parallel~
* ~wait for them to finish~
* ~~rebase from main~~
* ~~see if we still need the `extra_args is None` check in `_run_proverif`~`
* ~~set colors differently to prevent injection attack~~
* ~~by calling a function~~
* ~~by prepared statements~~
* ~~standalone function parse_result_line is no longer necessary~~
* ~~is the clean function still necessary?~~
* ~~implement better main function for click~~
* ~~why does analyze fail when the target/proverif directory is not empty?~~
* ~~return an exit status that is meaningful for CI~~
* ~~exception handling in analyze() and in run_proverif()~~
* ~~refactor filtering in run_proverif (see karo's comment)~~
* ~configurable target directory~
* ~lark parser: multiline comments, how???~
* ~parse errors~
* ~error when trying with: `nix run .# -- parse ../target/proverif/01_secrecy.entry.i.pv`~
* ~`in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));`~
* ~ ^~
* ~04_dos… has a syntax error (see below)~
## Next Steps
* integrate marzipan.awk into Python, somehow
* options term special cases (c.f. manual page 133, starting with "fun" term)
* complete with CryptoVerif options
* rewrite marzipan.awk into Python/LARK
* define a LARK grammar for marzipan.awk rules
* write python code for processing marzipan rules, e.g. alias replacement (step: i.pv->o.pv)
* do not assume that the repo path has subdir marzipan
* do not assume that the repo path has subdir analysis
* rewrite cpp into Python/LARK (step: mpv->i.pv)
* integrate the Nix flake into the main Nix flake
* pull the gawk dependency into the Nix flake
* think about next steps
* integrate this upstream, into the CI?
* “make it beautiful” steps? more resiliency to working directory?
* rewrite our awk usages into Python/…?
* yes, possibly as extension to the LARK grammar
* and rewrite the AST within Python
* reconstruct ProVerif input file for ProVerif
* rewrite our CPP usages into Python/…?
* low priority: nested comments in ProVerif code
“it replaces the Bash script and is idiomatic Python code”

190
marzipan/flake.lock generated
View File

@@ -1,190 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1736166416,
"narHash": "sha256-U47xeACNBpkSO6IcCm0XvahsVXpJXzjPIQG7TZlOToU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b30f97d8c32d804d2d832ee837d0f1ca0695faa5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1730157240,
"narHash": "sha256-P8wF4ag6Srmpb/gwskYpnIsnspbjZlRvu47iN527ABQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "75e28c029ef2605f9841e0baa335d70065fe7ae2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable-small",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs_2",
"systems": "systems_3",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1736280331,
"narHash": "sha256-mkVHnky9h/s2EA+t9eEC8qxgcNTE3V+vb/9XgG4fCig=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "4d260d908f3d95fa4b3ef6a98781ff64e1eede22",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1730120726,
"narHash": "sha256-LqHYIxMrl/1p3/kvm2ir925tZ8DkI0KA10djk8wecSk=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "9ef337e492a5555d8e17a51c911ff1f02635be15",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,18 +0,0 @@
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.poetry2nix.url = "github:nix-community/poetry2nix";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = (inputs:
let scoped = (scope: scope.result);
in scoped rec {
inherit (builtins) removeAttrs;
result = (import ./nix/init.nix) {
scoped = scoped;
flake.self = inputs.self;
flake.inputs = removeAttrs inputs ["self"];
};
}
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
[tool.poetry]
name = "hyuga-language-server-installer"
version = "0.1.0"
description = ""
authors = []
[tool.poetry.dependencies]
python = ">=3.12,<3.13"
[tool.poetry.group.dev.dependencies]
hyuga = "^1.0.0"
poetry = "^2.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,32 +0,0 @@
outer_ctx: outer_ctx.scoped rec {
inherit (builtins) trace;
ctx = outer_ctx // { inherit config; };
inherit (ctx) scoped;
inherit (ctx.flake.inputs) nixpkgs flake-utils;
inherit (nixpkgs.lib) genAttrs zipAttrsWith;
inherit (nixpkgs.lib.debug) traceVal;
inherit (flake-utils.lib) allSystems eachSystem;
result = {
devShells = eachSupportedSystem (system: (setupSystem system).devShells);
packages = eachSupportedSystem (system: (setupSystem system).packages);
apps = eachSupportedSystem (system: (setupSystem system).apps);
};
setupSystem = (system_name: scoped rec {
result = (import ./system.nix) (ctx // {
system.name = system_name;
system.pkgs = nixpkgs.legacyPackages.${system_name};
});
});
config = {
supportedSystems = allSystems;
poetry.projectDir = ctx.flake.self;
};
eachSupportedSystem = genAttrs config.supportedSystems;
}

View File

@@ -1,47 +0,0 @@
ctx: ctx.scoped rec {
inherit (ctx.system) pkgs;
inherit (ctx.flake.inputs) poetry2nix flake-utils;
inherit (pkgs) mkShellNoCC writeShellApplication;
inherit (flake-utils.lib) mkApp;
poetryCtx = poetry2nix.lib.mkPoetry2Nix { inherit pkgs; };
inherit (poetryCtx) mkPoetryEnv mkPoetryApplication;
deps = [poetryEnv];
dev-deps = []
++ deps
++ [poetryHyugaEnv]
++ (with pkgs; [poetry]);
poetryCfg = ctx.config.poetry // { overrides = poetryOverrides; };
poetryEnv = mkPoetryEnv poetryCfg;
poetryHyugaCfg = poetryCfg // { projectDir = ./hyuga; };
poetryHyugaEnv = mkPoetryEnv poetryHyugaCfg;
poetryOverrides = poetryCtx.defaultPoetryOverrides.extend (final: prev: {
hyuga = prev.hyuga.overridePythonAttrs (old: {
buildInputs = []
++ (old.buildInputs or [ ])
++ [ final.poetry-core ];
preferWheel = true;
}
);
});
result.packages.default = mkPoetryApplication poetryCfg;
result.devShells.default = mkShellNoCC {
packages = dev-deps;
};
result.apps.replPython = mkShellApp "python-repl" ''python'';
result.apps.replHy = mkShellApp "hy-repl" ''hy'';
mkShellApp = (name: script: mkApp {
drv = writeShellApplication {
inherit name;
text = script;
runtimeInputs = dev-deps;
};
});
}

1415
marzipan/poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
[tool.poetry]
name = "rosenpass-marzipan"
version = "0.1.0"
description = ""
authors = ["Author Name <author@example.com>"]
# readme = "README.md"
# license = "BSD"
packages = [
{ include = "**/*.[hp]y", from = "src", to = "rosenpass_marzipan" },
{ include = "**/*.sh", from = "src", to = "rosenpass_marzipan" },
#{ include = "**/*.lark", from = "src", to = "rosenpass_marzipan" },
]
[tool.poetry.scripts]
rosenpass-marzipan = 'rosenpass_marzipan:main'
[tool.poetry.dependencies]
python = ">=3.12,<3.13"
hy = "^1.0.0"
lark = "^1.2.2"
hyrule = "^0.8.0"
ipython = "^8.32.0"
click = "^8.1.8"
rich = "^13.9.4"
[tool.poetry.group.dev.dependencies]
poetry = "^2.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,312 +0,0 @@
from .util import pkgs, setup_exports, export, rename
from .parser import *
# from rich.console import Console
import click
target_subdir = "target/proverif"
(__all__, export) = setup_exports()
export(setup_exports)
console = pkgs.rich.console.Console()
logger = pkgs.logging.getLogger(__name__)
def set_logging_logrecordfactory(filename):
old_factory = pkgs.logging.getLogRecordFactory()
def record_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.filename = filename
return record
pkgs.logging.setLogRecordFactory(record_factory)
#def set_logging_format(max_length):
# pass
# #format_str = "{levelname:<8} {filename:<" + str(max_length + 2) + "} {message}"
# #pkgs.logging.basicConfig(level=pkgs.logging.DEBUG, style="{", format=format_str)
@click.group()
def main():
pkgs.logging.basicConfig(level=pkgs.logging.DEBUG)
def eprint(*args, **kwargs):
print(*args, **{"file": pkgs.sys.stderr, **kwargs})
def exc(argv, **kwargs):
eprint("$", *argv)
command = pkgs.subprocess.run(argv, **kwargs)
if command.returncode != 0:
logger.error("subprocess with terminated with non-zero return code.")
eprint("", *argv)
exit(command.returncode)
if command.stdout is not None:
return command.stdout.decode("utf-8")
return ""
def exc_piped(argv, **kwargs):
eprint("$", *argv)
return pkgs.subprocess.Popen(argv, **kwargs)
def clean_line(prev_line, line):
line = line.rstrip()
if pkgs.re.match(r"^Warning: identifier \w+ rebound.$", line) or prev_line is None:
return None
return prev_line
def run_proverif(file, log_file, extra_args=[]):
params = ["proverif", "-test", *extra_args, file]
logger.debug(params)
process = exc_piped(
params,
stderr=pkgs.subprocess.PIPE,
stdout=pkgs.subprocess.PIPE,
text=True,
bufsize=1,
)
try:
prev_line = None
with open(log_file, "a") as log:
for line in process.stdout:
log.write(line)
cleaned_line = clean_line(prev_line, line)
prev_line = line
if cleaned_line is not None:
yield cleaned_line
if prev_line is not None:
yield prev_line
except Exception as e:
# When does this happen? Should the error even be ignored? Metaverif should probably just abort here, right? --karo
logger.error(f"Proverif generated an exception with {params}: {e}")
exit(1)
finally:
process.stdout.close()
return_code = process.wait()
if return_code != 0:
logger.error(
f"Proverif exited with a non-zero error code {params}: {return_code}"
)
if prev_line is not None:
logger.error(f"Last line of log file {log_file}:")
logger.error(f"> {prev_line.rstrip()}")
exit(return_code)
def cpp(file, cpp_prep):
logger.debug(f"_cpp: {file}, {cpp_prep}")
file_path = pkgs.pathlib.Path(file)
dirname = file_path.parent
cwd = pkgs.pathlib.Path.cwd()
params = ["cpp", "-P", f"-I{dirname}", file, "-o", cpp_prep]
return exc(params, stderr=pkgs.sys.stderr)
def awk(repo_path, cpp_prep, awk_prep):
params = [
"awk",
"-f",
str(pkgs.os.path.join(repo_path, "marzipan/marzipan.awk")),
cpp_prep,
]
with open(awk_prep, "w") as file:
exc(params, stderr=pkgs.sys.stderr, stdout=file)
file.write("\nprocess main")
def pretty_output_line(prefix, mark, color, text):
content = f"{mark} {text}"
console.print(prefix, style="grey42", end="", no_wrap=True)
console.print(content, style=color)
def pretty_output_init(file_path):
expected = []
descs = []
with open(file_path, "r") as file:
content = file.read()
# Process lemmas first
result = pkgs.re.findall(r"@(lemma)(?=\s+\"([^\"]*)\")", content)
if result:
# The regex only returns lemmas. For lemmas, we always expect the result 'true' from ProVerif.
expected.extend([True for _ in range(len(result))])
descs.extend([e[1] for e in result])
# Then process regular queries
result = pkgs.re.findall(r'@(query|reachable)(?=\s+"[^\"]*")', content)
if result:
# For queries, we expect 'true' from ProVerif, for reachable, we expect 'false'.
expected.extend([e == "@query" for e in result])
reachable_result = pkgs.re.findall(
r'@(query|reachable)\s+"([^\"]*)"', content
)
descs.extend([e[1] for e in reachable_result])
ta = pkgs.time.time()
res = 0
ctr = 0
return (ta, res, ctr, expected, descs)
def pretty_output_step(file_path, line, expected, descs, res, ctr, ta):
tz = pkgs.time.time()
# Output from ProVerif contains a trailing newline, which we do not have in the expected output. Remove it for meaningful matching.
outp_clean_raw = line.rstrip()
if outp_clean_raw == "true":
outp_clean = True
elif outp_clean_raw == "false":
outp_clean = False
else:
outp_clean = outp_clean_raw
if outp_clean == expected[ctr]:
pretty_output_line(f"{int(tz - ta)}s ", "", "green", descs[ctr])
else:
res = 1
pretty_output_line(f"{int(tz - ta)}s ", "", "red", descs[ctr])
ctr += 1
ta = tz
return (res, ctr, ta)
def pretty_output(file_path):
(ta, res, ctr, expected, descs) = pretty_output_init(file_path)
for line in pkgs.sys.stdin:
(res, ctr, ta) = pretty_output_step(
file_path, line, expected, descs, res, ctr, ta
)
def get_target_dir(path, output):
if output is not None and not output == "":
return pkgs.pathlib.Path(output)
else:
return pkgs.os.path.join(path, target_subdir)
@main.command()
@click.option("--output", "output", required=False)
@click.argument("repo_path")
def analyze(repo_path, output):
target_dir = get_target_dir(repo_path, output)
pkgs.os.makedirs(target_dir, exist_ok=True)
entries = []
analysis_dir = pkgs.os.path.join(repo_path, "analysis")
full_paths = sorted(pkgs.glob.glob(str(analysis_dir) + "/*.entry.mpv"))
entries.extend(full_paths)
#modelnames = [pkgs.os.path.basename(path).replace('.entry.mpv', '') for path in full_paths]
#max_length = max(len(modelname) for modelname in modelnames) if modelnames else 0
#set_logging_format(max_length)
with pkgs.concurrent.futures.ProcessPoolExecutor() as executor:
futures = {
executor.submit(metaverif, repo_path, target_dir, entry): entry
for entry in entries
}
for future in pkgs.concurrent.futures.as_completed(futures):
cmd = futures[future]
logger.info(f"Metaverif {cmd} finished.")
print("all processes finished.")
@main.command()
@click.option("--output", "output", required=False)
@click.argument("repo_path")
def clean(repo_path, output):
cleans_failed = 0
target_dir = get_target_dir(repo_path, output)
if pkgs.os.path.isdir(target_dir):
for filename in pkgs.os.listdir(target_dir):
file_path = pkgs.os.path.join(target_dir, filename)
if pkgs.os.path.isfile(file_path) and pkgs.os.path.splitext(file_path)[
1
] in [".pv", ".log"]:
try:
pkgs.os.remove(file_path)
except Exception as e:
print(f"Error deleting {file_path}: {str(e)}")
cleans_failed += 1
if cleans_failed > 0:
print(f"{cleans_failed} could not be deleted.")
exit(1)
def metaverif(repo_path, tmpdir, file):
# Extract the name using regex
name_match = pkgs.re.search(r"([^/]*)(?=\.entry\.mpv)", file)
if name_match:
name = name_match.group(0) # Get the matched name
set_logging_logrecordfactory(name)
logger.info(f"Start metaverif on {file}")
# Create the file paths
cpp_prep = pkgs.os.path.join(tmpdir, f"{name}.i.pv")
awk_prep = pkgs.os.path.join(tmpdir, f"{name}.o.pv")
# Output the results
print(f"Name: {name}")
print(f"CPP Prep Path: {cpp_prep}")
print(f"AWK Prep Path: {awk_prep}")
cpp(file, cpp_prep)
awk(repo_path, cpp_prep, awk_prep)
log_file = pkgs.os.path.join(tmpdir, f"{name}.log")
ta, res, ctr, expected, descs = pretty_output_init(cpp_prep)
with open(log_file, "a") as log:
generator = run_proverif(awk_prep, log_file)
for line in generator:
# parse-result-line:
match = pkgs.re.search(r"^RESULT .* \b(true|false)\b\.$", line)
if match:
result = match.group(1)
# pretty-output:
res, ctr, ta = pretty_output_step(
cpp_prep, result, expected, descs, res, ctr, ta
)
else:
logger.error(
f"No match found for the filename {file}: extension should be .mpv"
)
exit(1)
@main.command()
@click.argument("file_path")
def parse(file_path):
try:
parse_main(file_path)
except pkgs.lark.exceptions.UnexpectedCharacters as e:
logger.error(f"Error {type(e).__name__} parsing {file_path}: {e}")
if __name__ == "__main__":
main()

View File

@@ -1,104 +0,0 @@
#!/usr/bin/env bash
exc() {
echo >&2 "\$" "$@"
"$@"
}
run_proverif() {
local file; file="$1"; shift
local log; log="$1"; shift # intentionally unused
exc rosenpass-marzipan run-proverif "${file}" "${@}"
}
clean_warnings() {
exc rosenpass-marzipan clean-warnings
}
color_red='red'
color_green='green'
color_gray='gray'
color_clear=''
checkmark="✔"
cross="❌"
pretty_output() {
exc rosenpass-marzipan pretty-output "${@}"
}
metaverif() {
local file; file="$1"; shift
local name; name="$(echo "${file}" | grep -Po '[^/]*(?=\.mpv)')"
local cpp_prep; cpp_prep="${tmpdir}/${name}.i.pv"
local awk_prep; awk_prep="${tmpdir}/${name}.o.pv"
exc rosenpass-marzipan cpp ${file} ${cpp_prep}
exc rosenpass-marzipan awk-prep ${cpp_prep} ${awk_prep}
local log; log="${tmpdir}/${name}.log"
{
run_proverif "${awk_prep}" "$@" \
| clean_warnings \
| tee "${log}" \
| exc rosenpass-marzipan parse-result-line \
| pretty_output "${cpp_prep}"
} || {
echo "TODO: Commented out some debug output"
#if ! grep -q "^Verification summary" "${log}"; then
# echo -ne "\033[0\r"
# cat "${log}"
#fi
}
}
analyze() {
mkdir -p "${tmpdir}"
entries=()
readarray -t -O "${#entries[@]}" entries < <(
find analysis -iname '*.entry.mpv' | sort)
local entry
local procs; procs=()
for entry in "${entries[@]}"; do
echo "call metaverif"
# TODO: commented out for testing
#exc rosenpass-marzipan metaverif "${tmpdir}" "${entry}" >&2 & procs+=("$!")
exc rosenpass-marzipan metaverif "${tmpdir}" "${entry}" >&2
done
# TODO: commented out for testing
# for entry in "${procs[@]}"; do
# exc wait -f "${entry}"
# done
}
err_usage() {
echo >&1 "USAGE: ${0} analyze PATH"
echo >&1 "The script will cd into PATH and continue there."
exit 1
}
main() {
set -e -o pipefail
local cmd="$1"; shift || err_usage
local dir="$1"; shift || err_usage
cd -- "${dir}"
tmpdir="target/proverif"
echo "call main"
case "${cmd}" in
analyze) analyze ;;
clean_warnings) clean_warnings ;;
*) err_usage
esac
}
# Do not execute main if sourced
(return 0 2>/dev/null) || main "$@"

View File

@@ -1,468 +0,0 @@
import sys
from lark import Lark, Token, Transformer, exceptions, tree
# taken from Page 17 in the ProVerif manual
# At the moment, we do not reject a ProVerif model that uses reserved words as identifier,
# because this caused problems with the LARK grammar. We plan to check this in a later
# processing step.
reserved_words = [
"among",
"axiom",
"channel",
"choice",
"clauses",
"const",
"def",
"diff",
"do",
"elimtrue",
"else",
"equation",
"equivalence", # no rule yet (this is CryptoVerif-specific)
"event",
"expand",
"fail",
"for",
"forall",
"foreach",
"free",
"fun",
"get",
"if",
"implementation", # no rule yet (this is CryptoVerif-specific)
"in",
"inj-event",
"insert",
"lemma",
"let",
"letfun",
"letproba",
"new",
"noninterf",
"noselect",
"not",
"nounif",
"or",
"otherwise",
"out",
"param",
"phase",
"pred",
"proba",
"process",
"proof",
"public_vars",
"putbegin",
"query",
"reduc",
"restriction",
"secret",
"select",
"set",
"suchthat",
"sync",
"table",
"then",
"type",
"weaksecret",
"yield",
]
ident_regex = (
"/^" + "".join(f"(?!{w}$)" for w in reserved_words) + "[a-zA-Z][a-zA-Z0-9À-ÿ'_]*/"
)
proverif_grammar = Lark(
grammar="""
PROCESS: "process"
EQUIVALENCE: "equivalence"
start: decl* PROCESS process | decl* EQUIVALENCE process process
YIELD: "yield"
channel: CHANNEL
CHANNEL: "channel"
"""
+ "IDENT: /[a-zA-Z][a-zA-Z0-9À-ÿ'_]*/"
+ """
ZERO: "0"
INFIX: "||"
| "&&"
| "="
| "<>"
| "<="
| ">="
| "<"
| ">"
typeid: channel
| IDENT
_non_empty_seq{x}: x ("," x)*
_maybe_empty_seq{x}: [ _non_empty_seq{x} ]
OPTIONS_FUN_CONST: "data" | "private" | "typeConverter"
OPTIONS_FUN: OPTIONS_FUN_CONST
OPTIONS_CONST: OPTIONS_FUN_CONST
OPTIONS_FREE_REDUC: "private"
OPTIONS_PRED: "memberOptim" | "block"
OPTIONS_PROCESS: "precise"
OPTIONS_QUERY_LEMMA_AXIOM: "noneSat" | "discardSat" | "instantiateSat" | "fullSat" | "noneVerif" | "discardVerif" | "instantiateVerif" | "fullVerif"
OPTIONS_AXIOM: OPTIONS_QUERY_LEMMA_AXIOM
OPTIONS_QUERY_LEMMA: OPTIONS_QUERY_LEMMA_AXIOM | "induction" | "noInduction"
OPTIONS_LEMMA: OPTIONS_QUERY_LEMMA_AXIOM | "maxSubset"
OPTIONS_QUERY: OPTIONS_QUERY_LEMMA | "proveAll"
OPTIONS_QUERY_SECRET: "reachability" | "pv_reachability" | "real_or_random" | "pv_real_or_random" | "/cv_[a-zA-Z0-9À-ÿ'_]*/"
OPTIONS_RESTRICTION: "removeEvents" | "keepEvents" | "keep" # transl_option_lemma_query in pitsyntax.ml
OPTIONS_EQUATION: "convergent" | "linear" # check_equations in pitsyntax.ml
OPTIONS_TYPE: "fixed" | "bounded" | "large" | "nonuniform" | "password" | "size" NAT | "size" NAT "_" NAT | "pcoll" NAT | "small" | "ghost" # from Page 22 in CryptoVerif reference manual
options{idents}: [ "[" _non_empty_seq{idents} "]" ]
process: ZERO
| YIELD
| IDENT [ "(" _maybe_empty_seq{pterm} ")" ]
| bracketed_process
| piped_process
| replicated_process
| replicated_process_bounds
| sample_process
| if_process
| in_process
| out_process
| let_process
| insert_process
| get_process
| event_process
| phase
| sync
bracketed_process: "(" process ")"
piped_process: process "|" process
replicated_process: "!" process
replicated_process_bounds: "!" IDENT "<=" IDENT process
| "foreach" IDENT "<=" IDENT "do" process
sample_process: "new" IDENT [ "[" _maybe_empty_seq{IDENT} "]" ] ":" typeid [";" process]
| IDENT "<-R" typeid [";" process]
let_process: "let" pattern "=" pterm ["in" process [ "else" process ]]
| IDENT [":" typeid] "<-" pterm [";" process]
| "let" typedecl "suchthat" pterm options{OPTIONS_PROCESS} [ "in" process [ "else" process ] ]
if_process: "if" pterm "then" process [ "else" process ]
in_process: "in" "(" pterm "," pattern ")" options{OPTIONS_PROCESS} [ ";" process ]
get_process: "get" IDENT "(" _maybe_empty_seq{pattern} ")" [ "suchthat" pterm ] options{OPTIONS_PROCESS} [ "in" process [ "else" process ] ]
out_process: "out" "(" pterm "," pterm ")" [ ";" process ]
insert_process: "insert" IDENT "(" _maybe_empty_seq{pterm} ")" [ ";" process ]
event_process: "event" IDENT [ "(" _maybe_empty_seq{pterm} ")" ] [ ";" process ]
term: IDENT
| NAT
| "(" _maybe_empty_seq{term} ")"
| IDENT "(" _maybe_empty_seq{term} ")"
| term ( "+" | "-" ) NAT
| NAT "+" term
| term INFIX term
| "not" "(" term ")"
query: gterm ["public_vars" _non_empty_seq{IDENT}] [";" query]
| "secret" IDENT ["public_vars" _non_empty_seq{IDENT}] options{OPTIONS_QUERY_SECRET} [";" query]
| "putbegin" "event" ":" _non_empty_seq{IDENT} [";" query] // Opportunistically left a space between "event" and ":", ProVerif might not accept it with spaces.
| "putbegin" "inj-event" ":" _non_empty_seq{IDENT} [";" query]
lemma: gterm [";" lemma]
| gterm "for" "{" "public_vars" _non_empty_seq{IDENT} "}" [";" lemma]
| gterm "for" "{" "secret" IDENT [ "public_vars" _non_empty_seq{IDENT}] "[real_or_random]" "}" [";" lemma]
gterm: ident_gterm
| fun_gterm
| choice_gterm
| infix_gterm
| arith_gterm
| arith2_gterm
| event_gterm
| injevent_gterm
| implies_gterm
| paren_gterm
| sample_gterm
| let_gterm
ident_gterm: IDENT
fun_gterm: IDENT "(" _maybe_empty_seq{gterm} ")" ["phase" NAT] ["@" IDENT]
choice_gterm: "choice" "[" gterm "," gterm "]"
infix_gterm: gterm INFIX gterm
arith_gterm: gterm ( "+" | "-" ) NAT
arith2_gterm: NAT "+" gterm
event_gterm: "event" "(" _maybe_empty_seq{gterm} ")" ["@" IDENT]
injevent_gterm: "inj-event" "(" _maybe_empty_seq{gterm} ")" ["@" IDENT]
implies_gterm: gterm "==>" gterm
paren_gterm: "(" _maybe_empty_seq{gterm} ")"
sample_gterm: "new" IDENT [ "[" [ gbinding ] "]" ]
let_gterm: "let" IDENT "=" gterm "in" gterm
gbinding: "!" NAT "=" gterm [";" gbinding]
| IDENT "=" gterm [";" gbinding]
nounifdecl: "let" IDENT "=" gformat "in" nounifdecl
| IDENT ["(" _maybe_empty_seq{gformat} ")" ["phase" NAT]]
gformat: IDENT
| "*" IDENT
| IDENT "(" _maybe_empty_seq{gformat} ")"
| "choice" "[" gformat "," gformat "]"
| "not" "(" _maybe_empty_seq{gformat} ")"
| "new" IDENT [ "[" [ fbinding ] "]" ]
| "let" IDENT "=" gformat "in" gformat
fbinding: "!" NAT "=" gformat [";" fbinding]
| IDENT "=" gformat [";" fbinding]
nounifoption: "hypothesis"
| "conclusion"
| "ignoreAFewTimes"
| "inductionOn" "=" IDENT
| "inductionOn" "=" "{" _non_empty_seq{IDENT} "}"
pterm: IDENT
| NAT
| "(" _maybe_empty_seq{pterm} ")"
| IDENT "(" _maybe_empty_seq{pterm} ")"
| choice_pterm
| pterm ("+" | "-") NAT
| NAT "+" pterm
| pterm INFIX pterm
| not_pterm
| sample_pterm
| if_pterm
| let_pterm
| insert_pterm
| get_pterm
| event_pterm
choice_pterm: "choice[" pterm "," pterm "]"
if_pterm: "if" pterm "then" pterm [ "else" pterm ]
not_pterm: "not" "(" pterm ")"
let_pterm: "let" pattern "=" pterm "in" pterm [ "else" pterm ]
| IDENT [":" typeid] "<-" pterm ";" pterm
| "let" typedecl "suchthat" pterm "in" pterm [ "else" pterm ]
sample_pterm: "new" IDENT [ "[" _maybe_empty_seq{IDENT} "]" ] ":" typeid [";" pterm]
| IDENT "<-R" typeid [";" pterm]
insert_pterm: "insert" IDENT "(" _maybe_empty_seq{pterm} ")" ";" pterm
event_pterm: "event" IDENT [ "(" _maybe_empty_seq{pterm} ")" ] ";" pterm
get_pterm: "get" IDENT "(" _maybe_empty_seq{pattern} ")" [ "suchthat" pterm ] options{OPTIONS_PROCESS} [ "in" pterm [ "else" pterm ] ]
pattern: IDENT [":" typeid]
| "_" [ ":" typeid ]
| NAT
| pattern "+" NAT
| NAT "+" pattern
| "(" _maybe_empty_seq{pattern} ")"
| IDENT "(" _maybe_empty_seq{pattern} ")"
| "=" pterm
mayfailterm: term
| "fail"
mayfailterm_seq: "(" _non_empty_seq{mayfailterm} ")"
typedecl: _non_empty_seq{IDENT} ":" typeid [ "," typedecl ]
failtypedecl: _non_empty_seq{IDENT} ":" typeid [ "or fail" ] [ "," failtypedecl ]
decl: type_decl
| channel_decl
| free_decl
| const_decl
| fun_decl
| letfun_decl
| reduc_decl
| fun_reduc_decl
| equation_decl
| pred_decl
| table_decl
| let_decl
| set_settings_decl
| event_decl
| query_decl
| axiom_decl
| restriction_decl
| lemma_decl
| noninterf_decl
| weaksecret_decl
| not_decl
| select_decl
| noselect_decl
| nounif_decl
| elimtrue_decl
| clauses_decl
| module_decl
#| param_decl
#| proba_decl
#| letproba_decl
#| proof_decl
#| def_decl
#| expand_decl
type_decl: "type" IDENT options{OPTIONS_TYPE} "."
channel_decl: "channel" _non_empty_seq{IDENT} "."
free_decl: "free" _non_empty_seq{IDENT} ":" typeid options{OPTIONS_FREE_REDUC} "."
const_decl: "const" _non_empty_seq{IDENT} ":" typeid options{OPTIONS_FUN_CONST} "."
fun_decl: "fun" IDENT "(" _maybe_empty_seq{typeid} ")" ":" typeid options{OPTIONS_FUN_CONST} "."
letfun_decl: "letfun" IDENT [ "(" [ typedecl ] ")" ] "=" pterm "."
reduc_decl: "reduc" eqlist options{OPTIONS_FREE_REDUC} "."
fun_reduc_decl: "fun" IDENT "(" _maybe_empty_seq{typeid} ")" ":" typeid "reduc" mayfailreduc options{OPTIONS_FUN_CONST} "."
equation_decl: "equation" eqlist options{OPTIONS_EQUATION} "."
pred_decl: "pred" IDENT [ "(" [ _maybe_empty_seq{typeid} ] ")" ] options{OPTIONS_PRED} "."
table_decl: "table" IDENT "(" _maybe_empty_seq{typeid} ")" "."
let_decl: "let" IDENT [ "(" [ typedecl ] ")" ] "=" process "."
BOOL : "true" | "false"
NONE: "none"
FULL: "full"
ALL: "all"
FUNC: IDENT
ignoretype_options: BOOL | ALL | NONE | "attacker"
boolean_settings_names: "privateCommOnPublicTerms"
| "rejectChoiceTrueFalse"
| "rejectNoSimplif"
| "allowDiffPatterns"
| "inductionQueries"
| "inductionLemmas"
| "movenew"
| "movelet"
| "stopTerm"
| "removeEventsForLemma"
| "simpEqAll"
| "eqInNames"
| "preciseLetExpand"
| "expandSimplifyIfCst"
| "featureFuns"
| "featureNames"
| "featurePredicates"
| "featureEvents"
| "featureTables"
| "featureDepth"
| "featureWidth"
| "simplifyDerivation"
| "abbreviateDerivation"
| "explainDerivation"
| "unifyDerivation"
| "reconstructDerivation"
| "displayDerivation"
| "traceBacktracking"
| "interactiveSwapping"
| "color"
| "verboseLemmas"
| "abbreviateClauses"
| "removeUselessClausesBeforeDisplay"
| "verboseEq"
| "verboseDestructors"
| "verboseTerm"
| "verboseStatistics"
| "verboseRules"
| "verboseBase"
| "verboseRedundant"
| "verboseCompleted"
| "verboseGoalReachable"
_decl_pair{name, value}: "set" name "=" value "."
set_settings_boolean_decl: _decl_pair{boolean_settings_names, BOOL}
ignore_types_values: BOOL | "all" | "none" | "attacker"
simplify_process_values: BOOL | "interactive"
precise_actions_values: BOOL | "trueWithoutArgsInNames"
redundant_hyp_elim_values: BOOL | "beginOnly"
reconstruct_trace_values: BOOL | NAT | "yes" | "no" # TODO(blipp): check whether yes/no is always allowed for BOOL
attacker_values: "active" | "passive"
key_compromise_values: "none" | "approx" | "strict"
predicates_implementable: "check" | "nocheck"
application_values: "instantiate" | "full" | "none" | "discard"
max_values: "none" | "n"
sel_fun_values: "TermMaxsize" | "Term"| "NounifsetMaxsize" | "Nounifset"
redundancy_elim_values: "best" | "simple" | "no"
nounif_ignore_a_few_times_values: "none" | "auto" | "all"
nounif_ignore_ntimes_values: "n"
trace_display_values: "short" | "long" | "none"
verbose_clauses_values: "none" | "explained" | "short"
set_settings_decl: set_settings_boolean_decl
| _decl_pair{"ignoreTypes", ignore_types_values}
| _decl_pair{"simplifyProcess", simplify_process_values}
| _decl_pair{"preciseActions", precise_actions_values}
| _decl_pair{"redundantHypElim", redundant_hyp_elim_values}
| _decl_pair{"reconstructTrace", reconstruct_trace_values}
| _decl_pair{"attacker", attacker_values}
| _decl_pair{"keyCompromise", key_compromise_values}
| _decl_pair{"predicatesImplementable", predicates_implementable}
| _decl_pair{"saturationApplication", application_values}
| _decl_pair{"verificationApplication", application_values}
| _decl_pair{"maxDepth", max_values}
| _decl_pair{"maxHyp", max_values}
| _decl_pair{"selFun", sel_fun_values}
| _decl_pair{"redundancyElim", redundancy_elim_values}
| _decl_pair{"nounifIgnoreAFewTimes", nounif_ignore_a_few_times_values}
| _decl_pair{"nounifIgnoreNtimes", nounif_ignore_ntimes_values}
| _decl_pair{"traceDisplay", trace_display_values}
| _decl_pair{"verboseClauses", verbose_clauses_values}
| set_strategy
| set_symb_order
_swap_strategy_seq{x}: x ("->" x)*
set_strategy: "set" "swapping" "=" _swap_strategy_seq{TAG} "."
_symb_ord_seq{x}: x (">" x)*
set_symb_order: "set" "symbOrder" "=" _symb_ord_seq{FUNC} "."
event_decl: "event" IDENT ["(" _maybe_empty_seq{typeid} ")"] "."
query_decl: "query" [ typedecl ";"] query options{OPTIONS_QUERY} "."
axiom_decl: "axiom" [ typedecl ";"] lemma options{OPTIONS_AXIOM} "."
restriction_decl: "restriction" [ typedecl ";"] lemma options{OPTIONS_RESTRICTION} "."
lemma_decl: "lemma" [ typedecl ";"] lemma options{OPTIONS_LEMMA} "."
noninterf_decl: "noninterf" [ typedecl ";"] _maybe_empty_seq{nidecl} "."
weaksecret_decl: "weaksecret" IDENT "."
not_decl: "not" [ typedecl ";"] gterm "."
INT: NAT | "-" NAT
select_decl: "select" [ typedecl ";"] nounifdecl [ "/" INT ] [ "[" _non_empty_seq{nounifoption} "]" ] "."
noselect_decl: "noselect" [ typedecl ";"] nounifdecl [ "/" INT ] [ "[" _non_empty_seq{nounifoption} "]" ] "."
nounif_decl: "nounif" [ typedecl ";"] nounifdecl [ "/" INT ] [ "["_non_empty_seq{nounifoption} "]" ] "."
elimtrue_decl: "elimtrue" [ failtypedecl ";" ] term "."
clauses_decl: "clauses" clauses "."
module_decl: "@module" " " IDENT
# TODO: finish defining these (comes from Cryptoverif)
#param_decl: "param" _non_empty_seq{IDENT} options "."
#proba_decl: "proba" IDENT ["(...)"] options "."
#letproba_decl: "letproba" IDENT ["(...)"] "= ..." "."
#proof_decl: "proof" "{" proof "}"
#def_decl: "def" IDENT "(" _maybe_empty_seq{typeid} ")" "{" decl* "}"
#expand_decl: "expand" IDENT "(" _maybe_empty_seq{typeid} ")" "."
nidecl: IDENT [ "among" "(" _non_empty_seq{term} ")" ]
equality: term "=" term
| "let" IDENT "=" term "in" equality
mayfailequality: IDENT mayfailterm_seq "=" mayfailterm
eqlist: [ "forall" typedecl ";" ] equality [ ";" eqlist ]
clause: term
| term "->" term
| term "<->" term
| term "<=>" term
clauses: [ "forall" failtypedecl ";" ] clause [ ";" clauses ]
mayfailreduc: [ "forall" failtypedecl ";" ] mayfailequality [ "otherwise" mayfailreduc ]
NAT: DIGIT+
phase: "phase" NAT [";" process]
TAG: IDENT
sync: "sync" NAT ["[" TAG "]"] [";" process]
COMMENT: /\(\*(\*(?!\))|[^*])*\*\)/
%import common (WORD, DIGIT, NUMBER, WS) // imports from terminal library
%ignore WS // Disregard spaces in text
%ignore COMMENT
""",
debug=True,
# lexer_callbacks={"COMMENT": comments.append},
)
# COMMENT: /\(\*(\*(?!\))|[^*])*\*\)/
# COMMENT: "(*" /(\*(?!\))|[^*])*/ "*)"
# comment: /\(\*(?:(?!\(\*|\*\)).|(?R))*\*\)/
# TODO Open ProVerif compatibility questions
# TODO * does it allow leading zeros for NAT?
# TODO * tag is not defined? is it ident?
# TODO * are spaces between "event" and ":" allowed?
# TODO * spaces between "nat" and "("? "choice" and "["?
def parsertest(input):
parsetree = proverif_grammar.parse(input)
# tree.pydot__tree_to_png(parsetree, name + ".png")
return parsetree
def parse_main(file_path):
with open(file_path, "r") as f:
content = f.read()
# print(content)
parsertest(content)

View File

@@ -1,130 +0,0 @@
from typing import Callable, Any, Tuple, List, TypeVar
from types import ModuleType as Module
from importlib import import_module
from dataclasses import dataclass
T = TypeVar('T')
def setup_exports() -> Tuple[List[str], Callable[[T], T]]:
__all__ = []
"""
Helper to provide an export() function with little boilerplate.
```
from marzipan.util import setup_exports
(__all__, export) = setup_exports()
```
"""
def export(what: T) -> T:
match what:
case str():
__all__.append(what)
case object(__name__ = name):
__all__.append(name)
case _:
raise TypeError(
f"Unsupported export type `{what}`: Export is neither `str` nor has it an attribute named `__name__`.")
return what
return (__all__, export)
(__all__, export) = setup_exports()
export(setup_exports)
@export
def rename(name: str) -> Callable[[T], T]:
def rename_impl(v: T) -> T:
v.__name__ = name
return v
return rename_impl
@export
def attempt(fn):
# TODO: Documentation tests
"""
Call a function returning a tuple of (result, exception).
The following example uses safe_call to implement a checked_divide
function that returns None if the division by zero is caught.
```python
try_divide = attempt(lambda a, b: a/b)
def checked_divide(a, b):
match try_divide(a, b):
case (result, None):
return result
case (None, ZeroDivisionError()):
return None
case _:
raise RuntimeError("Unreachable")
assert(checked_divide(1, 0) == None)
assert(checked_divide(0, 1) == 0)
assert(checked_divide(1, 1) == 1)
```
"""
def retfn(*args, **kwargs):
try:
return (fn(*args, **kwargs), None)
except Exception as e:
return (None, e)
retfn.__name__ = f"try_{fn.__name__}"
return retfn
@export
def scoped(fn: Callable[[], Any]) -> Any:
"""
Scoped variable assignment.
Just an alias for `call`. Use as a decorator to immediately call a function,
assigning the return value to the function name.
"""
return fn()
@export
def try_import(name : str) -> Tuple[Module | None, Exception | None]:
return attempt(import_module)(name)
@dataclass(frozen=True)
class Pkgs:
__mod__: Module | None
__prefix__: str | None
def __get__(self, k: str):
return getattr(self, k)
def __getattribute__(self, k: str):
match k:
case "__mod__" | "__prefix__" | "__class__":
# Access the underlying module value
return super().__getattribute__(k)
match self:
case Pkgs(None, None):
# Import package from root
return Pkgs(import_module(k), k)
# Try importing a subpackage
name = f"{self.__prefix__}.{k}"
match try_import(name):
case (child, None):
# Imported subpackage
return Pkgs(child, name)
case (_, ModuleNotFoundError()):
# No such module; access module property instead
return getattr(self.__mod__, k)
case (_, err):
# Unknown error, pass error on
raise err
@scoped
@export
def pkgs() -> Pkgs:
"""
Global package scope.
`pkgs.marzipan` imports the package `marzipan`
"""
return Pkgs(None, None)

View File

@@ -1,265 +0,0 @@
#!/usr/bin/env python3
# Below is a **more “Pythonic”** rewrite of the original AWKtoPython translator.
# The logic is exactly the same the same error messages, line numbers and exit
# codes but the code is organized into small, reusable functions, uses
# `dataclasses`, type hints, `Path.read_text()`, `re.sub()` and other idiomatic
# constructs. It is also easier to read and to extend.
"""
py_awk_translator.py
A linebyline preprocessor that implements the same behaviour as the
original AWK script you posted (handling @module, @alias, @longalias,
privatevariable expansion, @query/@reachable/@lemma checks and tokenwise
alias substitution).
Usage
python3 py_awk_translator.py file1.pv file2.pv
# or
cat file.pv | python3 py_awk_translator.py
"""
from __future__ import annotations
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, Iterable
# ----------------------------------------------------------------------
# Helper utilities
# ----------------------------------------------------------------------
TOKEN_RE = re.compile(r"[0-9A-Za-z_']")
def is_token_char(ch: str) -> bool:
"""Return True if *ch* can be part of an identifier token."""
return bool(TOKEN_RE.fullmatch(ch))
def die(msg: str, fname: str, lineno: int) -> None:
"""Print an error to stderr and exit with status1 (exactly like AWK)."""
sys.stderr.write(f"{fname}:{lineno}: {msg}\n")
sys.exit(1)
# ----------------------------------------------------------------------
# Core translator holds the mutable state that the AWK script kept in
# global variables.
# ----------------------------------------------------------------------
@dataclass
class Translator:
"""Collects state while processing a file linebyline."""
# final output buffer
out: list[str] = field(default_factory=list)
# current @module name (used when expanding "~")
module: str = ""
# simple oneline aliases: name → replacement text
aliases: Dict[str, str] = field(default_factory=dict)
# multiline alias handling
long_name: str = ""
long_value: str = ""
# error flag mirrors the AWK variable `err`
err: int = 0
# ------------------------------------------------------------------
# Public entry point for a single line
# ------------------------------------------------------------------
def process(self, raw: str, fname: str, lineno: int) -> None:
"""Apply all transformation rules to *raw* and store the result."""
line = raw.rstrip("\n") # keep a copy for error messages
original = line # keep the untouched line for later
# --------------------------------------------------------------
# 1⃣ @module
# --------------------------------------------------------------
if line.startswith("@module"):
parts = line.split(maxsplit=1)
self.module = parts[1] if len(parts) > 1 else ""
self.aliases.clear()
line = ""
# --------------------------------------------------------------
# 2⃣ @alias
# --------------------------------------------------------------
elif line.startswith("@alias"):
for token in line.split()[1:]:
if "=" in token:
name, value = token.split("=", 1)
self.aliases[name] = value
line = ""
# --------------------------------------------------------------
# 3⃣ @long-aliasend
# --------------------------------------------------------------
elif line.startswith("@long-alias-end"):
if not self.long_name:
die("Long alias not started", fname, lineno)
# collapse multiple spaces → single space, strip trailing space
self.long_value = re.sub(r" +", " ", self.long_value).strip()
self.aliases[self.long_name] = self.long_value
self.long_name = self.long_value = ""
line = ""
# --------------------------------------------------------------
# 4⃣ @long-alias (start)
# --------------------------------------------------------------
elif line.startswith("@long-alias"):
parts = line.split(maxsplit=1)
self.long_name = parts[1] if len(parts) > 1 else ""
self.long_value = ""
line = ""
# --------------------------------------------------------------
# 5⃣ PRIVATE__ detection (illegal use of "~")
# --------------------------------------------------------------
elif "PRIVATE__" in line:
die(
"Used private variable without ~:\n\n"
f" {lineno} > {original}",
fname,
lineno,
)
# --------------------------------------------------------------
# 6⃣ @query / @reachable / @lemma validation
# --------------------------------------------------------------
elif re.search(r"@(query|reachable|lemma)", line):
if not re.search(r'@(query|reachable|lemma)\s+"[^"]*"', line):
die(
"@query or @reachable statement without parameter:\n\n"
f" {lineno} > {original}",
fname,
lineno,
)
# replace the quoted part with blanks (preserve line length)
m = re.search(r'@(query|reachable|lemma)\s+"[^"]*"', line)
start, end = m.span()
line = line[:start] + " " * (end - start) + line[end:]
# --------------------------------------------------------------
# 7⃣ Expand "~" to the privatevariable prefix
# --------------------------------------------------------------
if "~" in line:
line = line.replace("~", f"PRIVATE__{self.module}__")
# --------------------------------------------------------------
# 8⃣ Tokenwise alias substitution (the long AWK loop)
# --------------------------------------------------------------
line = self._expand_aliases(line)
# --------------------------------------------------------------
# 9⃣ Accumulate a multiline alias, if we are inside one
# --------------------------------------------------------------
if self.long_name:
self.long_value += line + " "
line = "" # the line itself must not appear in output
# --------------------------------------------------------------
# 🔟 Store the (possibly empty) line for final output
# --------------------------------------------------------------
self.out.append(line + "\n")
# ------------------------------------------------------------------
# Helper that implements the tokenwise alias replacement
# ------------------------------------------------------------------
def _expand_aliases(self, text: str) -> str:
"""Replace every wholetoken alias in *text* with its value."""
i = 0
result = ""
while i < len(text):
# a = previous char, c = current char
a = text[i - 1] if i > 0 else ""
c = text[i]
# If we are already inside a token, just move forward
if i > 0 and is_token_char(a):
i += 1
continue
# If the current char does not start a token, skip it
if not is_token_char(c):
i += 1
continue
# ----------------------------------------------------------
# At a token boundary try to match any alias
# ----------------------------------------------------------
matched = False
for name, value in self.aliases.items():
if text.startswith(name, i):
after = text[i + len(name) : i + len(name) + 1]
if is_token_char(after): # name is only a prefix
continue
# Alias matches replace it
result += text[:i] + value
text = text[i + len(name) :] # continue scanning the suffix
i = 0
matched = True
break
if not matched:
i += 1
return result + text
# ------------------------------------------------------------------
# Finalisation
# ------------------------------------------------------------------
def finish(self) -> None:
"""Write the accumulated output to stdout (unless an error occurred)."""
if self.err == 0:
sys.stdout.write("".join(self.out))
# ----------------------------------------------------------------------
# Commandline driver
# ----------------------------------------------------------------------
def _process_path(path: Path, translator: Translator) -> None:
"""Read *path* linebyline and feed it to *translator*."""
for lineno, raw in enumerate(path.read_text(encoding="utf-8").splitlines(True), start=1):
translator.process(raw, str(path), lineno)
def main() -> None:
translator = Translator()
# No file arguments → read from stdin (named "<stdin>")
if len(sys.argv) == 1:
# stdin may contain multiple lines; we treat it as a single “virtual”
# file so that line numbers are still correct.
for lineno, raw in enumerate(sys.stdin, start=1):
translator.process(raw, "<stdin>", lineno)
else:
for name in sys.argv[1:]:
p = Path(name)
if not p.is_file():
sys.stderr.write(f"File not found: {name}\n")
sys.exit(1)
_process_path(p, translator)
translator.finish()
if __name__ == "__main__":
main()
## What makes this version more Pythonic?
# | Aspect | Original style | Refactored style |
# |--------|----------------|------------------|
# | **State handling** | Global variables (`buf`, `module`, …) | `@dataclass Translator` encapsulates all mutable state |
# | **Regularexpression reuse** | Recompiled on every call (`match`, `gsub`) | Compiled once (`TOKEN_RE`) and reused |
# | **String manipulation** | Manual `substr`, concatenation in loops | Slicing, `str.replace`, `re.sub` for clarity |
# | **Loop logic** | `for (i=1; i<length($0); i+=1)` with many manual index tricks | A single `while` loop with earlycontinue guards; the inner aliassearch is a clean `for name, value in self.aliases.items()` |
# | **Error handling** | `print(... > "/dev/stderr")` and `exit(1)` | Dedicated `die()` helper that writes to `stderr` and exits |
# | **File I/O** | Manual `while (getline ...)` in AWK → `for` over `sys.stdin` / `Path.read_text()` | Uses `Path.read_text()` and `enumerate` for line numbers |
# | **Readability** | Mixed AWKstyle comments, oneliner `if` statements | Docstrings, section comments, type hints, and small helper methods |
# | **Extensibility** | Adding a new rule required editing a monolithic block | New rules can be added as separate `elif` blocks or new methods without touching the core loop |
# The script can be saved as `py_awk_translator.py`, made executable (`chmod +x py_awk_translator.py`), and used exactly like the original AWK program while being easier to maintain and understand.

View File

@@ -1,292 +0,0 @@
#!/usr/bin/env python3
# **Python3 translation of the AWK script**
# Below is a dropin replacement that can be used the same way as the original
# `awk` program (give it one or more file names, or let it read from *stdin*).
# All the logic of the AWK version is kept the only difference is that the
# code is now ordinary, readable Python3.
"""
translate_awk_to_py.py
A linebyline translator for the “@module / @alias / @longalias …”
preprocessor that was originally written in AWK. The behaviour is
identical to the AWK script you posted, including the exact error
messages and exit codes.
Usage
python3 translate_awk_to_py.py file1.pv file2.pv
# or
cat file.pv | python3 translate_awk_to_py.py
The script prints the transformed source to *stdout* and writes any
diagnostic messages to *stderr* (exactly like the AWK version).
"""
import sys
import re
from pathlib import Path
# ----------------------------------------------------------------------
# Helper functions
# ----------------------------------------------------------------------
def istok(ch: str) -> bool:
"""Return True if *ch* is a token character (alnum, '_' or ''')."""
return bool(re.match(r"[0-9a-zA-Z_']", ch))
def error(msg: str, fname: str, lineno: int) -> None:
"""Print an error message to stderr and exit with status 1."""
sys.stderr.write(f"{fname}:{lineno}: {msg}\n")
sys.exit(1)
# ----------------------------------------------------------------------
# Main processing class (keeps the same global state as the AWK script)
# ----------------------------------------------------------------------
class Translator:
def __init__(self):
self.buf = "" # final output buffer
self.module = "" # current @module name
self.err = 0 # error flag (mirrors AWK's)
self.long_alias_name = "" # name of a multiline alias
self.long_alias_value = "" # accumulated value of that alias
self.aliases: dict[str, str] = {} # simple oneline aliases
# ----------------------------------| AWK rule | Python implementation |
# |----------|-----------------------|
# | `BEGIN` block initialise variables | `Translator.__init__` |
# | `@module` line set `module`, clear `aliases` | first `if` in `process_line` |
# | `@alias` line split `name=value` pairs into `aliases` | second `elif` |
# | `@long-alias` / `@long-alias-end` handling | third/fourth `elif` blocks + the `if self.long_alias_name` section |
# | Detection of illegal `PRIVATE__` usage | `elif "PRIVATE__" in orig_line` (the same string that the AWK script would have produced after the `~` replacement) |
# | Validation of `@query|@reachable|@lemma` statements | `elif re.search(r"@(query|reachable|lemma)", …)` |
# | Replacement of `~` with `PRIVATE__<module>__` | `line.replace("~", …)` |
# | Tokenwise alias substitution (the long `for (i=1; …)` loop) | the `while i < len(line): …` loop that restarts from the beginning after each successful replacement |
# | Accumulating the final output in `buf` | `self.buf += line + "\n"` |
# | `END` block print buffer if no error | `Translator.finish()` |
# The script can be saved as `translate_awk_to_py.py`, made executable (`chmod +x translate_awk_to_py.py`) and used exactly like the original AWK program. All error messages, line numbers and exit codes are identical, so any surrounding tooling that expects the AWK behaviour will continue to work.--------------------------------
# Linebyline processing (mirrors the order of the AWK rules)
# ------------------------------------------------------------------
def process_line(self, line: str, fname: str, lineno: int) -> None:
"""Transform *line* according to all the rules."""
# keep the original line for error reporting
orig_line = line.rstrip("\n")
# ------------------------------------------------------------------
# 1) @module
# ------------------------------------------------------------------
if orig_line.startswith("@module"):
parts = orig_line.split()
if len(parts) >= 2:
self.module = parts[1]
else:
self.module = ""
self.aliases.clear()
line = "" # AWK does: $0 = ""
# fall through nothing else on this line matters
# ------------------------------------------------------------------
# 2) @alias
# ------------------------------------------------------------------
elif orig_line.startswith("@alias"):
# everything after the keyword is a list of name=value pairs
for token in orig_line.split()[1:]:
if "=" in token:
name, value = token.split("=", 1)
self.aliases[name] = value
line = ""
# ------------------------------------------------------------------
# 3) @long-alias-end
# ------------------------------------------------------------------
elif orig_line.startswith("@long-alias-end"):
if not self.long_alias_name:
error("Long alias not started", fname, lineno)
# compress multiple spaces to a single space
self.long_alias_value = re.sub(r" +", " ", self.long_alias_value)
self.aliases[self.long_alias_name] = self.long_alias_value.strip()
# reset the temporary variables
self.long_alias_name = ""
self.long_alias_value = ""
line = ""
# ------------------------------------------------------------------
# 4) @long-alias (start of a multiline alias)
# ------------------------------------------------------------------
elif orig_line.startswith("@long-alias"):
parts = orig_line.split()
if len(parts) >= 2:
self.long_alias_name = parts[1]
self.long_alias_value = ""
else:
self.long_alias_name = ""
self.long_alias_value = ""
line = ""
# ------------------------------------------------------------------
# 5) PRIVATE__ detection (illegal use of "~")
# ------------------------------------------------------------------
elif "PRIVATE__" in orig_line:
# The AWK version looks for the literal string PRIVATE__ (which
# appears only after the "~" replacement). We keep the same
# behaviour.
error(
"Used private variable without ~:\n\n"
f" {lineno} > {orig_line}",
fname,
lineno,
)
# ------------------------------------------------------------------
# 6) @query / @reachable / @lemma validation
# ------------------------------------------------------------------
elif re.search(r"@(query|reachable|lemma)", orig_line):
# Must contain a quoted string after the keyword
if not re.search(r'@(query|reachable|lemma)\s+"[^"]*"', orig_line):
error(
"@query or @reachable statement without parameter:\n\n"
f" {lineno} > {orig_line}",
fname,
lineno,
)
# Replace the quoted part with spaces (preserve line length)
m = re.search(r'@(query|reachable|lemma)\s+"[^"]*"', orig_line)
start, end = m.start(), m.end()
pre = orig_line[:start]
mat = orig_line[start:end]
post = orig_line[end:]
mat_spaced = " " * len(mat)
line = pre + mat_spaced + post
# ------------------------------------------------------------------
# 7) Replace "~" with the privatevariable prefix
# ------------------------------------------------------------------
else:
# No special rule matched yet we keep the line asis for now.
line = orig_line
# ------------------------------------------------------------------
# 8) Insert the privatevariable prefix (if any "~" is present)
# ------------------------------------------------------------------
if "~" in line:
line = line.replace("~", f"PRIVATE__{self.module}__")
# ------------------------------------------------------------------
# 9) Alias substitution (tokenwise, exactly like the AWK loop)
# ------------------------------------------------------------------
# The algorithm walks through the line character by character,
# looking for the start of a token. When a token matches a key in
# *self.aliases* it is replaced by the stored value and the scan
# restarts from the beginning of the (now shorter) line.
i = 0
minibuf = ""
while i < len(line):
# a = previous character, c = current character
a = line[i - 1] if i > 0 else ""
c = line[i]
# If we are already inside a token, just move on
if i > 0 and istok(a):
i += 1
continue
# If the current character does NOT start a token, skip it
if not istok(c):
i += 1
continue
# --------------------------------------------------------------
# We are at a token boundary try to match any alias
# --------------------------------------------------------------
matched = False
for alias, value in self.aliases.items():
klen = len(alias)
token = line[i : i + klen]
after = line[i + klen : i + klen + 1] # char after the token
if token != alias:
continue
if istok(after): # alias is only a prefix of a longer token
continue
# ---- alias matches -------------------------------------------------
matched = True
prefix = line[:i] # everything before the token
suffix = line[i + klen :] # everything after the token
minibuf += prefix + value
line = suffix # continue scanning the suffix
i = 0 # restart from the beginning
break
if not matched:
# No alias matched keep the current character and move on
i += 1
# Append whatever is left of the line after the last replacement
line = minibuf + line
# ------------------------------------------------------------------
# 10) If we are inside a multiline alias, accumulate the line
# ------------------------------------------------------------------
if self.long_alias_name:
self.long_alias_value += line + " "
line = "" # the line itself must not appear in the output
# ------------------------------------------------------------------
# 11) Append the (possibly empty) line to the global buffer
# ------------------------------------------------------------------
self.buf += line + "\n"
# ------------------------------------------------------------------
# Final output
# ------------------------------------------------------------------
def finish(self) -> None:
"""Print the accumulated buffer if no error occurred."""
if self.err == 0:
sys.stdout.write(self.buf)
# ----------------------------------------------------------------------
# Entry point
# ----------------------------------------------------------------------
def main() -> None:
translator = Translator()
# If no file name is given we read from stdin (named "<stdin>")
if len(sys.argv) == 1:
translator.process_line(sys.stdin.read(), "<stdin>", 1)
else:
for fname in sys.argv[1:]:
path = Path(fname)
try:
with path.open(encoding="utf-8") as f:
for lineno, raw in enumerate(f, start=1):
translator.process_line(raw, str(path), lineno)
except FileNotFoundError:
sys.stderr.write(f"File not found: {fname}\n")
sys.exit(1)
translator.finish()
if __name__ == "__main__":
main()
### How the Python version mirrors the AWK script
# | AWK rule | Python implementation |
# |----------|-----------------------|
# | `BEGIN` block initialise variables | `Translator.__init__` |
# | `@module` line set `module`, clear `aliases` | first `if` in `process_line` |
# | `@alias` line split `name=value` pairs into `aliases` | second `elif` |
# | `@long-alias` / `@long-alias-end` handling | third/fourth `elif` blocks + the `if self.long_alias_name` section |
# | Detection of illegal `PRIVATE__` usage | `elif "PRIVATE__" in orig_line` (the same string that the AWK script would have produced after the `~` replacement) |
# | Validation of `@query|@reachable|@lemma` statements | `elif re.search(r"@(query|reachable|lemma)", …)` |
# | Replacement of `~` with `PRIVATE__<module>__` | `line.replace("~", …)` |
# | Tokenwise alias substitution (the long `for (i=1; …)` loop) | the `while i < len(line): …` loop that restarts from the beginning after each successful replacement |
# | Accumulating the final output in `buf` | `self.buf += line + "\n"` |
# | `END` block print buffer if no error | `Translator.finish()` |
# The script can be saved as `translate_awk_to_py.py`, made executable (`chmod +x translate_awk_to_py.py`) and used exactly like the original AWK program. All error messages, line numbers and exit codes are identical, so any surrounding tooling that expects the AWK behaviour will continue to work.

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env bash
shopt -s nullglob globstar
proverif_repo=$1
# Prerequisites:
# * built ProVerif
# * ran ./test (and aborted it) such that the preparation scripts have been run
# Test pitype files
for f in $proverif_repo/examples/pitype/**/*.pv; do
[[ $f == *.m4.pv ]] && continue
echo "$f"
nix run .# -- parse "$f"
done
# Test cryptoverif files
for f in $proverif_repo/examples/cryptoverif/**/*.pcv; do
[[ $f == *.m4.pcv ]] && continue
echo "$f"
nix run .# -- parse "$f"
done

View File

@@ -8,7 +8,6 @@ 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.0"
[dependencies] [dependencies]
rosenpass-cipher-traits = { workspace = true } rosenpass-cipher-traits = { workspace = true }

View File

@@ -2,10 +2,11 @@
/// Generate bindings to a liboqs-provided KEM /// Generate bindings to a liboqs-provided KEM
macro_rules! oqs_kem { macro_rules! oqs_kem {
($name:ident, $algo_trait:path) => { ::paste::paste!{ ($name:ident) => { ::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::primitives::{Kem, KemError}; use rosenpass_cipher_traits::Kem;
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 = ""]
@@ -13,7 +14,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::primitives::Kem;"] #[doc = "use rosenpass_cipher_traits::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 = ""]
@@ -22,26 +23,21 @@ 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(), &mut pk);"] #[doc = "MyKem::keygen(sk.secret_mut(), pk.borrow_mut());"]
#[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(), &mut ct, &pk);"] #[doc = "MyKem::encaps(shk_enc.secret_mut(), ct.borrow_mut(), pk.borrow());"]
#[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_mut(), &ct);"] #[doc = "MyKem::decaps(shk_dec.secret_mut(), sk.secret(), ct.borrow());"]
#[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(), shk_dec.secret()) == 0);"] #[doc = "assert!(rosenpass_constant_time::compare(shk_enc.secret_mut(), shk_dec.secret_mut()) == 0);"]
#[doc = "```"] #[doc = "```"]
pub struct [< $name:camel >]; pub enum [< $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
/// ///
@@ -55,8 +51,17 @@ 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<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> for [< $name:camel >] { impl Kem for [< $name:camel >] {
fn keygen(&self, sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), KemError> { type Error = ::std::convert::Infallible;
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 >],
@@ -68,7 +73,10 @@ macro_rules! oqs_kem {
Ok(()) Ok(())
} }
fn encaps(&self, shk: &mut [u8; SHK_LEN], ct: &mut [u8; CT_LEN], pk: &[u8; PK_LEN]) -> Result<(), KemError> { fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
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 >],
@@ -81,7 +89,10 @@ macro_rules! oqs_kem {
Ok(()) Ok(())
} }
fn decaps(&self, shk: &mut [u8; SHK_LEN], sk: &[u8; SK_LEN], ct: &[u8; CT_LEN]) -> Result<(), KemError> { fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
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 >],
@@ -94,16 +105,9 @@ 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 >];
}} }}
} }

View File

@@ -22,8 +22,5 @@ macro_rules! oqs_call {
#[macro_use] #[macro_use]
mod kem_macro; mod kem_macro;
oqs_kem!(kyber_512, rosenpass_cipher_traits::algorithms::KemKyber512); oqs_kem!(kyber_512);
oqs_kem!( oqs_kem!(classic_mceliece_460896);
classic_mceliece_460896,
rosenpass_cipher_traits::algorithms::KemClassicMceliece460896
);

View File

@@ -1,5 +1,6 @@
final: prev: { final: prev: {
# #
### Actual rosenpass software ### ### Actual rosenpass software ###
# #
@@ -26,10 +27,7 @@ final: prev: {
"marzipan(/marzipan.awk)?" "marzipan(/marzipan.awk)?"
"analysis(/.*)?" "analysis(/.*)?"
]; ];
nativeBuildInputs = [ nativeBuildInputs = [ final.proverif final.graphviz ];
final.proverif
final.graphviz
];
CRYPTOVERIF_LIB = final.proverif-patched + "/lib/cryptoverif.pvl"; CRYPTOVERIF_LIB = final.proverif-patched + "/lib/cryptoverif.pvl";
installPhase = '' installPhase = ''
mkdir -p $out mkdir -p $out

View File

@@ -196,13 +196,3 @@ Vadim Lyubashevsky and John M. Schanck and Peter Schwabe and Gregor Seiler and D
type = {NIST Post-Quantum Cryptography Selected Algorithm}, type = {NIST Post-Quantum Cryptography Selected Algorithm},
url = {https://pq-crystals.org/kyber/} url = {https://pq-crystals.org/kyber/}
} }
@misc{SHAKE256,
author = "National Institute of Standards and Technology",
title = "FIPS PUB 202: SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions",
year = {2015},
month = {August},
doi = {10.6028/NIST.FIPS.202}
}

View File

@@ -2,7 +2,6 @@
\usepackage{amssymb} \usepackage{amssymb}
\usepackage{mathtools} \usepackage{mathtools}
\usepackage{fontspec} \usepackage{fontspec}
\usepackage{dirtytalk}
%font fallback %font fallback
\directlua{luaotfload.add_fallback \directlua{luaotfload.add_fallback

View File

@@ -8,15 +8,13 @@ author:
- Lisa Schmidt = {Scientific Illustrator \\url{mullana.de}} - Lisa Schmidt = {Scientific Illustrator \\url{mullana.de}}
- Prabhpreet Dua - Prabhpreet Dua
abstract: | abstract: |
Rosenpass is a post-quantum-secure authenticated key exchange protocol. Its main practical use case is creating post-quantum-secure VPNs by combining WireGuard and Rosenpass. Rosenpass is used to create post-quantum-secure VPNs. Rosenpass computes a shared key, WireGuard (WG) [@wg] uses the shared key to establish a secure connection. Rosenpass can also be used without WireGuard, deriving post-quantum-secure symmetric keys for another application. The Rosenpass protocol builds on “Post-quantum WireGuard” (PQWG) [@pqwg] and improves it by using a cookie mechanism to provide security against state disruption attacks.
In this combination, Rosenpass generates a post-quantum-secure shared key every two minutes that is then used by WireGuard (WG) [@wg] to establish a secure connection. Rosenpass can also be used without WireGuard, providing post-quantum-secure symmetric keys for other applications, as long as the other application accepts a pre-shared key and provides cryptographic security based on the pre-shared key alone. The WireGuard implementation enjoys great trust from the cryptography community and has excellent performance characteristics. To preserve these features, the Rosenpass application runs side-by-side with WireGuard and supplies a new post-quantum-secure pre-shared key (PSK) every two minutes. WireGuard itself still performs the pre-quantum-secure key exchange and transfers any transport data with no involvement from Rosenpass at all.
The Rosenpass protocol builds on “Post-quantum WireGuard” (PQWG) [@pqwg] and improves it by using a cookie mechanism to provide security against state disruption attacks. From a cryptographic perspective, Rosenpass can be thought of as a post-quantum secure variant of the Noise IK[@noise] key exchange. \say{Noise IK} means that the protocol makes both parties authenticate themselves, but that the initiator knows before the protocol starts which other party they are communicating with. There is no negotiation step where the responder communicates their identity to the initiator.
The Rosenpass project consists of a protocol description, an implementation written in Rust, and a symbolic analysis of the protocols security using ProVerif [@proverif]. We are working on a cryptographic security proof using CryptoVerif [@cryptoverif]. The Rosenpass project consists of a protocol description, an implementation written in Rust, and a symbolic analysis of the protocols security using ProVerif [@proverif]. We are working on a cryptographic security proof using CryptoVerif [@cryptoverif].
This document is a guide for engineers and researchers implementing the protocol. This document is a guide for engineers and researchers implementing the protocol; a scientific paper discussing the security properties of Rosenpass is work in progress.
--- ---
\enlargethispage{5mm} \enlargethispage{5mm}
@@ -33,10 +31,10 @@ abstract: |
# Security # Security
Rosenpass inherits most security properties from Post-Quantum WireGuard (PQWG). The security properties mentioned here are covered by the symbolic analysis in the Rosenpass repository. Rosenpass inherits most security properties from Post-Quantum WireGuard (PQWG). The security properties mentioned here are covered by the symbolic analysis in the Rosenpass repository.
## Secrecy ## Secrecy
Three key encapsulations using the keypairs `sski`/`spki`, `sskr`/`spkr`, and `eski`/`epki` provide secrecy (see Section \ref{variables} for an introduction of the variables). Their respective ciphertexts are called `scti`, `sctr`, and `ectr` and the resulting keys are called `spti`, `sptr`, `epti`. A single secure encapsulation is sufficient to provide secrecy. We use two different KEMs (Key Encapsulation Mechanisms; see Section \ref{skem}): Kyber and Classic McEliece. Three key encapsulations using the keypairs `sski`/`spki`, `sskr`/`spkr`, and `eski`/`epki` provide secrecy (see Section \ref{variables} for an introduction of the variables). Their respective ciphertexts are called `scti`, `sctr`, and `ectr` and the resulting keys are called `spti`, `sptr`, `epti`. A single secure encapsulation is sufficient to provide secrecy. We use two different KEMs (Key Encapsulation Mechanisms; see section \ref{skem}): Kyber and Classic McEliece.
## Authenticity ## Authenticity
@@ -66,12 +64,9 @@ Note that while Rosenpass is secure against state disruption, using it does not
All symmetric keys and hash values used in Rosenpass are 32 bytes long. All symmetric keys and hash values used in Rosenpass are 32 bytes long.
### Hash {#hash} ### Hash
A keyed hash function with one 32-byte input, one variable-size input, and one 32-byte output. As keyed hash function we offer two options that can be configured on a peer-basis, with Blake2s being the default: A keyed hash function with one 32-byte input, one variable-size input, and one 32-byte output. As keyed hash function we use the HMAC construction [@rfc_hmac] with BLAKE2s [@rfc_blake2] as the inner hash function.
1. the HMAC construction [@rfc_hmac] with BLAKE2s [@rfc_blake2] as the inner hash function.
2. the SHAKE256 extendable output function (XOF) [@SHAKE256] truncated to a 32-byte output. The result is produced be concatenating the 32-byte input with the variable-size input in this order.
```pseudorust ```pseudorust
hash(key, data) -> key hash(key, data) -> key
@@ -156,18 +151,16 @@ Rosenpass uses two types of ID variables. See Figure \ref{img:HashingTree} for h
The first lower-case character indicates whether the variable is a session ID (`sid`) or a peer ID (`pid`). The final character indicates the role using the characters `i`, `r`, `m`, or `t`, for `initiator`, `responder`, `mine`, or `theirs` respectively. The first lower-case character indicates whether the variable is a session ID (`sid`) or a peer ID (`pid`). The final character indicates the role using the characters `i`, `r`, `m`, or `t`, for `initiator`, `responder`, `mine`, or `theirs` respectively.
### Symmetric Keys {#symmetric-keys} ### Symmetric Keys
Rosenpass uses two symmetric key variables `psk` and `osk` in its interface, and maintains the entire handshake state in a variable called the chaining key.
Rosenpass uses two main symmetric key variables `psk` and `osk` in its interface, and maintains the entire handshake state in a variable called the chaining key.
* `psk`: A pre-shared key that can be optionally supplied as input to Rosenpass. * `psk`: A pre-shared key that can be optionally supplied as input to Rosenpass.
* `osk`: The output shared key, generated by Rosenpass. The main use case is to supply the key to WireGuard for use as its pre-shared key. * `osk`: The output shared key, generated by Rosenpass and supplied to WireGuard for use as its pre-shared key.
* `ck`: The chaining key. This refers to various intermediate keys produced during the execution of the protocol, before the final `osk` is produced. * `ck`: The chaining key.
We mix all key material (e.g. `psk`) into the chaining key and derive symmetric keys such as `osk` from it. We authenticate public values by mixing them into the chaining key; in particular, we include the entire protocol transcript in the chaining key, i.e., all values transmitted over the network.
The protocol allows for multiple `osk`s to be generated; each of these keys is labeled with a domain separator to make sure different key usages are always given separate keys. The domain separator for using Rosenpass and WireGuard together is a token generated using the domain separator sequence `["rosenpass.eu", "wireguard psk"]` (see Fig. \ref{img:HashingTree}), as described in \ref{protocol-extension-wireguard-psk}. Third-parties using Rosenpass-keys for other purposes are asked to define their own protocol-extensions. Standard protocol extensions are described in \ref{protocol-extensions}.
We mix all key material (e.g. `psk`) into the chaining key, and derive symmetric keys such as `osk` from it. We authenticate public values by mixing them into the chaining key; in particular, we include the entire protocol transcript in the chaining key, i.e., all values transmitted over the network.
## Hashes ## Hashes
@@ -179,22 +172,20 @@ Rosenpass uses a cryptographic hash function for multiple purposes:
* Key derivation during and after the handshake * Key derivation during and after the handshake
* Computing the additional data for the biscuit encryption, to provide some privacy for its contents * Computing the additional data for the biscuit encryption, to provide some privacy for its contents
Recall from Section \ref{hash} that rosenpass supports using either BLAKE2s or SHAKE256 as hash function, which can be configured for each peer ID. However, as noted above, rosenpass uses a hash function to compute the peer ID and thus also to access the configuration for a peer ID. This is an issue when receiving an `InitHello`-message, because the correct hash function is not known when a responder receives this message and at the same the responders needs it in order to compute the peer ID and by that also identfy the hash function for that peer. The reference implementation resolves this issue by first trying to derive the peer ID using SHAKE256. If that does not work (i.e. leads to an AEAD decryption error), the reference implementation tries again with BLAKE2s. The reference implementation verifies that the hash function matches the one confgured for the peer. Similarly, if the correct peer ID is not cached when receiving an InitConf message, the reference implementation proceeds in the same manner.
Using one hash function for multiple purposes can cause real-world security issues and even key recovery attacks [@oraclecloning]. We choose a tree-based domain separation scheme based on a keyed hash function the previously introduced primitive `hash` to make sure all our hash function calls can be seen as distinct. Using one hash function for multiple purposes can cause real-world security issues and even key recovery attacks [@oraclecloning]. We choose a tree-based domain separation scheme based on a keyed hash function the previously introduced primitive `hash` to make sure all our hash function calls can be seen as distinct.
\setupimage{landscape,fullpage,label=img:HashingTree} \setupimage{landscape,fullpage,label=img:HashingTree}
![Rosenpass Hashing Tree](graphics/rosenpass-wp-hashing-tree-rgb.svg) ![Rosenpass Hashing Tree](graphics/rosenpass-wp-hashing-tree-rgb.svg)
Each tree node $\circ{}$ in Figure \ref{img:HashingTree} represents the application of the keyed hash function, using the previous chaining key value as first parameter. The root of the tree is the zero key. In level one, the `PROTOCOL` identifier is applied to the zero key to generate a label unique across cryptographic protocols (unless the same label is deliberately used elsewhere). In level two, purpose identifiers are applied to the protocol label to generate labels to use with each separate hash function application within the Rosenpass protocol. The following layers contain the inputs used in each separate usage of the hash function: Beneath the identifiers `"mac"`, `"cookie"`, `"peer id"`, and `"biscuit additional data"` are hash functions or message authentication codes with a small number of inputs. The second, third, and fourth column in Figure \ref{img:HashingTree} cover the long sequential branch beneath the identifier `"chaining key init"` representing the entire protocol execution, one column for each message processed during the handshake. The leaves beneath `"chaining key extract"` in the left column represent pseudo-random labels for use when extracting values from the chaining key during the protocol execution. These values such as `mix >` appear as outputs in the left column, and then as inputs `< mix` in the other three columns. Each tree node $\circ{}$ in Figure 3 represents the application of the keyed hash function, using the previous chaining key value as first parameter. The root of the tree is the zero key. In level one, the `PROTOCOL` identifier is applied to the zero key to generate a label unique across cryptographic protocols (unless the same label is deliberately used elsewhere). In level two, purpose identifiers are applied to the protocol label to generate labels to use with each separate hash function application within the Rosenpass protocol. The following layers contain the inputs used in each separate usage of the hash function: Beneath the identifiers `"mac"`, `"cookie"`, `"peer id"`, and `"biscuit additional data"` are hash functions or message authentication codes with a small number of inputs. The second, third, and fourth column in Figure 3 cover the long sequential branch beneath the identifier `"chaining key init"` representing the entire protocol execution, one column for each message processed during the handshake. The leaves beneath `"chaining key extract"` in the left column represent pseudo-random labels for use when extracting values from the chaining key during the protocol execution. These values such as `mix >` appear as outputs in the left column, and then as inputs `< mix` in the other three columns.
The protocol identifier depends on the hash function used with the respective peer is defined as follows if BLAKE2s [@rfc_blake2] is used: The protocol identifier is defined as follows:
```pseudorust ```pseudorust
PROTOCOL = "rosenpass 1 rosenpass.eu aead=chachapoly1305 hash=blake2s ekem=kyber512 skem=mceliece460896 xaead=xchachapoly1305" PROTOCOL = "rosenpass 1 rosenpass.eu aead=chachapoly1305 hash=blake2s ekem=kyber512 skem=mceliece460896 xaead=xchachapoly1305"
``` ```
If SHAKE256 [@SHAKE256] is used, `blake2s` is replaced by `shake256` in `PROTOCOL`. Since every tree node represents a sequence of `hash` calls, the node beneath `"handshake encryption"` called `hs_enc` can be written as follows: Since every tree node represents a sequence of `hash` calls, the node beneath `"handshake encryption"` called `hs_enc` can be written as follows:
```pseudorust ```pseudorust
hs_enc = hash(hash(hash(0, PROTOCOL), "chaining key extract"), "handshake encryption") hs_enc = hash(hash(hash(0, PROTOCOL), "chaining key extract"), "handshake encryption")
@@ -242,7 +233,6 @@ For each peer, the server stores:
* `psk` The pre-shared key used with the peer * `psk` The pre-shared key used with the peer
* `spkt` The peer's public key * `spkt` The peer's public key
* `biscuit_used` The `biscuit_no` from the last biscuit accepted for the peer as part of InitConf processing * `biscuit_used` The `biscuit_no` from the last biscuit accepted for the peer as part of InitConf processing
* `hash_function` The hash function, SHAKE256 or BLAKE2s, used with the peer.
### Handshake State and Biscuits ### Handshake State and Biscuits
@@ -293,7 +283,7 @@ fn lookup_session(sid);
The protocol framework used by Rosenpass allows arbitrarily many different keys to be extracted using labels for each key. The `extract_key` function is used to derive protocol-internal keys, its labels are under the “chaining key extract” node in Figure \ref{img:HashingTree}. The export key function is used to export application keys. The protocol framework used by Rosenpass allows arbitrarily many different keys to be extracted using labels for each key. The `extract_key` function is used to derive protocol-internal keys, its labels are under the “chaining key extract” node in Figure \ref{img:HashingTree}. The export key function is used to export application keys.
Third-party applications using the protocol are supposed to define a protocol extension (see \ref{protocol-extensions}) and choose a globally unique label, such as their domain name for custom labels of their own. The Rosenpass project itself uses the `["rosenpass.eu"]` namespace in the WireGuard PSK protocol extension (see \ref{protocol-extension-wireguard-psk}). Third-party applications using the protocol are supposed to choose a unique label (e.g., their domain name) and use that as their own namespace for custom labels. The Rosenpass project itself uses the rosenpass.eu namespace.
Applications can cache or statically compile the pseudo-random label values into their binary to improve performance. Applications can cache or statically compile the pseudo-random label values into their binary to improve performance.
@@ -399,7 +389,7 @@ fn load_biscuit(nct) {
// In December 2024, the InitConf retransmission mechanisim was redesigned // In December 2024, the InitConf retransmission mechanisim was redesigned
// in a backwards-compatible way. See the changelog. // in a backwards-compatible way. See the changelog.
// //
// -- 2024-11-30, Karolin Varner // -- 2024-11-30, Karolin Varner
if (protocol_version!(< "0.3.0")) { if (protocol_version!(< "0.3.0")) {
// Ensure that the biscuit is used only once // Ensure that the biscuit is used only once
@@ -425,18 +415,6 @@ fn enter_live() {
txkr ← extract_key("responder payload encryption"); txkr ← extract_key("responder payload encryption");
txnm ← 0; txnm ← 0;
txnt ← 0; txnt ← 0;
// Setup output keys for protocol extensions such as the
// WireGuard PSK protocol extension.
setup_osks();
}
```
The final step `setup_osks()` can be defined by protocol extensions (see \ref{protocol-extensions}) to set up `osk`s for custom use cases. By default, the WireGuard PSK (see \ref{protocol-extension-wireguard-psk}) is active.
```pseudorust
fn setup_osks() {
... // Defined by protocol extensions
} }
``` ```
@@ -464,24 +442,24 @@ ICR5 and ICR6 perform biscuit replay protection using the biscuit number. This i
### Denial of Service Mitigation and Cookies ### Denial of Service Mitigation and Cookies
Rosenpass derives its cookie-based DoS mitigation technique for a responder when receiving InitHello messages from Wireguard [@wg]. Rosenpass derives its cookie-based DoS mitigation technique for a responder when receiving InitHello messages from Wireguard [@wg].
When the responder is under load, it may choose to not process further InitHello handshake messages, but instead to respond with a cookie reply message (see Figure \ref{img:MessageTypes}). When the responder is under load, it may choose to not process further InitHello handshake messages, but instead to respond with a cookie reply message (see Figure \ref{img:MessageTypes}).
The sender of the exchange then uses this cookie in order to resend the message and have it accepted the following time by the reciever. The sender of the exchange then uses this cookie in order to resend the message and have it accepted the following time by the reciever.
For an initiator, Rosenpass ignores all messages when under load. For an initiator, Rosenpass ignores all messages when under load.
#### Cookie Reply Message #### Cookie Reply Message
The cookie reply message is sent by the responder on receiving an InitHello message when under load. It consists of the `sidi` of the initiator, a random 24-byte bitstring `nonce` and encrypting `cookie_value` into a `cookie_encrypted` reply field, which consists of the following: The cookie reply message is sent by the responder on receiving an InitHello message when under load. It consists of the `sidi` of the initiator, a random 24-byte bitstring `nonce` and encrypting `cookie_value` into a `cookie_encrypted` reply field which consists of the following:
```pseudorust ```pseudorust
cookie_value = lhash("cookie-value", cookie_secret, initiator_host_info)[0..16] cookie_value = lhash("cookie-value", cookie_secret, initiator_host_info)[0..16]
cookie_encrypted = XAEAD(lhash("cookie-key", spkm), nonce, cookie_value, mac_peer) cookie_encrypted = XAEAD(lhash("cookie-key", spkm), nonce, cookie_value, mac_peer)
``` ```
where `cookie_secret` is a secret variable that changes every two minutes to a random value. Moreover, `lhash` is always instantiated with SHAKE256 when computing `cookie_value` for compatability reasons. `initiator_host_info` is used to identify the initiator host, and is implementation-specific for the client. This paramaters used to identify the host must be carefully chosen to ensure there is a unique mapping, especially when using IPv4 and IPv6 addresses to identify the host (such as taking care of IPv6 link-local addresses). `cookie_value` is a truncated 16 byte value from the above hash operation. `mac_peer` is the `mac` field of the peer's handshake message to which message is the reply. where `cookie_secret` is a secret variable that changes every two minutes to a random value. `initiator_host_info` is used to identify the initiator host, and is implementation-specific for the client. This paramaters used to identify the host must be carefully chosen to ensure there is a unique mapping, especially when using IPv4 and IPv6 addresses to identify the host (such as taking care of IPv6 link-local addresses). `cookie_value` is a truncated 16 byte value from the above hash operation. `mac_peer` is the `mac` field of the peer's handshake message to which message is the reply.
#### Envelope `mac` Field #### Envelope `mac` Field
@@ -511,13 +489,13 @@ else {
Here, `seconds_since_update(peer.cookie_value)` is the amount of time in seconds ellapsed since last cookie was received, and `COOKIE_WIRE_DATA` are the message contents of all bytes of the retransmitted message prior to the `cookie` field. Here, `seconds_since_update(peer.cookie_value)` is the amount of time in seconds ellapsed since last cookie was received, and `COOKIE_WIRE_DATA` are the message contents of all bytes of the retransmitted message prior to the `cookie` field.
The inititator can use an invalid value for the `cookie` value, when the responder is not under load, and the responder must ignore this value. The inititator can use an invalid value for the `cookie` value, when the responder is not under load, and the responder must ignore this value.
However, when the responder is under load, it may reject InitHello messages with the invalid `cookie` value, and issue a cookie reply message. However, when the responder is under load, it may reject InitHello messages with the invalid `cookie` value, and issue a cookie reply message.
### Conditions to trigger DoS Mechanism ### Conditions to trigger DoS Mechanism
This whitepaper does not mandate any specific mechanism to detect responder contention (also mentioned as the under load condition) that would trigger use of the cookie mechanism. This whitepaper does not mandate any specific mechanism to detect responder contention (also mentioned as the under load condition) that would trigger use of the cookie mechanism.
For the reference implemenation, Rosenpass has derived inspiration from the Linux implementation of Wireguard. This implementation suggests that the reciever keep track of the number of messages it is processing at a given time. For the reference implemenation, Rosenpass has derived inspiration from the linux implementation of Wireguard. This implementation suggests that the reciever keep track of the number of messages it is processing at a given time.
On receiving an incoming message, if the length of the message queue to be processed exceeds a threshold `MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD`, the client is considered under load and its state is stored as under load. In addition, the timestamp of this instant when the client was last under load is stored. When recieving subsequent messages, if the client is still in an under load state, the client will check if the time ellpased since the client was last under load has exceeded `LAST_UNDER_LOAD_WINDOW` seconds. If this is the case, the client will update its state to normal operation, and process the message in a normal fashion. On receiving an incoming message, if the length of the message queue to be processed exceeds a threshold `MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD`, the client is considered under load and its state is stored as under load. In addition, the timestamp of this instant when the client was last under load is stored. When recieving subsequent messages, if the client is still in an under load state, the client will check if the time ellpased since the client was last under load has exceeded `LAST_UNDER_LOAD_WINDOW` seconds. If this is the case, the client will update its state to normal operation, and process the message in a normal fashion.
@@ -536,181 +514,25 @@ The responder uses less complex form of the same mechanism: The responder never
### Interaction with cookie reply system ### Interaction with cookie reply system
The cookie reply system does not interfere with the retransmission logic discussed above. The cookie reply system does not interfere with the retransmission logic discussed above.
When the initator is under load, it will ignore processing any incoming messages. When the initator is under load, it will ignore processing any incoming messages.
When a responder is under load and it receives an InitHello handshake message, the InitHello message will be discarded and a cookie reply message is sent. The initiator, then on the reciept of the cookie reply message, will store a decrypted `cookie_value` to set the `cookie` field to subsequently sent messages. As per the retransmission mechanism above, the initiator will send a retransmitted InitHello message with a valid `cookie` value appended. On receiving the retransmitted handshake message, the responder will validate the `cookie` value and resume with the handshake process. When a responder is under load and it receives an InitHello handshake message, the InitHello message will be discarded and a cookie reply message is sent. The initiator, then on the reciept of the cookie reply message, will store a decrypted `cookie_value` to set the `cookie` field to subsequently sent messages. As per the retransmission mechanism above, the initiator will send a retransmitted InitHello message with a valid `cookie` value appended. On receiving the retransmitted handshake message, the responder will validate the `cookie` value and resume with the handshake process.
When the responder is under load and it recieves an InitConf message, the message will be directly processed without checking the validity of the cookie field. When the responder is under load and it recieves an InitConf message, the message will be directly processed without checking the validity of the cookie field.
# Protocol extensions {#protocol-extensions}
The main extension point for the Rosenpass protocol is to generate `osk`s (speak output shared keys, see Sec. \ref{symmetric-keys}) for purposes other than using them to secure WireGuard. By default, the Rosenpass application generates keys for the WireGuard PSK (see \ref{protocol-extension-wireguard-psk}). It would not be impossible to use the keys generated for WireGuard in other use cases, but this might lead to attacks[@oraclecloning]. Specifying a custom protocol extension in practice just means settling on alternative domain separators (see Sec. \ref{symmetric-keys}, Fig. \ref{img:HashingTree}).
## Using custom domain separators in the Rosenpass application
The Rosenpass application supports protocol extensions to change the OSK domain separator without modification of the source code.
The following example configuration file can be used to execute Rosenpass in outfile mode with custom domain separators.
In this mode, the Rosenpass application will write keys to the file specified with `key_out` and send notifications when new keys are exchanged via standard out.
This can be used to embed Rosenpass into third-party application.
```toml
# peer-a.toml
public_key = "peer-a.pk"
secret_key = "peer-a.sk"
listen = ["[::1]:6789"]
verbosity = "Verbose"
[[peers]]
public_key = "peer-b.pk"
key_out = "peer-a.osk" # path to store the key
osk_organization = "myorg.com"
osk_label = ["My Custom Messenger app", "Backend VPN Example Subusecase"]
```
## Extension: WireGuard PSK {#protocol-extension-wireguard-psk}
The WireGuard PSK protocol extension is active by default; this is the mode where Rosenpass is used to provide post-quantum security for WireGuard. Hybrid security (i.e. redundant pre-quantum and post-quantum security) is achieved because WireGuard provides pre-quantum security, with or without Rosenpass.
This extension uses the `"rosenpass.eu"` namespace for user-labels and specifies a single additional user-label:
* `["rosenpass.eu", "wireguard psk"]`
The label's full domain separator is
* `[PROTOCOL, "user", "rosenpass.eu", "wireguard psk"]`
and can be seen in Figure \ref{img:HashingTree}.
We require two extra per-peer configuration variables:
* `wireguard_interface` — Name of a local network interface. Identifies local WireGuard interface we are supplying a PSK to.
* `wireguard_peer` — A WireGuard public key. Identifies the particular WireGuard peer whose connection we are supplying PSKs for.
When creating the WireGuard interface for use with Rosenpass, the PSK used by WireGuard must be initialized to a random value; otherwise, WireGuard can establish an insecure key before Rosenpass had a change to exchange its own key.
```pseudorust
fn on_wireguard_setup() {
// We use a random PSK to make sure the other side will never
// have a matching PSK when the WireGuard interface is created.
//
// Never use a fixed value here as this would lead to an attack!
let fake_wireguard_psk = random_key();
// How the interface is create
let wg_peer = WireGuard::setup_peer()
.public_key(wireguard_peer)
... // Supply any custom peerconfiguration
.psk(fake_wireguard_psk);
// The random PSK must be supplied before the
// WireGuard interface comes up
WireGuard::setup_interface()
.name(wireguard_interface)
... // Supply any custom configuration
.add_peer(wg_peer)
.create();
}
```
Every time a key is successfully negotiated, we upload the key to WireGuard.
For this protocol extension, the `setup_osks()` function is thus defined as:
```pseudorust
fn setup_osks() {
// Generate WireGuard OSK (output shared key) from Rosenpass'
// perspective, respectively the PSK (preshared key) from
// WireGuard's perspective
let wireguard_psk = export_key("rosenpass.eu", "wireguard psk");
/// Supply the PSK to WireGuard
WireGuard::get_interface(wireguard_interface)
.get_peer(wireguard_peer)
.set_psk(wireguard_psk);
}
```
The Rosenpass protocol uses key renegotiation, just like WireGuard.
If no new `osk` is produced within a set amount of time, the OSK generated by Rosenpass times out.
In this case, the WireGuard PSK must be overwritten with a random key.
This interaction is visualized in Figure \ref{img:ExtWireguardPSKHybridSecurity}.
```pseudorust
fn on_key_timeout() {
// Generate a random deliberately invalid WireGuard PSK.
// Never use a fixed value here as this would lead to an attack!
let fake_wireguard_psk = random_key();
// Securely erase the PSK currently used by WireGuard by
// overwriting it with the fake key we just generated.
WireGuard::get_interface(wireguard_interface)
.get_peer(wireguard_peer)
.set_psk(fake_wireguard_psk);
}
```
\setupimage{label=img:ExtWireguardPSKHybridSecurity,fullpage}
![Rosenpass + WireGuard: Hybrid Security](graphics/rosenpass-wireguard-hybrid-security.pdf)
# Changelog # Changelog
### 0.3.x ### 0.3.x
#### 2025-06-24 Specifying the `osk` used for WireGuard as a protocol extension
\vspace{0.5em}
Author: Karolin varner
PR: [#664](https://github.com/rosenpass/rosenpass/pull/664)
\vspace{0.5em}
We introduce the concept of protocol extensions to make the option of using Rosenpass for purposes other than encrypting WireGuard more explicit. This captures the status-quo in a better way and does not constitute a functional change of the protocol.
When we designed the Rosenpass protocol, we built it with support for alternative `osk`-labels in mind.
This is why we specified the domain separator for the `osk` to be `[PROTOCOL, "user", "rosenpass.eu", "wireguard psk"]`.
By choosing alternative values for the namespace (e.g. `"myorg.eu"` instead of `"rosenpass.eu`) and the label (e.g. `"MyApp Symmetric Encryption"`), the protocol could easily accommodate alternative usage scenarios.
By introducing the concept of protocol extensions, we make this possibility explicit.
1. Reworded the abstract to make it clearer that Rosenpass can be used for other purposes than to secure WireGuard
2. Reworded Section Symmetric Keys, adding references to the new section on protocol extension
3. Added a `setup_osks()` function in section Hashes, to make the reference to protocol extensions explicit
4. Added a new section on protocol extensions and the standard extension for using Rosenpass with WireGuard
5. Added a new graphic to showcase how Rosenpass and WireGuard interact
5. Minor formatting and intra-document references fixes
#### 2025-05-22 - SHAKE256 keyed hash
\vspace{0.5em}
Author: David Niehues
PR: [#653](https://github.com/rosenpass/rosenpass/pull/653)
\vspace{0.5em}
We document the support for SHAKE256 with prepended key as an alternative to BLAKE2s with HMAC.
Previously, BLAKE2s with HMAC was the only supported keyed hash function. Recently, SHAKE256 was added as an option. SHAKE256 is used as a keyed hash function by prepending the key to the variable-length data and then evaluating SHAKE256.
In order to maintain compatablity without introducing an explcit version number in the protocol messages, SHAKE256 is truncated to 32 bytes. In the update to the whitepaper, we explain where and how SHAKE256 is used. That is:
1. We explain that SHAKE256 or BLAKE2s can be configured to be used on a peer basis.
2. We explain under which circumstances, the reference implementation tries both hash functions for messages in order to determine the correct hash function.
3. We document that the cookie mechanism always uses SHAKE256.
#### 2024-10-30 InitConf retransmission updates #### 2024-10-30 InitConf retransmission updates
\vspace{0.5em} \vspace{0.5em}
Author: Karolin Varner Author: Karolin Varner
Issue: [#331](https://github.com/rosenpass/rosenpass/issues/331)
Issue: [#331](https://github.com/rosenpass/rosenpass/issues/331) PR: [#513](https://github.com/rosenpass/rosenpass/pull/513)
PR: [#513](https://github.com/rosenpass/rosenpass/pull/513)
\vspace{0.5em} \vspace{0.5em}
@@ -728,7 +550,7 @@ By removing all retransmission handling code from the cryptographic protocol, we
The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf. The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
\end{quote} \end{quote}
by by
\begin{quote} \begin{quote}
The responder uses less complex form of the same mechanism: The responder never retransmits RespHello, instead the responder generates a new RespHello message if InitHello is retransmitted. Responder confirmation messages of completed handshake (EmptyData) messages are retransmitted by storing the most recent InitConf messages (or their hashes) and caching the associated EmptyData messages. Through this cache, InitConf retransmission is detected and the associated EmptyData message is retransmitted. The responder uses less complex form of the same mechanism: The responder never retransmits RespHello, instead the responder generates a new RespHello message if InitHello is retransmitted. Responder confirmation messages of completed handshake (EmptyData) messages are retransmitted by storing the most recent InitConf messages (or their hashes) and caching the associated EmptyData messages. Through this cache, InitConf retransmission is detected and the associated EmptyData message is retransmitted.
@@ -751,7 +573,7 @@ By removing all retransmission handling code from the cryptographic protocol, we
\begin{minted}{pseudorust} \begin{minted}{pseudorust}
// In December 2024, the InitConf retransmission mechanisim was redesigned // In December 2024, the InitConf retransmission mechanisim was redesigned
// in a backwards-compatible way. See the changelog. // in a backwards-compatible way. See the changelog.
// //
// -- 2024-11-30, Karolin Varner // -- 2024-11-30, Karolin Varner
if (protocol_version!(< "0.3.0")) { if (protocol_version!(< "0.3.0")) {
// Ensure that the biscuit is used only once // Ensure that the biscuit is used only once
@@ -765,11 +587,9 @@ By removing all retransmission handling code from the cryptographic protocol, we
\vspace{0.5em} \vspace{0.5em}
Author: Prabhpreet Dua Author: Prabhpreet Dua
Issue: [#137](https://github.com/rosenpass/rosenpass/issues/137)
Issue: [#137](https://github.com/rosenpass/rosenpass/issues/137) PR: [#142](https://github.com/rosenpass/rosenpass/pull/142)
PR: [#142](https://github.com/rosenpass/rosenpass/pull/142)
\vspace{0.5em} \vspace{0.5em}

View File

@@ -1,9 +0,0 @@
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"

View File

@@ -1,34 +0,0 @@
{
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
''

View File

@@ -1,60 +0,0 @@
{
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
''

View File

@@ -1,24 +1,21 @@
{ { lib, stdenvNoCC, runCommandNoCC, pkgsStatic, rosenpass, rosenpass-oci-image, rp } @ args:
lib,
stdenvNoCC,
runCommandNoCC,
pkgsStatic,
rosenpass,
rosenpass-oci-image,
rp,
}@args:
let let
version = rosenpass.version; version = rosenpass.version;
# select static packages on Linux, default packages otherwise # select static packages on Linux, default packages otherwise
package = if stdenvNoCC.hostPlatform.isLinux then pkgsStatic.rosenpass else args.rosenpass; package =
rp = if stdenvNoCC.hostPlatform.isLinux then pkgsStatic.rp else args.rp; if stdenvNoCC.hostPlatform.isLinux then
pkgsStatic.rosenpass
else args.rosenpass;
rp =
if stdenvNoCC.hostPlatform.isLinux then
pkgsStatic.rp
else args.rp;
oci-image = oci-image =
if stdenvNoCC.hostPlatform.isLinux then if stdenvNoCC.hostPlatform.isLinux then
pkgsStatic.rosenpass-oci-image pkgsStatic.rosenpass-oci-image
else else args.rosenpass-oci-image;
args.rosenpass-oci-image;
in in
runCommandNoCC "lace-result" { } '' runCommandNoCC "lace-result" { } ''
mkdir {bin,$out} mkdir {bin,$out}

View File

@@ -1,8 +1,4 @@
{ { dockerTools, buildEnv, rosenpass }:
dockerTools,
buildEnv,
rosenpass,
}:
dockerTools.buildImage { dockerTools.buildImage {
name = rosenpass.name + "-oci"; name = rosenpass.name + "-oci";

View File

@@ -1,13 +1,4 @@
{ { lib, stdenv, rustPlatform, cmake, mandoc, removeReferencesTo, bash, package ? "rosenpass" }:
lib,
stdenv,
rustPlatform,
cmake,
mandoc,
removeReferencesTo,
bash,
package ? "rosenpass",
}:
let let
# whether we want to build a statically linked binary # whether we want to build a statically linked binary
@@ -26,30 +17,24 @@ let
"toml" "toml"
]; ];
# Files to explicitly include # Files to explicitly include
files = [ "to/README.md" ]; files = [
"to/README.md"
];
src = ../.; src = ../.;
filter = ( filter = (path: type: scoped rec {
path: type: inherit (lib) any id removePrefix hasSuffix;
scoped rec { anyof = (any id);
inherit (lib)
any
id
removePrefix
hasSuffix
;
anyof = (any id);
basename = baseNameOf (toString path); basename = baseNameOf (toString path);
relative = removePrefix (toString src + "/") (toString path); relative = removePrefix (toString src + "/") (toString path);
result = anyof [ result = anyof [
(type == "directory") (type == "directory")
(any (ext: hasSuffix ".${ext}" basename) extensions) (any (ext: hasSuffix ".${ext}" basename) extensions)
(any (file: file == relative) files) (any (file: file == relative) files)
]; ];
} });
);
result = lib.sources.cleanSourceWith { inherit src filter; }; result = lib.sources.cleanSourceWith { inherit src filter; };
}; };
@@ -62,14 +47,8 @@ rustPlatform.buildRustPackage {
version = cargoToml.package.version; version = cargoToml.package.version;
inherit src; inherit src;
cargoBuildOptions = [ cargoBuildOptions = [ "--package" package ];
"--package" cargoTestOptions = [ "--package" package ];
package
];
cargoTestOptions = [
"--package"
package
];
doCheck = true; doCheck = true;
@@ -78,8 +57,6 @@ 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=";
"libcrux-macros-0.0.3" = "sha256-Tb5uRirwhRhoFEK8uu1LvXl89h++40pxzZ+7kXe8RAI=";
}; };
}; };
@@ -103,10 +80,7 @@ rustPlatform.buildRustPackage {
meta = { meta = {
inherit (cargoToml.package) description homepage; inherit (cargoToml.package) description homepage;
license = with lib.licenses; [ license = with lib.licenses; [ mit asl20 ];
mit
asl20
];
maintainers = [ lib.maintainers.wucke13 ]; maintainers = [ lib.maintainers.wucke13 ];
platforms = lib.platforms.all; platforms = lib.platforms.all;
}; };

View File

@@ -1,53 +1,13 @@
{ { stdenvNoCC, texlive, ncurses, python3Packages, which }:
stdenvNoCC,
texlive,
ncurses,
python3Packages,
which,
}:
let let
customTexLiveSetup = ( customTexLiveSetup = (texlive.combine {
texlive.combine { inherit (texlive) acmart amsfonts biber biblatex biblatex-software
inherit (texlive) biblatex-trad ccicons csquotes csvsimple doclicense eso-pic fancyvrb
acmart fontspec gitinfo2 gobble ifmtarg koma-script latexmk lm lualatex-math
amsfonts markdown mathtools minted noto nunito paralist pgf scheme-basic soul
biber unicode-math upquote xifthen xkeyval xurl;
biblatex });
biblatex-software
biblatex-trad
ccicons
csquotes
csvsimple
doclicense
eso-pic
fancyvrb
fontspec
gitinfo2
gobble
ifmtarg
koma-script
latexmk
lm
lualatex-math
markdown
mathtools
minted
noto
nunito
paralist
pgf
scheme-basic
soul
unicode-math
upquote
xifthen
xkeyval
xurl
dirtytalk
;
}
);
in in
stdenvNoCC.mkDerivation { stdenvNoCC.mkDerivation {
name = "whitepaper"; name = "whitepaper";

View File

@@ -14,7 +14,7 @@ This repository contains
## Getting started ## Getting started
First, [install rosenpass](#getting-rosenpass). Then, check out the help functions of `rp` & `rosenpass`: First, [install rosenpass](#Getting-Rosenpass). Then, check out the help functions of `rp` & `rosenpass`:
```sh ```sh
rp help rp help
@@ -64,7 +64,11 @@ The analysis is implemented according to modern software engineering principles:
The code uses a variety of optimizations to speed up analysis such as using secret functions to model trusted/malicious setup. We split the model into two separate entry points which can be analyzed in parallel. Each is much faster than both models combined. The code uses a variety of optimizations to speed up analysis such as using secret functions to model trusted/malicious setup. We split the model into two separate entry points which can be analyzed in parallel. Each is much faster than both models combined.
A wrapper script provides instant feedback about which queries execute as expected in color: A red cross if a query fails and a green check if it succeeds. A wrapper script provides instant feedback about which queries execute as expected in color: A red cross if a query fails and a green check if it succeeds.
[^liboqs]: <https://openquantumsafe.org/liboqs/> [^liboqs]: https://openquantumsafe.org/liboqs/
[^wg]: https://www.wireguard.com/
[^pqwg]: https://eprint.iacr.org/2020/379
[^pqwg-statedis]: Unless supplied with a pre-shared-key, but this defeats the purpose of a key exchange protocol
[^wg-statedis]: https://lists.zx2c4.com/pipermail/wireguard/2021-August/006916.htmlA
# Getting Rosenpass # Getting Rosenpass
@@ -74,56 +78,6 @@ Rosenpass is packaged for more and more distributions, maybe also for the distri
[![Packaging status](https://repology.org/badge/vertical-allrepos/rosenpass.svg)](https://repology.org/project/rosenpass/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/rosenpass.svg)](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).
## Benchmarks
This repository contains facilities for benchmarking both the Rosenpass
protocol code and the implementations of the cryptographic primitives used
by it. The primitives are benchmarked using criterion. For the protocol code
benchmarks we use a library for instrumenting the code such that events are
written to a trace, which is then inspected after a run.
Benchmarks are automatically run on CI. The measurements are visualized in the
[Benchmark Dashboard].
[Benchmark Dashboard]: https://rosenpass.github.io/rosenpass/benchmarks
### Primitive Benchmarks
There are benchmarks for the functions of the traits `Kem`, `Aead` and
`KeyedHash`. They are run for all implementations in the `primitives`
benchmark of `rosenpass-ciphers`. Run the benchmarks and view their results using
```
cargo bench -p rosenpass-ciphers --bench primitives -F bench
```
Note that the `bench` feature enables the inclusion of the libcrux-backed
trait implementations in the module tree, but does not enable them
as default.
### Protocol Benchmarks
The trace that is being written to lives in a new module
`trace_bench` in the util crate. A basic benchmark that
performs some minor statistical analysis of the trace can be run using
```
cargo bench -p rosenpass --bench trace_handshake -F trace_bench
```
This runs the benchmarks and prints the results in machine-readable JSON.
---
# 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:

View File

@@ -8,7 +8,6 @@ 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.0"
[[bin]] [[bin]]
name = "rosenpass" name = "rosenpass"
@@ -27,19 +26,6 @@ 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]]
name = "trace_handshake"
harness = false
required-features = ["trace_bench"]
[[bench]] [[bench]]
name = "handshake" name = "handshake"
harness = false harness = false
@@ -77,7 +63,6 @@ command-fds = { workspace = true, optional = true }
rustix = { workspace = true, optional = true } rustix = { workspace = true, optional = true }
uds = { workspace = true, optional = true, features = ["mio_1xx"] } uds = { workspace = true, optional = true, features = ["mio_1xx"] }
signal-hook = { workspace = true, optional = true } signal-hook = { workspace = true, optional = true }
libcrux-test-utils = { workspace = true, optional = true }
[build-dependencies] [build-dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
@@ -92,27 +77,20 @@ tempfile = { workspace = true }
rustix = { workspace = true } rustix = { workspace = true }
[features] [features]
#default = ["experiment_libcrux_all"] default = []
experiment_cookie_dos_mitigation = []
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"] experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
experiment_libcrux_all = ["rosenpass-ciphers/experiment_libcrux_all"] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
experiment_libcrux_blake2 = ["rosenpass-ciphers/experiment_libcrux_blake2"]
experiment_libcrux_chachapoly = [
"rosenpass-ciphers/experiment_libcrux_chachapoly",
]
experiment_libcrux_kyber = ["rosenpass-ciphers/experiment_libcrux_kyber"]
experiment_api = [ experiment_api = [
"hex-literal", "hex-literal",
"uds", "uds",
"command-fds", "command-fds",
"rustix", "rustix",
"rosenpass-util/experiment_file_descriptor_passing", "rosenpass-util/experiment_file_descriptor_passing",
"rosenpass-wireguard-broker/experiment_api", "rosenpass-wireguard-broker/experiment_api",
] ]
internal_signal_handling_for_coverage_reports = ["signal-hook"] internal_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"]
trace_bench = ["rosenpass-util/trace_bench", "dep:libcrux-test-utils"]
[lints.rust] [lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] } unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -1,16 +1,13 @@
use anyhow::Result;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey};
use std::ops::DerefMut; use std::ops::DerefMut;
use anyhow::Result; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rosenpass_cipher_traits::primitives::Kem;
use rosenpass_ciphers::StaticKem;
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets; use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion};
fn handle( fn handle(
tx: &mut CryptoServer, tx: &mut CryptoServer,
msgb: &mut MsgBuf, msgb: &mut MsgBuf,
@@ -44,43 +41,25 @@ 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(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> { fn make_server_pair() -> 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( a.add_peer(Some(psk.clone()), pkb)?;
Some(psk.clone()), b.add_peer(Some(psk), pka)?;
pkb,
protocol_version.clone(),
OskDomainSeparator::default(),
)?;
b.add_peer(
Some(psk),
pka,
protocol_version,
OskDomainSeparator::default(),
)?;
Ok((a, b)) Ok((a, b))
} }
fn criterion_benchmark_v02(c: &mut Criterion) { fn criterion_benchmark(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(protocol_version).unwrap(); let (mut a, mut b) = make_server_pair().unwrap();
c.bench_function("cca_secret_alloc", |bench| { c.bench_function("cca_secret_alloc", |bench| {
bench.iter(|| { bench.iter(|| {
SSk::zero(); SSk::zero();
@@ -103,6 +82,5 @@ fn criterion_benchmark(c: &mut Criterion, protocol_version: ProtocolVersion) {
}); });
} }
criterion_group!(benches_v02, criterion_benchmark_v02); criterion_group!(benches, criterion_benchmark);
criterion_group!(benches_v03, criterion_benchmark_v03); criterion_main!(benches);
criterion_main!(benches_v02, benches_v03);

View File

@@ -1,393 +0,0 @@
use std::io::{self, Write};
use std::time::{Duration, Instant};
use std::{collections::HashMap, hint::black_box, ops::DerefMut};
use anyhow::Result;
use libcrux_test_utils::tracing::{EventType, Trace as _};
use rosenpass_cipher_traits::primitives::Kem;
use rosenpass_ciphers::StaticKem;
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
use rosenpass_util::trace_bench::RpEventType;
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion};
const ITERATIONS: usize = 100;
/// Performs a full protocol run by processing a message and recursing into handling that message,
/// until no further response is produced. Returns the keys produce by the two parties.
///
/// Ensures that each party produces one of the two keys.
fn handle(
tx: &mut CryptoServer,
msgb: &mut MsgBuf,
msgl: usize,
rx: &mut CryptoServer,
resb: &mut MsgBuf,
) -> Result<(Option<SymKey>, Option<SymKey>)> {
let HandleMsgResult {
exchanged_with: xch,
resp,
} = rx.handle_msg(&msgb[..msgl], &mut **resb)?;
assert!(matches!(xch, None | Some(PeerPtr(0))));
let xch = xch.map(|p| rx.osk(p).unwrap());
let (rxk, txk) = resp
.map(|resl| handle(rx, resb, resl, tx, msgb))
.transpose()?
.unwrap_or((None, None));
assert!(rxk.is_none() || xch.is_none());
Ok((txk, rxk.or(xch)))
}
/// Performs the full handshake by calling `handle` with the correct values, based on just two
/// `CryptoServer`s.
///
/// Ensures that both parties compute the same keys.
fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
let (mut inib, mut resb) = (MsgBuf::zero(), MsgBuf::zero());
let sz = ini.initiate_handshake(PeerPtr(0), &mut *inib)?;
let (kini, kres) = handle(ini, &mut inib, sz, res, &mut resb)?;
assert!(kini.unwrap().secret() == kres.unwrap().secret());
Ok(())
}
/// Generates a new key pair.
fn keygen() -> Result<(SSk, SPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem.keygen(sk.secret_mut(), pk.deref_mut())?;
Ok((sk, pk))
}
/// Creates two instanves of `CryptoServer`, generating key pairs for each.
fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> {
let psk = SymKey::random();
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
let (mut a, mut b) = (
CryptoServer::new(ska, pka.clone()),
CryptoServer::new(skb, pkb.clone()),
);
a.add_peer(
Some(psk.clone()),
pkb,
protocol_version.clone(),
OskDomainSeparator::default(),
)?;
b.add_peer(
Some(psk),
pka,
protocol_version,
OskDomainSeparator::default(),
)?;
Ok((a, b))
}
fn main() {
let trace = rosenpass_util::trace_bench::trace();
// Attempt to use memfd_secrets for storing sensitive key material
secret_policy_try_use_memfd_secrets();
// Run protocol for V02
let (mut a_v02, mut b_v02) = make_server_pair(ProtocolVersion::V02).unwrap();
for _ in 0..ITERATIONS {
hs(black_box(&mut a_v02), black_box(&mut b_v02)).unwrap();
}
// Emit a marker event to separate V02 and V03 trace sections
trace.emit_on_the_fly("start-hs-v03");
// Run protocol for V03
let (mut a_v03, mut b_v03) = make_server_pair(ProtocolVersion::V03).unwrap();
for _ in 0..ITERATIONS {
hs(black_box(&mut a_v03), black_box(&mut b_v03)).unwrap();
}
// Collect the trace events generated during the handshakes
let trace: Vec<_> = trace.clone().report();
// Split the trace into V02 and V03 sections based on the marker
let (trace_v02, trace_v03) = {
let cutoff = trace
.iter()
.position(|entry| entry.label == "start-hs-v03")
.unwrap();
// Exclude the marker itself from the V03 trace
let (v02, v03_with_marker) = trace.split_at(cutoff);
(v02, &v03_with_marker[1..])
};
// Perform statistical analysis on both trace sections and write results as JSON
write_json_arrays(
&mut std::io::stdout(), // Write to standard output
vec![
("V02", statistical_analysis(trace_v02.to_vec())),
("V03", statistical_analysis(trace_v03.to_vec())),
],
)
.expect("error writing json data");
}
/// Performs a simple statistical analysis:
/// - bins trace events by label
/// - extracts durations of spamns
/// - filters out empty bins
/// - calculates aggregate statistics (mean, std dev)
fn statistical_analysis(trace: Vec<RpEventType>) -> Vec<(&'static str, AggregateStat<Duration>)> {
bin_events(trace)
.into_iter()
.map(|(label, spans)| (label, extract_span_durations(label, spans.as_slice())))
.filter(|(_, durations)| !durations.is_empty())
.map(|(label, durations)| (label, AggregateStat::analyze_durations(&durations)))
.collect()
}
/// Takes an iterator of ("protocol_version", iterator_of_stats) pairs and writes them
/// as a single flat JSON array to the provided writer.
///
/// # Arguments
/// * `w` - The writer to output JSON to (e.g., stdout, file).
/// * `item_groups` - An iterator producing tuples `(version, stats): (&'static str, II)`.
/// Here `II` is itself an iterator producing `(label, agg_stat): (&'static str, AggregateStat<Duration>)`,
/// where the label is the label of the span, e.g. "IHI2".
///
/// # Type Parameters
/// * `W` - A type that implements `std::io::Write`.
/// * `II` - An iterator type yielding (`&'static str`, `AggregateStat<Duration>`).
fn write_json_arrays<W: Write, II: IntoIterator<Item = (&'static str, AggregateStat<Duration>)>>(
w: &mut W,
item_groups: impl IntoIterator<Item = (&'static str, II)>,
) -> io::Result<()> {
// Flatten the groups into a single iterator of (protocol_version, label, stats)
let iter = item_groups.into_iter().flat_map(|(version, items)| {
items
.into_iter()
.map(move |(label, agg_stat)| (version, label, agg_stat))
});
let mut delim = ""; // Start with no delimiter
// Start the JSON array
write!(w, "[")?;
// Write the flattened statistics as JSON objects, separated by commas.
for (version, label, agg_stat) in iter {
write!(w, "{delim}")?; // Write delimiter (empty for first item, "," for subsequent)
agg_stat.write_json_ns(label, version, w)?; // Write the JSON object for the stat entry
delim = ","; // Set delimiter for the next iteration
}
// End the JSON array
write!(w, "]")
}
/// Used to group benchmark results in visualizations
enum RunTimeGroup {
/// For particularly long operations.
Long,
/// Operations of moderate duration.
Medium,
/// Operations expected to complete under a millisecond.
BelowMillisec,
/// Very fast operations, likely under a microsecond.
BelowMicrosec,
}
impl std::fmt::Display for RunTimeGroup {
/// Used when writing the group information to JSON output.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let txt = match self {
RunTimeGroup::Long => "long",
RunTimeGroup::Medium => "medium",
RunTimeGroup::BelowMillisec => "below 1ms",
RunTimeGroup::BelowMicrosec => "below 1us",
};
write!(f, "{txt}")
}
}
/// Maps specific internal timing labels (likely from rosenpass internals)
/// to the broader SpanGroup categories.
fn run_time_group(label: &str) -> RunTimeGroup {
match label {
// Explicitly categorized labels based on expected performance characteristics
"handle_init_hello" | "handle_resp_hello" | "RHI5" | "IHR5" => RunTimeGroup::Long,
"RHR1" | "IHI2" | "ICR6" => RunTimeGroup::BelowMicrosec,
"RHI6" | "ICI7" | "ICR7" | "RHR3" | "ICR3" | "IHR8" | "ICI4" | "RHI3" | "RHI4" | "RHR4"
| "RHR7" | "ICI3" | "IHI3" | "IHI8" | "ICR2" | "ICR4" | "IHR4" | "IHR6" | "IHI4"
| "RHI7" => RunTimeGroup::BelowMillisec,
// Default protocol_version for any other labels
_ => RunTimeGroup::Medium,
}
}
/// Used temporarily within `extract_span_durations` to track open spans
/// and calculated durations.
#[derive(Debug, Clone)]
enum StatEntry {
/// Represents an unmatched SpanOpen event with its timestamp.
Start(Instant),
/// Represents a completed span with its calculated duration.
Duration(Duration),
}
/// Takes a flat list of events and organizes them into a HashMap where keys
/// are event labels and values are vectors of events with that label.
fn bin_events(events: Vec<RpEventType>) -> HashMap<&'static str, Vec<RpEventType>> {
let mut spans = HashMap::<_, Vec<_>>::new();
for event in events {
// Get the vector for the event's label, or create a new one
let spans_for_label = spans.entry(event.label).or_default();
// Add the event to the vector
spans_for_label.push(event);
}
spans
}
/// Processes a list of events (assumed to be for the same label), matching
/// `SpanOpen` and `SpanClose` events to calculate the duration of each span.
/// It handles potentially interleaved spans correctly.
fn extract_span_durations(label: &str, events: &[RpEventType]) -> Vec<Duration> {
let mut processing_list: Vec<StatEntry> = vec![]; // List to track open spans and final durations
for entry in events {
match &entry.ty {
EventType::SpanOpen => {
// Record the start time of a new span
processing_list.push(StatEntry::Start(entry.at));
}
EventType::SpanClose => {
// Find the most recent unmatched 'Start' entry
let start_index = processing_list
.iter()
.rposition(|span| matches!(span, StatEntry::Start(_))); // Find last Start
match start_index {
Some(index) => {
// Retrieve the start time
let start_time = match processing_list[index] {
StatEntry::Start(t) => t,
_ => unreachable!(), // Should always be Start based on rposition logic
};
// Calculate duration and replace the 'Start' entry with 'Duration'
processing_list[index] = StatEntry::Duration(entry.at - start_time);
}
None => {
// This should not happen with well-formed traces
eprintln!(
"Warning: Found SpanClose without a matching SpanOpen for label '{}': {:?}",
label, entry
);
}
}
}
EventType::OnTheFly => {
// Ignore OnTheFly events for duration calculation
}
}
}
// Collect all calculated durations, reporting any unmatched starts
processing_list
.into_iter()
.filter_map(|span| match span {
StatEntry::Start(at) => {
// Report error if a span was opened but never closed
eprintln!(
"Warning: Unmatched SpanOpen at {:?} for label '{}'",
at, label
);
None // Discard unmatched starts
}
StatEntry::Duration(dur) => Some(dur), // Keep calculated durations
})
.collect()
}
/// Stores the mean, standard deviation, relative standard deviation (sd/mean),
/// and the number of samples used for calculation.
#[derive(Debug)]
struct AggregateStat<T> {
/// Average duration.
mean_duration: T,
/// Standard deviation of durations.
sd_duration: T,
/// Standard deviation as a percentage of the mean.
sd_by_mean: String,
/// Number of duration measurements.
sample_size: usize,
}
impl AggregateStat<Duration> {
/// Calculates mean, variance, and standard deviation for a slice of Durations.
fn analyze_durations(durations: &[Duration]) -> Self {
let sample_size = durations.len();
assert!(sample_size > 0, "Cannot analyze empty duration slice");
// Calculate the sum of durations
let sum: Duration = durations.iter().sum();
// Calculate the mean duration
let mean = sum / (sample_size as u32);
// Calculate mean in nanoseconds, adding 1 to avoid potential division by zero later
// (though highly unlikely with realistic durations)
let mean_ns = mean.as_nanos().saturating_add(1);
// Calculate variance (sum of squared differences from the mean) / N
let variance = durations
.iter()
.map(Duration::as_nanos)
.map(|d_ns| d_ns.abs_diff(mean_ns).pow(2)) // (duration_ns - mean_ns)^2
.sum::<u128>() // Sum of squares
/ (sample_size as u128); // Divide by sample size
// Calculate standard deviation (sqrt of variance)
let sd_ns = (variance as f64).sqrt() as u128;
let sd = Duration::from_nanos(sd_ns as u64); // Convert back to Duration
// Calculate relative standard deviation (sd / mean) as a percentage string
let sd_rel_permille = (10000 * sd_ns).checked_div(mean_ns).unwrap_or(0); // Calculate sd/mean * 10000
let sd_rel_formatted = format!("{}.{:02}%", sd_rel_permille / 100, sd_rel_permille % 100);
AggregateStat {
mean_duration: mean,
sd_duration: sd,
sd_by_mean: sd_rel_formatted,
sample_size,
}
}
/// Writes the statistics as a JSON object to the provided writer.
/// Includes metadata like label, protocol_version, OS, architecture, and run time group.
///
/// # Arguments
/// * `label` - The specific benchmark/span label.
/// * `protocol_version` - Version of the protocol that is benchmarked.
/// * `w` - The output writer (must implement `std::io::Write`).
fn write_json_ns(
&self,
label: &str,
protocol_version: &str,
w: &mut impl io::Write,
) -> io::Result<()> {
// Format the JSON string using measured values and environment constants
writeln!(
w,
r#"{{"name":"{name}", "unit":"ns/iter", "value":"{value}", "range":"± {range}", "protocol version":"{protocol_version}", "sample size":"{sample_size}", "operating system":"{os}", "architecture":"{arch}", "run time":"{run_time}"}}"#,
name = label, // Benchmark name
value = self.mean_duration.as_nanos(), // Mean duration in nanoseconds
range = self.sd_duration.as_nanos(), // Standard deviation in nanoseconds
sample_size = self.sample_size, // Number of samples
os = std::env::consts::OS, // Operating system
arch = std::env::consts::ARCH, // CPU architecture
run_time = run_time_group(label), // Run time group category (long, medium, etc.)
protocol_version = protocol_version // Overall protocol_version (e.g., protocol version)
)
}
}

View File

@@ -158,10 +158,10 @@ where
); );
// Actually read the secrets // Actually read the secrets
let mut sk = crate::protocol::basic_types::SSk::zero(); let mut sk = crate::protocol::SSk::zero();
sk_io.read_exact_til_end(sk.secret_mut()).einvalid_req()?; sk_io.read_exact_til_end(sk.secret_mut()).einvalid_req()?;
let mut pk = crate::protocol::basic_types::SPk::zero(); let mut pk = crate::protocol::SPk::zero();
pk_io.read_exact_til_end(pk.borrow_mut()).einvalid_req()?; pk_io.read_exact_til_end(pk.borrow_mut()).einvalid_req()?;
// Retrieve the construction site // Retrieve the construction site

View File

@@ -8,250 +8,210 @@ 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()
} }
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
/// [RefMakerRawMsgTypeExt::parse_request_msg_type] self.zk_parse()
}
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_prefix()
}
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_suffix()
}
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> { 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>> {

View File

@@ -4,41 +4,16 @@ 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>>;
} }
@@ -48,27 +23,6 @@ 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())
} }

Some files were not shown because too many files have changed in this diff Show More