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
79 changed files with 1662 additions and 4953 deletions

View File

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

View File

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

View File

@@ -17,14 +17,6 @@ jobs:
with:
args: --check .
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
cargo-audit:
runs-on: ubuntu-latest
steps:
@@ -74,75 +66,3 @@ jobs:
# - https://github.com/rosenpass/rosenpass/issues/62
# - https://github.com/rust-lang/rust/issues/108378
- run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
cargo-test:
runs-on: ubuntu-latest
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
cargo-fuzz:
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
- name: Install nightly toolchain
run: |
rustup toolchain install nightly
rustup default nightly
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Run fuzzing
run: |
cargo fuzz run fuzz_aead_enc_into -- -max_total_time=60
cargo fuzz run fuzz_blake2b -- -max_total_time=60
cargo fuzz run fuzz_handle_msg -- -max_total_time=60
cargo fuzz run fuzz_kyber_encaps -- -max_total_time=60
cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=60

View File

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

934
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +1,40 @@
[workspace]
resolver = "2"
[package]
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 = [
"rosenpass",
"ciphers",
"util",
"constant-time",
"sodium",
"to",
"fuzz",
]
[[bench]]
name = "handshake"
harness = false
default-members = [
"rosenpass"
]
[workspace.metadata.release]
# ensure that adding `--package` as argument to `cargo release` still creates version tags in the form of `vx.y.z`
tag-prefix = ""
[workspace.dependencies]
rosenpass = { path = "rosenpass" }
rosenpass-util = { path = "util" }
rosenpass-constant-time = { path = "constant-time" }
rosenpass-sodium = { path = "sodium" }
rosenpass-ciphers = { path = "ciphers" }
rosenpass-to = { path = "to" }
criterion = "0.4.0"
test_bin = "0.4.0"
libfuzzer-sys = "0.4"
stacker = "0.1.15"
doc-comment = "0.3.3"
[dependencies]
base64 = "0.21.1"
zeroize = "1.7.0"
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"
env_logger = "0.10.0"
toml = "0.7.4"
static_assertions = "1.1.0"
log = { version = "0.4.17" }
clap = { version = "4.3.0", features = ["derive"] }
log = { version = "0.4.17", optional = true }
env_logger = { version = "0.10.0", optional = true }
serde = { version = "1.0.163", features = ["derive"] }
arbitrary = { version = "1.3.2", features = ["derive"] }
anyhow = { version = "1.0.71", features = ["backtrace"] }
toml = "0.7.4"
clap = { version = "4.3.0", features = ["derive"] }
mio = { version = "0.8.6", features = ["net", "os-poll"] }
libsodium-sys-stable= { version = "1.19.28", features = ["use-pkg-config"] }
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
[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,8 @@
use anyhow::Result;
use rosenpass::pqkem::KEM;
use rosenpass::{
pqkem::StaticKEM,
protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey},
pqkem::{EphemeralKEM, CCAKEM},
protocol::{CcaPk, CcaSk, CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SymKey},
sodium::sodium_init,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
@@ -38,9 +38,9 @@ fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
Ok(())
}
fn keygen() -> Result<(SSk, SPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?;
fn keygen() -> Result<(CcaSk, CcaPk)> {
let (mut sk, mut pk) = (CcaSk::zero(), CcaPk::zero());
CCAKEM::keygen(sk.secret_mut(), pk.secret_mut())?;
Ok((sk, pk))
}
@@ -57,16 +57,16 @@ fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
}
fn criterion_benchmark(c: &mut Criterion) {
rosenpass_sodium::init().unwrap();
sodium_init().unwrap();
let (mut a, mut b) = make_server_pair().unwrap();
c.bench_function("cca_secret_alloc", |bench| {
bench.iter(|| {
SSk::zero();
CcaSk::zero();
})
});
c.bench_function("cca_public_alloc", |bench| {
bench.iter(|| {
SPk::zero();
CcaPk::zero();
})
});
c.bench_function("keygen", |bench| {

View File

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

View File

@@ -1,18 +0,0 @@
[package]
name = "rosenpass-ciphers"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal ciphers and other cryptographic primitives used by rosenpass."
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
anyhow = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-to = { workspace = true }
rosenpass-constant-time = { workspace = true }
static_assertions = { workspace = true }
zeroize = { workspace = true }

View File

@@ -1,5 +0,0 @@
# Rosenpass internal cryptographic primitives
Ciphers and other cryptographic primitives used by rosenpass.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -1,28 +0,0 @@
use static_assertions::const_assert;
pub mod subtle;
pub const KEY_LEN: usize = 32;
const_assert!(KEY_LEN == aead::KEY_LEN);
const_assert!(KEY_LEN == xaead::KEY_LEN);
const_assert!(KEY_LEN == hash::KEY_LEN);
/// Authenticated encryption with associated data
pub mod aead {
pub use rosenpass_sodium::aead::chacha20poly1305_ietf::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
}
/// Authenticated encryption with associated data with a constant nonce
pub mod xaead {
pub use rosenpass_sodium::aead::xchacha20poly1305_ietf::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
};
}
pub mod hash {
pub use crate::subtle::incorrect_hmac_blake2b::{
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
};
}

View File

@@ -1,44 +0,0 @@
use anyhow::ensure;
use rosenpass_constant_time::xor;
use rosenpass_sodium::hash::blake2b;
use rosenpass_to::{ops::copy_slice, with_destination, To};
use zeroize::Zeroizing;
pub const KEY_LEN: usize = 32;
pub const KEY_MIN: usize = KEY_LEN;
pub const KEY_MAX: usize = KEY_LEN;
pub const OUT_MIN: usize = blake2b::OUT_MIN;
pub const OUT_MAX: usize = blake2b::OUT_MAX;
/// This is a woefully incorrect implementation of hmac_blake2b.
/// See <https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563612222>
///
/// It accepts 32 byte keys, exclusively.
///
/// This will be replaced, likely by Kekkac at some point soon.
/// <https://github.com/rosenpass/rosenpass/pull/145>
#[inline]
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];
const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN];
with_destination(|out: &mut [u8]| {
// Not bothering with padding; the implementation
// uses appropriately sized keys.
ensure!(key.len() == KEY_LEN);
type Key = Zeroizing<[u8; KEY_LEN]>;
let mut tmp_key = Key::default();
copy_slice(key).to(tmp_key.as_mut());
xor(&IPAD).to(tmp_key.as_mut());
let mut outer_data = Key::default();
blake2b::hash(tmp_key.as_ref(), data).to(outer_data.as_mut())?;
copy_slice(key).to(tmp_key.as_mut());
xor(&OPAD).to(tmp_key.as_mut());
blake2b::hash(tmp_key.as_ref(), outer_data.as_ref()).to(out)?;
Ok(())
})
}

View File

@@ -1 +0,0 @@
pub mod incorrect_hmac_blake2b;

View File

@@ -1,15 +0,0 @@
[package]
name = "rosenpass-constant-time"
version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal utilities for constant time crypto implementations"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rosenpass-to = { workspace = true }

View File

@@ -1,5 +0,0 @@
# Rosenpass constant time library
Rosenpass internal library providing basic constant-time operations.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -1,26 +0,0 @@
use rosenpass_to::{with_destination, To};
/// Xors the source into the destination
///
/// # Examples
///
/// ```
/// use rosenpass_constant_time::xor;
/// use rosenpass_to::To;
/// assert_eq!(
/// xor(b"world").to_this(|| b"hello".to_vec()),
/// b"\x1f\n\x1e\x00\x0b");
/// ```
///
/// # Panics
///
/// If source and destination are of different sizes.
#[inline]
pub fn xor<'a>(src: &'a [u8]) -> impl To<[u8], ()> + 'a {
with_destination(|dst: &mut [u8]| {
assert!(src.len() == dst.len());
for (dv, sv) in dst.iter_mut().zip(src.iter()) {
*dv ^= *sv;
}
})
}

View File

@@ -12,13 +12,13 @@
.Sh DESCRIPTION
.Nm
performs cryptographic key exchanges that are secure against quantum-computers
and then outputs the keys.
These keys can then be passed to various services, such as wireguard or other
vpn services, as pre-shared-keys to achieve security against attackers with
and outputs the keys.
These keys can then be passed to various services such as wireguard or other
vpn services as pre-shared-keys to achieve security against attackers with
quantum computers.
.Pp
This is a research project and quantum computers are not thought to become
practical in fewer than ten years.
practical in less than ten years.
If you are not specifically tasked with developing post-quantum secure systems,
you probably do not need this tool.
.Ss COMMANDS
@@ -31,7 +31,7 @@ file secret!
Start a process to exchange keys with the specified peers.
You should specify at least one peer.
.Pp
Its
It's
.Ar OPTIONS
are as follows:
.Bl -tag -width Ds
@@ -39,7 +39,7 @@ are as follows:
Instructs
.Nm
to listen on the specified interface and port.
By default,
By default
.Nm
will listen on all interfaces and select a random port.
.It Ar verbose
@@ -91,18 +91,9 @@ This makes it possible to add peers entirely from
.Sh SEE ALSO
.Xr rp 1 ,
.Xr wg 1
.Rs
.%A Karolin Varner
.%A Benjamin Lipp
.%A Wanja Zaeske
.%A Lisa Schmidt
.%D 2023
.%T Rosenpass
.%U https://rosenpass.eu/whitepaper.pdf
.Re
.Sh STANDARDS
This tool is the reference implementation of the Rosenpass protocol, as
specified within the whitepaper referenced above.
This tool is the reference implementation of the Rosenpass protocol, written
by Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt.
.Sh AUTHORS
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.

View File

@@ -59,10 +59,6 @@ listening on the provided IP and port combination, allowing connections from
.Sh EXIT STATUS
.Ex -std
.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.
.Bd -literal -offset indent
rp genkey server.rosenpass-secret

30
flake.lock generated
View File

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

View File

@@ -29,7 +29,6 @@
]
(system:
let
scoped = (scope: scope.result);
lib = nixpkgs.lib;
# normal nixpkgs
@@ -56,38 +55,15 @@
};
# parsed Cargo.toml
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
# source files relevant for rust
src = scoped rec {
# File suffices to include
extensions = [
"lock"
"rs"
"toml"
];
# Files to explicitly include
files = [
"to/README.md"
];
src = ./.;
filter = (path: type: scoped rec {
inherit (lib) any id removePrefix hasSuffix;
anyof = (any id);
basename = baseNameOf (toString path);
relative = removePrefix (toString src + "/") (toString path);
result = anyof [
(type == "directory")
(any (ext: hasSuffix ".${ext}" basename) extensions)
(any (file: file == relative) files)
];
});
result = pkgs.lib.sources.cleanSourceWith { inherit src filter; };
};
src = pkgs.lib.sourceByRegex ./. [
"Cargo\\.(toml|lock)"
"build.rs"
"(src|benches)(/.*\\.(rs|md))?"
"rp"
];
# builds a bin path for all dependencies for the `rp` shellscript
rpBinPath = p: with p; lib.makeBinPath [
@@ -136,9 +112,6 @@
version = cargoToml.package.version;
inherit src;
cargoBuildOptions = x: x ++ [ "-p" "rosenpass" ];
cargoTestOptions = x: x ++ [ "-p" "rosenpass" ];
doCheck = true;
nativeBuildInputs = with pkgs; [
@@ -184,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...
CARGO_BUILD_TARGET = target;
@@ -312,7 +290,7 @@
packages.proof-proverif = pkgs.stdenv.mkDerivation {
name = "rosenpass-proverif-proof";
version = "unstable";
src = pkgs.lib.sources.sourceByRegex ./. [
src = pkgs.lib.sourceByRegex ./. [
"analyze.sh"
"marzipan(/marzipan.awk)?"
"analysis(/.*)?"
@@ -331,6 +309,7 @@
#
devShells.default = pkgs.mkShell {
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
inherit (packages.rosenpass) RUST_MIN_STACK;
inputsFrom = [ packages.default ];
nativeBuildInputs = with pkgs; [
cmake # override the fakecmake from the main step above
@@ -343,6 +322,7 @@
};
devShells.coverage = pkgs.mkShell {
inputsFrom = [ packages.default ];
inherit (packages.rosenpass) RUST_MIN_STACK;
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
};
@@ -350,7 +330,7 @@
checks = {
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
cargo fmt --manifest-path=${./.}/Cargo.toml --check && touch $out
'';
nixpkgs-fmt = pkgs.runCommand "check-nixpkgs-fmt"
{ nativeBuildInputs = [ pkgs.nixpkgs-fmt ]; } ''

4
fuzz/.gitignore vendored
View File

@@ -1,4 +0,0 @@
target
corpus
artifacts
coverage

1286
fuzz/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +0,0 @@
[package]
name = "rosenpass-fuzzing"
version = "0.0.1"
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
[dependencies]
arbitrary = { workspace = true }
libfuzzer-sys = { workspace = true }
stacker = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-ciphers = { workspace = true }
rosenpass-to = { workspace = true }
rosenpass = { workspace = true }
[[bin]]
name = "fuzz_handle_msg"
path = "fuzz_targets/handle_msg.rs"
test = false
doc = false
[[bin]]
name = "fuzz_blake2b"
path = "fuzz_targets/blake2b.rs"
test = false
doc = false
[[bin]]
name = "fuzz_aead_enc_into"
path = "fuzz_targets/aead_enc_into.rs"
test = false
doc = false
[[bin]]
name = "fuzz_mceliece_encaps"
path = "fuzz_targets/mceliece_encaps.rs"
test = false
doc = false
[[bin]]
name = "fuzz_kyber_encaps"
path = "fuzz_targets/kyber_encaps.rs"
test = false
doc = false

View File

@@ -1,32 +0,0 @@
#![no_main]
extern crate arbitrary;
extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass_ciphers::aead;
use rosenpass_sodium::init as sodium_init;
#[derive(arbitrary::Arbitrary, Debug)]
pub struct Input {
pub key: [u8; 32],
pub nonce: [u8; 12],
pub ad: Box<[u8]>,
pub plaintext: Box<[u8]>,
}
fuzz_target!(|input: Input| {
sodium_init().unwrap();
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
ciphertext.resize(input.plaintext.len() + 16, 0);
aead::encrypt(
ciphertext.as_mut_slice(),
&input.key,
&input.nonce,
&input.ad,
&input.plaintext,
)
.unwrap();
});

View File

@@ -1,22 +0,0 @@
#![no_main]
extern crate arbitrary;
extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass_sodium::{hash::blake2b, init as sodium_init};
use rosenpass_to::To;
#[derive(arbitrary::Arbitrary, Debug)]
pub struct Blake2b {
pub key: [u8; 32],
pub data: Box<[u8]>,
}
fuzz_target!(|input: Blake2b| {
sodium_init().unwrap();
let mut out = [0u8; 32];
blake2b::hash(&input.key, &input.data).to(&mut out).unwrap();
});

View File

@@ -1,21 +0,0 @@
#![no_main]
extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass::coloring::Secret;
use rosenpass::protocol::CryptoServer;
use rosenpass_sodium::init as sodium_init;
fuzz_target!(|rx_buf: &[u8]| {
sodium_init().unwrap();
let sk = Secret::from_slice(&[0; 13568]);
let pk = Secret::from_slice(&[0; 524160]);
let mut cs = CryptoServer::new(sk, pk);
let mut tx_buf = [0; 10240];
// We expect errors while fuzzing therefore we do not check the result.
let _ = cs.handle_msg(rx_buf, &mut tx_buf);
});

View File

@@ -1,19 +0,0 @@
#![no_main]
extern crate arbitrary;
extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass::pqkem::{EphemeralKEM, KEM};
#[derive(arbitrary::Arbitrary, Debug)]
pub struct Input {
pub pk: [u8; 800],
}
fuzz_target!(|input: Input| {
let mut ciphertext = [0u8; 768];
let mut shared_secret = [0u8; 32];
EphemeralKEM::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
});

View File

@@ -1,14 +0,0 @@
#![no_main]
extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass::pqkem::{StaticKEM, KEM};
fuzz_target!(|input: &[u8]| {
let mut ciphertext = [0u8; 188];
let mut shared_secret = [0u8; 32];
// We expect errors while fuzzing therefore we do not check the result.
let _ = StaticKEM::encaps(&mut shared_secret, &mut ciphertext, input);
});

View File

@@ -6,7 +6,6 @@ author:
- Benjamin Lipp = Max Planck Institute for Security and Privacy (MPI-SP)
- Wanja Zaeske
- Lisa Schmidt = {Scientific Illustrator \\url{mullana.de}}
- Prabhpreet Dua
abstract: |
Rosenpass is used to create post-quantum-secure VPNs. Rosenpass computes a shared key, WireGuard (WG) [@wg] uses the shared key to establish a secure connection. Rosenpass can also be used without WireGuard, deriving post-quantum-secure symmetric keys for another application. The Rosenpass protocol builds on “Post-quantum WireGuard” (PQWG) [@pqwg] and improves it by using a cookie mechanism to provide security against state disruption attacks.
@@ -429,89 +428,12 @@ The responder code handling InitConf needs to deal with the biscuits and package
ICR5 and ICR6 perform biscuit replay protection using the biscuit number. This is not handled in `load_biscuit()` itself because there is the case that `biscuit_no = biscuit_used` which needs to be dealt with for retransmission handling.
### Denial of Service Mitigation and Cookies
Rosenpass derives its cookie-based DoS mitigation technique for a responder from Wireguard [@wg].
When the responder is under load, it may choose to not process further handshake messages, but instead to respond with a cookie reply message (see Figure \ref{img:MessageTypes}).
The sender of the exchange then uses this cookie in order to resend the message and have it accepted the following time by the reciever.
For an initiator, Rosenpass ignores the message when under load.
#### Cookie Reply Message
The cookie reply message consists of the `sid` of the client under load, a random 24-byte bitstring `nonce` and encrypting `cookie_tau` into a `cookie` reply field which consists of the following (from the perspective of the cookie reply sender):
```
cookie_tau = lhash("cookie-tau",r_m, a_m)[0..16]
cookie = XAEAD(lhash("cookie-key", spkm), nonce, cookie_tau , mac_peer)
```
where `r_m` is a secret variable that changes every two minutes to a random value. `a_m` is a concatenation of the source IP address and UDP source port of the client's peer. `cookie_tau` will result in a truncated 16 byte value from the above hash operation. `mac_peer` is the `mac` field of the peer's handshake message to which message is the reply.
#### Envelope `mac` Field
Similar to `mac.1` in Wireguard handshake messages, the `mac` field of a Rosenpass envelope from a handshake packet sender's point of view consists of the following:
```
mac = lhash("mac", spkt, MAC_WIRE_DATA)[0..16]
```
where `MAC_WIRE_DATA` represents all bytes of msg prior to `mac` field in the envelope.
If a client receives an invalid `mac` value for any message, it will discard the message.
#### Envelope cookie field
The `cookie_tau` value encrypted as part of `cookie` field in the cookie reply message is decrypted by its receiver and stored as the `last_recvd_cookie` for a limited time (120 seconds). This value is then used by the sender to append a `cookie` field to the sender's message envelope to retransmit the handshake message. This is the equivalent of Wireguard's `mac.2` field and is determined as follows:
```
if (is_zero_length_bitstring(last_recvd_cookie) || last_cookie_time_ellapsed >= 120) {
cookie = 0^16; //zeroed out 16 bytes bitstring
}
else {
cookie = lhash("cookie",last_recvd_cookie,COOKIE_WIRE_DATA)
}
```
Here, `last_recvd_cookie` is the last received decrypted data of the `cookie` field from a cookie reply message by a hanshake message sender, `last_cookie_time_ellapsed` is the amount of time in seconds ellapsed since last cookie was received, and `COOKIE_WIRE_DATA` are the message contents of all bytes of the retransmitted message prior to the `cookie` field.
The sender can use an invalid value for the `cookie` value, when the receiver is not under load, and the receiver must ignore this value.
However, when the receiver is under load, it may reject messages with the invalid `cookie` value, and issue a cookie reply message.
### Conditions to trigger DoS Mechanism
Rosenpass implementations are expected to detect conditions in which they are under computational load to trigger the cookie based DoS mitigation mechanism by replying with a cookie reply message.
For the reference implemenation, Rosenpass has derived inspiration from the linux implementation of Wireguard.
This implementation suggests that the reciever keep track of the number of messages it is processing at a given time.
On receiving an incoming message, if the length of the message queue to be processed exceeds a threshold `MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD`, the client is considered under load and its state is stored as under load. In addition, the timestamp of this instant when the client was last under load is stored. When recieving subsequent messages, if the client is still in an under load state, the client will check if the time ellpased since the client was last under load has exceeded `LAST_UNDER_LOAD_WINDOW` seconds. If this is the case, the client will update its state to normal operation, and process the message in a normal fashion.
Currently, the following constants are derived from the Linux kernel implementation of Wireguard:
```
MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD = 4096
LAST_UNDER_LOAD_WINDOW = 1 //seconds
```
## Dealing with Packet Loss
The initiator deals with packet loss by storing the messages it sends to the responder and retransmitting them in randomized, exponentially increasing intervals until they get a response. Receiving RespHello terminates retransmission of InitHello. A Data or EmptyData message serves as acknowledgement of receiving InitConf and terminates its retransmission.
The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
### Interaction with cookie reply system
The cookie reply system does not interfere with the retransmission logic discussed above.
When the initator is under load, it will ignore processing any incoming messages.
When a responder is under load, a handshake message will be discarded and a cookie reply message is sent. The initiator, then on the reciept of the cookie reply message, will store a decrypted `cookie_tau` value to use when appending a `cookie` to subsequently sent messages. As per the retransmission mechanism above, the initiator will send a retransmitted InitHello or InitConf message with a valid `cookie` value appended. On receiving the retransmitted handshake message, the responder will validate the `cookie` value and resume with the handshake process.
\printbibliography
\setupimage{landscape,fullpage,label=img:HandlingCode}

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)
# 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
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,43 +0,0 @@
[package]
name = "rosenpass"
version = "0.2.1"
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]
rosenpass-util = { workspace = true }
rosenpass-constant-time = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-ciphers = { workspace = true }
rosenpass-to = { workspace = true }
anyhow = { workspace = true }
static_assertions = { workspace = true }
memoffset = { workspace = true }
libsodium-sys-stable = { workspace = true }
oqs-sys = { workspace = true }
lazy_static = { workspace = true }
thiserror = { workspace = true }
paste = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }
clap = { workspace = true }
mio = { workspace = true }
[build-dependencies]
anyhow = { workspace = true }
[dev-dependencies]
criterion = { workspace = true }
test_bin = { workspace = true }
stacker = { workspace = true }

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
[package]
name = "rosenpass-sodium"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal bindings to libsodium"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
rosenpass-util = { workspace = true }
rosenpass-to = { workspace = true }
anyhow = { workspace = true }
libsodium-sys-stable = { workspace = true }
log = { workspace = true }

View File

@@ -1,5 +0,0 @@
# Rosenpass internal libsodium bindings
Rosenpass internal library providing bindings to libsodium.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -1,63 +0,0 @@
use libsodium_sys as libsodium;
use std::ffi::c_ulonglong;
use std::ptr::{null, null_mut};
pub const KEY_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize;
pub const TAG_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_ABYTES as usize;
pub const NONCE_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize;
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + TAG_LEN);
assert!(key.len() == KEY_LEN);
assert!(nonce.len() == NONCE_LEN);
let mut clen: u64 = 0;
sodium_call!(
crypto_aead_chacha20poly1305_ietf_encrypt,
ciphertext.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
null(), // nsec is not used
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(clen as usize == ciphertext.len());
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + TAG_LEN);
assert!(key.len() == KEY_LEN);
assert!(nonce.len() == NONCE_LEN);
let mut mlen: u64 = 0;
sodium_call!(
crypto_aead_chacha20poly1305_ietf_decrypt,
plaintext.as_mut_ptr(),
&mut mlen as *mut c_ulonglong,
null_mut(), // nsec is not used
ciphertext.as_ptr(),
ciphertext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(mlen as usize == plaintext.len());
Ok(())
}

View File

@@ -1,2 +0,0 @@
pub mod chacha20poly1305_ietf;
pub mod xchacha20poly1305_ietf;

View File

@@ -1,63 +0,0 @@
use libsodium_sys as libsodium;
use std::ffi::c_ulonglong;
use std::ptr::{null, null_mut};
pub const KEY_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize;
pub const TAG_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_ietf_ABYTES as usize;
pub const NONCE_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_NPUBBYTES as usize;
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + NONCE_LEN + TAG_LEN);
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
let (n, ct) = ciphertext.split_at_mut(NONCE_LEN);
n.copy_from_slice(nonce);
let mut clen: u64 = 0;
sodium_call!(
crypto_aead_xchacha20poly1305_ietf_encrypt,
ct.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
null(), // nsec is not used
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(clen as usize == ct.len());
Ok(())
}
#[inline]
pub fn decrypt(
plaintext: &mut [u8],
key: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> anyhow::Result<()> {
assert!(ciphertext.len() == plaintext.len() + NONCE_LEN + TAG_LEN);
assert!(key.len() == KEY_LEN);
let (n, ct) = ciphertext.split_at(NONCE_LEN);
let mut mlen: u64 = 0;
sodium_call!(
crypto_aead_xchacha20poly1305_ietf_decrypt,
plaintext.as_mut_ptr(),
&mut mlen as *mut c_ulonglong,
null_mut(), // nsec is not used
ct.as_ptr(),
ct.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
n.as_ptr(),
key.as_ptr()
)?;
assert!(mlen as usize == plaintext.len());
Ok(())
}

View File

@@ -1,31 +0,0 @@
use libsodium_sys as libsodium;
use rosenpass_to::{with_destination, To};
use std::ffi::c_ulonglong;
use std::ptr::null;
pub const KEY_MIN: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MIN as usize;
pub const KEY_MAX: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MAX as usize;
pub const OUT_MIN: usize = libsodium::crypto_generichash_blake2b_BYTES_MIN as usize;
pub const OUT_MAX: usize = libsodium::crypto_generichash_blake2b_BYTES_MAX as usize;
#[inline]
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
with_destination(|out: &mut [u8]| {
assert!(key.is_empty() || (KEY_MIN <= key.len() && key.len() <= KEY_MAX));
assert!(OUT_MIN <= out.len() && out.len() <= OUT_MAX);
let kptr = match key.len() {
// NULL key
0 => null(),
_ => key.as_ptr(),
};
sodium_call!(
crypto_generichash_blake2b,
out.as_mut_ptr(),
out.len(),
data.as_ptr(),
data.len() as c_ulonglong,
kptr,
key.len()
)
})
}

View File

@@ -1 +0,0 @@
pub mod blake2b;

View File

@@ -1,52 +0,0 @@
use libsodium_sys as libsodium;
use std::os::raw::c_void;
#[inline]
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
a.len() == b.len()
&& unsafe {
let r = libsodium::sodium_memcmp(
a.as_ptr() as *const c_void,
b.as_ptr() as *const c_void,
a.len(),
);
r == 0
}
}
#[inline]
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
unsafe { libsodium::sodium_compare(a.as_ptr(), b.as_ptr(), a.len()) }
}
#[inline]
pub fn increment(v: &mut [u8]) {
unsafe {
libsodium::sodium_increment(v.as_mut_ptr(), v.len());
}
}
#[inline]
pub fn randombytes_buf(buf: &mut [u8]) {
unsafe { libsodium::randombytes_buf(buf.as_mut_ptr() as *mut c_void, buf.len()) };
}
#[inline]
pub fn memzero(buf: &mut [u8]) {
unsafe { libsodium::sodium_memzero(buf.as_mut_ptr() as *mut c_void, buf.len()) };
}
// Choose a fully random u64
// TODO: Replace with ::rand::random
pub fn rand_u64() -> u64 {
let mut buf = [0u8; 8];
randombytes_buf(&mut buf);
u64::from_le_bytes(buf)
}
// Choose a random f64 in [0; 1] inclusive; quick and dirty
// TODO: Replace with ::rand::random
pub fn rand_f64() -> f64 {
(rand_u64() as f64) / (u64::MAX as f64)
}

View File

@@ -1,20 +0,0 @@
use libsodium_sys as libsodium;
macro_rules! sodium_call {
($name:ident, $($args:expr),*) => { ::rosenpass_util::attempt!({
anyhow::ensure!(unsafe{libsodium::$name($($args),*)} > -1,
"Error in libsodium's {}.", stringify!($name));
Ok(())
})};
($name:ident) => { sodium_call!($name, ) };
}
#[inline]
pub fn init() -> anyhow::Result<()> {
log::trace!("initializing libsodium");
sodium_call!(sodium_init)
}
pub mod aead;
pub mod hash;
pub mod helpers;

View File

@@ -1,10 +1,6 @@
use anyhow::bail;
use anyhow::Result;
use log::{debug, error, info, warn};
use log::{error, info, warn};
use mio::Interest;
use mio::Token;
use rosenpass_util::file::fopen_w;
use std::cell::Cell;
use std::io::Write;
@@ -20,25 +16,20 @@ use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use std::slice;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use crate::util::fopen_w;
use crate::RosenpassError;
use crate::{
config::Verbosity,
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
util::{b64_writer, fmt_b64},
Result,
};
use rosenpass_util::attempt;
use rosenpass_util::b64::{b64_writer, fmt_b64};
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
// Using values from Linux Kernel implementation
// TODO: Customize values for rosenpass
const MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD: usize = 4096;
const LAST_UNDER_LOAD_WINDOW: Duration = Duration::from_secs(1);
fn ipv4_any_binding() -> SocketAddr {
// addr, port
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
@@ -73,12 +64,6 @@ pub struct WireguardOut {
pub extra_params: Vec<String>,
}
#[derive(Debug)]
pub enum DoSOperation {
UnderLoad { last_under_load: Instant },
Normal,
}
/// Holds the state of the application, namely the external IO
///
/// Responsible for file IO, network IO
@@ -92,7 +77,6 @@ pub struct AppServer {
pub peers: Vec<AppPeer>,
pub verbosity: Verbosity,
pub all_sockets_drained: bool,
pub under_load: DoSOperation,
}
/// A socket pointer is an index assigned to a socket;
@@ -113,8 +97,8 @@ impl SocketPtr {
&mut srv.sockets[self.0]
}
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
self.get(srv).send_to(buf, addr)?;
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> Result<()> {
self.get(srv).send_to(&buf, addr)?;
Ok(())
}
}
@@ -196,7 +180,7 @@ impl Endpoint {
}
/// 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)?;
Ok(Endpoint::Discovery(host))
}
@@ -226,7 +210,7 @@ impl Endpoint {
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::*;
match self {
SocketBoundAddress { socket, addr } => socket.send_to(srv, buf, *addr),
@@ -285,7 +269,7 @@ impl HostPathDiscoveryEndpoint {
}
/// Lookup a hostname
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
pub fn lookup(hostname: String) -> Result<Self> {
Ok(Self {
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
scouting_state: Cell::new((0, 0)),
@@ -306,16 +290,16 @@ impl HostPathDiscoveryEndpoint {
/// Attempt to reach the host
///
/// 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 mut addrs = (self.addresses)
let mut addrs = (&self.addresses)
.iter()
.enumerate()
.cycle()
.skip(addr_off)
.take(self.addresses.len());
let mut sockets = (srv.sockets)
let mut sockets = (&srv.sockets)
.iter()
.enumerate()
.cycle()
@@ -345,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 {
pub fn new(
sk: SSk,
pk: SPk,
addrs: Vec<SocketAddr>,
verbosity: Verbosity,
) -> anyhow::Result<Self> {
pub fn new(sk: SSk, pk: SPk, addrs: Vec<SocketAddr>, verbosity: Verbosity) -> Result<Self> {
// setup mio
let mio_poll = mio::Poll::new()?;
let events = mio::Events::with_capacity(8);
// 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();
let mut sockets = maybe_sockets?;
@@ -428,7 +408,8 @@ impl AppServer {
}
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
@@ -448,7 +429,6 @@ impl AppServer {
events,
mio_poll,
all_sockets_drained: false,
under_load: DoSOperation::Normal,
})
}
@@ -463,7 +443,7 @@ impl AppServer {
outfile: Option<PathBuf>,
outwg: Option<WireguardOut>,
hostname: Option<String>,
) -> anyhow::Result<AppPeerPtr> {
) -> Result<AppPeerPtr> {
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
assert!(pn == self.peers.len());
let initial_endpoint = hostname
@@ -479,7 +459,7 @@ impl AppServer {
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 MAX_FAILURES: i32 = 10;
let mut failure_cnt = 0;
@@ -500,10 +480,11 @@ impl AppServer {
let sleep = INIT_SLEEP * 2.0f64.powf(f64::from(failure_cnt - 1));
let tries_left = MAX_FAILURES - (failure_cnt - 1);
error!(
"unexpected error after processing {} messages: {:?} {}",
"unexpected error after processing {} messages: {:?}",
msgs_processed,
err,
err.backtrace()
// TODO do we need backtraces?
// err.backtrace()
);
if tries_left > 0 {
error!("re-initializing networking in {sleep}! {tries_left} tries left.");
@@ -511,11 +492,12 @@ impl AppServer {
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());
/// if socket address for peer is known, call closure
@@ -540,11 +522,9 @@ impl AppServer {
use AppPollResult::*;
use KeyOutputReason::*;
match self.poll(&mut *rx)? {
#[allow(clippy::redundant_closure_call)]
SendInitiation(peer) => tx_maybe_with!(peer, || self
.crypt
.initiate_handshake(peer.lower(), &mut *tx))?,
#[allow(clippy::redundant_closure_call)]
SendRetransmission(peer) => tx_maybe_with!(peer, || self
.crypt
.retransmit_handshake(peer.lower(), &mut *tx))?,
@@ -563,44 +543,12 @@ impl AppServer {
}
ReceivedMessage(len, endpoint) => {
let msg_result = match self.under_load {
DoSOperation::UnderLoad { last_under_load: _ } => {
//TODO: Lookup peer through addresses (hash)
let index = self
.peers
.iter()
.enumerate()
.find(|(_num, p)| {
if let Some(ep) = p.endpoint() {
ep.addresses() == endpoint.addresses()
} else {
false
}
})
.ok_or(anyhow::anyhow!("Received message from unknown endpoint"))?
.0;
let socket_addr = endpoint
.addresses()
.first()
.copied()
.ok_or(anyhow::anyhow!("No socket address for endpoint"))?;
self.crypt.handle_msg_under_load(
&rx[..len],
&mut *tx,
PeerPtr(index),
socket_addr,
)
}
DoSOperation::Normal => self.crypt.handle_msg(&rx[..len], &mut *tx),
};
match msg_result {
match self.crypt.handle_msg(&rx[..len], &mut *tx) {
Err(ref e) => {
self.verbose().then(|| {
info!(
"error processing incoming message from {:?}: {:?} {}",
endpoint,
e,
e.backtrace()
error!(
"error processing incoming message from {:?}: {:?}",
endpoint, e
);
});
}
@@ -628,12 +576,7 @@ impl AppServer {
}
}
pub fn output_key(
&self,
peer: AppPeerPtr,
why: KeyOutputReason,
key: &SymKey,
) -> anyhow::Result<()> {
pub fn output_key(&self, peer: AppPeerPtr, why: KeyOutputReason, key: &SymKey) -> Result<()> {
let peerid = peer.lower().get(&self.crypt).pidt()?;
let ap = peer.get_app(self);
@@ -668,7 +611,7 @@ impl AppServer {
}
if let Some(owg) = ap.outwg.as_ref() {
let mut child = Command::new("wg")
let child = Command::new("wg")
.arg("set")
.arg(&owg.dev)
.arg("peer")
@@ -678,27 +621,13 @@ impl AppServer {
.stdin(Stdio::piped())
.args(&owg.extra_params)
.spawn()?;
b64_writer(child.stdin.take().unwrap()).write_all(key.secret())?;
thread::spawn(move || {
let status = child.wait();
if let Ok(status) = status {
if status.success() {
debug!("successfully passed psk to wg")
} else {
error!("could not pass psk to wg {:?}", status)
}
} else {
error!("wait failed: {:?}", status)
}
});
b64_writer(child.stdin.unwrap()).write_all(key.secret())?;
}
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 AppPollResult as A;
loop {
@@ -723,7 +652,7 @@ impl AppServer {
&mut self,
buf: &mut [u8],
timeout: Timing,
) -> anyhow::Result<Option<(usize, Endpoint)>> {
) -> Result<Option<(usize, Endpoint)>> {
let timeout = Duration::from_secs_f64(timeout);
// if there is no time to wait on IO, well, then, lets not waste any time!
@@ -748,29 +677,11 @@ impl AppServer {
// desired on a non-dual-stack OS), thus just checking every socket after any
// readiness event seems to be good enough™ for now.
println!("All sockets drained: {}", self.all_sockets_drained);
// only poll if we drained all sockets before
if self.all_sockets_drained {
self.mio_poll.poll(&mut self.events, Some(timeout))?;
let queue_length = self.events.iter().peekable().count();
println!("queue length: {}", queue_length);
if queue_length > MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD {
self.under_load = DoSOperation::UnderLoad {
last_under_load: Instant::now(),
}
}
}
if let DoSOperation::UnderLoad { last_under_load } = self.under_load {
if last_under_load.elapsed() > LAST_UNDER_LOAD_WINDOW {
self.under_load = DoSOperation::Normal;
}
}
// drain all sockets
let mut would_block_count = 0;
for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
match socket.recv_from(buf) {

View File

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

View File

@@ -6,23 +6,18 @@
//! - guard pages before and after each allocation trap accidential sequential reads that creep towards our secrets
//! - the memory is mlocked, e.g. it is never swapped
use anyhow::Context;
use crate::{
sodium::{rng, zeroize},
util::{cpy, mutating},
};
use lazy_static::lazy_static;
use libsodium_sys as libsodium;
use rosenpass_util::{
b64::b64_reader,
file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd, StoreValue},
functional::mutating,
mem::cpy,
};
use std::result::Result;
use std::{
collections::HashMap,
convert::TryInto,
fmt,
ops::{Deref, DerefMut},
os::raw::c_void,
path::Path,
ptr::null_mut,
sync::Mutex,
};
@@ -178,12 +173,12 @@ impl<const N: usize> Secret<N> {
/// Sets all data of an existing secret to null bytes
pub fn zeroize(&mut self) {
rosenpass_sodium::helpers::memzero(self.secret_mut());
zeroize(self.secret_mut());
}
/// Sets all data an existing secret to random bytes
pub fn randomize(&mut self) {
rosenpass_sodium::helpers::randombytes_buf(self.secret_mut());
rng(self.secret_mut());
}
/// Borrows the data
@@ -248,7 +243,7 @@ impl<const N: usize> Public<N> {
/// Randomize all bytes in an existing [Public]
pub fn randomize(&mut self) {
rosenpass_sodium::helpers::randombytes_buf(&mut self.value);
rng(&mut self.value);
}
}
@@ -364,80 +359,3 @@ mod test {
assert_eq!(new_secret.secret(), &[0; N]);
}
}
trait StoreSecret {
type Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}
impl<T: StoreValue> StoreSecret for T {
type Error = <T as StoreValue>::Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
self.store(path)
}
}
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
fopen_r(p)?
.read_exact_to_end(v.secret_mut())
.with_context(|| format!("Could not load file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
fn load_b64<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
use std::io::Read;
let mut v = Self::random();
let p = path.as_ref();
// This might leave some fragments of the secret on the stack;
// in practice this is likely not a problem because the stack likely
// will be overwritten by something else soon but this is not exactly
// guaranteed. It would be possible to remedy this, but since the secret
// data will linger in the Linux page cache anyways with the current
// implementation, going to great length to erase the secret here is
// not worth it right now.
b64_reader(&mut fopen_r(p)?)
.read_exact(v.secret_mut())
.with_context(|| format!("Could not load base64 file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, self.secret())?;
Ok(())
}
}
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
Ok(v)
}
}
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
}
}

View File

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

View File

@@ -1,10 +1,10 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use crate::prftree::PrfTree;
use anyhow::Result;
use rosenpass_ciphers::KEY_LEN;
use {
crate::Result,
crate::{prftree::PrfTree, sodium::KEY_SIZE},
};
pub fn protocol() -> Result<PrfTree> {
PrfTree::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
@@ -23,8 +23,6 @@ macro_rules! prflabel {
prflabel!(protocol, mac, "mac");
prflabel!(protocol, cookie, "cookie");
prflabel!(protocol, cookie_tau, "cookie-tau");
prflabel!(protocol, cookie_key, "cookie-key");
prflabel!(protocol, peerid, "peer id");
prflabel!(protocol, biscuit_ad, "biscuit additional data");
prflabel!(protocol, ckinit, "chaining key init");
@@ -32,7 +30,7 @@ prflabel!(protocol, _ckextract, "chaining key extract");
macro_rules! prflabel_leaf {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
pub fn $name() -> Result<[u8; KEY_SIZE]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())

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),
}
}
}

106
src/lprf.rs Normal file
View File

@@ -0,0 +1,106 @@
//! The rosenpass protocol relies on a special type
//! of hash function for most of its hashing or
//! message authentication needs: an incrementable
//! pseudo random function.
//!
//! This is a generalization of a PRF operating
//! on a sequence of inputs instead of a single input.
//!
//! Like a Dec function the Iprf features efficient
//! incrementability.
//!
//! You can also think of an Iprf as a Dec function with
//! a fixed size output.
//!
//! The idea behind a Iprf is that it can be efficiently
//! constructed from an Dec function as well as a PRF.
//!
//! TODO Base the construction on a proper Dec function
pub struct Iprf([u8; KEY_SIZE]);
pub struct IprfBranch([u8; KEY_SIZE]);
pub struct SecretIprf(Secret<KEY_SIZE>);
pub struct SecretIprfBranch(Secret<KEY_SIZE>);
pub fn prf_into(out: &mut [u8], key: &[u8], data: &[u8]) {
// TODO: The error handling with sodium is a scurge
hmac_into(out, key, data).unwrap()
}
pub fn prf(key: &[u8], data: &[u8]) -> [u8; KEY_SIZE]{
mutating([0u8; KEY_SIZE], |r| prf_into(r, key, data))
}
impl Iprf {
fn zero() -> Self {
Self([0u8; KEY_SIZE])
}
fn dup(self) -> IprfBranch {
IprfBranch(self.0)
}
// TODO: Protocol! Use domain separation to ensure that
fn mix(self, v: &[u8]) -> Self {
Self(prf(&self.0, v))
}
fn mix_secret<const N: usize>(self, v: Secret<N>) -> SecretIprf {
SecretIprf::prf_invoc(&self.0, v.secret())
}
fn into_value(self) -> [u8; KEY_SIZE] {
self.0
}
fn extract(self, v: &[u8], dst: &mut [u8]) {
prf_into(&self.0, v, dst)
}
}
impl IprfBranch {
fn mix(&self, v: &[u8]) -> Iprf {
Iprf(prf(self.0, v))
}
fn mix_secret<const N: usize>(&self, v: Secret<N>) -> SecretIprf {
SecretIprf::prf_incov(self.0, v.secret())
}
}
impl SecretIprf {
fn prf_invoc(k: &[u8], d: &[u8]) -> SecretIprf {
mutating(SecretIprf(Secret::zero()), |r|
prf_into(k, d, r.secret_mut()))
}
fn from_key(k: Secret<N>) -> SecretIprf {
Self(k)
}
fn mix(self, v: &[u8]) -> SecretIprf {
Self::prf_invoc(self.0.secret(), v)
}
fn mix_secret<const N: usize>(self, v: Secret<N>) -> SecretIprf {
Self::prf_invoc(self.0.secret(), v.secret())
}
fn into_secret(self) -> Secret<KEY_SIZE> {
self.0
}
fn into_secret_slice(self, v: &[u8], dst: &[u8]) {
prf_into(self.0.secret(), v, dst)
}
}
impl SecretIprfBranch {
fn mix(&self, v: &[u8]) -> SecretIprf {
SecretIprf::prf_invoc(self.0.secret(), v)
}
fn mix_secret<const N: usize>(&self, v: Secret<N>) -> SecretIprf {
SecretIprf::prf_invoc(self.0.secret(), v.secret())
}
}

View File

@@ -1,18 +1,11 @@
use log::error;
use rosenpass::cli::Cli;
use rosenpass_util::attempt;
use rosenpass::{cli::Cli, sodium::sodium_init};
use std::process::exit;
/// Catches errors, prints them through the logger, then exits
pub fn main() {
env_logger::init();
let res = attempt!({
rosenpass_sodium::init()?;
Cli::run()
});
match res {
match sodium_init().and_then(|()| Cli::run()) {
Ok(_) => {}
Err(e) => {
error!("{e}");

View File

@@ -44,11 +44,7 @@
//! ```
use super::RosenpassError;
use crate::pqkem::*;
use rosenpass_ciphers::{aead, xaead};
pub const MAC_SIZE: usize = 16;
pub const COOKIE_SIZE: usize = 16;
use crate::{pqkem::*, sodium};
// Macro magic ////////////////////////////////////////////////////////////////
@@ -147,7 +143,7 @@ macro_rules! data_lense(
pub fn check_size(len: usize) -> Result<(), RosenpassError>{
let required_size = $( $len + )+ 0;
let actual_size = len;
if required_size != actual_size {
if required_size < actual_size {
Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
@@ -203,53 +199,23 @@ macro_rules! data_lense(
type __ContainerType;
/// Create a lense to the byte slice
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (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>;
fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
}
impl<'a> [< $type Ext >] for &'a [u8] {
type __ContainerType = &'a [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> ),+ )? ))
}
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] {
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> ),+ )? ))
}
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])
}
}
});
);
@@ -268,9 +234,9 @@ data_lense! { Envelope<M> :=
payload: M::LEN,
/// Message Authentication Code (mac) over all bytes until (exclusive)
/// `mac` itself
mac: MAC_SIZE,
/// Cookie value
cookie: COOKIE_SIZE
mac: sodium::MAC_SIZE,
/// Currently unused, TODO: do something with this
cookie: sodium::MAC_SIZE
}
data_lense! { InitHello :=
@@ -281,9 +247,9 @@ data_lense! { InitHello :=
/// Classic McEliece Ciphertext
sctr: StaticKEM::CT_LEN,
/// Encryped: 16 byte hash of McEliece initiator static key
pidic: aead::TAG_LEN + 32,
pidic: sodium::AEAD_TAG_LEN + 32,
/// Encrypted TAI64N Time Stamp (against replay attacks)
auth: aead::TAG_LEN
auth: sodium::AEAD_TAG_LEN
}
data_lense! { RespHello :=
@@ -296,7 +262,7 @@ data_lense! { RespHello :=
/// Classic McEliece Ciphertext
scti: StaticKEM::CT_LEN,
/// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN,
auth: sodium::AEAD_TAG_LEN,
/// Responders handshake state in encrypted form
biscuit: BISCUIT_CT_LEN
}
@@ -309,7 +275,7 @@ data_lense! { InitConf :=
/// Responders handshake state in encrypted form
biscuit: BISCUIT_CT_LEN,
/// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN
auth: sodium::AEAD_TAG_LEN
}
data_lense! { EmptyData :=
@@ -318,16 +284,16 @@ data_lense! { EmptyData :=
/// Nonce
ctr: 8,
/// Empty encrypted message (just an auth tag)
auth: aead::TAG_LEN
auth: sodium::AEAD_TAG_LEN
}
data_lense! { Biscuit :=
/// H(spki) Ident ifies the initiator
pidi: aead::KEY_LEN,
pidi: sodium::KEY_SIZE,
/// The biscuit number (replay protection)
biscuit_no: 12,
/// Chaining key
ck: aead::KEY_LEN
ck: sodium::KEY_SIZE
}
data_lense! { DataMsg :=
@@ -335,9 +301,7 @@ data_lense! { DataMsg :=
}
data_lense! { CookieReply :=
sid: 4,
nonce: xaead::NONCE_LEN,
cookie_encrypted: MAC_SIZE + xaead::TAG_LEN
dummy: 4
}
// Traits /////////////////////////////////////////////////////////////////////
@@ -390,28 +354,30 @@ impl TryFrom<u8> for MsgType {
pub const BISCUIT_PT_LEN: usize = Biscuit::<()>::LEN;
/// Length in bytes of an encrypted Biscuit (cipher text)
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + sodium::XAEAD_NONCE_LEN + sodium::XAEAD_TAG_LEN;
#[cfg(test)]
mod test_constants {
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
use rosenpass_ciphers::{xaead, KEY_LEN};
use crate::{
msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN},
sodium,
};
#[test]
fn sodium_keysize() {
assert_eq!(KEY_LEN, 32);
assert_eq!(sodium::KEY_SIZE, 32);
}
#[test]
fn biscuit_pt_len() {
assert_eq!(BISCUIT_PT_LEN, 2 * KEY_LEN + 12);
assert_eq!(BISCUIT_PT_LEN, 2 * sodium::KEY_SIZE + 12);
}
#[test]
fn biscuit_ct_len() {
assert_eq!(
BISCUIT_CT_LEN,
BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN
BISCUIT_PT_LEN + sodium::XAEAD_NONCE_LEN + sodium::XAEAD_TAG_LEN
);
}
}

View File

@@ -1,23 +1,23 @@
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
use crate::coloring::Secret;
use anyhow::Result;
use rosenpass_ciphers::{hash, KEY_LEN};
use rosenpass_to::To;
use crate::{
coloring::Secret,
sodium::{hmac, hmac_into, KEY_SIZE},
Result,
};
// TODO Use a proper Dec interface
#[derive(Clone, Debug)]
pub struct PrfTree([u8; KEY_LEN]);
pub struct PrfTree([u8; KEY_SIZE]);
#[derive(Clone, Debug)]
pub struct PrfTreeBranch([u8; KEY_LEN]);
pub struct PrfTreeBranch([u8; KEY_SIZE]);
#[derive(Clone, Debug)]
pub struct SecretPrfTree(Secret<KEY_LEN>);
pub struct SecretPrfTree(Secret<KEY_SIZE>);
#[derive(Clone, Debug)]
pub struct SecretPrfTreeBranch(Secret<KEY_LEN>);
pub struct SecretPrfTreeBranch(Secret<KEY_SIZE>);
impl PrfTree {
pub fn zero() -> Self {
Self([0u8; KEY_LEN])
Self([0u8; KEY_SIZE])
}
pub fn dup(self) -> PrfTreeBranch {
@@ -30,21 +30,21 @@ impl PrfTree {
// TODO: Protocol! Use domain separation to ensure that
pub fn mix(self, v: &[u8]) -> Result<Self> {
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
Ok(Self(hmac(&self.0, v)?))
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(&self.0, v.secret())
}
pub fn into_value(self) -> [u8; KEY_LEN] {
pub fn into_value(self) -> [u8; KEY_SIZE] {
self.0
}
}
impl PrfTreeBranch {
pub fn mix(&self, v: &[u8]) -> Result<PrfTree> {
Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
Ok(PrfTree(hmac(&self.0, v)?))
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
@@ -55,7 +55,7 @@ impl PrfTreeBranch {
impl SecretPrfTree {
pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result<SecretPrfTree> {
let mut r = SecretPrfTree(Secret::zero());
hash::hash(k, d).to(r.0.secret_mut())?;
hmac_into(r.0.secret_mut(), k, d)?;
Ok(r)
}
@@ -67,7 +67,7 @@ impl SecretPrfTree {
SecretPrfTreeBranch(self.0)
}
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
pub fn danger_from_secret(k: Secret<KEY_SIZE>) -> Self {
Self(k)
}
@@ -79,12 +79,12 @@ impl SecretPrfTree {
Self::prf_invoc(self.0.secret(), v.secret())
}
pub fn into_secret(self) -> Secret<KEY_LEN> {
pub fn into_secret(self) -> Secret<KEY_SIZE> {
self.0
}
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
hash::hash(v, dst).to(self.0.secret_mut())
hmac_into(self.0.secret_mut(), v, dst)
}
}
@@ -100,7 +100,7 @@ impl SecretPrfTreeBranch {
// TODO: This entire API is not very nice; we need this for biscuits, but
// it might be better to extract a special "biscuit"
// labeled subkey and reinitialize the chain with this
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
pub fn danger_into_secret(self) -> Secret<KEY_SIZE> {
self.0
}
}

File diff suppressed because it is too large Load Diff

291
src/sodium.rs Normal file
View File

@@ -0,0 +1,291 @@
//! Bindings and helpers for accessing libsodium functions
use crate::{util::*, Result, RosenpassError};
use libsodium_sys as libsodium;
use log::trace;
use static_assertions::const_assert_eq;
use std::os::raw::{c_ulonglong, c_void};
use std::ptr::{null as nullptr, null_mut as nullptr_mut};
pub const AEAD_TAG_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_ABYTES as usize;
pub const AEAD_NONCE_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize;
pub const XAEAD_TAG_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_ietf_ABYTES as usize;
pub const XAEAD_NONCE_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_NPUBBYTES as usize;
pub const NONCE0: [u8; libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize] =
[0u8; libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize];
pub const NOTHING: [u8; 0] = [0u8; 0];
pub const KEY_SIZE: usize = 32;
pub const MAC_SIZE: usize = 16;
const_assert_eq!(
KEY_SIZE,
libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize
);
const_assert_eq!(KEY_SIZE, libsodium::crypto_generichash_BYTES as usize);
macro_rules! sodium_call {
($name:ident, $($args:expr),*) => { attempt!({
if unsafe{libsodium::$name($($args),*)} > -1 {
Ok(())
}else{
Err(RosenpassError::LibsodiumError(concat!("Error in libsodium's {}.", stringify!($name))))
}
})};
($name:ident) => { sodium_call!($name, ) };
}
#[inline]
pub fn sodium_init() -> Result<()> {
trace!("initializing libsodium");
sodium_call!(sodium_init)
}
#[inline]
pub fn sodium_memcmp(a: &[u8], b: &[u8]) -> bool {
a.len() == b.len()
&& unsafe {
let r = libsodium::sodium_memcmp(
a.as_ptr() as *const c_void,
b.as_ptr() as *const c_void,
a.len(),
);
r == 0
}
}
#[inline]
pub fn sodium_bigint_cmp(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
unsafe { libsodium::sodium_compare(a.as_ptr(), b.as_ptr(), a.len()) }
}
#[inline]
pub fn sodium_bigint_inc(v: &mut [u8]) {
unsafe {
libsodium::sodium_increment(v.as_mut_ptr(), v.len());
}
}
#[inline]
pub fn rng(buf: &mut [u8]) {
unsafe { libsodium::randombytes_buf(buf.as_mut_ptr() as *mut c_void, buf.len()) };
}
#[inline]
pub fn zeroize(buf: &mut [u8]) {
unsafe { libsodium::sodium_memzero(buf.as_mut_ptr() as *mut c_void, buf.len()) };
}
#[inline]
pub fn aead_enc_into(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> Result<()> {
assert!(ciphertext.len() == plaintext.len() + AEAD_TAG_LEN);
assert!(key.len() == libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize);
assert!(nonce.len() == libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize);
let mut clen: u64 = 0;
sodium_call!(
crypto_aead_chacha20poly1305_ietf_encrypt,
ciphertext.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
nullptr(), // nsec is not used
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(clen as usize == ciphertext.len());
Ok(())
}
#[inline]
pub fn aead_dec_into(
plaintext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> Result<()> {
assert!(ciphertext.len() == plaintext.len() + AEAD_TAG_LEN);
assert!(key.len() == libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize);
assert!(nonce.len() == libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize);
let mut mlen: u64 = 0;
sodium_call!(
crypto_aead_chacha20poly1305_ietf_decrypt,
plaintext.as_mut_ptr(),
&mut mlen as *mut c_ulonglong,
nullptr_mut(), // nsec is not used
ciphertext.as_ptr(),
ciphertext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(mlen as usize == plaintext.len());
Ok(())
}
#[inline]
pub fn xaead_enc_into(
ciphertext: &mut [u8],
key: &[u8],
nonce: &[u8],
ad: &[u8],
plaintext: &[u8],
) -> Result<()> {
assert!(ciphertext.len() == plaintext.len() + XAEAD_NONCE_LEN + XAEAD_TAG_LEN);
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
let (n, ct) = ciphertext.split_at_mut(XAEAD_NONCE_LEN);
n.copy_from_slice(nonce);
let mut clen: u64 = 0;
sodium_call!(
crypto_aead_xchacha20poly1305_ietf_encrypt,
ct.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
nullptr(), // nsec is not used
nonce.as_ptr(),
key.as_ptr()
)?;
assert!(clen as usize == ct.len());
Ok(())
}
#[inline]
pub fn xaead_dec_into(
plaintext: &mut [u8],
key: &[u8],
ad: &[u8],
ciphertext: &[u8],
) -> Result<()> {
assert!(ciphertext.len() == plaintext.len() + XAEAD_NONCE_LEN + XAEAD_TAG_LEN);
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
let (n, ct) = ciphertext.split_at(XAEAD_NONCE_LEN);
let mut mlen: u64 = 0;
sodium_call!(
crypto_aead_xchacha20poly1305_ietf_decrypt,
plaintext.as_mut_ptr(),
&mut mlen as *mut c_ulonglong,
nullptr_mut(), // nsec is not used
ct.as_ptr(),
ct.len() as c_ulonglong,
ad.as_ptr(),
ad.len() as c_ulonglong,
n.as_ptr(),
key.as_ptr()
)?;
assert!(mlen as usize == plaintext.len());
Ok(())
}
#[inline]
fn blake2b_flexible(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
const KEY_MIN: usize = libsodium::crypto_generichash_KEYBYTES_MIN as usize;
const KEY_MAX: usize = libsodium::crypto_generichash_KEYBYTES_MAX as usize;
const OUT_MIN: usize = libsodium::crypto_generichash_BYTES_MIN as usize;
const OUT_MAX: usize = libsodium::crypto_generichash_BYTES_MAX as usize;
assert!(key.is_empty() || (KEY_MIN <= key.len() && key.len() <= KEY_MAX));
assert!(OUT_MIN <= out.len() && out.len() <= OUT_MAX);
let kptr = match key.len() {
// NULL key
0 => nullptr(),
_ => key.as_ptr(),
};
sodium_call!(
crypto_generichash_blake2b,
out.as_mut_ptr(),
out.len(),
data.as_ptr(),
data.len() as c_ulonglong,
kptr,
key.len()
)
}
// TODO: Use proper streaming hash; for mix_hash too.
#[inline]
pub fn hash_into(out: &mut [u8], data: &[u8]) -> Result<()> {
assert!(out.len() == KEY_SIZE);
blake2b_flexible(out, &NOTHING, data)
}
#[inline]
pub fn hash(data: &[u8]) -> Result<[u8; KEY_SIZE]> {
let mut r = [0u8; KEY_SIZE];
hash_into(&mut r, data)?;
Ok(r)
}
#[inline]
pub fn mac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
assert!(out.len() == KEY_SIZE);
assert!(key.len() == KEY_SIZE);
blake2b_flexible(out, key, data)
}
#[inline]
pub fn mac(key: &[u8], data: &[u8]) -> Result<[u8; KEY_SIZE]> {
let mut r = [0u8; KEY_SIZE];
mac_into(&mut r, key, data)?;
Ok(r)
}
#[inline]
pub fn mac16(key: &[u8], data: &[u8]) -> Result<[u8; 16]> {
assert!(key.len() == KEY_SIZE);
let mut out = [0u8; 16];
blake2b_flexible(&mut out, key, data)?;
Ok(out)
}
#[inline]
pub fn hmac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
// Not bothering with padding; the implementation
// uses appropriately sized keys.
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];
let mut temp_key = [0u8; KEY_SIZE];
temp_key.copy_from_slice(key);
xor_into(&mut temp_key, &IPAD);
let outer_data = mac(&temp_key, data)?;
const OPAD: [u8; KEY_SIZE] = [0x5Cu8; KEY_SIZE];
temp_key.copy_from_slice(key);
xor_into(&mut temp_key, &OPAD);
mac_into(out, &temp_key, &outer_data)
}
#[inline]
pub fn hmac(key: &[u8], data: &[u8]) -> Result<[u8; KEY_SIZE]> {
let mut r = [0u8; KEY_SIZE];
hmac_into(&mut r, key, data)?;
Ok(r)
}
// Choose a fully random u64
pub fn rand_u64() -> u64 {
let mut buf = [0u8; 8];
rng(&mut buf);
u64::from_le_bytes(buf)
}
// Choose a random f64 in [0; 1] inclusive; quick and dirty
pub fn rand_f64() -> f64 {
(rand_u64() as f64) / (u64::MAX as f64)
}

244
src/util.rs Normal file
View File

@@ -0,0 +1,244 @@
//! Helper functions and macros
use base64::{
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
write::EncoderWriter as B64Writer,
};
use log::error;
use std::{
borrow::{Borrow, BorrowMut},
cmp::min,
fs::{File, OpenOptions},
io::{Read, Write},
path::Path,
time::{Duration, Instant},
};
use crate::{
coloring::{Public, Secret},
Result,
};
#[inline]
pub fn xor_into(a: &mut [u8], b: &[u8]) {
assert!(a.len() == b.len());
for (av, bv) in a.iter_mut().zip(b.iter()) {
*av ^= *bv;
}
}
/// Concatenate two byte arrays
// TODO: Zeroize result?
#[macro_export]
macro_rules! cat {
($len:expr; $($toks:expr),+) => {{
let mut buf = [0u8; $len];
let mut off = 0;
$({
let tok = $toks;
let tr = ::std::borrow::Borrow::<[u8]>::borrow(tok);
(&mut buf[off..(off + tr.len())]).copy_from_slice(tr);
off += tr.len();
})+
assert!(off == buf.len(), "Size mismatch in cat!()");
buf
}}
}
// TODO: consistent inout ordering
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
dst.borrow_mut().copy_from_slice(src.borrow());
}
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
let src = src.borrow();
let dst = dst.borrow_mut();
let len = min(src.len(), dst.len());
dst[..len].copy_from_slice(&src[..len]);
}
/// Try block basically…returns a result and allows the use of the question mark operator inside
#[macro_export]
macro_rules! attempt {
($block:expr) => {
(|| -> crate::Result<_> { $block })()
};
}
use base64::engine::general_purpose::GeneralPurpose as Base64Engine;
const B64ENGINE: Base64Engine = base64::engine::general_purpose::STANDARD;
pub fn fmt_b64<'a>(payload: &'a [u8]) -> B64Display<'a, 'static, Base64Engine> {
B64Display::<'a, 'static>::new(payload, &B64ENGINE)
}
pub fn b64_writer<W: Write>(w: W) -> B64Writer<'static, Base64Engine, W> {
B64Writer::new(w, &B64ENGINE)
}
pub fn b64_reader<R: Read>(r: R) -> B64Reader<'static, Base64Engine, R> {
B64Reader::new(r, &B64ENGINE)
}
// TODO remove this once std::cmp::max becomes const
pub const fn max_usize(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}
#[derive(Clone, Debug)]
pub struct Timebase(Instant);
impl Default for Timebase {
fn default() -> Self {
Self(Instant::now())
}
}
impl Timebase {
pub fn now(&self) -> f64 {
self.0.elapsed().as_secs_f64()
}
pub fn dur(&self, t: f64) -> Duration {
Duration::from_secs_f64(t)
}
}
pub fn mutating<T, F>(mut v: T, f: F) -> T
where
F: Fn(&mut T),
{
f(&mut v);
v
}
pub fn sideeffect<T, F>(v: T, f: F) -> T
where
F: Fn(&T),
{
f(&v);
v
}
/// load'n store
/// Open a file writable
pub fn fopen_w<P: AsRef<Path>>(path: P) -> Result<File> {
Ok(OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(path)?)
}
/// Open a file readable
pub fn fopen_r<P: AsRef<Path>>(path: P) -> Result<File> {
Ok(OpenOptions::new()
.read(true)
.write(false)
.create(false)
.truncate(false)
.open(path)?)
}
pub trait ReadExactToEnd {
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()>;
}
impl<R: Read> ReadExactToEnd for R {
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()> {
let mut dummy = [0u8; 8];
self.read_exact(buf)?;
if self.read(&mut dummy)? != 0 {
error!("File too long!");
Err(crate::RosenpassError::RuntimeError)
} else {
Ok(())
}
}
}
pub trait LoadValue {
fn load<P: AsRef<Path>>(path: P) -> Result<Self>
where
Self: Sized;
}
pub trait LoadValueB64 {
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self>
where
Self: Sized;
}
trait StoreValue {
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()>;
}
trait StoreSecret {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
}
impl<T: StoreValue> StoreSecret for T {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.store(path)
}
}
impl<const N: usize> LoadValue for Secret<N> {
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
fopen_r(p)?.read_exact_to_end(v.secret_mut()).map_err(|e| {
error!("Could not load file {p:?}");
e
})?;
Ok(v)
}
}
impl<const N: usize> LoadValueB64 for Secret<N> {
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
// This might leave some fragments of the secret on the stack;
// in practice this is likely not a problem because the stack likely
// will be overwritten by something else soon but this is not exactly
// guaranteed. It would be possible to remedy this, but since the secret
// data will linger in the Linux page cache anyways with the current
// implementation, going to great length to erase the secret here is
// not worth it right now.
b64_reader(&mut fopen_r(p)?)
.read_exact(v.secret_mut())
.map_err(|e| {
error!("Could not load base64 file {p:?}");
e
})?;
Ok(v)
}
}
impl<const N: usize> StoreSecret for Secret<N> {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
std::fs::write(path, self.secret())?;
Ok(())
}
}
impl<const N: usize> LoadValue for Public<N> {
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
Ok(v)
}
}
impl<const N: usize> StoreValue for Public<N> {
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()> {
std::fs::write(path, **self)?;
Ok(())
}
}

View File

@@ -1,13 +0,0 @@
[package]
name = "rosenpass-to"
version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Flexible destination parameters"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dev-dependencies]
doc-comment = { workspace = true }

View File

@@ -1,477 +0,0 @@
# The To Crate Patterns for dealing with destination parameters in rust functions
<!-- The code blocks in this file double as tests. -->
![crates.io](https://img.shields.io/crates/v/rosenpass-to.svg)
![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/cargo/rosenpass-to)
The To Crate provides a pattern for declaring and dealing with destination parameters in rust functions. It improves over stock rust by providing an interface that allows the caller to choose whether to place the destination parameter first through a `to(dest, copy(source))` function or last through a chained function `copy(source).to(dest)`.
The crate provides chained functions to simplify allocating the destination parameter on the fly and it provides well defined patterns for dealing with error handling and destination parameters.
For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may.
```rust
use std::ops::BitXorAssign;
use rosenpass_to::{To, to, with_destination};
use rosenpass_to::ops::copy_array;
// Destination functions return some value that implements the To trait.
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
// be without destination parameters
fn xor_slice<'a, T>(src: &'a[T]) -> impl To<[T], ()> + 'a
where T: BitXorAssign + Clone {
// Custom implementations of the to trait can be created, but the easiest
with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d ^= s.clone();
}
})
}
let flip0 = b"\xff\x00\x00\x00";
let flip1 = b"\x00\xff\x00\x00";
let flip01 = b"\xff\xff\x00\x00";
// You can specify a destination by using the to method
let mut dst = [0u8; 4];
xor_slice(flip0).to(&mut dst);
xor_slice(flip1).to(&mut dst);
assert_eq!(&dst[..], &flip01[..]);
// Or using the to function
let mut dst = [0u8; 4];
to(&mut dst, xor_slice(flip0));
to(&mut dst, xor_slice(flip1));
assert_eq!(&dst[..], &flip01[..]);
// You can pass a function to generate the destination on the fly
let dst = xor_slice(flip1).to_this(|| flip0.to_vec());
assert_eq!(&dst[..], &flip01[..]);
// If xor_slice used a return value that could be created using Default::default(),
// you could just use `xor_slice(flip01).to_value()` to generate the destination
// on the fly. Since [u8] is unsized, it can only be used for references.
//
// You can however use collect to specify the storage value explicitly.
// This works for any type that implements Default::default() and BorrowMut<...> for
// the destination value.
// Collect in an array with a fixed size
let dst = xor_slice(flip01).collect::<[u8; 4]>();
assert_eq!(&dst[..], &flip01[..]);
// The builtin function copy_array supports to_value() since its
// destination parameter is a fixed size array, which can be allocated
// using default()
let dst : [u8; 4] = copy_array(flip01).to_value();
assert_eq!(&dst, flip01);
```
The to crate really starts to shine when error handling (through result) is combined with destination parameters. See the tutorial below for details.
## Motivation
Destination parameters are often used when simply returning the value is undesirable or impossible.
Using stock rust features, functions can declare destination parameters by accepting mutable references as arguments.
This pattern introduces some shortcomings; developers have to make a call on whether to place destination parameters before or after source parameters and they have to enforce consistency across their codebase or accept inconsistencies, leading to hard-to-remember interfaces.
Functions declared like this are more cumbersome to use when the destination parameter should be allocated on the fly.
```rust
use std::ops::BitXorAssign;
fn xor_slice<T>(dst: &mut [T], src: &[T])
where T: BitXorAssign + Clone {
assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d ^= s.clone();
}
}
let flip0 = b"\xff\x00\x00\x00";
let flip1 = b"\x00\xff\x00\x00";
let flip01 = b"\xff\xff\x00\x00";
// Copy a slice from src to dest; its unclear whether src or dest should come first
let mut dst = [0u8; 4];
xor_slice(&mut dst, flip0);
xor_slice(&mut dst, flip1);
assert_eq!(&dst[..], &flip01[..]);
// The other examples can not be translated to use the standard rust pattern,
// since using mutable references for destination parameters does not allow
// for specifying the destination parameter on the right side or allocating
// the destination parameter on the fly.
```
## Tutorial
### Using a function with destination
There are a couple of ways to use a function with destination:
```rust
use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice_least};
let mut dst = b" ".to_vec();
// Using the to function to have data flowing from the right to the left,
// performing something akin to a variable assignment
to(&mut dst[..], copy_slice_least(b"Hello World"));
assert_eq!(&dst[..], b"Hello World");
// Using the to method to have information flowing from the left to the right
copy_slice_least(b"This is fin").to(&mut dst[..]);
assert_eq!(&dst[..], b"This is fin");
// You can allocate the destination variable on the fly using `.to_this(...)`
let tmp = copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned());
assert_eq!(&tmp[..], b"This is new---verwritten");
// You can allocate the destination variable on the fly `.collect(..)` if it implements default
let tmp = copy_slice_least(b"This is ad-hoc").collect::<[u8; 16]>();
assert_eq!(&tmp[..], b"This is ad-hoc\0\0");
// Finally, if the destination variable specified by the function implements default,
// you can simply use `.to_value()` to allocate it on the fly.
let tmp = copy_array(b"Fixed").to_value();
assert_eq!(&tmp[..], b"Fixed");
```
### Builtin functions with destination
The to crate provides basic functions with destination for copying data between slices and arrays.
```rust
use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice, try_copy_slice_least_src};
let mut dst = b" ".to_vec();
// Copy a slice, source and destination must match exactly
to(&mut dst[..], copy_slice(b"Hello World"));
assert_eq!(&dst[..], b"Hello World");
// Copy a slice, destination must be at least as long as the destination
to(&mut dst[4..], copy_slice_least_src(b"!!!"));
assert_eq!(&dst[..], b"Hell!!!orld");
// Copy a slice, copying as many bytes as possible
to(&mut dst[6..], copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
assert_eq!(&dst[..], b"Hell!!xxxxx");
// Copy a slice, will return None and abort if the sizes do not much
assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World")));
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---------------------")));
assert_eq!(&dst[..], b"Hello World");
// Copy a slice, will return None and abort if source is longer than destination
assert_eq!(Some(()), to(&mut dst[4..], try_copy_slice_least_src(b"!!!")));
assert_eq!(None, to(&mut dst[4..], try_copy_slice_least_src(b"-------------------------")));
assert_eq!(&dst[..], b"Hell!!!orld");
// Copy fixed size arrays all at once
let mut dst = [0u8; 5];
to(&mut dst, copy_array(b"Hello"));
assert_eq!(&dst, b"Hello");
```
### Declaring a function with destination
The easiest way to declare a function with destination is to use the with_destination function.
```rust
use rosenpass_to::{To, to, with_destination};
use rosenpass_to::ops::copy_array;
/// Copy the given slice to the start of a vector, reusing its memory if possible
fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To<Vec<T>, ()> + 'a
where T: Clone {
with_destination(move |dst: &mut Vec<T>| {
dst.clear();
dst.extend_from_slice(src);
})
}
let mut buf = copy_to_vec(b"Hello World, this is a long text.").to_value();
assert_eq!(&buf[..], b"Hello World, this is a long text.");
to(&mut buf, copy_to_vec(b"Avoids allocation"));
assert_eq!(&buf[..], b"Avoids allocation");
```
This example also shows of some of the advantages of using To: The function gains a very slight allocate over using `.to_vec()` by reusing memory:
```rust
let mut buf = b"Hello World, this is a long text.".to_vec();
buf = b"This allocates".to_vec(); // This uses memory allocation
```
The same pattern can be implemented without `to`, at the cost of being slightly more verbose
```rust
/// Copy the given slice to the start of a vector, reusing its memory if possible
fn copy_to_vec<T>(dst: &mut Vec<T>, src: &[T])
where T: Clone {
dst.clear();
dst.extend_from_slice(src);
}
let mut buf = Vec::default();
copy_to_vec(&mut buf, b"Hello World, this is a long text.");
assert_eq!(&buf[..], b"Hello World, this is a long text.");
copy_to_vec(&mut buf, b"Avoids allocation");
assert_eq!(&buf[..], b"Avoids allocation");
```
This usability enhancement might seem minor, but when many functions take destination parameters, manually allocating all of these can really become annoying.
## Beside values: Functions with destination and return value
Return values are supported, but `from_this()`, `to_value()`, and `collect()` cannot be used together with return values (unless they implement CondenseBeside see the next section), since that would erase the return value.
Alternative functions are returned, that return a `to::Beside` value, containing both the
destination variable and the return value.
```rust
use std::cmp::{min, max};
use rosenpass_to::{To, to, with_destination, Beside};
/// Copy an array of floats and calculate the average
pub fn copy_and_average<'a>(src: &'a[f64]) -> impl To<[f64], f64> + 'a {
with_destination(move |dst: &mut [f64]| {
assert!(src.len() == dst.len());
let mut sum = 0f64;
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d = *s;
sum = sum + *d;
}
sum / (src.len() as f64)
})
}
let src = [12f64, 13f64, 14f64];
// `.to()` and `to(...)` function as normal, but return the value now
let mut dst = [0f64; 3];
let avg = copy_and_average(&src).to(&mut dst);
assert_eq!((&dst[..], avg), (&src[..], 13f64));
let mut dst = [0f64; 3];
let avg = to(&mut dst, copy_and_average(&src));
assert_eq!((&dst[..], avg), (&src[..], 13f64));
// Instead of .to_this, .to_value, or .collect variants returning a beside value have to be used
let Beside(dst, avg) = copy_and_average(&src).to_this_beside(|| [0f64; 3]);
assert_eq!((&dst[..], avg), (&src[..], 13f64));
let Beside(dst, avg) = copy_and_average(&src).collect_beside::<[f64; 3]>();
assert_eq!((&dst[..], avg), (&src[..], 13f64));
// Beside values are simple named tuples
let b = copy_and_average(&src).collect_beside::<[f64; 3]>();
assert_eq!(b, Beside(dst, avg));
// They can convert from and to tuples
let b_tup = (dst, avg);
assert_eq!(b, (dst, avg).into());
assert_eq!(b, Beside::from(b_tup));
// Simple accessors for the value and returned value are provided
assert_eq!(&dst, b.dest());
assert_eq!(&avg, b.ret());
let mut tmp = b;
*tmp.dest_mut() = [42f64; 3];
*tmp.ret_mut() = 42f64;
assert_eq!(tmp, Beside([42f64; 3], 42f64));
```
## Beside Condensation: Working with destinations and Optional or Result
When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value.
```rust
use std::result::Result;
use rosenpass_to::{Beside};
assert_eq!((), Beside((), ()).condense());
assert_eq!(42, Beside(42, ()).condense());
assert_eq!(None, Beside(42, None).condense());
let ok_unit = Result::<(), ()>::Ok(());
assert_eq!(Ok(42), Beside(42, ok_unit).condense());
let err_unit = Result::<(), ()>::Err(());
assert_eq!(Err(()), Beside(42, err_unit).condense());
```
When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value:
```rust
use rosenpass_to::To;
use rosenpass_to::ops::try_copy_slice;;
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>();
assert_eq!(tmp, Some(*b"Hello World"));
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 2]>();
assert_eq!(tmp, None);
let tmp = try_copy_slice(b"Hello World").to_this(|| [0u8; 11].to_vec());
assert_eq!(tmp, Some(b"Hello World".to_vec()));
let tmp = try_copy_slice(b"Hello World").to_this(|| [0u8; 2].to_vec());
assert_eq!(tmp, None);
```
The same naturally also works for Results, but the example is a bit harder to motivate:
```rust
use std::result::Result;
use rosenpass_to::{to, To, with_destination};
#[derive(PartialEq, Eq, Debug, Default)]
struct InvalidFloat;
fn check_float(f: f64) -> Result<(), InvalidFloat> {
if f.is_normal() || f == 0.0 {
Ok(())
} else {
Err(InvalidFloat)
}
}
fn checked_add<'a>(src: f64) -> impl To<f64, Result<(), InvalidFloat>> + 'a {
with_destination(move |dst: &mut f64| {
check_float(src)?;
check_float(*dst)?;
*dst += src;
Ok(())
})
}
let mut tmp = 0.0;
checked_add(14.0).to(&mut tmp).unwrap();
checked_add(12.0).to(&mut tmp).unwrap();
assert_eq!(tmp, 26.0);
assert_eq!(Ok(78.0), checked_add(14.0).to_this(|| 64.0));
assert_eq!(Ok(14.0), checked_add(14.0).to_value());
assert_eq!(Ok(14.0), checked_add(14.0).collect());
assert_eq!(Err(InvalidFloat), checked_add(f64::NAN).to_this(|| 64.0));
assert_eq!(Err(InvalidFloat), checked_add(f64::INFINITY).to_value());
```
## Custom condensation
Condensation is implemented through a trait called CondenseBeside ([local](CondenseBeside) | [docs.rs](https://docs.rs/to/latest/rosenpass-to/trait.CondenseBeside.html)). You can implement it for your own types.
If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules.
```rust
use rosenpass_to::{To, with_destination, Beside, CondenseBeside};
use rosenpass_to::ops::copy_slice;
#[derive(PartialEq, Eq, Debug, Default)]
struct MyTuple<Left, Right>(Left, Right);
impl<Val, Right> CondenseBeside<Val> for MyTuple<(), Right> {
type Condensed = MyTuple<Val, Right>;
fn condense(self, val: Val) -> MyTuple<Val, Right> {
let MyTuple((), right) = self;
MyTuple(val, right)
}
}
fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a
where T: Copy, U: 'a {
with_destination(move |dst: &mut [T]| {
copy_slice(src).to(dst);
something
})
}
let tmp = Beside(42, MyTuple((), 23)).condense();
assert_eq!(tmp, MyTuple(42, 23));
let tmp = copy_slice_and_return_something(b"23", MyTuple((), 42)).collect::<[u8; 2]>();
assert_eq!(tmp, MyTuple(*b"23", 42));
```
## Manually implementing the To trait
Using `with_destination(...)` is convenient, but since it uses closures it results in an type that can not be written down, which is why the `-> impl To<...>` pattern is used everywhere in this tutorial.
Implementing the ToTrait manual is the right choice for library use cases.
```rust
use rosenpass_to::{to, To, with_destination};
struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T],
}
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len())
.then(|| dst.copy_from_slice(self.src))
}
}
fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T>
where T: Copy {
TryCopySliceSource { src }
}
let mut dst = try_copy_slice(b"Hello World").collect::<[u8; 11]>().unwrap();
assert_eq!(&dst[..], b"Hello World");
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
```
## Methods with destination
Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation.
```rust
use std::borrow::Borrow;
use rosenpass_to::{to, To, with_destination};
struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T],
}
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len())
.then(|| dst.copy_from_slice(self.src))
}
}
trait TryCopySliceExt<'a, T: Copy> {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>;
}
impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> {
TryCopySliceSource {
src: self.borrow()
}
}
}
let mut dst = b"Hello World".try_copy_slice().collect::<[u8; 11]>().unwrap();
assert_eq!(&dst[..], b"Hello World");
assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice()));
```

View File

@@ -1,14 +0,0 @@
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
// Core implementation
mod to;
pub use crate::to::{
beside::Beside, condense::CondenseBeside, dst_coercion::DstCoercion, to_function::to,
to_trait::To, with_destination::with_destination,
};
// Example use cases
pub mod ops;

View File

@@ -1,80 +0,0 @@
//! Functions with destination copying data between slices and arrays.
use crate::{with_destination, To};
/// Function with destination that copies data from
/// origin into the destination.
///
/// # Panics
///
/// This function will panic if the two slices have different lengths.
pub fn copy_slice<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a
where
T: Copy,
{
with_destination(|out: &mut [T]| out.copy_from_slice(origin))
}
/// Function with destination that copies all data from
/// origin into the destination.
///
/// Destination may be longer than origin.
///
/// # Panics
///
/// This function will panic if destination is shorter than origin.
pub fn copy_slice_least_src<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a
where
T: Copy,
{
with_destination(|out: &mut [T]| copy_slice(origin).to(&mut out[..origin.len()]))
}
/// Function with destination that copies as much data as possible from origin to the
/// destination.
///
/// Copies as much data as is present in the shorter slice.
pub fn copy_slice_least<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a
where
T: Copy,
{
with_destination(|out: &mut [T]| {
let len = std::cmp::min(origin.len(), out.len());
copy_slice(&origin[..len]).to(&mut out[..len])
})
}
/// Function with destination that attempts to copy data from origin into the destination.
///
/// Will return None if the slices are of different lengths.
pub fn try_copy_slice<'a, T>(origin: &'a [T]) -> impl To<[T], Option<()>> + 'a
where
T: Copy,
{
with_destination(|out: &mut [T]| {
(origin.len() == out.len()).then(|| copy_slice(origin).to(out))
})
}
/// Function with destination that tries to copy all data from
/// origin into the destination.
///
/// Destination may be longer than origin.
///
/// Will return None if the destination is shorter than origin.
pub fn try_copy_slice_least_src<'a, T>(origin: &'a [T]) -> impl To<[T], Option<()>> + 'a
where
T: Copy,
{
with_destination(|out: &mut [T]| {
(origin.len() <= out.len()).then(|| copy_slice_least_src(origin).to(out))
})
}
/// Function with destination that copies all data between two array references.
pub fn copy_array<'a, T, const N: usize>(origin: &'a [T; N]) -> impl To<[T; N], ()> + 'a
where
T: Copy,
{
with_destination(|out: &mut [T; N]| out.copy_from_slice(origin))
}

View File

@@ -1,45 +0,0 @@
use crate::CondenseBeside;
/// Named tuple holding the return value and the output from a function with destinations.
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
pub struct Beside<Val, Ret>(pub Val, pub Ret);
impl<Val, Ret> Beside<Val, Ret> {
pub fn dest(&self) -> &Val {
&self.0
}
pub fn ret(&self) -> &Ret {
&self.1
}
pub fn dest_mut(&mut self) -> &mut Val {
&mut self.0
}
pub fn ret_mut(&mut self) -> &mut Ret {
&mut self.1
}
/// Perform beside condensation. See [CondenseBeside]
pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
where
Ret: CondenseBeside<Val>,
{
self.1.condense(self.0)
}
}
impl<Val, Ret> From<(Val, Ret)> for Beside<Val, Ret> {
fn from(tuple: (Val, Ret)) -> Self {
let (val, ret) = tuple;
Self(val, ret)
}
}
impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
fn from(beside: Beside<Val, Ret>) -> Self {
let Beside(val, ret) = beside;
(val, ret)
}
}

View File

@@ -1,37 +0,0 @@
/// Beside condensation.
///
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
/// types.
///
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
/// condense trait.
pub trait CondenseBeside<Val> {
type Condensed;
fn condense(self, ret: Val) -> Self::Condensed;
}
impl<Val> CondenseBeside<Val> for () {
type Condensed = Val;
fn condense(self, ret: Val) -> Val {
ret
}
}
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
type Condensed = Result<Val, Error>;
fn condense(self, ret: Val) -> Result<Val, Error> {
self.map(|()| ret)
}
}
impl<Val> CondenseBeside<Val> for Option<()> {
type Condensed = Option<Val>;
fn condense(self, ret: Val) -> Option<Val> {
self.map(|()| ret)
}
}

View File

@@ -1,17 +0,0 @@
/// Helper performing explicit unsized coercion.
/// Used by the [to](crate::to()) function.
pub trait DstCoercion<Dst: ?Sized> {
fn coerce_dest(&mut self) -> &mut Dst;
}
impl<T: ?Sized> DstCoercion<T> for T {
fn coerce_dest(&mut self) -> &mut T {
self
}
}
impl<T, const N: usize> DstCoercion<[T]> for [T; N] {
fn coerce_dest(&mut self) -> &mut [T] {
self
}
}

View File

@@ -1,20 +0,0 @@
//! Module implementing the core function with destination functionality.
//!
//! Parameter naming scheme
//!
//! - `Src: impl To<Dst, Ret>` The value of an instance of something implementing the `To` trait
//! - `Dst: ?Sized`; (e.g. [u8]) The target to write to
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) A reference to the target to write to
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) Some value that
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
//! `Dst` (e.g. `[u8; 64]`).
//! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as `Dst`
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
pub mod beside;
pub mod condense;
pub mod dst_coercion;
pub mod to_function;
pub mod to_trait;
pub mod with_destination;

View File

@@ -1,14 +0,0 @@
use crate::{DstCoercion, To};
/// Alias for [To::to] moving the destination to the left.
///
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
/// the variable to assign to on the left and the generating function on the right.
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
where
Coercable: ?Sized + DstCoercion<Dst>,
Src: To<Dst, Ret>,
Dst: ?Sized,
{
src.to(dst.coerce_dest())
}

View File

@@ -1,96 +0,0 @@
use crate::{Beside, CondenseBeside};
use std::borrow::BorrowMut;
// The To trait is the core of the to crate; most functions with destinations will either return
// an object that is an instance of this trait or they will return `-> impl To<Destination,
// Return_value`.
//
// A quick way to implement a function with destination is to use the
// [with_destination(|param: &mut Type| ...)] higher order function.
pub trait To<Dst: ?Sized, Ret>: Sized {
fn to(self, out: &mut Dst) -> Ret;
/// Generate a destination on the fly with a lambda.
///
/// Calls the provided closure to create a value,
/// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
where
Val: BorrowMut<Dst>,
Fun: FnOnce() -> Val,
{
let mut val = fun();
let ret = self.to(val.borrow_mut());
Beside(val, ret)
}
/// Generate a destination on the fly using default.
///
/// Uses [Default] to create a value,
/// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
fn to_value_beside(self) -> Beside<Dst, Ret>
where
Dst: Sized + Default,
{
self.to_this_beside(|| Dst::default())
}
/// Generate a destination on the fly using default and a custom storage type.
///
/// Uses [Default] to create a value of the given type,
/// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return
/// value.
///
/// Using collect_beside with an explicit type instead of [Self::to_value_beside] is mainly useful
/// when the Destination is unsized.
///
/// This could be the case when the destination is an `[u8]` for instance.
fn collect_beside<Val>(self) -> Beside<Val, Ret>
where
Val: Default + BorrowMut<Dst>,
{
self.to_this_beside(|| Val::default())
}
/// Generate a destination on the fly with a lambda, condensing the destination and the
/// return value into one.
///
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
where
Ret: CondenseBeside<Val>,
Val: BorrowMut<Dst>,
Fun: FnOnce() -> Val,
{
self.to_this_beside(fun).condense()
}
/// Generate a destination on the fly using default, condensing the destination and the
/// return value into one.
///
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
where
Dst: Sized + Default,
Ret: CondenseBeside<Dst>,
{
self.to_value_beside().condense()
}
/// Generate a destination on the fly using default, condensing the destination and the
/// return value into one.
///
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
where
Val: Default + BorrowMut<Dst>,
Ret: CondenseBeside<Val>,
{
self.collect_beside::<Val>().condense()
}
}

View File

@@ -1,35 +0,0 @@
use crate::To;
use std::marker::PhantomData;
struct ToClosure<Dst, Ret, Fun>
where
Dst: ?Sized,
Fun: FnOnce(&mut Dst) -> Ret,
{
fun: Fun,
_val: PhantomData<Box<Dst>>,
}
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
where
Dst: ?Sized,
Fun: FnOnce(&mut Dst) -> Ret,
{
fn to(self, out: &mut Dst) -> Ret {
(self.fun)(out)
}
}
/// Used to create a function with destination.
///
/// See the tutorial in [readme.me]..
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
where
Dst: ?Sized,
Fun: FnOnce(&mut Dst) -> Ret,
{
ToClosure {
fun,
_val: PhantomData,
}
}

View File

@@ -1,16 +0,0 @@
[package]
name = "rosenpass-util"
version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal utilities"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = { workspace = true }
anyhow = { workspace = true }

View File

@@ -1,20 +0,0 @@
use base64::{
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
write::EncoderWriter as B64Writer,
};
use std::io::{Read, Write};
use base64::engine::general_purpose::GeneralPurpose as Base64Engine;
const B64ENGINE: Base64Engine = base64::engine::general_purpose::STANDARD;
pub fn fmt_b64<'a>(payload: &'a [u8]) -> B64Display<'a, 'static, Base64Engine> {
B64Display::<'a, 'static>::new(payload, &B64ENGINE)
}
pub fn b64_writer<W: Write>(w: W) -> B64Writer<'static, Base64Engine, W> {
B64Writer::new(w, &B64ENGINE)
}
pub fn b64_reader<R: Read>(r: R) -> B64Reader<'static, Base64Engine, R> {
B64Reader::new(r, &B64ENGINE)
}

View File

@@ -1,63 +0,0 @@
use anyhow::ensure;
use std::fs::File;
use std::io::Read;
use std::result::Result;
use std::{fs::OpenOptions, path::Path};
/// Open a file writable
pub fn fopen_w<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
Ok(OpenOptions::new()
.read(false)
.write(true)
.create(true)
.truncate(true)
.open(path)?)
}
/// Open a file readable
pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
Ok(OpenOptions::new()
.read(true)
.write(false)
.create(false)
.truncate(false)
.open(path)?)
}
pub trait ReadExactToEnd {
type Error;
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
}
impl<R: Read> ReadExactToEnd for R {
type Error = anyhow::Error;
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()> {
let mut dummy = [0u8; 8];
self.read_exact(buf)?;
ensure!(self.read(&mut dummy)? == 0, "File too long!");
Ok(())
}
}
pub trait LoadValue {
type Error;
fn load<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized;
}
pub trait LoadValueB64 {
type Error;
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized;
}
pub trait StoreValue {
type Error;
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

View File

@@ -1,15 +0,0 @@
pub fn mutating<T, F>(mut v: T, f: F) -> T
where
F: Fn(&mut T),
{
f(&mut v);
v
}
pub fn sideeffect<T, F>(v: T, f: F) -> T
where
F: Fn(&T),
{
f(&v);
v
}

View File

@@ -1,7 +0,0 @@
pub mod b64;
pub mod file;
pub mod functional;
pub mod mem;
pub mod ord;
pub mod result;
pub mod time;

View File

@@ -1,33 +0,0 @@
use std::borrow::{Borrow, BorrowMut};
use std::cmp::min;
/// Concatenate two byte arrays
// TODO: Zeroize result?
#[macro_export]
macro_rules! cat {
($len:expr; $($toks:expr),+) => {{
let mut buf = [0u8; $len];
let mut off = 0;
$({
let tok = $toks;
let tr = ::std::borrow::Borrow::<[u8]>::borrow(tok);
(&mut buf[off..(off + tr.len())]).copy_from_slice(tr);
off += tr.len();
})+
assert!(off == buf.len(), "Size mismatch in cat!()");
buf
}}
}
// TODO: consistent inout ordering
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
dst.borrow_mut().copy_from_slice(src.borrow());
}
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
let src = src.borrow();
let dst = dst.borrow_mut();
let len = min(src.len(), dst.len());
dst[..len].copy_from_slice(&src[..len]);
}

View File

@@ -1,8 +0,0 @@
// TODO remove this once std::cmp::max becomes const
pub const fn max_usize(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}

View File

@@ -1,7 +0,0 @@
/// Try block basically…returns a result and allows the use of the question mark operator inside
#[macro_export]
macro_rules! attempt {
($block:expr) => {
(|| -> ::anyhow::Result<_> { $block })()
};
}

View File

@@ -1,20 +0,0 @@
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct Timebase(Instant);
impl Default for Timebase {
fn default() -> Self {
Self(Instant::now())
}
}
impl Timebase {
pub fn now(&self) -> f64 {
self.0.elapsed().as_secs_f64()
}
pub fn dur(&self, t: f64) -> Duration {
Duration::from_secs_f64(t)
}
}