Compare commits

..

2 Commits

Author SHA1 Message Date
wucke13
63ff75b93c remove anyhow
This is the first of a couple of commits aiming to completely remove anyhow in favor of using a mixture of `log` and a `thiserror` based enum. Partially fixes #93.
2023-06-17 22:43:45 +02:00
wucke13
5bebdd9284 fix broken devShell
The use of a fakecmake in the main step of the Rosenpass build removed real CMake from the devShell, essentially breaking cargo build from within it. This commit fixes that, by explicitly placing the real CMake in the devShell's nativeBuildInputs.
2023-06-17 22:41:56 +02:00
33 changed files with 991 additions and 1165 deletions

View File

@@ -33,7 +33,7 @@ let systems_map = {
# aarch64-linux # aarch64-linux
i686-linux: ubuntu-latest, i686-linux: ubuntu-latest,
x86_64-darwin: macos-13, x86_64-darwin: macos-latest,
x86_64-linux: ubuntu-latest x86_64-linux: ubuntu-latest
} }
@@ -64,7 +64,7 @@ let runner_setup = [
uses: "actions/checkout@v3" uses: "actions/checkout@v3"
} }
{ {
uses: "cachix/install-nix-action@v22", uses: "cachix/install-nix-action@v21",
with: { nix_path: "nixpkgs=channel:nixos-unstable" } with: { nix_path: "nixpkgs=channel:nixos-unstable" }
} }
{ {

View File

@@ -15,7 +15,7 @@ jobs:
- i686-linux---rosenpass - i686-linux---rosenpass
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -31,7 +31,7 @@ jobs:
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -48,7 +48,7 @@ jobs:
- i686-linux---rosenpass - i686-linux---rosenpass
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -63,7 +63,7 @@ jobs:
- ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -75,12 +75,12 @@ jobs:
x86_64-darwin---default: x86_64-darwin---default:
name: Build x86_64-darwin.default name: Build x86_64-darwin.default
runs-on: runs-on:
- macos-13 - macos-latest
needs: needs:
- x86_64-darwin---rosenpass - x86_64-darwin---rosenpass
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -92,13 +92,13 @@ jobs:
x86_64-darwin---release-package: x86_64-darwin---release-package:
name: Build x86_64-darwin.release-package name: Build x86_64-darwin.release-package
runs-on: runs-on:
- macos-13 - macos-latest
needs: needs:
- x86_64-darwin---rosenpass - x86_64-darwin---rosenpass
- x86_64-darwin---rosenpass-oci-image - x86_64-darwin---rosenpass-oci-image
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -110,11 +110,11 @@ jobs:
x86_64-darwin---rosenpass: x86_64-darwin---rosenpass:
name: Build x86_64-darwin.rosenpass name: Build x86_64-darwin.rosenpass
runs-on: runs-on:
- macos-13 - macos-latest
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -126,12 +126,12 @@ jobs:
x86_64-darwin---rosenpass-oci-image: x86_64-darwin---rosenpass-oci-image:
name: Build x86_64-darwin.rosenpass-oci-image name: Build x86_64-darwin.rosenpass-oci-image
runs-on: runs-on:
- macos-13 - macos-latest
needs: needs:
- x86_64-darwin---rosenpass - x86_64-darwin---rosenpass
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -143,10 +143,10 @@ jobs:
x86_64-darwin---check: x86_64-darwin---check:
name: Run Nix checks on x86_64-darwin name: Run Nix checks on x86_64-darwin
runs-on: runs-on:
- macos-13 - macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -163,7 +163,7 @@ jobs:
- x86_64-linux---rosenpass - x86_64-linux---rosenpass
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -180,7 +180,7 @@ jobs:
- x86_64-linux---proverif-patched - x86_64-linux---proverif-patched
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -196,7 +196,7 @@ jobs:
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -210,11 +210,11 @@ jobs:
runs-on: runs-on:
- ubuntu-latest - ubuntu-latest
needs: needs:
- x86_64-linux---rosenpass-static-oci-image
- x86_64-linux---rosenpass-static - x86_64-linux---rosenpass-static
- x86_64-linux---rosenpass-static-oci-image
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -230,7 +230,7 @@ jobs:
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -247,7 +247,7 @@ jobs:
- x86_64-linux---rosenpass - x86_64-linux---rosenpass
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -263,7 +263,7 @@ jobs:
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -280,7 +280,7 @@ jobs:
- x86_64-linux---rosenpass-static - x86_64-linux---rosenpass-static
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -296,7 +296,7 @@ jobs:
needs: [] needs: []
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -311,7 +311,7 @@ jobs:
- ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -326,7 +326,7 @@ jobs:
if: ${{ github.ref == 'refs/heads/main' }} if: ${{ github.ref == 'refs/heads/main' }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12

View File

@@ -17,14 +17,6 @@ jobs:
with: with:
args: --check . args: --check .
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
cargo-audit: cargo-audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -74,46 +66,3 @@ jobs:
# - https://github.com/rosenpass/rosenpass/issues/62 # - https://github.com/rosenpass/rosenpass/issues/62
# - https://github.com/rust-lang/rust/issues/108378 # - https://github.com/rust-lang/rust/issues/108378
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items - run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
cargo-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install libsodium
run: sudo apt-get install -y libsodium-dev
# liboqs requires quite a lot of stack memory, thus we adjust
# the default stack size picked for new threads (which is used
# by `cargo test`) to be _big enough_. Setting it to 8 MiB
- run: RUST_MIN_STACK=8388608 cargo test
cargo-test-nix-devshell-x86_64-linux:
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: cachix/install-nix-action@v21
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12
with:
name: rosenpass
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix develop --command cargo test

View File

@@ -12,7 +12,7 @@ jobs:
- ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -30,10 +30,10 @@ jobs:
x86_64-darwin---release: x86_64-darwin---release:
name: Build release artifacts for x86_64-darwin name: Build release artifacts for x86_64-darwin
runs-on: runs-on:
- macos-13 - macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12
@@ -54,7 +54,7 @@ jobs:
- ubuntu-latest - ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v21
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v12 - uses: cachix/cachix-action@v12

View File

@@ -1,17 +0,0 @@
# TODO use CI_JOB_TOKEN once https://gitlab.com/groups/gitlab-org/-/epics/6310 is fixed
pull-from-gh:
only: ["schedules"]
variables:
REMOTE: "https://github.com/rosenpass/rosenpass.git"
LOCAL: " git@gitlab.com:rosenpass/rosenpass.git"
GIT_STRATEGY: none
before_script:
- mkdir ~/.ssh/
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- echo "$REPO_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 --recursive ~/.ssh/
- git config --global user.email "ci@gitlab.com"
- git config --global user.name "CI"
script:
- git clone --mirror $REMOTE rosenpass
- cd rosenpass && git push --mirror $LOCAL

819
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,40 @@
[workspace] [package]
resolver = "2" name = "rosenpass"
version = "0.1.2-rc.4"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Build post-quantum-secure VPNs with WireGuard!"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
members = [ [[bench]]
"rosenpass", name = "handshake"
] harness = false
[workspace.metadata.release] [dependencies]
# ensure that adding `--package` as argument to `cargo release` still creates version tags in the form of `vx.y.z` base64 = "0.21.1"
tag-prefix = "" static_assertions = "1.1.0"
memoffset = "0.9.0"
libsodium-sys-stable = { version = "1.19.28", features = ["use-pkg-config"] }
oqs-sys = { version = "0.7.2", default-features = false, features = ['classic_mceliece', 'kyber'] }
lazy_static = "1.4.0"
thiserror = "1.0.40"
paste = "1.0.12"
log = { version = "0.4.17", optional = true }
env_logger = { version = "0.10.0", optional = true }
serde = { version = "1.0.163", features = ["derive"] }
toml = "0.7.4"
clap = { version = "4.3.0", features = ["derive"] }
mio = { version = "0.8.6", features = ["net", "os-poll"] }
[build-dependencies]
anyhow = "1.0.71"
[dev-dependencies]
criterion = "0.4.0"
test_bin = "0.4.0"
[features]
default = ["log", "env_logger"]

View File

@@ -1,8 +1,7 @@
use anyhow::Result; use anyhow::Result;
use rosenpass::pqkem::KEM;
use rosenpass::{ use rosenpass::{
pqkem::StaticKEM, pqkem::{EphemeralKEM, CCAKEM},
protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey}, protocol::{CcaPk, CcaSk, CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SymKey},
sodium::sodium_init, sodium::sodium_init,
}; };
@@ -39,9 +38,9 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
Ok(()) Ok(())
} }
fn keygen() -> Result<(SSk, SPk)> { fn keygen() -> Result<(CcaSk, CcaPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); let (mut sk, mut pk) = (CcaSk::zero(), CcaPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?; CCAKEM::keygen(sk.secret_mut(), pk.secret_mut())?;
Ok((sk, pk)) Ok((sk, pk))
} }
@@ -62,12 +61,12 @@ fn criterion_benchmark(c: &mut Criterion) {
let (mut a, mut b) = make_server_pair().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(); CcaSk::zero();
}) })
}); });
c.bench_function("cca_public_alloc", |bench| { c.bench_function("cca_public_alloc", |bench| {
bench.iter(|| { bench.iter(|| {
SPk::zero(); CcaPk::zero();
}) })
}); });
c.bench_function("keygen", |bench| { c.bench_function("keygen", |bench| {

View File

@@ -21,13 +21,13 @@ fn generate_man() -> String {
// This function is purposely stupid and redundant // This function is purposely stupid and redundant
let man = render_man("mandoc", "./doc/rosenpass.1"); let man = render_man("mandoc", "./doc/rosenpass.1");
if let Ok(man) = man { if man.is_ok() {
return man; return man.unwrap();
} }
let man = render_man("groff", "./doc/rosenpass.1"); let man = render_man("groff", "./doc/rosenpass.1");
if let Ok(man) = man { if man.is_ok() {
return man; return man.unwrap();
} }
// TODO: Link to online manual here // TODO: Link to online manual here

View File

@@ -12,13 +12,13 @@
.Sh DESCRIPTION .Sh DESCRIPTION
.Nm .Nm
performs cryptographic key exchanges that are secure against quantum-computers performs cryptographic key exchanges that are secure against quantum-computers
and then outputs the keys. and outputs the keys.
These keys can then be passed to various services, such as wireguard or other These keys can then be passed to various services such as wireguard or other
vpn services, as pre-shared-keys to achieve security against attackers with vpn services as pre-shared-keys to achieve security against attackers with
quantum computers. quantum computers.
.Pp .Pp
This is a research project and quantum computers are not thought to become This is a research project and quantum computers are not thought to become
practical in fewer than ten years. practical in less than ten years.
If you are not specifically tasked with developing post-quantum secure systems, If you are not specifically tasked with developing post-quantum secure systems,
you probably do not need this tool. you probably do not need this tool.
.Ss COMMANDS .Ss COMMANDS
@@ -31,7 +31,7 @@ file secret!
Start a process to exchange keys with the specified peers. Start a process to exchange keys with the specified peers.
You should specify at least one peer. You should specify at least one peer.
.Pp .Pp
Its It's
.Ar OPTIONS .Ar OPTIONS
are as follows: are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
@@ -39,7 +39,7 @@ are as follows:
Instructs Instructs
.Nm .Nm
to listen on the specified interface and port. to listen on the specified interface and port.
By default, By default
.Nm .Nm
will listen on all interfaces and select a random port. will listen on all interfaces and select a random port.
.It Ar verbose .It Ar verbose

View File

@@ -59,10 +59,6 @@ listening on the provided IP and port combination, allowing connections from
.Sh EXIT STATUS .Sh EXIT STATUS
.Ex -std .Ex -std
.Sh EXAMPLES .Sh EXAMPLES
In this example, we will assume that the server has an interface bound to
192.168.0.1, that accepts incoming connections on port 9999/UDP for Rosenpass
and port 10000/UDP for WireGuard.
.Pp
To create a VPN connection, start by generating secret keys on both hosts. To create a VPN connection, start by generating secret keys on both hosts.
.Bd -literal -offset indent .Bd -literal -offset indent
rp genkey server.rosenpass-secret rp genkey server.rosenpass-secret

30
flake.lock generated
View File

@@ -8,11 +8,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1699770036, "lastModified": 1686291735,
"narHash": "sha256-bZmI7ytPAYLpyFNgj5xirDkKuAniOkj1xHdv5aIJ5GM=", "narHash": "sha256-mpq2m6TN3ImqqUqA4u93NvkZu5vH//3spqjmPRbRlvA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "81ab0b4f7ae9ebb57daa0edf119c4891806e4d3a", "rev": "6e6a94c4d0cac4821b6452fbae46609b89a8ddcf",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -26,11 +26,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1694529238, "lastModified": 1685518550,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -46,11 +46,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1698420672, "lastModified": 1679567394,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", "narHash": "sha256-ZvLuzPeARDLiQUt6zSZFGOs+HZmE+3g4QURc8mkBsfM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9", "rev": "88cd22380154a2c36799fe8098888f0f59861a15",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -61,11 +61,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1698846319, "lastModified": 1686237827,
"narHash": "sha256-4jyW/dqFBVpWFnhl0nvP6EN4lP7/ZqPxYRjl6var0Oc=", "narHash": "sha256-fAZB+Zkcmc+qlauiFnIH9+2qgwM0NO/ru5pWEw3tDow=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "34bdaaf1f0b7fb6d9091472edc968ff10a8c2857", "rev": "81ed90058a851eb73be835c770e062c6938c8a9e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -84,11 +84,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1699715108, "lastModified": 1686239338,
"narHash": "sha256-yPozsobJU55gj+szgo4Lpcg1lHvGQYAT6Y4MrC80mWE=", "narHash": "sha256-c6Mm7UnDf3j3akY3YB3rELFA76QRbB8ttSBsh00LWi0=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "5fcf5289e726785d20d3aa4d13d90a43ed248e83", "rev": "9c03aa1ac2e67051db83a85baf3cfee902e4dd84",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -55,13 +55,14 @@
}; };
# parsed Cargo.toml # parsed Cargo.toml
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml); cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
# source files relevant for rust # source files relevant for rust
src = pkgs.lib.sources.sourceFilesBySuffices ./. [ src = pkgs.lib.sourceByRegex ./. [
".lock" "Cargo\\.(toml|lock)"
".rs" "build.rs"
".toml" "(src|benches)(/.*\\.(rs|md))?"
"rp"
]; ];
# builds a bin path for all dependencies for the `rp` shellscript # builds a bin path for all dependencies for the `rp` shellscript
@@ -111,9 +112,6 @@
version = cargoToml.package.version; version = cargoToml.package.version;
inherit src; inherit src;
cargoBuildOptions = x: x ++ [ "-p" "rosenpass" ];
cargoTestOptions = x: x ++ [ "-p" "rosenpass" ];
doCheck = true; doCheck = true;
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
@@ -159,6 +157,11 @@
''; '';
}; };
# 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_
RUST_MIN_STACK = 8 * 1024 * 1024; # 8 MiB
# We want to build for a specific target... # We want to build for a specific target...
CARGO_BUILD_TARGET = target; CARGO_BUILD_TARGET = target;
@@ -287,7 +290,7 @@
packages.proof-proverif = pkgs.stdenv.mkDerivation { packages.proof-proverif = pkgs.stdenv.mkDerivation {
name = "rosenpass-proverif-proof"; name = "rosenpass-proverif-proof";
version = "unstable"; version = "unstable";
src = pkgs.lib.sources.sourceByRegex ./. [ src = pkgs.lib.sourceByRegex ./. [
"analyze.sh" "analyze.sh"
"marzipan(/marzipan.awk)?" "marzipan(/marzipan.awk)?"
"analysis(/.*)?" "analysis(/.*)?"
@@ -306,6 +309,7 @@
# #
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
inherit (packages.proof-proverif) CRYPTOVERIF_LIB; inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
inherit (packages.rosenpass) RUST_MIN_STACK;
inputsFrom = [ packages.default ]; inputsFrom = [ packages.default ];
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
cmake # override the fakecmake from the main step above cmake # override the fakecmake from the main step above
@@ -318,6 +322,7 @@
}; };
devShells.coverage = pkgs.mkShell { devShells.coverage = pkgs.mkShell {
inputsFrom = [ packages.default ]; inputsFrom = [ packages.default ];
inherit (packages.rosenpass) RUST_MIN_STACK;
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ]; nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
}; };

View File

@@ -71,13 +71,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)
# Mirrors
Don't want to use GitHub or only have an IPv6 connection? Rosenpass has set up two mirrors for this:
- [NotABug](https://notabug.org/rosenpass/rosenpass)
- [GitLab](https://gitlab.com/rosenpass/rosenpass/)
# Supported by # Supported by
Funded through <a href="https://nlnet.nl/">NLNet</a> with financial support for the European Commission's <a href="https://nlnet.nl/assure">NGI Assure</a> program. Funded through <a href="https://nlnet.nl/">NLNet</a> with financial support for the European Commission's <a href="https://nlnet.nl/assure">NGI Assure</a> program.

View File

@@ -1,42 +0,0 @@
[package]
name = "rosenpass"
version = "0.2.2"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Build post-quantum-secure VPNs with WireGuard!"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[[bench]]
name = "handshake"
harness = false
[dependencies]
anyhow = { version = "1.0.71", features = ["backtrace"] }
base64 = "0.21.1"
static_assertions = "1.1.0"
memoffset = "0.9.0"
libsodium-sys-stable = { version = "1.19.28", features = ["use-pkg-config"] }
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
lazy_static = "1.4.0"
thiserror = "1.0.40"
paste = "1.0.12"
log = { version = "0.4.17", optional = true }
env_logger = { version = "0.10.0", optional = true }
serde = { version = "1.0.163", features = ["derive"] }
toml = "0.7.4"
clap = { version = "4.3.0", features = ["derive"] }
mio = { version = "0.8.6", features = ["net", "os-poll"] }
[build-dependencies]
anyhow = "1.0.71"
[dev-dependencies]
criterion = "0.4.0"
test_bin = "0.4.0"
stacker = "0.1.15"
[features]
default = ["log", "env_logger"]

View File

@@ -1 +0,0 @@
../readme.md

View File

@@ -1,60 +0,0 @@
#[macro_use]
pub mod util;
#[macro_use]
pub mod sodium;
pub mod coloring;
#[rustfmt::skip]
pub mod labeled_prf;
pub mod app_server;
pub mod cli;
pub mod config;
pub mod msgs;
pub mod pqkem;
pub mod prftree;
pub mod protocol;
#[derive(thiserror::Error, Debug)]
pub enum RosenpassError {
#[error("error in OQS")]
Oqs,
#[error("error from external library while calling OQS")]
OqsExternalLib,
#[error("buffer size mismatch, required {required_size} but found {actual_size}")]
BufferSizeMismatch {
required_size: usize,
actual_size: usize,
},
#[error("invalid message type")]
InvalidMessageType(u8),
}
impl RosenpassError {
/// Helper function to check a buffer size
fn check_buffer_size(required_size: usize, actual_size: usize) -> Result<(), Self> {
if required_size != actual_size {
Err(Self::BufferSizeMismatch {
required_size,
actual_size,
})
} else {
Ok(())
}
}
}
/// Extension trait to attach function calls to foreign types.
trait RosenpassMaybeError {
/// Checks whether something is an error or not
fn to_rg_error(&self) -> Result<(), RosenpassError>;
}
impl RosenpassMaybeError for oqs_sys::common::OQS_STATUS {
fn to_rg_error(&self) -> Result<(), RosenpassError> {
use oqs_sys::common::OQS_STATUS;
match self {
OQS_STATUS::OQS_SUCCESS => Ok(()),
OQS_STATUS::OQS_ERROR => Err(RosenpassError::Oqs),
OQS_STATUS::OQS_EXTERNAL_LIB_ERROR_OPENSSL => Err(RosenpassError::OqsExternalLib),
}
}
}

48
rp
View File

@@ -197,7 +197,7 @@ exchange() {
lip="${listen%:*}"; lip="${listen%:*}";
lport="${listen/*:/}"; lport="${listen/*:/}";
if [[ "$lip" = "$lport" ]]; then if [[ "$lip" = "$lport" ]]; then
lip="[::]" lip="[0::0]"
fi fi
shift;; shift;;
-h | -help | --help | help) usage; return 0;; -h | -help | --help | help) usage; return 0;;
@@ -209,41 +209,15 @@ exchange() {
fatal "Needs at least one peer specified" fatal "Needs at least one peer specified"
fi fi
# os dependent setup frag "
case "$OSTYPE" in # Create the Wireguard interface
linux-*) # could be linux-gnu or linux-musl ip link add dev $(enquote "${dev}") type wireguard || true"
frag "
# Create the WireGuard interface
ip link add dev $(enquote "${dev}") type wireguard || true"
cleanup " cleanup "
ip link del dev $(enquote "${dev}") || true" ip link del dev $(enquote "${dev}") || true"
frag " frag "
ip link set dev $(enquote "${dev}") up" ip link set dev $(enquote "${dev}") up"
;;
freebsd*)
frag "
# load the WireGuard kernel module
kldload -n if_wg || fatal 'Cannot load if_wg kernel module'"
frag "
# Create the WireGuard interface
ifconfig wg create name $(enquote "${dev}") || true"
cleanup "
ifconfig $(enquote "${dev}") destroy || true"
frag "
ifconfig $(enquote "${dev}") up"
;;
*)
fatal "Your system $OSTYPE is not yet supported. We are happy to receive patches to address this :)"
;;
esac
frag " frag "
# Deploy the classic wireguard private key # Deploy the classic wireguard private key
@@ -281,7 +255,7 @@ exchange() {
local arg; arg="$1"; shift local arg; arg="$1"; shift
case "${arg}" in case "${arg}" in
peer) set -- "peer" "$@"; break;; # Next peer peer) set -- "peer" "$@"; break;; # Next peer
endpoint) ip="${1%:*}"; port="${1##*:}"; shift;; endpoint) ip="${1%:*}"; port="${1/*:/}"; shift;;
persistent-keepalive) keepalive="${1}"; shift;; persistent-keepalive) keepalive="${1}"; shift;;
allowed-ips) allowedips="${1}"; shift;; allowed-ips) allowedips="${1}"; shift;;
-h | -help | --help | help) usage; return 0;; -h | -help | --help | help) usage; return 0;;
@@ -352,9 +326,7 @@ main() {
verbose=0 verbose=0
scriptdir="$(dirname "${script}")" scriptdir="$(dirname "${script}")"
gitdir="$(detect_git_dir)" || true gitdir="$(detect_git_dir)" || true
if [[ -d /nix ]]; then nixdir="$(readlink -f result/bin/rp | grep -Pio '^/nix/store/[^/]+(?=/bin/[^/]+)')" || true
nixdir="$(readlink -f result/bin/rp | grep -Pio '^/nix/store/[^/]+(?=/bin/[^/]+)')" || true
fi
binary="$(find_rosenpass_binary)" binary="$(find_rosenpass_binary)"
# Parse command # Parse command

View File

@@ -1,7 +1,4 @@
use anyhow::bail; use log::{error, info, warn};
use anyhow::Result;
use log::{debug, error, info, warn};
use mio::Interest; use mio::Interest;
use mio::Token; use mio::Token;
@@ -19,14 +16,15 @@ use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
use std::slice; use std::slice;
use std::thread;
use std::time::Duration; use std::time::Duration;
use crate::util::fopen_w; use crate::util::fopen_w;
use crate::RosenpassError;
use crate::{ use crate::{
config::Verbosity, config::Verbosity,
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing}, protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
util::{b64_writer, fmt_b64}, util::{b64_writer, fmt_b64},
Result,
}; };
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
@@ -99,8 +97,8 @@ impl SocketPtr {
&mut srv.sockets[self.0] &mut srv.sockets[self.0]
} }
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> { pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> Result<()> {
self.get(srv).send_to(buf, addr)?; self.get(srv).send_to(&buf, addr)?;
Ok(()) Ok(())
} }
} }
@@ -182,7 +180,7 @@ impl Endpoint {
} }
/// Start endpoint discovery from a hostname /// Start endpoint discovery from a hostname
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> { pub fn discovery_from_hostname(hostname: String) -> Result<Self> {
let host = HostPathDiscoveryEndpoint::lookup(hostname)?; let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
Ok(Endpoint::Discovery(host)) Ok(Endpoint::Discovery(host))
} }
@@ -212,7 +210,7 @@ impl Endpoint {
Some(Self::discovery_from_addresses(addrs)) Some(Self::discovery_from_addresses(addrs))
} }
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> { pub fn send(&self, srv: &AppServer, buf: &[u8]) -> Result<()> {
use Endpoint::*; use Endpoint::*;
match self { match self {
SocketBoundAddress { socket, addr } => socket.send_to(srv, buf, *addr), SocketBoundAddress { socket, addr } => socket.send_to(srv, buf, *addr),
@@ -271,7 +269,7 @@ impl HostPathDiscoveryEndpoint {
} }
/// Lookup a hostname /// Lookup a hostname
pub fn lookup(hostname: String) -> anyhow::Result<Self> { pub fn lookup(hostname: String) -> Result<Self> {
Ok(Self { Ok(Self {
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(), addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
scouting_state: Cell::new((0, 0)), scouting_state: Cell::new((0, 0)),
@@ -292,16 +290,16 @@ impl HostPathDiscoveryEndpoint {
/// Attempt to reach the host /// Attempt to reach the host
/// ///
/// Will round-robin-try different socket-ip-combinations on each call. /// Will round-robin-try different socket-ip-combinations on each call.
pub fn send_scouting(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> { pub fn send_scouting(&self, srv: &AppServer, buf: &[u8]) -> Result<()> {
let (addr_off, sock_off) = self.scouting_state.get(); let (addr_off, sock_off) = self.scouting_state.get();
let mut addrs = (self.addresses) let mut addrs = (&self.addresses)
.iter() .iter()
.enumerate() .enumerate()
.cycle() .cycle()
.skip(addr_off) .skip(addr_off)
.take(self.addresses.len()); .take(self.addresses.len());
let mut sockets = (srv.sockets) let mut sockets = (&srv.sockets)
.iter() .iter()
.enumerate() .enumerate()
.cycle() .cycle()
@@ -331,23 +329,19 @@ impl HostPathDiscoveryEndpoint {
} }
} }
bail!("Unable to send message: All sockets returned errors.") error!("Unable to send message: All sockets returned errors.");
return Err(RosenpassError::RuntimeError);
} }
} }
impl AppServer { impl AppServer {
pub fn new( pub fn new(sk: SSk, pk: SPk, addrs: Vec<SocketAddr>, verbosity: Verbosity) -> Result<Self> {
sk: SSk,
pk: SPk,
addrs: Vec<SocketAddr>,
verbosity: Verbosity,
) -> anyhow::Result<Self> {
// setup mio // setup mio
let mio_poll = mio::Poll::new()?; let mio_poll = mio::Poll::new()?;
let events = mio::Events::with_capacity(8); let events = mio::Events::with_capacity(8);
// bind each SocketAddr to a socket // bind each SocketAddr to a socket
let maybe_sockets: Result<Vec<_>, _> = let maybe_sockets: std::result::Result<Vec<_>, std::io::Error> =
addrs.into_iter().map(mio::net::UdpSocket::bind).collect(); addrs.into_iter().map(mio::net::UdpSocket::bind).collect();
let mut sockets = maybe_sockets?; let mut sockets = maybe_sockets?;
@@ -414,7 +408,8 @@ impl AppServer {
} }
if sockets.is_empty() { if sockets.is_empty() {
bail!("No sockets to listen on!") error!("No sockets to listen on!");
return Err(RosenpassError::RuntimeError);
} }
// register all sockets to mio // register all sockets to mio
@@ -448,7 +443,7 @@ impl AppServer {
outfile: Option<PathBuf>, outfile: Option<PathBuf>,
outwg: Option<WireguardOut>, outwg: Option<WireguardOut>,
hostname: Option<String>, hostname: Option<String>,
) -> anyhow::Result<AppPeerPtr> { ) -> Result<AppPeerPtr> {
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?; let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
assert!(pn == self.peers.len()); assert!(pn == self.peers.len());
let initial_endpoint = hostname let initial_endpoint = hostname
@@ -464,7 +459,7 @@ impl AppServer {
Ok(AppPeerPtr(pn)) Ok(AppPeerPtr(pn))
} }
pub fn listen_loop(&mut self) -> anyhow::Result<()> { pub fn listen_loop(&mut self) -> Result<()> {
const INIT_SLEEP: f64 = 0.01; const INIT_SLEEP: f64 = 0.01;
const MAX_FAILURES: i32 = 10; const MAX_FAILURES: i32 = 10;
let mut failure_cnt = 0; let mut failure_cnt = 0;
@@ -485,10 +480,11 @@ impl AppServer {
let sleep = INIT_SLEEP * 2.0f64.powf(f64::from(failure_cnt - 1)); let sleep = INIT_SLEEP * 2.0f64.powf(f64::from(failure_cnt - 1));
let tries_left = MAX_FAILURES - (failure_cnt - 1); let tries_left = MAX_FAILURES - (failure_cnt - 1);
error!( error!(
"unexpected error after processing {} messages: {:?} {}", "unexpected error after processing {} messages: {:?}",
msgs_processed, msgs_processed,
err, err,
err.backtrace() // TODO do we need backtraces?
// err.backtrace()
); );
if tries_left > 0 { if tries_left > 0 {
error!("re-initializing networking in {sleep}! {tries_left} tries left."); error!("re-initializing networking in {sleep}! {tries_left} tries left.");
@@ -496,11 +492,12 @@ impl AppServer {
continue; continue;
} }
bail!("too many network failures"); error!("too many network failures");
return Err(RosenpassError::RuntimeError);
} }
} }
pub fn event_loop(&mut self) -> anyhow::Result<()> { pub fn event_loop(&mut self) -> Result<()> {
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero()); let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
/// if socket address for peer is known, call closure /// if socket address for peer is known, call closure
@@ -525,11 +522,9 @@ impl AppServer {
use AppPollResult::*; use AppPollResult::*;
use KeyOutputReason::*; use KeyOutputReason::*;
match self.poll(&mut *rx)? { match self.poll(&mut *rx)? {
#[allow(clippy::redundant_closure_call)]
SendInitiation(peer) => tx_maybe_with!(peer, || self SendInitiation(peer) => tx_maybe_with!(peer, || self
.crypt .crypt
.initiate_handshake(peer.lower(), &mut *tx))?, .initiate_handshake(peer.lower(), &mut *tx))?,
#[allow(clippy::redundant_closure_call)]
SendRetransmission(peer) => tx_maybe_with!(peer, || self SendRetransmission(peer) => tx_maybe_with!(peer, || self
.crypt .crypt
.retransmit_handshake(peer.lower(), &mut *tx))?, .retransmit_handshake(peer.lower(), &mut *tx))?,
@@ -551,11 +546,9 @@ impl AppServer {
match self.crypt.handle_msg(&rx[..len], &mut *tx) { match self.crypt.handle_msg(&rx[..len], &mut *tx) {
Err(ref e) => { Err(ref e) => {
self.verbose().then(|| { self.verbose().then(|| {
info!( error!(
"error processing incoming message from {:?}: {:?} {}", "error processing incoming message from {:?}: {:?}",
endpoint, endpoint, e
e,
e.backtrace()
); );
}); });
} }
@@ -583,12 +576,7 @@ impl AppServer {
} }
} }
pub fn output_key( pub fn output_key(&self, peer: AppPeerPtr, why: KeyOutputReason, key: &SymKey) -> Result<()> {
&self,
peer: AppPeerPtr,
why: KeyOutputReason,
key: &SymKey,
) -> anyhow::Result<()> {
let peerid = peer.lower().get(&self.crypt).pidt()?; let peerid = peer.lower().get(&self.crypt).pidt()?;
let ap = peer.get_app(self); let ap = peer.get_app(self);
@@ -623,7 +611,7 @@ impl AppServer {
} }
if let Some(owg) = ap.outwg.as_ref() { if let Some(owg) = ap.outwg.as_ref() {
let mut child = Command::new("wg") let child = Command::new("wg")
.arg("set") .arg("set")
.arg(&owg.dev) .arg(&owg.dev)
.arg("peer") .arg("peer")
@@ -633,27 +621,13 @@ impl AppServer {
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.args(&owg.extra_params) .args(&owg.extra_params)
.spawn()?; .spawn()?;
b64_writer(child.stdin.take().unwrap()).write_all(key.secret())?; b64_writer(child.stdin.unwrap()).write_all(key.secret())?;
thread::spawn(move || {
let status = child.wait();
if let Ok(status) = status {
if status.success() {
debug!("successfully passed psk to wg")
} else {
error!("could not pass psk to wg {:?}", status)
}
} else {
error!("wait failed: {:?}", status)
}
});
} }
Ok(()) Ok(())
} }
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> { pub fn poll(&mut self, rx_buf: &mut [u8]) -> Result<AppPollResult> {
use crate::protocol::PollResult as C; use crate::protocol::PollResult as C;
use AppPollResult as A; use AppPollResult as A;
loop { loop {
@@ -678,7 +652,7 @@ impl AppServer {
&mut self, &mut self,
buf: &mut [u8], buf: &mut [u8],
timeout: Timing, timeout: Timing,
) -> anyhow::Result<Option<(usize, Endpoint)>> { ) -> Result<Option<(usize, Endpoint)>> {
let timeout = Duration::from_secs_f64(timeout); let timeout = Duration::from_secs_f64(timeout);
// if there is no time to wait on IO, well, then, lets not waste any time! // if there is no time to wait on IO, well, then, lets not waste any time!

View File

@@ -1,7 +1,3 @@
use anyhow::{bail, ensure};
use clap::Parser;
use std::path::{Path, PathBuf};
use crate::app_server; use crate::app_server;
use crate::app_server::AppServer; use crate::app_server::AppServer;
use crate::util::{LoadValue, LoadValueB64}; use crate::util::{LoadValue, LoadValueB64};
@@ -10,7 +6,12 @@ use crate::{
coloring::Secret, coloring::Secret,
pqkem::{StaticKEM, KEM}, pqkem::{StaticKEM, KEM},
protocol::{SPk, SSk, SymKey}, protocol::{SPk, SSk, SymKey},
Result,
RosenpassError,
}; };
use clap::Parser;
use log::error;
use std::path::{Path, PathBuf};
use super::config; use super::config;
@@ -98,25 +99,21 @@ pub enum Cli {
} }
impl Cli { impl Cli {
pub fn run() -> anyhow::Result<()> { pub fn run() -> Result<()> {
let cli = Self::parse(); let cli = Self::parse();
use Cli::*; use Cli::*;
match cli { match cli {
Man => { Man => {
let man_cmd = std::process::Command::new("man") let _man_cmd = std::process::Command::new("man")
.args(["1", "rosenpass"]) .args(["1", "rosenpass"])
.status(); .status();
if !(man_cmd.is_ok() && man_cmd.unwrap().success()) {
println!(include_str!(env!("ROSENPASS_MAN")));
}
} }
GenConfig { config_file, force } => { GenConfig { config_file, force } => {
ensure!( if !force && config_file.exists() {
force || !config_file.exists(), error!("config file {config_file:?} already exists");
"config file {config_file:?} already exists" return Err(RosenpassError::RuntimeError);
); }
config::Rosenpass::example_config().store(config_file)?; config::Rosenpass::example_config().store(config_file)?;
} }
@@ -130,51 +127,52 @@ impl Cli {
// figure out where the key file is specified, in the config file or directly as flag? // figure out where the key file is specified, in the config file or directly as flag?
let (pkf, skf) = match (config_file, public_key, secret_key) { let (pkf, skf) = match (config_file, public_key, secret_key) {
(Some(config_file), _, _) => { (Some(config_file), _, _) => {
ensure!( if !config_file.exists() {
config_file.exists(), error!("config file {config_file:?} does not exist");
"config file {config_file:?} does not exist" return Err(RosenpassError::RuntimeError);
); }
let config = config::Rosenpass::load(config_file)?; let config = config::Rosenpass::load(config_file)?;
(config.public_key, config.secret_key) (config.public_key, config.secret_key)
} }
(_, Some(pkf), Some(skf)) => (pkf, skf), (_, Some(pkf), Some(skf)) => (pkf, skf),
_ => { _ => return Err(RosenpassError::ConfigError(
bail!("either a config-file or both public-key and secret-key file are required") "either a config-file or both public-key and secret-key file are required"
} .into(),
)),
}; };
// check that we are not overriding something unintentionally // check that we are not overriding something unintentionally
let mut problems = vec![]; let mut problems = false;
if !force && pkf.is_file() { if !force && pkf.is_file() {
problems.push(format!( problems = true;
"public-key file {pkf:?} exist, refusing to overwrite it" error!("public-key file {pkf:?} exist, refusing to overwrite it");
));
} }
if !force && skf.is_file() { if !force && skf.is_file() {
problems.push(format!( problems = true;
"secret-key file {skf:?} exist, refusing to overwrite it" error!("secret-key file {skf:?} exist, refusing to overwrite it");
));
} }
if !problems.is_empty() { if problems {
bail!(problems.join("\n")); return Err(RosenpassError::RuntimeError);
} }
// generate the keys and store them in files // generate the keys and store them in files
let mut ssk = crate::protocol::SSk::random(); let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random(); let mut spk = crate::protocol::SPk::random();
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(skf)?; unsafe {
spk.store_secret(pkf)?; StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(skf)?;
spk.store_secret(pkf)?;
}
} }
ExchangeConfig { config_file } => { ExchangeConfig { config_file } => {
ensure!( if !config_file.exists() {
config_file.exists(), error!("config file '{config_file:?}' does not exist");
"config file '{config_file:?}' does not exist" return Err(RosenpassError::RuntimeError);
); }
let config = config::Rosenpass::load(config_file)?; let config = config::Rosenpass::load(config_file)?;
config.validate()?; config.validate()?;
@@ -217,7 +215,7 @@ impl Cli {
Ok(()) Ok(())
} }
fn event_loop(config: config::Rosenpass) -> anyhow::Result<()> { fn event_loop(config: config::Rosenpass) -> Result<()> {
// load own keys // load own keys
let sk = SSk::load(&config.secret_key)?; let sk = SSk::load(&config.secret_key)?;
let pk = SPk::load(&config.public_key)?; let pk = SPk::load(&config.public_key)?;
@@ -250,11 +248,11 @@ impl Cli {
} }
trait StoreSecret { trait StoreSecret {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>; unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
} }
impl<const N: usize> StoreSecret for Secret<N> { impl<const N: usize> StoreSecret for Secret<N> {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> { unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
std::fs::write(path, self.secret())?; std::fs::write(path, self.secret())?;
Ok(()) Ok(())
} }

View File

@@ -6,10 +6,10 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::{bail, ensure}; use log::{error, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::util::fopen_w; use crate::{util::fopen_w, Result, RosenpassError};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass { pub struct Rosenpass {
@@ -55,8 +55,6 @@ pub struct RosenpassPeer {
pub struct WireGuard { pub struct WireGuard {
pub device: String, pub device: String,
pub peer: String, pub peer: String,
#[serde(default)]
pub extra_params: Vec<String>, pub extra_params: Vec<String>,
} }
@@ -64,7 +62,7 @@ impl Rosenpass {
/// Load a config file from a file path /// Load a config file from a file path
/// ///
/// no validation is conducted /// no validation is conducted
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> { pub fn load<P: AsRef<Path>>(p: P) -> Result<Self> {
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?; let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
config.config_file_path = p.as_ref().to_owned(); config.config_file_path = p.as_ref().to_owned();
@@ -72,7 +70,7 @@ impl Rosenpass {
} }
/// Write a config to a file /// Write a config to a file
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> { pub fn store<P: AsRef<Path>>(&self, p: P) -> Result<()> {
let serialized_config = let serialized_config =
toml::to_string_pretty(&self).expect("unable to serialize the default config"); toml::to_string_pretty(&self).expect("unable to serialize the default config");
fs::write(p, serialized_config)?; fs::write(p, serialized_config)?;
@@ -80,7 +78,7 @@ impl Rosenpass {
} }
/// Commit the configuration to where it came from, overwriting the original file /// Commit the configuration to where it came from, overwriting the original file
pub fn commit(&self) -> anyhow::Result<()> { pub fn commit(&self) -> Result<()> {
let mut f = fopen_w(&self.config_file_path)?; let mut f = fopen_w(&self.config_file_path)?;
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?; f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
@@ -88,36 +86,40 @@ impl Rosenpass {
} }
/// Validate a configuration /// Validate a configuration
pub fn validate(&self) -> anyhow::Result<()> { pub fn validate(&self) -> Result<()> {
// check the public-key file exists // check the public-key file exists
ensure!( if !(self.public_key.is_file()) {
self.public_key.is_file(), return Err(RosenpassError::ConfigError(format!(
"public-key file {:?} does not exist", "public-key file {:?} does not exist",
self.public_key self.public_key
); )));
}
// check the secret-key file exists // check the secret-key file exists
ensure!( if !(self.secret_key.is_file()) {
self.secret_key.is_file(), return Err(RosenpassError::ConfigError(format!(
"secret-key file {:?} does not exist", "secret-key file {:?} does not exist",
self.secret_key self.secret_key
); )));
}
for (i, peer) in self.peers.iter().enumerate() { for (i, peer) in self.peers.iter().enumerate() {
// check peer's public-key file exists // check peer's public-key file exists
ensure!( if !(peer.public_key.is_file()) {
peer.public_key.is_file(), return Err(RosenpassError::ConfigError(format!(
"peer {i} public-key file {:?} does not exist", "peer {i} public-key file {:?} does not exist",
peer.public_key peer.public_key
); )));
}
// check endpoint is usable // check endpoint is usable
if let Some(addr) = peer.endpoint.as_ref() { if let Some(addr) = peer.endpoint.as_ref() {
ensure!( if !(addr.to_socket_addrs().is_ok()) {
addr.to_socket_addrs().is_ok(), return Err(RosenpassError::ConfigError(format!(
"peer {i} endpoint {} can not be parsed to a socket address", "peer {i} endpoint {} can not be parsed to a socket address",
addr addr
); )));
}
} }
// TODO warn if neither out_key nor exchange_command is defined // TODO warn if neither out_key nor exchange_command is defined
@@ -153,7 +155,7 @@ impl Rosenpass {
/// from chaotic args /// from chaotic args
/// Quest: the grammar is undecideable, what do we do here? /// Quest: the grammar is undecideable, what do we do here?
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> { pub fn parse_args(args: Vec<String>) -> Result<Self> {
let mut config = Self::new("", ""); let mut config = Self::new("", "");
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
@@ -176,6 +178,7 @@ impl Rosenpass {
// TODO idea: use config.peers.len() to give index of peer with conflicting argument // TODO idea: use config.peers.len() to give index of peer with conflicting argument
use State::*; use State::*;
let mut problem = false;
let mut state = Own; let mut state = Own;
let mut current_peer = None; let mut current_peer = None;
let p_exists = "a peer should exist by now"; let p_exists = "a peer should exist by now";
@@ -185,9 +188,7 @@ impl Rosenpass {
(Own, "public-key", None) => OwnPublicKey, (Own, "public-key", None) => OwnPublicKey,
(Own, "secret-key", None) => OwnSecretKey, (Own, "secret-key", None) => OwnSecretKey,
(Own, "private-key", None) => { (Own, "private-key", None) => {
log::warn!( warn!("the private-key argument is deprecated, please use secret-key instead");
"the private-key argument is deprecated, please use secret-key instead"
);
OwnSecretKey OwnSecretKey
} }
(Own, "listen", None) => OwnListen, (Own, "listen", None) => OwnListen,
@@ -196,14 +197,16 @@ impl Rosenpass {
Own Own
} }
(Own, "peer", None) => { (Own, "peer", None) => {
ensure!( if !(already_set.contains(&OwnPublicKey)) {
already_set.contains(&OwnPublicKey), return Err(RosenpassError::ConfigError(
"public-key file must be set" "public-key file must be set".into(),
); ));
ensure!( }
already_set.contains(&OwnSecretKey), if !(already_set.contains(&OwnSecretKey)) {
"secret-key file must be set" return Err(RosenpassError::ConfigError(
); "secret-key file must be set".into(),
));
}
already_set.clear(); already_set.clear();
current_peer = Some(RosenpassPeer::default()); current_peer = Some(RosenpassPeer::default());
@@ -211,18 +214,20 @@ impl Rosenpass {
Peer Peer
} }
(OwnPublicKey, pk, None) => { (OwnPublicKey, pk, None) => {
ensure!( if !(already_set.insert(OwnPublicKey)) {
already_set.insert(OwnPublicKey), return Err(RosenpassError::ConfigError(
"public-key was already set" "public-key was already set".into(),
); ));
}
config.public_key = pk.into(); config.public_key = pk.into();
Own Own
} }
(OwnSecretKey, sk, None) => { (OwnSecretKey, sk, None) => {
ensure!( if !(already_set.insert(OwnSecretKey)) {
already_set.insert(OwnSecretKey), return Err(RosenpassError::ConfigError(
"secret-key was already set" "secret-key was already set".into(),
); ));
}
config.secret_key = sk.into(); config.secret_key = sk.into();
Own Own
} }
@@ -250,36 +255,45 @@ impl Rosenpass {
(Peer, "outfile", Some(_)) => PeerOutfile, (Peer, "outfile", Some(_)) => PeerOutfile,
(Peer, "wireguard", Some(_)) => PeerWireguardDev, (Peer, "wireguard", Some(_)) => PeerWireguardDev,
(PeerPublicKey, pk, Some(peer)) => { (PeerPublicKey, pk, Some(peer)) => {
ensure!( if !(already_set.insert(PeerPublicKey)) {
already_set.insert(PeerPublicKey), return Err(RosenpassError::ConfigError(
"public-key was already set" "public-key was already set".into(),
); ));
}
peer.public_key = pk.into(); peer.public_key = pk.into();
Peer Peer
} }
(PeerEndpoint, e, Some(peer)) => { (PeerEndpoint, e, Some(peer)) => {
ensure!(already_set.insert(PeerEndpoint), "endpoint was already set"); if !already_set.insert(PeerEndpoint) {
error!("endpoint was already set");
problem = true;
}
peer.endpoint = Some(e.to_owned()); peer.endpoint = Some(e.to_owned());
Peer Peer
} }
(PeerPsk, psk, Some(peer)) => { (PeerPsk, psk, Some(peer)) => {
ensure!(already_set.insert(PeerEndpoint), "peer psk was already set"); if !already_set.insert(PeerEndpoint) {
error!("peer psk was already set");
problem = true;
}
peer.pre_shared_key = Some(psk.into()); peer.pre_shared_key = Some(psk.into());
Peer Peer
} }
(PeerOutfile, of, Some(peer)) => { (PeerOutfile, of, Some(peer)) => {
ensure!( if !(already_set.insert(PeerOutfile)) {
already_set.insert(PeerOutfile), return Err(RosenpassError::ConfigError(
"peer outfile was already set" "peer outfile was already set".into(),
); ));
}
peer.key_out = Some(of.into()); peer.key_out = Some(of.into());
Peer Peer
} }
(PeerWireguardDev, dev, Some(peer)) => { (PeerWireguardDev, dev, Some(peer)) => {
ensure!( if !(already_set.insert(PeerWireguardDev)) {
already_set.insert(PeerWireguardDev), return Err(RosenpassError::ConfigError(
"peer wireguard-dev was already set" "peer wireguard-dev was already set".into(),
); ));
}
assert!(peer.wg.is_none()); assert!(peer.wg.is_none());
peer.wg = Some(WireGuard { peer.wg = Some(WireGuard {
device: dev.to_string(), device: dev.to_string(),
@@ -289,10 +303,11 @@ impl Rosenpass {
PeerWireguardPeer PeerWireguardPeer
} }
(PeerWireguardPeer, p, Some(peer)) => { (PeerWireguardPeer, p, Some(peer)) => {
ensure!( if !(already_set.insert(PeerWireguardPeer)) {
already_set.insert(PeerWireguardPeer), return Err(RosenpassError::ConfigError(
"peer wireguard-peer was already set" "peer wireguard-peer was already set".into(),
); ));
}
peer.wg.as_mut().expect(wg_exists).peer = p.to_string(); peer.wg.as_mut().expect(wg_exists).peer = p.to_string();
PeerWireguardExtraArgs PeerWireguardExtraArgs
} }
@@ -307,14 +322,16 @@ impl Rosenpass {
// error cases // error cases
(Own, x, None) => { (Own, x, None) => {
bail!("unrecognised argument {x}"); error!("unrecognised argument {x}");
return Err(RosenpassError::RuntimeError);
} }
(Own | OwnPublicKey | OwnSecretKey | OwnListen, _, Some(_)) => { (Own | OwnPublicKey | OwnSecretKey | OwnListen, _, Some(_)) => {
panic!("current_peer is not None while in Own* state, this must never happen") panic!("current_peer is not None while in Own* state, this must never happen")
} }
(State::Peer, arg, Some(_)) => { (State::Peer, arg, Some(_)) => {
bail!("unrecongnised argument {arg}"); error!("unrecongnised argument {arg}");
return Err(RosenpassError::RuntimeError);
} }
( (
Peer Peer
@@ -333,6 +350,10 @@ impl Rosenpass {
}; };
} }
if problem {
return Err(RosenpassError::RuntimeError);
}
if let Some(p) = current_peer { if let Some(p) = current_peer {
// TODO ensure peer is propagated with sufficient information // TODO ensure peer is propagated with sufficient information
config.peers.push(p); config.peers.push(p);

View File

@@ -2,8 +2,8 @@
//! ensures their uniqueness //! ensures their uniqueness
use { use {
crate::Result,
crate::{prftree::PrfTree, sodium::KEY_SIZE}, crate::{prftree::PrfTree, sodium::KEY_SIZE},
anyhow::Result,
}; };
pub fn protocol() -> Result<PrfTree> { pub fn protocol() -> Result<PrfTree> {

131
src/lib.rs Normal file
View File

@@ -0,0 +1,131 @@
use protocol::{HandshakeStateMachine, PeerId, PeerPtr, SessionId};
#[macro_use]
pub mod util;
#[macro_use]
pub mod sodium;
pub mod coloring;
#[rustfmt::skip]
pub mod labeled_prf;
pub mod app_server;
pub mod cli;
pub mod config;
pub mod msgs;
pub mod pqkem;
pub mod prftree;
pub mod protocol;
#[derive(thiserror::Error, Debug)]
pub enum RosenpassError {
#[error("error in OQS")]
Oqs,
#[error("error from external library while calling OQS")]
OqsExternalLib,
#[error("error while calling into libsodium")]
LibsodiumError(&'static str),
#[error("buffer size mismatch, required {required_size} but only found {actual_size}")]
BufferSizeMismatch {
required_size: usize,
actual_size: usize,
},
#[error("invalid message type")]
InvalidMessageType(u8),
#[error("peer id {0:?} already taken")]
PeerIdAlreadyTaken(PeerId),
#[error("session id {0:?} already taken")]
SessionIdAlreadyTaken(SessionId),
#[error("{0}")]
NotImplemented(&'static str),
#[error("{0}")]
ConfigError(String),
#[error("see last log messages")]
RuntimeError,
#[error("{0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
TomlDeserError(#[from] toml::de::Error),
#[error("{0}")]
TomlSerError(#[from] toml::ser::Error),
#[error("invalid session id {0:?} was used")]
InvalidSessionId(SessionId),
#[error("no session available")]
NoSession,
#[error("the peer {0:?} does not exist")]
NoSuchPeer(PeerPtr),
#[error("the peer id {0:?} does not exist")]
NoSuchPeerId(PeerId),
#[error("the session {0:?} does not exist")]
NoSuchSessionId(SessionId),
#[error("no current handshake with peer {0:?}")]
NoCurrentHs(PeerPtr),
// TODO implement Display for Peer/Session ptr?
#[error("message seal broken")]
SealBroken,
#[error("received empty message")]
EmptyMessage,
#[error("biscuit with invalid number")]
InvalidBiscuitNo,
#[error("got unexpected message")]
UnexpectedMessage {
session: SessionId,
expected: Option<HandshakeStateMachine>,
got: Option<HandshakeStateMachine>,
},
#[error("???")]
StaleNonce,
}
/// Rosenpass Result type
pub type Result<T> = core::result::Result<T, RosenpassError>;
impl RosenpassError {
/// Helper function to check a buffer size
fn check_buffer_size(required_size: usize, actual_size: usize) -> Result<()> {
if required_size != actual_size {
Err(Self::BufferSizeMismatch {
required_size,
actual_size,
})
} else {
Ok(())
}
}
}
/// Extension trait to attach function calls to foreign types.
trait RosenpassMaybeError {
/// Checks whether something is an error or not
fn to_rg_error(&self) -> Result<()>;
}
impl RosenpassMaybeError for oqs_sys::common::OQS_STATUS {
fn to_rg_error(&self) -> Result<()> {
use oqs_sys::common::OQS_STATUS;
match self {
OQS_STATUS::OQS_SUCCESS => Ok(()),
OQS_STATUS::OQS_ERROR => Err(RosenpassError::Oqs),
OQS_STATUS::OQS_EXTERNAL_LIB_ERROR_OPENSSL => Err(RosenpassError::OqsExternalLib),
}
}
}

View File

@@ -27,7 +27,7 @@ pub fn prf_into(out: &mut [u8], key: &[u8], data: &[u8]) {
hmac_into(out, key, data).unwrap() hmac_into(out, key, data).unwrap()
} }
pub fn prf(key: &[u8], data: &[u8]) -> [u8; KEY_SIZE] { pub fn prf(key: &[u8], data: &[u8]) -> [u8; KEY_SIZE]{
mutating([0u8; KEY_SIZE], |r| prf_into(r, key, data)) mutating([0u8; KEY_SIZE], |r| prf_into(r, key, data))
} }
@@ -70,9 +70,8 @@ impl IprfBranch {
impl SecretIprf { impl SecretIprf {
fn prf_invoc(k: &[u8], d: &[u8]) -> SecretIprf { fn prf_invoc(k: &[u8], d: &[u8]) -> SecretIprf {
mutating(SecretIprf(Secret::zero()), |r| { mutating(SecretIprf(Secret::zero()), |r|
prf_into(k, d, r.secret_mut()) prf_into(k, d, r.secret_mut()))
})
} }
fn from_key(k: Secret<N>) -> SecretIprf { fn from_key(k: Secret<N>) -> SecretIprf {

View File

@@ -131,6 +131,9 @@ macro_rules! data_lense(
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{ impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
$( $(
/// Size in bytes of the field `
#[doc = !($field)]
/// `
pub const fn [< $field _len >]() -> usize{ pub const fn [< $field _len >]() -> usize{
$len $len
} }
@@ -140,7 +143,7 @@ macro_rules! data_lense(
pub fn check_size(len: usize) -> Result<(), RosenpassError>{ pub fn check_size(len: usize) -> Result<(), RosenpassError>{
let required_size = $( $len + )+ 0; let required_size = $( $len + )+ 0;
let actual_size = len; let actual_size = len;
if required_size != actual_size { if required_size < actual_size {
Err(RosenpassError::BufferSizeMismatch { Err(RosenpassError::BufferSizeMismatch {
required_size, required_size,
actual_size, actual_size,
@@ -196,53 +199,23 @@ macro_rules! data_lense(
type __ContainerType; type __ContainerType;
/// Create a lense to the byte slice /// Create a lense to the byte slice
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>; fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
/// Create a lense to the byte slice, automatically truncating oversized buffers
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
} }
impl<'a> [< $type Ext >] for &'a [u8] { impl<'a> [< $type Ext >] for &'a [u8] {
type __ContainerType = &'a [u8]; type __ContainerType = &'a [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
} }
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
let required_size = $( $len + )+ 0;
let actual_size = self.len();
if actual_size < required_size {
return Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
});
}
[< $type Ext >]::[< $type:snake >](&self[..required_size])
}
} }
impl<'a> [< $type Ext >] for &'a mut [u8] { impl<'a> [< $type Ext >] for &'a mut [u8] {
type __ContainerType = &'a mut [u8]; type __ContainerType = &'a mut [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?; fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
} }
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
let required_size = $( $len + )+ 0;
let actual_size = self.len();
if actual_size < required_size {
return Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
});
}
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
}
} }
}); });
); );

View File

@@ -1,10 +1,8 @@
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf) //! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
use { use crate::{
crate::{ coloring::Secret,
coloring::Secret, sodium::{hmac, hmac_into, KEY_SIZE},
sodium::{hmac, hmac_into, KEY_SIZE}, Result,
},
anyhow::Result,
}; };
// TODO Use a proper Dec interface // TODO Use a proper Dec interface

View File

@@ -23,10 +23,10 @@
//! pqkem::{StaticKEM, KEM}, //! pqkem::{StaticKEM, KEM},
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey}, //! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
//! }; //! };
//! # fn main() -> anyhow::Result<()> { //! # fn main() -> Result<(), rosenpass::RosenpassError> {
//! //!
//! // always init libsodium before anything //! // always init libsodium before anything
//! rosenpass::sodium::sodium_init()?; //! rosenpass::sodium::sodium_init().unwrap();
//! //!
//! // initialize secret and public key for peer a ... //! // initialize secret and public key for peer a ...
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero()); //! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
@@ -42,31 +42,32 @@
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone()); //! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
//! //!
//! // introduce peers to each other //! // introduce peers to each other
//! a.add_peer(Some(psk.clone()), peer_b_pk)?; //! a.add_peer(Some(psk.clone()), peer_b_pk).unwrap();
//! b.add_peer(Some(psk), peer_a_pk)?; //! b.add_peer(Some(psk), peer_a_pk).unwrap();
//! //!
//! // declare buffers for message exchange //! // declare buffers for message exchange
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero()); //! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
//! //!
//! // let a initiate a handshake //! // let a initiate a handshake
//! let mut maybe_len = Some(a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice())?); //! let length = a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice());
//! //!
//! // let a and b communicate //! // let b respond to a and a respond to b, in two rounds
//! while let Some(len) = maybe_len { //! for _ in 0..2 {
//! maybe_len = b.handle_msg(&a_buf[..len], &mut b_buf[..])?.resp; //! b.handle_msg(&a_buf[..], &mut b_buf[..]);
//! std::mem::swap(&mut a, &mut b); //! a.handle_msg(&b_buf[..], &mut a_buf[..]);
//! std::mem::swap(&mut a_buf, &mut b_buf);
//! } //! }
//! //!
//! // all done! Extract the shared keys and ensure they are identical //! // all done! Extract the shared keys and ensure they are identical
//! let a_key = a.osk(PeerPtr(0))?; //! let a_key = a.osk(PeerPtr(0));
//! let b_key = b.osk(PeerPtr(0))?; //! let b_key = b.osk(PeerPtr(0));
//! assert_eq!(a_key.secret(), b_key.secret(), //! assert_eq!(a_key.unwrap().secret(), b_key.unwrap().secret(),
//! "the key exchanged failed to establish a shared secret"); //! "the key exchanged failed to establish a shared secret");
//! # Ok(()) //! # Ok(())
//! # } //! # }
//! ``` //! ```
use log::{trace, warn};
use crate::{ use crate::{
coloring::*, coloring::*,
labeled_prf as lprf, labeled_prf as lprf,
@@ -75,8 +76,8 @@ use crate::{
prftree::{SecretPrfTree, SecretPrfTreeBranch}, prftree::{SecretPrfTree, SecretPrfTreeBranch},
sodium::*, sodium::*,
util::*, util::*,
Result, RosenpassError,
}; };
use anyhow::{bail, ensure, Context, Result};
use std::collections::hash_map::{ use std::collections::hash_map::{
Entry::{Occupied, Vacant}, Entry::{Occupied, Vacant},
HashMap, HashMap,
@@ -486,10 +487,8 @@ impl CryptoServer {
let peerid = peer.pidt()?; let peerid = peer.pidt()?;
let peerno = self.peers.len(); let peerno = self.peers.len();
match self.index.entry(IndexKey::Peer(peerid)) { match self.index.entry(IndexKey::Peer(peerid)) {
Occupied(_) => bail!( // TODO improve type handling, use PeerPtr
"Cannot insert peer with id {:?}; peer with this id already registered.", Occupied(_) => return Err(RosenpassError::PeerIdAlreadyTaken(peerid)),
peerid
),
Vacant(e) => e.insert(peerno), Vacant(e) => e.insert(peerno),
}; };
self.peers.push(peer); self.peers.push(peer);
@@ -501,7 +500,7 @@ impl CryptoServer {
pub fn register_session(&mut self, id: SessionId, peer: PeerPtr) -> Result<()> { pub fn register_session(&mut self, id: SessionId, peer: PeerPtr) -> Result<()> {
match self.index.entry(IndexKey::Sid(id)) { match self.index.entry(IndexKey::Sid(id)) {
Occupied(p) if PeerPtr(*p.get()) == peer => {} // Already registered Occupied(p) if PeerPtr(*p.get()) == peer => {} // Already registered
Occupied(_) => bail!("Cannot insert session with id {:?}; id is in use.", id), Occupied(_) => return Err(RosenpassError::SessionIdAlreadyTaken(id)),
Vacant(e) => { Vacant(e) => {
e.insert(peer.0); e.insert(peer.0);
} }
@@ -523,8 +522,11 @@ impl CryptoServer {
}; };
} }
pub fn find_peer(&self, id: PeerId) -> Option<PeerPtr> { pub fn find_peer(&self, id: PeerId) -> Result<PeerPtr> {
self.index.get(&IndexKey::Peer(id)).map(|no| PeerPtr(*no)) self.index
.get(&IndexKey::Peer(id))
.map(|no| PeerPtr(*no))
.ok_or_else(|| RosenpassError::NoSuchPeerId(id))
} }
// lookup_session in whitepaper // lookup_session in whitepaper
@@ -737,7 +739,7 @@ impl CryptoServer {
// TODO remove unnecessary copying between global tx_buf and per-peer buf // TODO remove unnecessary copying between global tx_buf and per-peer buf
// TODO move retransmission storage to io server // TODO move retransmission storage to io server
pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result<usize> { pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result<usize> {
let mut msg = tx_buf.envelope_truncating::<InitHello<()>>()?; // Envelope::<InitHello>::default(); // TODO let mut msg = tx_buf.envelope::<InitHello<()>>()?; // Envelope::<InitHello>::default(); // TODO
self.handle_initiation(peer, msg.payload_mut().init_hello()?)?; self.handle_initiation(peer, msg.payload_mut().init_hello()?)?;
let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?; let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?;
peer.hs() peer.hs()
@@ -782,19 +784,23 @@ impl CryptoServer {
/// | t2 | `InitConf` | -> | | /// | t2 | `InitConf` | -> | |
/// | t3 | | <- | `EmptyData` | /// | t3 | | <- | `EmptyData` |
pub fn handle_msg(&mut self, rx_buf: &[u8], tx_buf: &mut [u8]) -> Result<HandleMsgResult> { pub fn handle_msg(&mut self, rx_buf: &[u8], tx_buf: &mut [u8]) -> Result<HandleMsgResult> {
let seal_broken = "Message seal broken!";
// length of the response. We assume no response, so None for now // length of the response. We assume no response, so None for now
let mut len = 0; let mut len = 0;
let mut exchanged = false; let mut exchanged = false;
ensure!(!rx_buf.is_empty(), "received empty message, ignoring it"); if rx_buf.is_empty() {
trace!("received empty message, ignoring it");
return Err(RosenpassError::EmptyMessage);
}
let peer = match rx_buf[0].try_into() { let peer = match rx_buf[0].try_into() {
Ok(MsgType::InitHello) => { Ok(MsgType::InitHello) => {
let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?; let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?;
ensure!(msg_in.check_seal(self)?, seal_broken); if msg_in.check_seal(self)? {
return Err(RosenpassError::SealBroken);
}
let mut msg_out = tx_buf.envelope_truncating::<RespHello<&mut [u8]>>()?; let mut msg_out = tx_buf.envelope::<RespHello<&mut [u8]>>()?;
let peer = self.handle_init_hello( let peer = self.handle_init_hello(
msg_in.payload().init_hello()?, msg_in.payload().init_hello()?,
msg_out.payload_mut().resp_hello()?, msg_out.payload_mut().resp_hello()?,
@@ -804,9 +810,11 @@ impl CryptoServer {
} }
Ok(MsgType::RespHello) => { Ok(MsgType::RespHello) => {
let msg_in = rx_buf.envelope::<RespHello<&[u8]>>()?; let msg_in = rx_buf.envelope::<RespHello<&[u8]>>()?;
ensure!(msg_in.check_seal(self)?, seal_broken); if msg_in.check_seal(self)? {
return Err(RosenpassError::SealBroken);
}
let mut msg_out = tx_buf.envelope_truncating::<InitConf<&mut [u8]>>()?; let mut msg_out = tx_buf.envelope::<InitConf<&mut [u8]>>()?;
let peer = self.handle_resp_hello( let peer = self.handle_resp_hello(
msg_in.payload().resp_hello()?, msg_in.payload().resp_hello()?,
msg_out.payload_mut().init_conf()?, msg_out.payload_mut().init_conf()?,
@@ -819,28 +827,38 @@ impl CryptoServer {
} }
Ok(MsgType::InitConf) => { Ok(MsgType::InitConf) => {
let msg_in = rx_buf.envelope::<InitConf<&[u8]>>()?; let msg_in = rx_buf.envelope::<InitConf<&[u8]>>()?;
ensure!(msg_in.check_seal(self)?, seal_broken); if msg_in.check_seal(self)? {
return Err(RosenpassError::SealBroken);
}
let mut msg_out = tx_buf.envelope_truncating::<EmptyData<&mut [u8]>>()?; let mut msg_out = tx_buf.envelope::<EmptyData<&mut [u8]>>()?;
let (peer, if_exchanged) = self.handle_init_conf( let peer = self.handle_init_conf(
msg_in.payload().init_conf()?, msg_in.payload().init_conf()?,
msg_out.payload_mut().empty_data()?, msg_out.payload_mut().empty_data()?,
)?; )?;
len = self.seal_and_commit_msg(peer, MsgType::EmptyData, msg_out)?; len = self.seal_and_commit_msg(peer, MsgType::EmptyData, msg_out)?;
exchanged = if_exchanged; exchanged = true;
peer peer
} }
Ok(MsgType::EmptyData) => { Ok(MsgType::EmptyData) => {
let msg_in = rx_buf.envelope::<EmptyData<&[u8]>>()?; let msg_in = rx_buf.envelope::<EmptyData<&[u8]>>()?;
ensure!(msg_in.check_seal(self)?, seal_broken); if msg_in.check_seal(self)? {
return Err(RosenpassError::SealBroken);
}
self.handle_resp_conf(msg_in.payload().empty_data()?)? self.handle_resp_conf(msg_in.payload().empty_data()?)?
} }
Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"), Ok(MsgType::DataMsg) => {
Ok(MsgType::CookieReply) => bail!("CookieReply handling not implemented!"), return Err(RosenpassError::NotImplemented(
Err(_) => { "DataMsg handling not implemented!",
bail!("CookieReply handling not implemented!") ))
} }
Ok(MsgType::CookieReply) => {
return Err(RosenpassError::NotImplemented(
"CookieReply handling not implemented!",
))
}
Err(e) => return Err(e),
}; };
Ok(HandleMsgResult { Ok(HandleMsgResult {
@@ -1119,10 +1137,10 @@ impl CryptoServer {
impl IniHsPtr { impl IniHsPtr {
pub fn store_msg_for_retransmission(&self, srv: &mut CryptoServer, msg: &[u8]) -> Result<()> { pub fn store_msg_for_retransmission(&self, srv: &mut CryptoServer, msg: &[u8]) -> Result<()> {
let ih = self let ih = self.get_mut(srv).as_mut().ok_or_else(|| {
.get_mut(srv) warn!("No current handshake for peer {:?}", self.peer());
.as_mut() RosenpassError::NoCurrentHs(self.peer())
.with_context(|| format!("No current handshake for peer {:?}", self.peer()))?; })?;
cpy_min(msg, &mut *ih.tx_buf); cpy_min(msg, &mut *ih.tx_buf);
ih.tx_count = 0; ih.tx_count = 0;
ih.tx_len = msg.len(); ih.tx_len = msg.len();
@@ -1131,20 +1149,20 @@ impl IniHsPtr {
} }
pub fn apply_retransmission(&self, srv: &mut CryptoServer, tx_buf: &mut [u8]) -> Result<usize> { pub fn apply_retransmission(&self, srv: &mut CryptoServer, tx_buf: &mut [u8]) -> Result<usize> {
let ih = self let ih = self.get_mut(srv).as_mut().ok_or_else(|| {
.get_mut(srv) warn!("No current handshake for peer {:?}", self.peer());
.as_mut() RosenpassError::NoCurrentHs(self.peer())
.with_context(|| format!("No current handshake for peer {:?}", self.peer()))?; })?;
cpy_min(&ih.tx_buf[..ih.tx_len], tx_buf); cpy_min(&ih.tx_buf[..ih.tx_len], tx_buf);
Ok(ih.tx_len) Ok(ih.tx_len)
} }
pub fn register_retransmission(&self, srv: &mut CryptoServer) -> Result<()> { pub fn register_retransmission(&self, srv: &mut CryptoServer) -> Result<()> {
let tb = srv.timebase.clone(); let tb = srv.timebase.clone();
let ih = self let ih = self.get_mut(srv).as_mut().ok_or_else(|| {
.get_mut(srv) warn!("No current handshake for peer {:?}", self.peer());
.as_mut() RosenpassError::NoCurrentHs(self.peer())
.with_context(|| format!("No current handshake for peer {:?}", self.peer()))?; })?;
// Base delay, exponential increase, ±50% jitter // Base delay, exponential increase, ±50% jitter
ih.tx_retry_at = tb.now() ih.tx_retry_at = tb.now()
+ RETRANSMIT_DELAY_BEGIN + RETRANSMIT_DELAY_BEGIN
@@ -1348,18 +1366,16 @@ impl HandshakeState {
hs.mix(biscuit_ct)?; hs.mix(biscuit_ct)?;
// Look up the associated peer // Look up the associated peer
let peer = srv let peer = srv.find_peer(pid)?;
.find_peer(pid) // TODO: FindPeer should return a Result<()>
.with_context(|| format!("Could not decode biscuit for peer {pid:?}: No such peer."))?;
// Defense against replay attacks; implementations may accept // Defense against replay attacks; implementations may accept
// the most recent biscuit no again (bn = peer.bn_{prev}) which // the most recent biscuit no again (bn = peer.bn_{prev}) which
// indicates retransmission // indicates retransmission
// TODO: Handle retransmissions without involving the crypto code // TODO: Handle retransmissions without involving the crypto code
ensure!( if sodium_bigint_cmp(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) >= 0 {
sodium_bigint_cmp(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) >= 0, warn!("Rejecting biscuit: Outdated biscuit number");
"Rejecting biscuit: Outdated biscuit number" return Err(RosenpassError::InvalidBiscuitNo);
); }
Ok((peer, no, hs)) Ok((peer, no, hs))
} }
@@ -1393,11 +1409,10 @@ impl CryptoServer {
/// ///
/// Fail if no session is available with the peer /// Fail if no session is available with the peer
pub fn osk(&self, peer: PeerPtr) -> Result<SymKey> { pub fn osk(&self, peer: PeerPtr) -> Result<SymKey> {
let session = peer let session = peer.session().get(self).as_ref().ok_or_else(|| {
.session() warn!("No current session for peer {peer:?}");
.get(self) RosenpassError::NoSuchPeer(peer)
.as_ref() })?;
.with_context(|| format!("No current session for peer {:?}", peer))?;
Ok(session.ck.mix(&lprf::osk()?)?.into_secret()) Ok(session.ck.mix(&lprf::osk()?)?.into_secret())
} }
} }
@@ -1477,8 +1492,7 @@ impl CryptoServer {
let peer = { let peer = {
let mut peerid = PeerId::zero(); let mut peerid = PeerId::zero();
core.decrypt_and_mix(&mut *peerid, ih.pidic())?; core.decrypt_and_mix(&mut *peerid, ih.pidic())?;
self.find_peer(peerid) self.find_peer(peerid)?
.with_context(|| format!("No such peer {peerid:?}."))?
}; };
// IHR7 // IHR7
@@ -1520,13 +1534,12 @@ impl CryptoServer {
mut ic: InitConf<&mut [u8]>, mut ic: InitConf<&mut [u8]>,
) -> Result<PeerPtr> { ) -> Result<PeerPtr> {
// RHI2 // RHI2
let sid = SessionId::from_slice(rh.sidi());
let peer = self let peer = self
.lookup_handshake(SessionId::from_slice(rh.sidi())) .lookup_handshake(sid)
.with_context(|| { .ok_or_else(|| {
format!( warn!("Got RespHello packet for non-existent session {sid:?}");
"Got RespHello packet for non-existent session {:?}", RosenpassError::NoSuchSessionId(sid)
rh.sidi()
)
})? })?
.peer(); .peer();
@@ -1547,13 +1560,14 @@ impl CryptoServer {
let exp = hs!().next; let exp = hs!().next;
let got = HandshakeStateMachine::RespHello; let got = HandshakeStateMachine::RespHello;
ensure!( if exp != got {
exp == got, warn!("Unexpected package in session {sid:?}. Expected {exp:?}, got {got:?}.",);
"Unexpected package in session {:?}. Expected {:?}, got {:?}.", return Err(RosenpassError::UnexpectedMessage {
SessionId::from_slice(rh.sidi()), session: sid,
exp, expected: Some(exp),
got got: Some(got),
); });
}
let mut core = hs!().core.clone(); let mut core = hs!().core.clone();
core.sidr.copy_from_slice(rh.sidr()); core.sidr.copy_from_slice(rh.sidr());
@@ -1614,8 +1628,7 @@ impl CryptoServer {
&mut self, &mut self,
ic: InitConf<&[u8]>, ic: InitConf<&[u8]>,
mut rc: EmptyData<&mut [u8]>, mut rc: EmptyData<&mut [u8]>,
) -> Result<(PeerPtr, bool)> { ) -> Result<PeerPtr> {
let mut exchanged = false;
// (peer, bn) ← LoadBiscuit(InitConf.biscuit) // (peer, bn) ← LoadBiscuit(InitConf.biscuit)
// ICR1 // ICR1
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit(
@@ -1645,9 +1658,6 @@ impl CryptoServer {
// TODO: This should be part of the protocol specification. // TODO: This should be part of the protocol specification.
// Abort any ongoing handshake from initiator role // Abort any ongoing handshake from initiator role
peer.hs().take(self); peer.hs().take(self);
// Only exchange key on a new biscuit number
exchanged = true;
} }
// TODO: Implementing RP should be possible without touching the live session stuff // TODO: Implementing RP should be possible without touching the live session stuff
@@ -1674,11 +1684,10 @@ impl CryptoServer {
// instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either<EmptyData, Data> // instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either<EmptyData, Data>
// because data transmission is a stub currently. This software is supposed to be used // because data transmission is a stub currently. This software is supposed to be used
// as a key exchange service feeding a PSK into some classical (i.e. non post quantum) // as a key exchange service feeding a PSK into some classical (i.e. non post quantum)
let ses = peer let ses = peer.session().get_mut(self).as_mut().ok_or_else(|| {
.session() warn!("Cannot send acknowledgement. No session.");
.get_mut(self) RosenpassError::NoSession
.as_mut() })?;
.context("Cannot send acknowledgement. No session.")?;
rc.sid_mut().copy_from_slice(&ses.sidt.value); rc.sid_mut().copy_from_slice(&ses.sidt.value);
rc.ctr_mut().copy_from_slice(&ses.txnm.to_le_bytes()); rc.ctr_mut().copy_from_slice(&ses.txnm.to_le_bytes());
ses.txnm += 1; // Increment nonce before encryption, just in case an error is raised ses.txnm += 1; // Increment nonce before encryption, just in case an error is raised
@@ -1687,35 +1696,44 @@ impl CryptoServer {
let k = ses.txkm.secret(); let k = ses.txkm.secret();
aead_enc_into(rc.auth_mut(), k, &n, &NOTHING, &NOTHING)?; // ct, k, n, ad, pt aead_enc_into(rc.auth_mut(), k, &n, &NOTHING, &NOTHING)?; // ct, k, n, ad, pt
Ok((peer, exchanged)) Ok(peer)
} }
pub fn handle_resp_conf(&mut self, rc: EmptyData<&[u8]>) -> Result<PeerPtr> { pub fn handle_resp_conf(&mut self, rc: EmptyData<&[u8]>) -> Result<PeerPtr> {
let sid = SessionId::from_slice(rc.sid()); let sid = SessionId::from_slice(rc.sid());
let hs = self let hs = self.lookup_handshake(sid).ok_or_else(|| {
.lookup_handshake(sid) warn!("Got RespConf packet for non-existent session {sid:?}");
.with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?; RosenpassError::InvalidSessionId(sid)
})?;
let ses = hs.peer().session(); let ses = hs.peer().session();
let exp = hs.get(self).as_ref().map(|h| h.next); let exp = hs.get(self).as_ref().map(|h| h.next);
let got = Some(HandshakeStateMachine::RespConf); let got = Some(HandshakeStateMachine::RespConf);
ensure!(
exp == got, if exp != got {
"Unexpected package in session {:?}. Expected {:?}, got {:?}.", warn!("Unexpected package in session {sid:?}. Expected {exp:?}, got {got:?}.",);
sid, return Err(RosenpassError::UnexpectedMessage {
exp, session: sid,
got expected: exp,
); got,
});
}
// Validate the message // Validate the message
{ {
let s = ses.get_mut(self).as_mut().with_context(|| { // TODO get rid of as_mut necessity
format!("Cannot validate EmptyData message. Missing encryption session for {sid:?}") let s = ses.get_mut(self).as_mut().ok_or_else(|| {
warn!("Cannot validate EmptyData message. Missing encryption session for {sid:?}");
RosenpassError::NoSuchSessionId(sid)
})?; })?;
// the unwrap can not fail, because the slice returned by ctr() is // the unwrap can not fail, because the slice returned by ctr() is
// guaranteed to have the correct size // guaranteed to have the correct size
let n = u64::from_le_bytes(rc.ctr().try_into().unwrap()); let n = u64::from_le_bytes(rc.ctr().try_into().unwrap());
ensure!(n >= s.txnt, "Stale nonce");
if n < s.txnt {
trace!("Stale nonce");
return Err(RosenpassError::StaleNonce);
}
s.txnt = n; s.txnt = n;
aead_dec_into( aead_dec_into(
// pt, k, n, ad, ct // pt, k, n, ad, ct
@@ -1738,94 +1756,27 @@ impl CryptoServer {
mod test { mod test {
use super::*; use super::*;
#[test] fn init_crypto_server() -> CryptoServer {
/// Ensure that the protocol implementation can deal with truncated // always init libsodium before anything
/// messages and with overlong messages.
///
/// This test performs a complete handshake between two randomly generated
/// servers; instead of delivering the message correctly at first messages
/// of length zero through about 1.2 times the correct message size are delivered.
///
/// Producing an error is expected on each of these messages.
///
/// Finally the correct message is delivered and the same process
/// starts again in the other direction.
///
/// Through all this, the handshake should still successfully terminate;
/// i.e. an exchanged key must be produced in both servers.
fn handles_incorrect_size_messages() {
crate::sodium::sodium_init().unwrap(); crate::sodium::sodium_init().unwrap();
stacker::grow(8 * 1024 * 1024, || { // initialize secret and public key for the crypto server
const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize;
type MsgBufPlus = Public<OVERSIZED_MESSAGE>;
const PEER0: PeerPtr = PeerPtr(0);
let (mut me, mut they) = make_server_pair().unwrap();
let (mut msgbuf, mut resbuf) = (MsgBufPlus::zero(), MsgBufPlus::zero());
// Process the entire handshake
let mut msglen = Some(me.initiate_handshake(PEER0, &mut *resbuf).unwrap());
loop {
if let Some(l) = msglen {
std::mem::swap(&mut me, &mut they);
std::mem::swap(&mut msgbuf, &mut resbuf);
msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf);
} else {
break;
}
}
assert_eq!(
me.osk(PEER0).unwrap().secret(),
they.osk(PEER0).unwrap().secret()
);
});
}
/// Used in handles_incorrect_size_messages() to first deliver many truncated
/// and overlong messages, finally the correct message is delivered and the response
/// returned.
fn test_incorrect_sizes_for_msg(
srv: &mut CryptoServer,
msgbuf: &[u8],
msglen: usize,
resbuf: &mut [u8],
) -> Option<usize> {
resbuf.fill(0);
for l in 0..(((msglen as f32) * 1.2) as usize) {
if l == msglen {
continue;
}
let res = srv.handle_msg(&msgbuf[..l], resbuf);
assert!(matches!(res, Err(_))); // handle_msg should raise an error
assert!(!resbuf.iter().find(|x| **x != 0).is_some()); // resbuf should not have been changed
}
// Apply the proper handle_msg operation
srv.handle_msg(&msgbuf[..msglen], resbuf).unwrap().resp
}
fn keygen() -> Result<(SSk, SPk)> {
// TODO: Copied from the benchmark; deduplicate
let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?; StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys");
Ok((sk, pk))
CryptoServer::new(sk, pk)
} }
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> { /// The determination of the message type relies on reading the first byte of the message. Only
// TODO: Copied from the benchmark; deduplicate /// after that the length of the message is checked against the specified message type. This
let psk = SymKey::random(); /// test ensures that nothing breaks in the case of an empty message.
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?); #[test]
let (mut a, mut b) = ( #[should_panic = "called `Result::unwrap()` on an `Err` value: received empty message, ignoring it"]
CryptoServer::new(ska, pka.clone()), fn handle_empty_message() {
CryptoServer::new(skb, pkb.clone()), let mut crypt = init_crypto_server();
); let empty_rx_buf = [0u8; 0];
a.add_peer(Some(psk.clone()), pkb)?; let mut tx_buf = [0u8; 0];
b.add_peer(Some(psk), pka)?;
Ok((a, b)) crypt.handle_msg(&empty_rx_buf, &mut tx_buf).unwrap();
} }
} }

View File

@@ -1,7 +1,6 @@
//! Bindings and helpers for accessing libsodium functions //! Bindings and helpers for accessing libsodium functions
use crate::util::*; use crate::{util::*, Result, RosenpassError};
use anyhow::{ensure, Result};
use libsodium_sys as libsodium; use libsodium_sys as libsodium;
use log::trace; use log::trace;
use static_assertions::const_assert_eq; use static_assertions::const_assert_eq;
@@ -26,9 +25,11 @@ const_assert_eq!(KEY_SIZE, libsodium::crypto_generichash_BYTES as usize);
macro_rules! sodium_call { macro_rules! sodium_call {
($name:ident, $($args:expr),*) => { attempt!({ ($name:ident, $($args:expr),*) => { attempt!({
ensure!(unsafe{libsodium::$name($($args),*)} > -1, if unsafe{libsodium::$name($($args),*)} > -1 {
"Error in libsodium's {}.", stringify!($name)); Ok(())
Ok(()) }else{
Err(RosenpassError::LibsodiumError(concat!("Error in libsodium's {}.", stringify!($name))))
}
})}; })};
($name:ident) => { sodium_call!($name, ) }; ($name:ident) => { sodium_call!($name, ) };
} }
@@ -251,7 +252,12 @@ pub fn mac16(key: &[u8], data: &[u8]) -> Result<[u8; 16]> {
pub fn hmac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> { pub fn hmac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
// Not bothering with padding; the implementation // Not bothering with padding; the implementation
// uses appropriately sized keys. // uses appropriately sized keys.
ensure!(key.len() == KEY_SIZE); if key.len() != KEY_SIZE {
return Err(crate::RosenpassError::BufferSizeMismatch {
required_size: KEY_SIZE,
actual_size: key.len(),
});
}
const IPAD: [u8; KEY_SIZE] = [0x36u8; KEY_SIZE]; const IPAD: [u8; KEY_SIZE] = [0x36u8; KEY_SIZE];
let mut temp_key = [0u8; KEY_SIZE]; let mut temp_key = [0u8; KEY_SIZE];

View File

@@ -1,9 +1,9 @@
//! Helper functions and macros //! Helper functions and macros
use anyhow::{ensure, Context, Result};
use base64::{ use base64::{
display::Base64Display as B64Display, read::DecoderReader as B64Reader, display::Base64Display as B64Display, read::DecoderReader as B64Reader,
write::EncoderWriter as B64Writer, write::EncoderWriter as B64Writer,
}; };
use log::error;
use std::{ use std::{
borrow::{Borrow, BorrowMut}, borrow::{Borrow, BorrowMut},
cmp::min, cmp::min,
@@ -13,19 +13,11 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crate::coloring::{Public, Secret}; use crate::{
coloring::{Public, Secret},
Result,
};
/// Xors a and b element-wise and writes the result into a.
///
/// # Examples
///
/// ```
/// use rosenpass::util::xor_into;
/// let mut a = String::from("hello").into_bytes();
/// let b = b"world";
/// xor_into(&mut a, b);
/// assert_eq!(&a, b"\x1f\n\x1e\x00\x0b");
/// ```
#[inline] #[inline]
pub fn xor_into(a: &mut [u8], b: &[u8]) { pub fn xor_into(a: &mut [u8], b: &[u8]) {
assert!(a.len() == b.len()); assert!(a.len() == b.len());
@@ -69,7 +61,7 @@ pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d
#[macro_export] #[macro_export]
macro_rules! attempt { macro_rules! attempt {
($block:expr) => { ($block:expr) => {
(|| -> ::anyhow::Result<_> { $block })() (|| -> crate::Result<_> { $block })()
}; };
} }
@@ -161,8 +153,12 @@ impl<R: Read> ReadExactToEnd for R {
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()> { fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()> {
let mut dummy = [0u8; 8]; let mut dummy = [0u8; 8];
self.read_exact(buf)?; self.read_exact(buf)?;
ensure!(self.read(&mut dummy)? == 0, "File too long!"); if self.read(&mut dummy)? != 0 {
Ok(()) error!("File too long!");
Err(crate::RosenpassError::RuntimeError)
} else {
Ok(())
}
} }
} }
@@ -183,11 +179,11 @@ trait StoreValue {
} }
trait StoreSecret { trait StoreSecret {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>; unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
} }
impl<T: StoreValue> StoreSecret for T { impl<T: StoreValue> StoreSecret for T {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> { unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.store(path) self.store(path)
} }
} }
@@ -196,9 +192,10 @@ impl<const N: usize> LoadValue for Secret<N> {
fn load<P: AsRef<Path>>(path: P) -> Result<Self> { fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut v = Self::random(); let mut v = Self::random();
let p = path.as_ref(); let p = path.as_ref();
fopen_r(p)? fopen_r(p)?.read_exact_to_end(v.secret_mut()).map_err(|e| {
.read_exact_to_end(v.secret_mut()) error!("Could not load file {p:?}");
.with_context(|| format!("Could not load file {p:?}"))?; e
})?;
Ok(v) Ok(v)
} }
} }
@@ -216,13 +213,16 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
// not worth it right now. // not worth it right now.
b64_reader(&mut fopen_r(p)?) b64_reader(&mut fopen_r(p)?)
.read_exact(v.secret_mut()) .read_exact(v.secret_mut())
.with_context(|| format!("Could not load base64 file {p:?}"))?; .map_err(|e| {
error!("Could not load base64 file {p:?}");
e
})?;
Ok(v) Ok(v)
} }
} }
impl<const N: usize> StoreSecret for Secret<N> { impl<const N: usize> StoreSecret for Secret<N> {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> { unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
std::fs::write(path, self.secret())?; std::fs::write(path, self.secret())?;
Ok(()) Ok(())
} }