mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-05 20:40:02 -08:00
Documentation and unit tests for the rosenpass crate (#520)
This commit is contained in:
12
.github/workflows/qc.yaml
vendored
12
.github/workflows/qc.yaml
vendored
@@ -194,19 +194,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- run: rustup default nightly
|
||||||
- run: rustup component add llvm-tools-preview
|
- run: rustup component add llvm-tools-preview
|
||||||
- run: |
|
- run: |
|
||||||
cargo install cargo-llvm-cov || true
|
cargo install cargo-llvm-cov || true
|
||||||
cargo llvm-cov \
|
cargo install grcov || true
|
||||||
--workspace\
|
./coverage_report.sh
|
||||||
--all-features \
|
|
||||||
--lcov \
|
|
||||||
--output-path coverage.lcov
|
|
||||||
# If using tarapulin
|
# If using tarapulin
|
||||||
#- run: cargo install cargo-tarpaulin
|
#- run: cargo install cargo-tarpaulin
|
||||||
#- run: cargo tarpaulin --out Xml
|
#- run: cargo tarpaulin --out Xml
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
files: ./coverage.lcov
|
files: ./target/grcov/lcov
|
||||||
verbose: true
|
verbose: true
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
**Making a new Release of Rosenpass — Cooking Recipe**
|
# Contributing to Rosenpass
|
||||||
|
|
||||||
If you have to change a file, do what it takes to get the change as commit on the main branch, then **start from step 0**.
|
## Common operations
|
||||||
If any other issue occurs
|
|
||||||
|
|
||||||
0. Make sure you are in the root directory of the project
|
### Apply code formatting
|
||||||
- `cd "$(git rev-parse --show-toplevel)"`
|
|
||||||
1. Make sure you locally checked out the head of the main branch
|
|
||||||
- `git stash --include-untracked && git checkout main && git pull`
|
|
||||||
2. Make sure all tests pass
|
|
||||||
- `cargo test --workspace --all-features`
|
|
||||||
3. Make sure the current version in `rosenpass/Cargo.toml` matches that in the [last release on GitHub](https://github.com/rosenpass/rosenpass/releases)
|
|
||||||
- Only normal releases count, release candidates and draft releases can be ignored
|
|
||||||
4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)
|
|
||||||
- See `cargo release --help` for more information on the available release types
|
|
||||||
- Pick `rc` if in doubt
|
|
||||||
5. Try to release a new version
|
|
||||||
- `cargo release rc --package rosenpass`
|
|
||||||
- An issue was reported? Go fix it, start again with step 0!
|
|
||||||
6. Actually make the release
|
|
||||||
- `cargo release rc --package rosenpass --execute`
|
|
||||||
- Tentatively wait for any interactions, such as entering ssh keys etc.
|
|
||||||
- You may be asked for your ssh key multiple times!
|
|
||||||
|
|
||||||
**Frequently Asked Questions (FAQ)**
|
Format rust code:
|
||||||
|
|
||||||
- You have untracked files, which `cargo release` complains about?
|
```bash
|
||||||
- `git stash --include-untracked`
|
cargo fmt
|
||||||
- You cannot push to crates.io because you are not logged in?
|
```
|
||||||
- Follow the steps displayed in [`cargo login`](https://doc.rust-lang.org/cargo/commands/cargo-login.html)
|
|
||||||
- How is the release page added to [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) itself?
|
Format rust code in markdown files:
|
||||||
- Our CI Pipeline will create the release, once `cargo release` pushed the new version tag to the repo. The new release should pop up almost immediately in [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) after the [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml) pipeline started.
|
|
||||||
- No new release pops up in the `Release` sidebar element on the [main page](https://github.com/rosenpass/rosenpass)
|
```bash
|
||||||
- Did you push a `rc` release? This view only shows non-draft release, but `rc` releases are considered as draft. See [Releases](https://github.com/rosenpass/rosenpass/releases) page to see all (including draft!) releases.
|
./format_rust_code.sh --mode fix
|
||||||
- The release page was created on GitHub, but there are no assets/artifacts other than the source code tar ball/zip?
|
```
|
||||||
- The artifacts are generated and pushed automatically to the release, but this takes some time (a couple of minutes). You can check the respective CI pipeline: [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml), which should start immediately after `cargo release` pushed the new release tag to the repo. The release artifacts only are added later to the release, once all jobs in bespoke pipeline finished.
|
|
||||||
- How are the release artifacts generated, and what are they?
|
### Spawn a development environment with nix
|
||||||
- The release artifacts are built using one Nix derivation per platform, `nix build .#release-package`. It contains both statically linked versions of `rosenpass` itself and OCI container images.
|
|
||||||
|
```bash
|
||||||
|
nix develop .#fullEnv
|
||||||
|
```
|
||||||
|
|
||||||
|
You need to [install this nix package manager](https://wiki.archlinux.org/title/Nix) first.
|
||||||
|
|
||||||
|
### Run our test
|
||||||
|
|
||||||
|
Make sure to increase the stack size available; some of our cryptography operations require a lot of stack memory.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RUST_MIN_STACK=8388608 cargo test --workspace --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate coverage reports
|
||||||
|
|
||||||
|
Keep in mind that many of Rosenpass' tests are doctests, so to get an accurate read on our code coverage, you have to include doctests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./coverage_report.sh
|
||||||
|
```
|
||||||
|
|||||||
44
coverage_report.sh
Executable file
44
coverage_report.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
OUTPUT_DIR="target/grcov"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo >&2 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
exc() {
|
||||||
|
echo '$' "$@"
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
exc cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
local open="0"
|
||||||
|
if [[ "$1" == "--open" ]]; then
|
||||||
|
open="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exc cargo llvm-cov --all-features --workspace --doctests
|
||||||
|
|
||||||
|
exc rm -rf "${OUTPUT_DIR}"
|
||||||
|
exc mkdir -p "${OUTPUT_DIR}"
|
||||||
|
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
|
||||||
|
--binary-path ./target/llvm-cov-target/debug/deps \
|
||||||
|
--ignore-not-existing --ignore '../*' --ignore "/*" \
|
||||||
|
--excl-line '^\s*#\[(derive|repr)\(' \
|
||||||
|
-t lcov,html,markdown -o "${OUTPUT_DIR}"
|
||||||
|
|
||||||
|
if (( "${open}" == 1 )); then
|
||||||
|
xdg-open "${PWD}/${OUTPUT_DIR}/html/index.html"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log ""
|
||||||
|
log "Generated reports in \"${PWD}/${OUTPUT_DIR}\"."
|
||||||
|
log "Open \"${PWD}/${OUTPUT_DIR}/html/index.html\" to view HTML report."
|
||||||
|
log ""
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -121,6 +121,7 @@
|
|||||||
proverif-patched
|
proverif-patched
|
||||||
inputs.fenix.packages.${system}.complete.toolchain
|
inputs.fenix.packages.${system}.complete.toolchain
|
||||||
pkgs.cargo-llvm-cov
|
pkgs.cargo-llvm-cov
|
||||||
|
pkgs.grcov
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
devShells.coverage = pkgs.mkShell {
|
devShells.coverage = pkgs.mkShell {
|
||||||
@@ -128,6 +129,7 @@
|
|||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
inputs.fenix.packages.${system}.complete.toolchain
|
inputs.fenix.packages.${system}.complete.toolchain
|
||||||
pkgs.cargo-llvm-cov
|
pkgs.cargo-llvm-cov
|
||||||
|
pkgs.grcov
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ rosenpass help
|
|||||||
|
|
||||||
Follow [quick start instructions](https://rosenpass.eu/#start) to get a VPN up and running.
|
Follow [quick start instructions](https://rosenpass.eu/#start) to get a VPN up and running.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are generally welcome. Join our [Matrix Chat](https://matrix.to/#/#rosenpass:matrix.org) if you are looking for guidance on how to contribute or for people to collaborate with.
|
||||||
|
|
||||||
|
We also have a – as of now, very minimal – [contributors guide](CONTRIBUTING.md).
|
||||||
|
|
||||||
## Software architecture
|
## Software architecture
|
||||||
|
|
||||||
The [rosenpass tool](./src/) is written in Rust and uses liboqs[^liboqs]. The tool establishes a symmetric key and provides it to WireGuard. Since it supplies WireGuard with key through the PSK feature using Rosenpass+WireGuard is cryptographically no less secure than using WireGuard on its own ("hybrid security"). Rosenpass refreshes the symmetric key every two minutes.
|
The [rosenpass tool](./src/) is written in Rust and uses liboqs[^liboqs]. The tool establishes a symmetric key and provides it to WireGuard. Since it supplies WireGuard with key through the PSK feature using Rosenpass+WireGuard is cryptographically no less secure than using WireGuard on its own ("hybrid security"). Rosenpass refreshes the symmetric key every two minutes.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! The bulk code relating to the Rosenpass unix socket API
|
||||||
|
|
||||||
mod api_handler;
|
mod api_handler;
|
||||||
mod boilerplate;
|
mod boilerplate;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use heck::ToShoutySnakeCase;
|
|||||||
|
|
||||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||||
|
|
||||||
|
/// Recursively calculate a concrete hash value for an API message type
|
||||||
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
|
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
|
||||||
match values.split_first() {
|
match values.split_first() {
|
||||||
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
|
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
|
||||||
@@ -10,6 +11,7 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print a hash literal for pasting into the Rosenpass source code
|
||||||
fn print_literal(path: &[&str]) -> Result<()> {
|
fn print_literal(path: &[&str]) -> Result<()> {
|
||||||
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
let val = calculate_hash_value(HashDomain::zero(), path)?;
|
||||||
let (last, prefix) = path.split_last().context("developer error!")?;
|
let (last, prefix) = path.split_last().context("developer error!")?;
|
||||||
@@ -33,6 +35,8 @@ fn print_literal(path: &[&str]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tree of domain separators where each leaf represents
|
||||||
|
/// an API message ID
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Tree {
|
enum Tree {
|
||||||
Branch(String, Vec<Tree>),
|
Branch(String, Vec<Tree>),
|
||||||
@@ -68,6 +72,7 @@ impl Tree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for generating hash-based message IDs for the IPC API
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let tree = Tree::Branch(
|
let tree = Tree::Branch(
|
||||||
"Rosenpass IPC API".to_owned(),
|
"Rosenpass IPC API".to_owned(),
|
||||||
|
|||||||
@@ -1,13 +1,68 @@
|
|||||||
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
|
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
|
||||||
//! ensures their uniqueness
|
//! ensures their uniqueness.
|
||||||
|
//!
|
||||||
|
//! This ensures [domain separation](https://en.wikipedia.org/wiki/Domain_separation) is used
|
||||||
|
//! across the Rosenpass protocol.
|
||||||
|
//!
|
||||||
|
//! There is a chart containing all hash domains used in Rosenpass in the
|
||||||
|
//! [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository).
|
||||||
|
//!
|
||||||
|
//! # Tutorial
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use rosenpass::{hash_domain, hash_domain_ns};
|
||||||
|
//! use rosenpass::hash_domains::protocol;
|
||||||
|
//!
|
||||||
|
//! // Declaring a custom hash domain
|
||||||
|
//! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label");
|
||||||
|
//!
|
||||||
|
//! // Declaring a custom hashers
|
||||||
|
//! hash_domain_ns!(custom_domain, hashers, "hashers");
|
||||||
|
//! hash_domain_ns!(hashers, hasher1, "1");
|
||||||
|
//! hash_domain_ns!(hashers, hasher2, "2");
|
||||||
|
//!
|
||||||
|
//! // Declaring specific domain separators
|
||||||
|
//! hash_domain_ns!(custom_domain, domain_separators, "domain separators");
|
||||||
|
//! hash_domain!(domain_separators, sep1, "1");
|
||||||
|
//! hash_domain!(domain_separators, sep2, "2");
|
||||||
|
//!
|
||||||
|
//! // Generating values under hasher1 with both domain separators
|
||||||
|
//! let h1 = hasher1()?.mix(b"some data")?.dup();
|
||||||
|
//! let h1v1 = h1.mix(&sep1()?)?.mix(b"More data")?.into_value();
|
||||||
|
//! let h1v2 = h1.mix(&sep2()?)?.mix(b"More data")?.into_value();
|
||||||
|
//!
|
||||||
|
//! // Generating values under hasher2 with both domain separators
|
||||||
|
//! let h2 = hasher2()?.mix(b"some data")?.dup();
|
||||||
|
//! let h2v1 = h2.mix(&sep1()?)?.mix(b"More data")?.into_value();
|
||||||
|
//! let h2v2 = h2.mix(&sep2()?)?.mix(b"More data")?.into_value();
|
||||||
|
//!
|
||||||
|
//! // All of the domain separators are now different, random strings
|
||||||
|
//! let values = [h1v1, h1v2, h2v1, h2v2];
|
||||||
|
//! for i in 0..values.len() {
|
||||||
|
//! for j in (i+1)..values.len() {
|
||||||
|
//! assert_ne!(values[i], values[j]);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! Ok::<(), anyhow::Error>(())
|
||||||
|
//! ```
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
use rosenpass_ciphers::hash_domain::HashDomain;
|
||||||
|
|
||||||
|
/// Declare a hash function
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source file for details about how this is used concretely.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general
|
||||||
// TODO Use labels that can serve as identifiers
|
// TODO Use labels that can serve as identifiers
|
||||||
|
#[macro_export]
|
||||||
macro_rules! hash_domain_ns {
|
macro_rules! hash_domain_ns {
|
||||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
||||||
pub fn $name() -> Result<HashDomain> {
|
$(#[$($attrss)*])*
|
||||||
|
pub fn $name() -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> {
|
||||||
let t = $base()?;
|
let t = $base()?;
|
||||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||||
Ok(t)
|
Ok(t)
|
||||||
@@ -15,9 +70,18 @@ macro_rules! hash_domain_ns {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Declare a concrete hash value
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source file for details about how this is used concretely.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general
|
||||||
|
#[macro_export]
|
||||||
macro_rules! hash_domain {
|
macro_rules! hash_domain {
|
||||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
|
||||||
pub fn $name() -> Result<[u8; KEY_LEN]> {
|
$(#[$($attrss)*])*
|
||||||
|
pub fn $name() -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> {
|
||||||
let t = $base()?;
|
let t = $base()?;
|
||||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||||
Ok(t.into_value())
|
Ok(t.into_value())
|
||||||
@@ -25,24 +89,227 @@ macro_rules! hash_domain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The hash domain containing the protocol string.
|
||||||
|
///
|
||||||
|
/// This serves as a global [domain separator](https://en.wikipedia.org/wiki/Domain_separation)
|
||||||
|
/// used in various places in the rosenpass protocol.
|
||||||
|
///
|
||||||
|
/// This is generally used to create further hash-domains for specific purposes. See
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source file for details about how this is used concretely.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general
|
||||||
pub fn protocol() -> Result<HashDomain> {
|
pub fn protocol() -> Result<HashDomain> {
|
||||||
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
|
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
hash_domain_ns!(protocol, mac, "mac");
|
hash_domain_ns!(
|
||||||
hash_domain_ns!(protocol, cookie, "cookie");
|
/// Hash domain based on [protocol] for calculating [crate::msgs::Envelope::mac].
|
||||||
hash_domain_ns!(protocol, cookie_value, "cookie-value");
|
///
|
||||||
hash_domain_ns!(protocol, cookie_key, "cookie-key");
|
/// # Examples
|
||||||
hash_domain_ns!(protocol, peerid, "peer id");
|
///
|
||||||
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
|
/// See the source of [crate::msgs::Envelope::seal] and [crate::msgs::Envelope::check_seal]
|
||||||
hash_domain_ns!(protocol, ckinit, "chaining key init");
|
/// to figure out how this is concretely used.
|
||||||
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, mac, "mac");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source of [crate::msgs::Envelope::seal_cookie],
|
||||||
|
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
|
||||||
|
/// [crate::protocol::CryptoServer::handle_cookie_reply]
|
||||||
|
/// to figure out how this is concretely used.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, cookie, "cookie");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source of [crate::msgs::Envelope::seal_cookie],
|
||||||
|
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
|
||||||
|
/// [crate::protocol::CryptoServer::handle_cookie_reply]
|
||||||
|
/// to figure out how this is concretely used.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, cookie_value, "cookie-value");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source of [crate::msgs::Envelope::seal_cookie],
|
||||||
|
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
|
||||||
|
/// [crate::protocol::CryptoServer::handle_cookie_reply]
|
||||||
|
/// to figure out how this is concretely used.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, cookie_key, "cookie-key");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Hash domain based on [protocol] for calculating the peer id as transmitted (encrypted)
|
||||||
|
/// in [crate::msgs::InitHello::pidic].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the source of [crate::protocol::CryptoServer::pidm] and
|
||||||
|
/// [crate::protocol::Peer::pidt]
|
||||||
|
/// to figure out how this is concretely used.
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, peerid, "peer id");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Hash domain based on [protocol] for calculating the additional data
|
||||||
|
/// during [crate::msgs::Biscuit] encryption, storing the biscuit into
|
||||||
|
/// [crate::msgs::RespHello::biscuit].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the biscuit is used, it is best to read
|
||||||
|
/// the code of [crate::protocol::HandshakeState::store_biscuit] and
|
||||||
|
/// [crate::protocol::HandshakeState::load_biscuit]
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, biscuit_ad, "biscuit additional data");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// This hash domain begins our actual handshake procedure, initializing the
|
||||||
|
/// chaining key [crate::protocol::HandshakeState::ck].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the chaining key is used, study
|
||||||
|
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||||
|
/// and [crate::protocol::HandshakeState::mix].
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, ckinit, "chaining key init");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Namespace for chaining key usage domain separators.
|
||||||
|
///
|
||||||
|
/// During the execution of the Rosenpass protocol, we use the chaining key for multiple
|
||||||
|
/// purposes, so to make sure that we have unique value domains, we mix a domain separator
|
||||||
|
/// into the chaining key before using it for any particular purpose.
|
||||||
|
///
|
||||||
|
/// We could use the full domain separation strings, but using a hash value here is nice
|
||||||
|
/// because it does not lead to any constraints about domain separator format and we can
|
||||||
|
/// even allow third parties to define their own separators by claiming a namespace.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the chaining key is used, study
|
||||||
|
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||||
|
/// and [crate::protocol::HandshakeState::mix].
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
protocol, _ckextract, "chaining key extract");
|
||||||
|
|
||||||
hash_domain!(_ckextract, mix, "mix");
|
hash_domain!(
|
||||||
hash_domain!(_ckextract, hs_enc, "handshake encryption");
|
/// Used to mix in further values into the chaining key during the handshake.
|
||||||
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
|
///
|
||||||
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the chaining key is used, study
|
||||||
|
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||||
|
/// and [crate::protocol::HandshakeState::mix].
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_ckextract, mix, "mix");
|
||||||
|
hash_domain!(
|
||||||
|
/// Chaining key domain separator for generating encryption keys that can
|
||||||
|
/// encrypt parts of the handshake.
|
||||||
|
///
|
||||||
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Encryption of data during the handshake happens in
|
||||||
|
/// [crate::protocol::HandshakeState::encrypt_and_mix] and decryption happens in
|
||||||
|
/// [crate::protocol::HandshakeState::decrypt_and_mix]. See their source code
|
||||||
|
/// for details.
|
||||||
|
///
|
||||||
|
/// To understand how the chaining key is used, study
|
||||||
|
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||||
|
/// and [crate::protocol::HandshakeState::mix].
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_ckextract, hs_enc, "handshake encryption");
|
||||||
|
hash_domain!(
|
||||||
|
/// Chaining key domain separator for live data encryption.
|
||||||
|
/// Live data encryption is only used to send confirmation of handshake
|
||||||
|
/// done in [crate::msgs::EmptyData].
|
||||||
|
///
|
||||||
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This domain separator finds use in [crate::protocol::HandshakeState::enter_live].
|
||||||
|
///
|
||||||
|
/// To understand how the chaining key is used, study
|
||||||
|
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||||
|
/// and [crate::protocol::HandshakeState::mix].
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_ckextract, ini_enc, "initiator handshake encryption");
|
||||||
|
hash_domain!(
|
||||||
|
/// Chaining key domain separator for live data encryption.
|
||||||
|
/// Live data encryption is only used to send confirmation of handshake
|
||||||
|
/// done in [crate::msgs::EmptyData].
|
||||||
|
///
|
||||||
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This domain separator finds use in [crate::protocol::HandshakeState::enter_live].
|
||||||
|
/// Check out its source code!
|
||||||
|
///
|
||||||
|
/// To understand how the chaining key is used, study
|
||||||
|
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
|
||||||
|
/// and [crate::protocol::HandshakeState::mix].
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_ckextract, res_enc, "responder handshake encryption");
|
||||||
|
|
||||||
hash_domain_ns!(_ckextract, _user, "user");
|
hash_domain_ns!(
|
||||||
hash_domain_ns!(_user, _rp, "rosenpass.eu");
|
/// Chaining key domain separator for any usage specific purposes.
|
||||||
hash_domain!(_rp, osk, "wireguard psk");
|
///
|
||||||
|
/// We do recommend that third parties base their specific domain separators
|
||||||
|
/// on a internet domain and/or mix in much more specific information.
|
||||||
|
///
|
||||||
|
/// We only really use this to derive a output key for wireguard; see [osk].
|
||||||
|
///
|
||||||
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_ckextract, _user, "user");
|
||||||
|
hash_domain_ns!(
|
||||||
|
/// Chaining key domain separator for any rosenpass specific purposes.
|
||||||
|
///
|
||||||
|
/// We only really use this to derive a output key for wireguard; see [osk].
|
||||||
|
///
|
||||||
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_user, _rp, "rosenpass.eu");
|
||||||
|
hash_domain!(
|
||||||
|
/// Chaining key domain separator for deriving the key sent to WireGuard.
|
||||||
|
///
|
||||||
|
/// See [_ckextract].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This domain separator finds use in [crate::protocol::CryptoServer::osk].
|
||||||
|
/// Check out its source code!
|
||||||
|
///
|
||||||
|
/// See the [module](self) documentation on how to use the hash domains in general.
|
||||||
|
_rp, osk, "wireguard psk");
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
//! This is the central rosenpass crate implementing the rosenpass protocol.
|
||||||
|
//!
|
||||||
|
//! - [crate::app_server] contains the business logic of rosenpass, handling networking
|
||||||
|
//! - [crate::cli] contains the cli parsing logic and contains quite a bit of startup logic; the
|
||||||
|
//! main function quickly hands over to [crate::cli::CliArgs::run] which contains quite a bit
|
||||||
|
//! of our startup logic
|
||||||
|
//! - [crate::config] has the code to parse and generate configuration files
|
||||||
|
//! - [crate::hash_domains] lists the different hash function domains used in the Rosenpass
|
||||||
|
//! protocol
|
||||||
|
//! - [crate::msgs] provides declarations of the Rosenpass protocol network messages and facilities
|
||||||
|
//! to parse those messages through the [::zerocopy] crate
|
||||||
|
//! - [crate::protocol] this is where the bulk of our code lives; this module contains the actual
|
||||||
|
//! cryptographic protocol logic
|
||||||
|
//! - crate::api implements the Rosenpass unix socket API, if feature "experiment_api" is active
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod app_server;
|
pub mod app_server;
|
||||||
@@ -7,14 +22,25 @@ pub mod hash_domains;
|
|||||||
pub mod msgs;
|
pub mod msgs;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
|
||||||
|
/// Error types used in diverse places across Rosenpass
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum RosenpassError {
|
pub enum RosenpassError {
|
||||||
|
/// Usually indicates that parsing a struct through the
|
||||||
|
/// [::zerocopy] crate failed
|
||||||
#[error("buffer size mismatch")]
|
#[error("buffer size mismatch")]
|
||||||
BufferSizeMismatch,
|
BufferSizeMismatch,
|
||||||
|
/// Mostly raised by the `TryFrom<u8>` implementation for [crate::msgs::MsgType]
|
||||||
|
/// to indicate that a message type is not defined
|
||||||
#[error("invalid message type")]
|
#[error("invalid message type")]
|
||||||
InvalidMessageType(u8),
|
InvalidMessageType(
|
||||||
|
/// The message type that could not be parsed
|
||||||
|
u8,
|
||||||
|
),
|
||||||
|
/// Raised by the `TryFrom<RawMsgType>` (crate::api::RawMsgType) implementation for crate::api::RequestMsgType
|
||||||
|
/// and crate::api::RequestMsgType to indicate that a message type is not defined
|
||||||
#[error("invalid API message type")]
|
#[error("invalid API message type")]
|
||||||
InvalidApiMessageType(u128),
|
InvalidApiMessageType(
|
||||||
#[error("could not parse API message")]
|
/// The message type that could not be parsed
|
||||||
InvalidApiMessage,
|
u128,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! For the main function
|
||||||
|
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap_mangen::roff::{roman, Roff};
|
use clap_mangen::roff::{roman, Roff};
|
||||||
@@ -5,6 +7,7 @@ use log::error;
|
|||||||
use rosenpass::cli::CliArgs;
|
use rosenpass::cli::CliArgs;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
/// Printing custom man sections when generating the man page
|
||||||
fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File) {
|
fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File) {
|
||||||
let mut roff = Roff::default();
|
let mut roff = Roff::default();
|
||||||
roff.control("SH", [section]);
|
roff.control("SH", [section]);
|
||||||
@@ -13,6 +16,8 @@ fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Catches errors, prints them through the logger, then exits
|
/// Catches errors, prints them through the logger, then exits
|
||||||
|
///
|
||||||
|
/// The bulk of the command line logic is handled inside [crate::cli::CliArgs::run].
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// parse CLI arguments
|
// parse CLI arguments
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
@@ -81,21 +86,27 @@ pub fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Custom main page section: Exit Status
|
||||||
static EXIT_STATUS_MAN: &str = r"
|
static EXIT_STATUS_MAN: &str = r"
|
||||||
The rosenpass utility exits 0 on success, and >0 if an error occurs.";
|
The rosenpass utility exits 0 on success, and >0 if an error occurs.";
|
||||||
|
|
||||||
|
/// Custom main page section: See also.
|
||||||
static SEE_ALSO_MAN: &str = r"
|
static SEE_ALSO_MAN: &str = r"
|
||||||
rp(1), wg(1)
|
rp(1), wg(1)
|
||||||
|
|
||||||
Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt, Rosenpass, https://rosenpass.eu/whitepaper.pdf, 2023.";
|
Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt, Rosenpass, https://rosenpass.eu/whitepaper.pdf, 2023.";
|
||||||
|
|
||||||
|
/// Custom main page section: Standards.
|
||||||
static STANDARDS_MAN: &str = r"
|
static STANDARDS_MAN: &str = r"
|
||||||
This tool is the reference implementation of the Rosenpass protocol, as
|
This tool is the reference implementation of the Rosenpass protocol, as
|
||||||
specified within the whitepaper referenced above.";
|
specified within the whitepaper referenced above.";
|
||||||
|
|
||||||
|
/// Custom main page section: Authors.
|
||||||
static AUTHORS_MAN: &str = r"
|
static AUTHORS_MAN: &str = r"
|
||||||
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske, Marei
|
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske, Marei
|
||||||
Peischl, Stephan Ajuvo, and Lisa Schmidt.";
|
Peischl, Stephan Ajuvo, and Lisa Schmidt.";
|
||||||
|
|
||||||
|
/// Custom main page section: Bugs.
|
||||||
static BUGS_MAN: &str = r"
|
static BUGS_MAN: &str = r"
|
||||||
The bugs are tracked at https://github.com/rosenpass/rosenpass/issues.";
|
The bugs are tracked at https://github.com/rosenpass/rosenpass/issues.";
|
||||||
|
|||||||
@@ -9,21 +9,73 @@
|
|||||||
//! To achieve this we utilize the zerocopy library.
|
//! To achieve this we utilize the zerocopy library.
|
||||||
//!
|
//!
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::u8;
|
||||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||||
|
|
||||||
use super::RosenpassError;
|
use super::RosenpassError;
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||||
pub const MSG_SIZE_LEN: usize = 1;
|
|
||||||
pub const RESERVED_LEN: usize = 3;
|
/// Length of a session ID such as [InitHello::sidi]
|
||||||
|
pub const SESSION_ID_LEN: usize = 4;
|
||||||
|
/// Length of a biscuit ID; i.e. size of the value in [Biscuit::biscuit_no]
|
||||||
|
pub const BISCUIT_ID_LEN: usize = 12;
|
||||||
|
|
||||||
|
/// TODO: Unused, remove!
|
||||||
|
pub const WIRE_ENVELOPE_LEN: usize = 1 + 3 + 16 + 16; // TODO verify this
|
||||||
|
|
||||||
|
/// Size required to fit any message in binary form
|
||||||
|
pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
|
||||||
|
|
||||||
|
/// length in bytes of an unencrypted Biscuit (plain text)
|
||||||
|
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
||||||
|
|
||||||
|
/// Length in bytes of an encrypted Biscuit (cipher text)
|
||||||
|
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
|
||||||
|
|
||||||
|
/// Size of the field [Envelope::mac]
|
||||||
pub const MAC_SIZE: usize = 16;
|
pub const MAC_SIZE: usize = 16;
|
||||||
pub const COOKIE_SIZE: usize = 16;
|
/// Size of the field [Envelope::cookie]
|
||||||
pub const SID_LEN: usize = 4;
|
pub const COOKIE_SIZE: usize = MAC_SIZE;
|
||||||
|
|
||||||
pub type MsgEnvelopeMac = [u8; 16];
|
/// Type of the mac field in [Envelope]
|
||||||
pub type MsgEnvelopeCookie = MsgEnvelopeMac;
|
pub type MsgEnvelopeMac = [u8; MAC_SIZE];
|
||||||
|
|
||||||
|
/// Type of the cookie field in [Envelope]
|
||||||
|
pub type MsgEnvelopeCookie = [u8; COOKIE_SIZE];
|
||||||
|
|
||||||
|
/// Header and footer included in all our packages,
|
||||||
|
/// including a type field.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::msgs::{Envelope, InitHello};
|
||||||
|
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||||
|
/// use memoffset::offset_of;
|
||||||
|
///
|
||||||
|
/// // Zero-initialization
|
||||||
|
/// let mut ih = Envelope::<InitHello>::new_zeroed();
|
||||||
|
///
|
||||||
|
/// // Edit fields normally
|
||||||
|
/// ih.mac[0] = 1;
|
||||||
|
///
|
||||||
|
/// // Edit as binary
|
||||||
|
/// ih.as_bytes_mut()[offset_of!(Envelope<InitHello>, msg_type)] = 23;
|
||||||
|
/// assert_eq!(ih.msg_type, 23);;
|
||||||
|
///
|
||||||
|
/// // Conversion to bytes
|
||||||
|
/// let mut ih2 = ih.as_bytes().to_owned();
|
||||||
|
///
|
||||||
|
/// // Setting msg_type field, again
|
||||||
|
/// ih2[0] = 42;
|
||||||
|
///
|
||||||
|
/// // Zerocopy parsing
|
||||||
|
/// let ih3 = Ref::<&mut [u8], Envelope<InitHello>>::new(&mut ih2).unwrap();
|
||||||
|
/// assert_ne!(ih.as_bytes(), ih3.as_bytes());
|
||||||
|
/// assert_eq!(ih3.msg_type, 42);
|
||||||
|
/// ```
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes, Clone)]
|
#[derive(AsBytes, FromBytes, FromZeroes, Clone)]
|
||||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||||
@@ -40,6 +92,40 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
|||||||
pub cookie: MsgEnvelopeCookie,
|
pub cookie: MsgEnvelopeCookie,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the first message sent by the initiator to the responder
|
||||||
|
/// during the execution of the Rosenpass protocol.
|
||||||
|
///
|
||||||
|
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Check out the code of [crate::protocol::CryptoServer::handle_initiation] (generation on
|
||||||
|
/// iniatiator side) and [crate::protocol::CryptoServer::handle_init_hello] (processing on
|
||||||
|
/// responder side) to understand how this is used.
|
||||||
|
///
|
||||||
|
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::msgs::{Envelope, InitHello};
|
||||||
|
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||||
|
/// use memoffset::span_of;
|
||||||
|
///
|
||||||
|
/// // Zero initialization
|
||||||
|
/// let mut ih = Envelope::<InitHello>::new_zeroed();
|
||||||
|
///
|
||||||
|
/// // Conversion to byte representation
|
||||||
|
/// let ih = ih.as_bytes_mut();
|
||||||
|
///
|
||||||
|
/// // Set value on byte representation
|
||||||
|
/// ih[span_of!(Envelope<InitHello>, payload)][span_of!(InitHello, sidi)]
|
||||||
|
/// .copy_from_slice(&[1,2,3,4]);
|
||||||
|
///
|
||||||
|
/// // Conversion from bytes
|
||||||
|
/// let ih = Ref::<&mut [u8], Envelope<InitHello>>::new(ih).unwrap();
|
||||||
|
///
|
||||||
|
/// // Check that write above on byte representation was effective
|
||||||
|
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
|
||||||
|
/// ```
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||||
pub struct InitHello {
|
pub struct InitHello {
|
||||||
@@ -55,6 +141,40 @@ pub struct InitHello {
|
|||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; aead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the second message sent by the responder to the initiator
|
||||||
|
/// during the execution of the Rosenpass protocol in response to [InitHello].
|
||||||
|
///
|
||||||
|
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Check out the code of [crate::protocol::CryptoServer::handle_init_hello] (generation on
|
||||||
|
/// responder side) and [crate::protocol::CryptoServer::handle_resp_hello] (processing on
|
||||||
|
/// initiator side) to understand how this is used.
|
||||||
|
///
|
||||||
|
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::msgs::{Envelope, RespHello};
|
||||||
|
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||||
|
/// use memoffset::span_of;
|
||||||
|
///
|
||||||
|
/// // Zero initialization
|
||||||
|
/// let mut ih = Envelope::<RespHello>::new_zeroed();
|
||||||
|
///
|
||||||
|
/// // Conversion to byte representation
|
||||||
|
/// let ih = ih.as_bytes_mut();
|
||||||
|
///
|
||||||
|
/// // Set value on byte representation
|
||||||
|
/// ih[span_of!(Envelope<RespHello>, payload)][span_of!(RespHello, sidi)]
|
||||||
|
/// .copy_from_slice(&[1,2,3,4]);
|
||||||
|
///
|
||||||
|
/// // Conversion from bytes
|
||||||
|
/// let ih = Ref::<&mut [u8], Envelope<RespHello>>::new(ih).unwrap();
|
||||||
|
///
|
||||||
|
/// // Check that write above on byte representation was effective
|
||||||
|
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
|
||||||
|
/// ```
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||||
pub struct RespHello {
|
pub struct RespHello {
|
||||||
@@ -72,6 +192,40 @@ pub struct RespHello {
|
|||||||
pub biscuit: [u8; BISCUIT_CT_LEN],
|
pub biscuit: [u8; BISCUIT_CT_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the third message sent by the initiator to the responder
|
||||||
|
/// during the execution of the Rosenpass protocol in response to [RespHello].
|
||||||
|
///
|
||||||
|
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Check out the code of [crate::protocol::CryptoServer::handle_resp_hello] (generation on
|
||||||
|
/// initiator side) and [crate::protocol::CryptoServer::handle_init_conf] (processing on
|
||||||
|
/// responder side) to understand how this is used.
|
||||||
|
///
|
||||||
|
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::msgs::{Envelope, InitConf};
|
||||||
|
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||||
|
/// use memoffset::span_of;
|
||||||
|
///
|
||||||
|
/// // Zero initialization
|
||||||
|
/// let mut ih = Envelope::<InitConf>::new_zeroed();
|
||||||
|
///
|
||||||
|
/// // Conversion to byte representation
|
||||||
|
/// let ih = ih.as_bytes_mut();
|
||||||
|
///
|
||||||
|
/// // Set value on byte representation
|
||||||
|
/// ih[span_of!(Envelope<InitConf>, payload)][span_of!(InitConf, sidi)]
|
||||||
|
/// .copy_from_slice(&[1,2,3,4]);
|
||||||
|
///
|
||||||
|
/// // Conversion from bytes
|
||||||
|
/// let ih = Ref::<&mut [u8], Envelope<InitConf>>::new(ih).unwrap();
|
||||||
|
///
|
||||||
|
/// // Check that write above on byte representation was effective
|
||||||
|
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
|
||||||
|
/// ```
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
|
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
|
||||||
pub struct InitConf {
|
pub struct InitConf {
|
||||||
@@ -85,6 +239,51 @@ pub struct InitConf {
|
|||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; aead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the fourth message sent by the initiator to the responder
|
||||||
|
/// during the execution of the Rosenpass protocol in response to [RespHello].
|
||||||
|
///
|
||||||
|
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
|
||||||
|
///
|
||||||
|
/// This message does not serve a cryptographic purpose; it just tells the initiator
|
||||||
|
/// to stop package retransmission.
|
||||||
|
///
|
||||||
|
/// This message should really be called `RespConf`, but when we wrote the protocol,
|
||||||
|
/// we initially designed the protocol we still though Rosenpass itself should do
|
||||||
|
/// payload transmission at some point so `EmptyData` could have served as a more generic
|
||||||
|
/// mechanism.
|
||||||
|
///
|
||||||
|
/// We might add payload transmission in the future again, but we will treat
|
||||||
|
/// it as a protocol extension if we do.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Check out the code of [crate::protocol::CryptoServer::handle_init_conf] (generation on
|
||||||
|
/// responder side) and [crate::protocol::CryptoServer::handle_resp_conf] (processing on
|
||||||
|
/// initiator side) to understand how this is used.
|
||||||
|
///
|
||||||
|
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::msgs::{Envelope, EmptyData};
|
||||||
|
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
|
||||||
|
/// use memoffset::span_of;
|
||||||
|
///
|
||||||
|
/// // Zero initialization
|
||||||
|
/// let mut ih = Envelope::<EmptyData>::new_zeroed();
|
||||||
|
///
|
||||||
|
/// // Conversion to byte representation
|
||||||
|
/// let ih = ih.as_bytes_mut();
|
||||||
|
///
|
||||||
|
/// // Set value on byte representation
|
||||||
|
/// ih[span_of!(Envelope<EmptyData>, payload)][span_of!(EmptyData, sid)]
|
||||||
|
/// .copy_from_slice(&[1,2,3,4]);
|
||||||
|
///
|
||||||
|
/// // Conversion from bytes
|
||||||
|
/// let ih = Ref::<&mut [u8], Envelope<EmptyData>>::new(ih).unwrap();
|
||||||
|
///
|
||||||
|
/// // Check that write above on byte representation was effective
|
||||||
|
/// assert_eq!(ih.payload.sid, [1,2,3,4]);
|
||||||
|
/// ```
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Copy)]
|
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Copy)]
|
||||||
pub struct EmptyData {
|
pub struct EmptyData {
|
||||||
@@ -96,6 +295,22 @@ pub struct EmptyData {
|
|||||||
pub auth: [u8; aead::TAG_LEN],
|
pub auth: [u8; aead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cookie encrypted and sent to the initiator by the responder in [RespHello]
|
||||||
|
/// and returned by the initiator in [InitConf].
|
||||||
|
///
|
||||||
|
/// The encryption key is randomly chosen by the responder and frequently regenerated.
|
||||||
|
/// Using this biscuit value in the protocol allows us to make sure that the responder
|
||||||
|
/// is mostly stateless until full initiator authentication is achieved, which is needed
|
||||||
|
/// to prevent denial of service attacks. See the [whitepaper](https://rosenpass.eu/whitepaper.pdf)
|
||||||
|
/// ([/papers/whitepaper.md] in this repository).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the biscuit is used, it is best to read
|
||||||
|
/// the code of [crate::protocol::HandshakeState::store_biscuit] and
|
||||||
|
/// [crate::protocol::HandshakeState::load_biscuit]
|
||||||
|
///
|
||||||
|
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||||
pub struct Biscuit {
|
pub struct Biscuit {
|
||||||
@@ -107,12 +322,20 @@ pub struct Biscuit {
|
|||||||
pub ck: [u8; KEY_LEN],
|
pub ck: [u8; KEY_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
/// Specialized message for use in the cookie mechanism.
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
///
|
||||||
pub struct DataMsg {
|
/// See the [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository) for details.
|
||||||
pub dummy: [u8; 4],
|
///
|
||||||
}
|
/// Generally used together with [CookieReply] which brings this up to the size
|
||||||
|
/// of [InitHello] to avoid amplification Denial of Service attacks.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the biscuit is used, it is best to read
|
||||||
|
/// the code of [crate::protocol::CryptoServer::handle_cookie_reply] and
|
||||||
|
/// [crate::protocol::CryptoServer::handle_msg_under_load].
|
||||||
|
///
|
||||||
|
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||||
pub struct CookieReplyInner {
|
pub struct CookieReplyInner {
|
||||||
@@ -126,6 +349,20 @@ pub struct CookieReplyInner {
|
|||||||
pub cookie_encrypted: [u8; xaead::NONCE_LEN + COOKIE_SIZE + xaead::TAG_LEN],
|
pub cookie_encrypted: [u8; xaead::NONCE_LEN + COOKIE_SIZE + xaead::TAG_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specialized message for use in the cookie mechanism.
|
||||||
|
///
|
||||||
|
/// This just brings [CookieReplyInner] up to the size
|
||||||
|
/// of [InitHello] to avoid amplification Denial of Service attacks.
|
||||||
|
///
|
||||||
|
/// See the [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository) for details.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// To understand how the biscuit is used, it is best to read
|
||||||
|
/// the code of [crate::protocol::CryptoServer::handle_cookie_reply] and
|
||||||
|
/// [crate::protocol::CryptoServer::handle_msg_under_load].
|
||||||
|
///
|
||||||
|
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||||
pub struct CookieReply {
|
pub struct CookieReply {
|
||||||
@@ -133,33 +370,46 @@ pub struct CookieReply {
|
|||||||
pub padding: [u8; size_of::<Envelope<InitHello>>() - size_of::<CookieReplyInner>()],
|
pub padding: [u8; size_of::<Envelope<InitHello>>() - size_of::<CookieReplyInner>()],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traits /////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
pub trait WireMsg: std::fmt::Debug {
|
|
||||||
const MSG_TYPE: MsgType;
|
|
||||||
const MSG_TYPE_U8: u8 = Self::MSG_TYPE as u8;
|
|
||||||
const BYTES: usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants //////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
pub const SESSION_ID_LEN: usize = 4;
|
|
||||||
pub const BISCUIT_ID_LEN: usize = 12;
|
|
||||||
|
|
||||||
pub const WIRE_ENVELOPE_LEN: usize = 1 + 3 + 16 + 16; // TODO verify this
|
|
||||||
|
|
||||||
/// Size required to fit any message in binary form
|
|
||||||
pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
|
|
||||||
|
|
||||||
/// Recognized message types
|
/// Recognized message types
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass::msgs::MsgType;
|
||||||
|
/// use rosenpass::msgs::MsgType as M;
|
||||||
|
///
|
||||||
|
/// let values = [M::InitHello, M::RespHello, M::InitConf, M::EmptyData, M::CookieReply];
|
||||||
|
/// let values_u8 = values.map(|v| -> u8 { v.into() });
|
||||||
|
///
|
||||||
|
/// // Can be converted to and from u8 using [::std::convert::Into] or [::std::convert::From]
|
||||||
|
/// for v in values.iter().copied() {
|
||||||
|
/// let v_u8 : u8 = v.into();
|
||||||
|
/// let v2 : MsgType = v_u8.try_into()?;
|
||||||
|
/// assert_eq!(v, v2);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Converting an unsupported type produces an error
|
||||||
|
/// let invalid_values = (u8::MIN..=u8::MAX)
|
||||||
|
/// .filter(|v| !values_u8.contains(v));
|
||||||
|
/// for v in invalid_values {
|
||||||
|
/// let res : Result<MsgType, _> = v.try_into();
|
||||||
|
/// assert!(res.is_err());
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
pub enum MsgType {
|
pub enum MsgType {
|
||||||
|
/// MsgType for [InitHello]
|
||||||
InitHello = 0x81,
|
InitHello = 0x81,
|
||||||
|
/// MsgType for [RespHello]
|
||||||
RespHello = 0x82,
|
RespHello = 0x82,
|
||||||
|
/// MsgType for [InitConf]
|
||||||
InitConf = 0x83,
|
InitConf = 0x83,
|
||||||
|
/// MsgType for [EmptyData]
|
||||||
EmptyData = 0x84,
|
EmptyData = 0x84,
|
||||||
DataMsg = 0x85,
|
/// MsgType for [CookieReply]
|
||||||
CookieReply = 0x86,
|
CookieReply = 0x86,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +422,6 @@ impl TryFrom<u8> for MsgType {
|
|||||||
0x82 => MsgType::RespHello,
|
0x82 => MsgType::RespHello,
|
||||||
0x83 => MsgType::InitConf,
|
0x83 => MsgType::InitConf,
|
||||||
0x84 => MsgType::EmptyData,
|
0x84 => MsgType::EmptyData,
|
||||||
0x85 => MsgType::DataMsg,
|
|
||||||
0x86 => MsgType::CookieReply,
|
0x86 => MsgType::CookieReply,
|
||||||
_ => return Err(RosenpassError::InvalidMessageType(value)),
|
_ => return Err(RosenpassError::InvalidMessageType(value)),
|
||||||
})
|
})
|
||||||
@@ -185,12 +434,6 @@ impl From<MsgType> for u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// length in bytes of an unencrypted Biscuit (plain text)
|
|
||||||
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
|
|
||||||
|
|
||||||
/// Length in bytes of an encrypted Biscuit (cipher text)
|
|
||||||
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_constants {
|
mod test_constants {
|
||||||
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
|
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
|
||||||
|
|||||||
@@ -1,3 +1,75 @@
|
|||||||
|
//! Module containing the cryptographic protocol implementation
|
||||||
|
//!
|
||||||
|
//! # Overview
|
||||||
|
//!
|
||||||
|
//! The most important types in this module probably are [PollResult]
|
||||||
|
//! & [CryptoServer]. Once a [CryptoServer] is created, the server is
|
||||||
|
//! provided with new messages via the [CryptoServer::handle_msg] method.
|
||||||
|
//! The [CryptoServer::poll] method can be used to let the server work, which
|
||||||
|
//! will eventually yield a [PollResult]. Said [PollResult] contains
|
||||||
|
//! prescriptive activities to be carried out. [CryptoServer::osk] can than
|
||||||
|
//! be used to extract the shared key for two peers, once a key-exchange was
|
||||||
|
//! successful.
|
||||||
|
//!
|
||||||
|
//! TODO explain briefly the role of epki
|
||||||
|
//!
|
||||||
|
//! # Example Handshake
|
||||||
|
//!
|
||||||
|
//! This example illustrates a minimal setup for a key-exchange between two
|
||||||
|
//! [CryptoServer].
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use std::ops::DerefMut;
|
||||||
|
//! use rosenpass_secret_memory::policy::*;
|
||||||
|
//! use rosenpass_cipher_traits::Kem;
|
||||||
|
//! use rosenpass_ciphers::kem::StaticKem;
|
||||||
|
//! use rosenpass::{
|
||||||
|
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
|
||||||
|
//! };
|
||||||
|
//! # fn main() -> anyhow::Result<()> {
|
||||||
|
//! // Set security policy for storing secrets
|
||||||
|
//!
|
||||||
|
//! secret_policy_try_use_memfd_secrets();
|
||||||
|
//!
|
||||||
|
//! // initialize secret and public key for peer a ...
|
||||||
|
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||||
|
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||||
|
//!
|
||||||
|
//! // ... and for peer b
|
||||||
|
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||||
|
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||||
|
//!
|
||||||
|
//! // initialize server and a pre-shared key
|
||||||
|
//! let psk = SymKey::random();
|
||||||
|
//! let mut a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
||||||
|
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
||||||
|
//!
|
||||||
|
//! // introduce peers to each other
|
||||||
|
//! a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
||||||
|
//! b.add_peer(Some(psk), peer_a_pk)?;
|
||||||
|
//!
|
||||||
|
//! // declare buffers for message exchange
|
||||||
|
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
|
||||||
|
//!
|
||||||
|
//! // let a initiate a handshake
|
||||||
|
//! let mut maybe_len = Some(a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice())?);
|
||||||
|
//!
|
||||||
|
//! // let a and b communicate
|
||||||
|
//! while let Some(len) = maybe_len {
|
||||||
|
//! maybe_len = b.handle_msg(&a_buf[..len], &mut b_buf[..])?.resp;
|
||||||
|
//! std::mem::swap(&mut a, &mut b);
|
||||||
|
//! std::mem::swap(&mut a_buf, &mut b_buf);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // all done! Extract the shared keys and ensure they are identical
|
||||||
|
//! let a_key = a.osk(PeerPtr(0))?;
|
||||||
|
//! let b_key = b.osk(PeerPtr(0))?;
|
||||||
|
//! assert_eq!(a_key.secret(), b_key.secret(),
|
||||||
|
//! "the key exchanged failed to establish a shared secret");
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod build_crypto_server;
|
mod build_crypto_server;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
|||||||
@@ -1,75 +1,3 @@
|
|||||||
//! Module containing the cryptographic protocol implementation
|
|
||||||
//!
|
|
||||||
//! # Overview
|
|
||||||
//!
|
|
||||||
//! The most important types in this module probably are [PollResult]
|
|
||||||
//! & [CryptoServer]. Once a [CryptoServer] is created, the server is
|
|
||||||
//! provided with new messages via the [CryptoServer::handle_msg] method.
|
|
||||||
//! The [CryptoServer::poll] method can be used to let the server work, which
|
|
||||||
//! will eventually yield a [PollResult]. Said [PollResult] contains
|
|
||||||
//! prescriptive activities to be carried out. [CryptoServer::osk] can than
|
|
||||||
//! be used to extract the shared key for two peers, once a key-exchange was
|
|
||||||
//! successful.
|
|
||||||
//!
|
|
||||||
//! TODO explain briefly the role of epki
|
|
||||||
//!
|
|
||||||
//! # Example Handshake
|
|
||||||
//!
|
|
||||||
//! This example illustrates a minimal setup for a key-exchange between two
|
|
||||||
//! [CryptoServer].
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! use std::ops::DerefMut;
|
|
||||||
//! use rosenpass_secret_memory::policy::*;
|
|
||||||
//! use rosenpass_cipher_traits::Kem;
|
|
||||||
//! use rosenpass_ciphers::kem::StaticKem;
|
|
||||||
//! use rosenpass::{
|
|
||||||
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
|
|
||||||
//! };
|
|
||||||
//! # fn main() -> anyhow::Result<()> {
|
|
||||||
//! // Set security policy for storing secrets
|
|
||||||
//!
|
|
||||||
//! secret_policy_try_use_memfd_secrets();
|
|
||||||
//!
|
|
||||||
//! // initialize secret and public key for peer a ...
|
|
||||||
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
|
||||||
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
|
||||||
//!
|
|
||||||
//! // ... and for peer b
|
|
||||||
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
|
||||||
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
|
||||||
//!
|
|
||||||
//! // initialize server and a pre-shared key
|
|
||||||
//! let psk = SymKey::random();
|
|
||||||
//! let mut a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
|
||||||
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
|
||||||
//!
|
|
||||||
//! // introduce peers to each other
|
|
||||||
//! a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
|
||||||
//! b.add_peer(Some(psk), peer_a_pk)?;
|
|
||||||
//!
|
|
||||||
//! // declare buffers for message exchange
|
|
||||||
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
|
|
||||||
//!
|
|
||||||
//! // let a initiate a handshake
|
|
||||||
//! let mut maybe_len = Some(a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice())?);
|
|
||||||
//!
|
|
||||||
//! // let a and b communicate
|
|
||||||
//! while let Some(len) = maybe_len {
|
|
||||||
//! maybe_len = b.handle_msg(&a_buf[..len], &mut b_buf[..])?.resp;
|
|
||||||
//! std::mem::swap(&mut a, &mut b);
|
|
||||||
//! std::mem::swap(&mut a_buf, &mut b_buf);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! // all done! Extract the shared keys and ensure they are identical
|
|
||||||
//! let a_key = a.osk(PeerPtr(0))?;
|
|
||||||
//! let b_key = b.osk(PeerPtr(0))?;
|
|
||||||
//! assert_eq!(a_key.secret(), b_key.secret(),
|
|
||||||
//! "the key exchanged failed to establish a shared secret");
|
|
||||||
//! # Ok(())
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -1358,7 +1286,6 @@ impl CryptoServer {
|
|||||||
|
|
||||||
self.handle_resp_conf(&msg_in.payload)?
|
self.handle_resp_conf(&msg_in.payload)?
|
||||||
}
|
}
|
||||||
Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"),
|
|
||||||
Ok(MsgType::CookieReply) => {
|
Ok(MsgType::CookieReply) => {
|
||||||
let msg_in: Ref<&[u8], CookieReply> =
|
let msg_in: Ref<&[u8], CookieReply> =
|
||||||
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
|
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
|
||||||
|
|||||||
99
rosenpass/tests/main-fn-generates-manpages.rs
Normal file
99
rosenpass/tests/main-fn-generates-manpages.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use rosenpass_util::functional::ApplyExt;
|
||||||
|
|
||||||
|
fn expect_section(manpage: &str, section: &str) -> anyhow::Result<()> {
|
||||||
|
anyhow::ensure!(manpage.lines().any(|line| { line.starts_with(section) }));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_sections(manpage: &str, sections: &[&str]) -> anyhow::Result<()> {
|
||||||
|
for section in sections.iter().copied() {
|
||||||
|
expect_section(manpage, section)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_contents(manpage: &str, patterns: &[&str]) -> anyhow::Result<()> {
|
||||||
|
for pat in patterns.iter().copied() {
|
||||||
|
anyhow::ensure!(manpage.contains(pat))
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_backspace(str: &str) -> anyhow::Result<String> {
|
||||||
|
let mut out = String::new();
|
||||||
|
for chr in str.chars() {
|
||||||
|
if chr == '\x08' {
|
||||||
|
anyhow::ensure!(out.pop().is_some());
|
||||||
|
} else {
|
||||||
|
out.push(chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spot tests about man page generation; these are by far not exhaustive.
|
||||||
|
#[test]
|
||||||
|
fn main_fn_generates_manpages() -> anyhow::Result<()> {
|
||||||
|
let dir = tempfile::TempDir::with_prefix("rosenpass-test-main-fn-generates-mangapges")?;
|
||||||
|
let cmd_out = test_bin::get_test_bin("rosenpass")
|
||||||
|
.args(["--generate-manpage", dir.path().to_str().unwrap()])
|
||||||
|
.output()?;
|
||||||
|
assert!(cmd_out.status.success());
|
||||||
|
|
||||||
|
let expected_manpages = [
|
||||||
|
"rosenpass.1",
|
||||||
|
"rosenpass-exchange.1",
|
||||||
|
"rosenpass-exchange-config.1",
|
||||||
|
"rosenpass-gen-config.1",
|
||||||
|
"rosenpass-gen-keys.1",
|
||||||
|
"rosenpass-keygen.1",
|
||||||
|
"rosenpass-validate.1",
|
||||||
|
];
|
||||||
|
|
||||||
|
let man_texts: std::collections::HashMap<&str, String> = expected_manpages
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|name| (name, dir.path().join(name)))
|
||||||
|
.map(|(name, path)| {
|
||||||
|
let res = std::process::Command::new("man").arg(path).output()?;
|
||||||
|
assert!(res.status.success());
|
||||||
|
let body = res
|
||||||
|
.stdout
|
||||||
|
.apply(String::from_utf8)?
|
||||||
|
.apply(|s| filter_backspace(&s))?;
|
||||||
|
Ok((name, body))
|
||||||
|
})
|
||||||
|
.collect::<anyhow::Result<_>>()?;
|
||||||
|
|
||||||
|
for (name, body) in man_texts.iter() {
|
||||||
|
expect_sections(body, &["NAME", "SYNOPSIS", "OPTIONS"])?;
|
||||||
|
|
||||||
|
if *name != "rosenpass.1" {
|
||||||
|
expect_section(body, "DESCRIPTION")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let body = man_texts.get("rosenpass.1").unwrap();
|
||||||
|
expect_sections(
|
||||||
|
body,
|
||||||
|
&["EXIT STATUS", "SEE ALSO", "STANDARDS", "AUTHORS", "BUGS"],
|
||||||
|
)?;
|
||||||
|
expect_contents(
|
||||||
|
body,
|
||||||
|
&[
|
||||||
|
"[--log-level]",
|
||||||
|
"rosenpass-exchange-config(1)",
|
||||||
|
"Start Rosenpass key exchanges based on a configuration file",
|
||||||
|
"https://rosenpass.eu/whitepaper.pdf",
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let body = man_texts.get("rosenpass-exchange.1").unwrap();
|
||||||
|
expect_contents(body, &["[-c|--config-file]", "PSK := preshared-key"])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
10
rosenpass/tests/main-fn-prints-errors.rs
Normal file
10
rosenpass/tests/main-fn-prints-errors.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#[test]
|
||||||
|
fn main_fn_prints_errors() -> anyhow::Result<()> {
|
||||||
|
let out = test_bin::get_test_bin("rosenpass")
|
||||||
|
.args(["exchange-config", "/"])
|
||||||
|
.output()?;
|
||||||
|
assert!(!out.status.success());
|
||||||
|
assert!(String::from_utf8(out.stderr)?.contains("Is a directory (os error 21)"));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user