Compare commits

..

7 Commits

Author SHA1 Message Date
wucke13
7fcc4d1b66 simplify flake.nix 2023-03-08 21:59:25 +01:00
wucke13
c361e41c18 chore: Release rosenpass version 0.1.2-rc.3 2023-03-02 22:18:06 +01:00
wucke13
f04461307f does darwin support static builds? 2023-03-02 22:17:45 +01:00
wucke13
83ef709519 chore: Release rosenpass version 0.1.2-rc.2 2023-03-02 22:01:52 +01:00
wucke13
f1d06658f6 fix typo in GitHub Actions 2023-03-02 22:01:32 +01:00
wucke13
3b1321d33f chore: Release rosenpass version 0.1.2-rc.1 2023-03-02 21:40:34 +01:00
wucke13
170e0e7088 add first draft of osx release 2023-03-02 21:33:48 +01:00
29 changed files with 271 additions and 1390 deletions

View File

@@ -12,23 +12,15 @@ jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actionsx/prettier@v2
with:
args: --check .
cargo-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
cargo-clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/cache@v3
with:
path: |
@@ -39,27 +31,17 @@ jobs:
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- run: rustup component add clippy
- name: Install libsodium
- name: Install xmllint
run: sudo apt-get install -y libsodium-dev
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
cargo-doc:
cargo-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- run: rustup component add clippy
- name: Install libsodium
run: sudo apt-get install -y libsodium-dev
- run: RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -12,7 +12,6 @@ jobs:
- nix
- ${{ matrix.nix-system }}
strategy:
fail-fast: false
matrix:
nix-system:
- x86_64-linux
@@ -28,3 +27,23 @@ jobs:
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
files: |
result/*
release-darwin:
name: Release for x86_64-darwin
runs-on:
- macos-latest
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Build release-package for ${{ matrix.nix-system }}
run: nix build .#release-package --print-build-logs
- name: Release
uses: softprops/action-gh-release@v1
with:
draft: ${{ contains(github.ref_name, 'rc') }}
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
files: |
result/*

2
Cargo.lock generated
View File

@@ -865,7 +865,7 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]]
name = "rosenpass"
version = "0.1.1"
version = "0.1.2-rc.3"
dependencies = [
"anyhow",
"base64",

View File

@@ -1,6 +1,6 @@
[package]
name = "rosenpass"
version = "0.1.1"
version = "0.1.2-rc.3"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"

View File

@@ -1,17 +1,17 @@
use anyhow::Result;
use rosenpass::{
pqkem::{EphemeralKEM, CCAKEM},
protocol::{CcaPk, CcaSk, CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SymKey},
pqkem::{CCAKEM, KEM},
protocol::{CcaPk, CcaSk, HandleMsgResult, MsgBuf, PeerPtr, Server, SymKey},
sodium::sodium_init,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn handle(
tx: &mut CryptoServer,
tx: &mut Server,
msgb: &mut MsgBuf,
msgl: usize,
rx: &mut CryptoServer,
rx: &mut Server,
resb: &mut MsgBuf,
) -> Result<(Option<SymKey>, Option<SymKey>)> {
let HandleMsgResult {
@@ -30,7 +30,7 @@ fn handle(
Ok((txk, rxk.or(xch)))
}
fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
fn hs(ini: &mut Server, res: &mut Server) -> Result<()> {
let (mut inib, mut resb) = (MsgBuf::zero(), MsgBuf::zero());
let sz = ini.initiate_handshake(PeerPtr(0), &mut *inib)?;
let (kini, kres) = handle(ini, &mut inib, sz, res, &mut resb)?;
@@ -44,13 +44,10 @@ fn keygen() -> Result<(CcaSk, CcaPk)> {
Ok((sk, pk))
}
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
fn make_server_pair() -> Result<(Server, Server)> {
let psk = SymKey::random();
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
let (mut a, mut b) = (
CryptoServer::new(ska, pka.clone()),
CryptoServer::new(skb, pkb.clone()),
);
let (mut a, mut b) = (Server::new(ska, pka.clone()), Server::new(skb, pkb.clone()));
a.add_peer(Some(psk.clone()), pkb)?;
b.add_peer(Some(psk), pka)?;
Ok((a, b))

View File

@@ -62,7 +62,6 @@
gawk
wireguard-tools
];
# a function to generate a nix derivation for rosenpass against any
# given set of nixpkgs
rpDerivation = p:
@@ -90,11 +89,6 @@
# otherwise pkg-config tries to link non-existent dynamic libs
PKG_CONFIG_ALL_STATIC = true;
# 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
# nix defaults to building for aarch64 _without_ the armv8-a
# crypto extensions, but liboqs depens on these
preBuild =
@@ -138,19 +132,15 @@
default = rosenpass;
rosenpass = rpDerivation pkgs;
rosenpass-oci-image = rosenpassOCI "rosenpass";
rosenpass-static = rpDerivation pkgs.pkgsStatic;
rosenpass-static-oci-image = rosenpassOCI "rosenpass-static";
# derivation for the release
release-package =
let
version = cargoToml.package.version;
package =
if pkgs.hostPlatform.isLinux then
packages.rosenpass-static
else packages.rosenpass;
oci-image =
if pkgs.hostPlatform.isLinux then
packages.rosenpass-static-oci-image
else packages.rosenpass-oci-image;
package = packages.rosenpass-static;
oci-image = packages.rosenpass-static-oci-image;
in
pkgs.runCommandNoCC "lace-result" { }
''
@@ -161,10 +151,7 @@
cp ${oci-image} \
$out/rosenpass-oci-image-${system}-${version}.tar.gz
'';
} // (if pkgs.stdenv.isLinux then rec {
rosenpass-static = rpDerivation pkgs.pkgsStatic;
rosenpass-static-oci-image = rosenpassOCI "rosenpass-static";
} else { });
};
}
))
@@ -247,7 +234,6 @@
#
devShells.default = pkgs.mkShell {
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
inherit (packages.rosenpass) RUST_MIN_STACK;
inputsFrom = [ packages.default ];
nativeBuildInputs = with pkgs; [
cargo-release
@@ -259,16 +245,17 @@
};
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 ];
};
checks = {
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
cargo fmt --manifest-path=${./.}/Cargo.toml --check > $out
'';
# Blocked by https://github.com/rust-lang/rustfmt/issues/4306
# @dakoraa wants a coding style suitable for her accessible coding setup
# cargo-fmt = pkgs.runCommand "check-cargo-fmt"
# { inherit (devShells.default) nativeBuildInputs buildInputs; } ''
# cargo fmt --manifest-path=${src}/Cargo.toml --check > $out
# '';
nixpkgs-fmt = pkgs.runCommand "check-nixpkgs-fmt"
{ nativeBuildInputs = [ pkgs.nixpkgs-fmt ]; } ''
nixpkgs-fmt --check ${./.} && touch $out
@@ -278,8 +265,6 @@
cd ${./.} && prettier --check . && touch $out
'';
};
formatter = pkgs.nixpkgs-fmt;
}))
];
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

View File

@@ -1,15 +0,0 @@
---
template: rosenpass
title: Running a financially \\& institutionally independent post-quantum cryptography research project conference: CrossFyre 2023 submission abstract
author:
- Karolin Varner = Independent Researcher
abstract: |
Rosenpass is a post-quantum-secure key exchange protocol with security against state\\babelhyphen{hard}disruption attacks, whose reference implementation provides post-quantum security to WireGuard deployments. The project's aim is not just to put out a research paper, but to solve the problem of providing a post-quantum VPN top to bottom: from defining a provably secure protocol to educating potential users about how to use it. To achieve this, the project team is composed of an interdisciplinary group of experts in fields inside and outside of cryptography. As such, it is quite different from many purely scientific cryptography projects.
In this presentation, I would like to share some of the learnings from running the project: how including team-members who are not researchers in the research process led us to better cryptography and how disability inclusion led to a more effective working environment. How feminist organizations shaped our communication style and finally, what funding sources we found to support this project.
Not all aspects of this project are favorable though; in particular, the extra, unpaid workload needed to coordinate this project has been enormous and the emotional drain from navigating conflicts, uncertain funding, and risk-taking is ever present.
I still think the community built in the process, the personal growth, the educational benefits and the scientific freedom are well worth the downsides; therefore I would like to exemplify the value in starting collaborations with enthusiastic colleagues from a variety of fields outside cryptographers to the CrossFyre attendees and to encourage other cryptographers to build independent projects for themselves.
tableofcontents: false
---

View File

@@ -1,137 +0,0 @@
%
\begin{frame}{Structure of the talk}
\begin{itemize}
\item Post-quantum WireGuard\footnote{
Andreas Hülsing, Kai-Chun Ning, Peter Schwabe, Florian Weber, and Philip R. Zimmermann. “Post-quantum WireGuard”. In: 42nd IEEE Symposium on Security and Privacy, SP 2021, San Francisco, CA, USA, 24-27 May 2021. Full version: https://eprint.iacr.org/2020/379
}: How to build an interactive key exchange from KEMs
\item Contribution: State Disruption Attacks \& cookies as a defense
\item Contribution: Symbolic analysis of the Rosenpass protocol
\item Contribution: Noise-like specification
\item Contribution: New hashing \& domain separation scheme
\item Contribution: Reference implementation Securing WireGuard in practice
\end{itemize}
\end{frame}
\begin{frame}{Post-quantum WireGuard: Three encapsulations}
\tikzset{shorten > = 1pt,shorten < = 1pt}
\begin{columns}
\begin{column}{.30\textwidth}
\begin{tikzpicture}
\draw (-1,0) node[above](initiator){Initiator\strut} --
coordinate[pos=.2](spkr-y)
coordinate[pos=.6](sctr-y)
coordinate[pos=.76](ack-y)+(0,-5);
\draw (1,0) node[above](responder){Responder\strut}-- +(0,-5);
\draw[<-](spkr-y-|initiator) -- node[above]{spkr} (spkr-y-|responder);
\draw[->](sctr-y-|initiator) -- node[above] {sctr} (sctr-y-|responder);
\draw[<-](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Responder Auth
\end{column}
\begin{column}{.30\textwidth}
\begin{tikzpicture}
\draw (-1,0) node[above](initiator){Initiator\strut} --
coordinate[pos=.2](spki-y)
coordinate[pos=.6](Hspki-y)
coordinate[pos=.76] (scti-y)
coordinate[pos=.92](ack-y)+(0,-5);
\draw (1,0) node[above](responder){Responder\strut}-- +(0,-5);
\draw[->](spki-y-|initiator) -- node[above]{spki} (spki-y-|responder);
\draw[->](Hspki-y-|initiator) -- node[above] {H(spki)} (Hspki-y-|responder);
\draw[<-](scti-y-|initiator) -- node[above]{scti} (scti-y-|responder);
\draw[->](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Initiator Auth
\end{column}
\begin{column}{.30\textwidth}
\begin{tikzpicture}
\draw (-1,0) node[above](initiator){Initiator\strut} --
coordinate[pos=.6](epki-y)
coordinate[pos=.76] (ecti-y)
coordinate[pos=.92](ack-y)+(0,-5);
\draw (1,0) node[above](responder){Responder\strut}-- +(0,-5);
\draw[->](epki-y-|initiator) -- node[above]{epki} (epki-y-|responder);
\draw[<-](ecti-y-|initiator) -- node[above]{ecti} (ecti-y-|responder);
\draw[->](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Forward secrecy
\end{column}
\end{columns}
\end{frame}
\begin{frame}{Combining the three encapsulations in one protocol}
\begin{tikzpicture}[shorten > = 1pt,shorten < = 1pt]
\draw (-3,0) node[above](initiator){Initiator\strut} -- coordinate[pos=.2](spki-y)
coordinate[pos=.35](spkr-y)
coordinate[pos=.6](epki-y)
coordinate[pos=.75](scti-y)
coordinate[pos=.9](ack-y)+(0,-5);
\draw (3,0) node[above](responder){Responder\strut}-- +(0,-5);
\draw[->](spki-y-|initiator) -- node[above] {spki} (spki-y-|responder);
\draw[<-](spkr-y-|initiator) -- node[above] {spkr} (spkr-y-|responder);
\draw[->](epki-y-|initiator) -- node[above] {epki, sctr, H(spki)} (epki-y-|responder);
\draw[<-](scti-y-|initiator) -- node[above] {scti,ecti} (scti-y-|responder);
\draw[->](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Note that the initiator is not authenticated until they send \enquote{(ack)}.
\end{frame}
\begin{frame}{The Rosenpass protocol}
\includegraphics[height=.9\textheight]{graphics/rosenpass-wp-key-exchange-protocol-rgb.pdf}
\end{frame}
\begin{frame}{CVE-2021-46873 DOS against WireGuard through NTP}
\begin{itemize}
\item The replay protection in classic WireGuard assumes a monotonic counter
\item But the system time is attacker controlled because NTP is insecure
\item This generates a kill packet that abuses replay protection and renders the initiator's key-pair useless
\item Attack is possible in the real world!
\item Similar attack in post-quantum WireGuard is worse since InitHello is unauthenticated
\item Solution: Biscuits
\end{itemize}
\end{frame}
\begin{frame}{Security analysis of rosenpass}
\begin{itemize}
\item CryptoVerif in progress
\item Symbolic analysis using ProVerif
\item Code is part of the software repository \& build system
\item Symbolic analysis is fast (about five minutes), runs in parallel and is
\end{itemize}
\end{frame}
\begin{frame}{Proverif in technicolor}
\includegraphics[height=.9\textheight]{assets/2023-03-20-symbolic-analysis-screenshot.png}
\end{frame}
\begin{frame}{Noise-like specification (easier for engineers)}
\includegraphics[height=.9\textheight]{graphics/rosenpass-wp-message-handling-code.pdf}
\end{frame}
\begin{frame}{New Hashing/Domain separation scheme}
\includegraphics[height=.9\textheight]{graphics/rosenpass-wp-hashing-tree.pdf}
\end{frame}
\begin{frame}{Reference implementation in rust, deploying post-quantum-secure WireGuard}
\includegraphics[height=.9\textheight]{assets/2023-03-20-rg-tutorial-screenshot.png}
\end{frame}

View File

@@ -1,233 +0,0 @@
\documentclass[10pt,aspectratio=169]{beamer}
%\documentclass[10pt]{beamer}
\usetheme[sectionpage=none] % TODO solve the arithmetic error problem later (on section pages)
{metropolis}
\usepackage{appendixnumberbeamer}
\usepackage{verbatim}
% strikethrough
% - normalem: do not redefine emph to do underline
\usepackage[normalem]{ulem}
% bold math
\usepackage{bm}
\usepackage{xcolor,soul}
\definecolor{lightblue}{rgb}{.90,.95,1}
\definecolor{lightred}{rgb}{1,.80,.80}
\definecolor{lightgreen}{rgb}{.80,1.,.80}
%\definecolor{lightred}{red!40}
\sethlcolor{lightblue}
\definecolor{rosenpass-pink}{RGB}{247, 4, 132}
\definecolor{rosenpass-orange}{RGB}{255, 166, 48}
\definecolor{rosenpass-gray}{RGB}{64, 63, 76}
\definecolor{rosenpass-lightblue}{RGB}{211, 243, 238}
\definecolor{rosenpass-blue}{RGB}{114, 161, 229}
\setbeamercolor{progress bar}{fg=rosenpass-pink,bg=blue}
\setbeamercolor{title separator}{fg=rosenpass-pink,bg=blue}
\renewcommand<>{\hl}[1]{\only#2{\beameroriginal{\hl}}{#1}}
\usepackage[beamer]{hf-tikz}
\usetikzlibrary{arrows.meta}
\tikzset{
>=Latex[round]
}
\urlstyle{same}
% https://tex.stackexchange.com/questions/41683/why-is-it-that-coloring-in-soul-in-beamer-is-not-visible
\makeatletter
\newcommand\SoulColor{%
\let\set@color\beamerorig@set@color
\let\reset@color\beamerorig@reset@color}
\makeatother
\SoulColor
\setlength{\fboxsep}{0pt}
\newcommand{\mathcolorbox}[2]{\colorbox{#1}{$\displaystyle #2$}}
\newcommand{\ah}[1]{\colorbox{lightblue}{$\displaystyle #1$}}
\newcommand{\bh}[1]{\colorbox{lightred}{$\displaystyle #1$}}
\newcommand{\ch}[1]{\colorbox{lightgreen}{$\displaystyle #1$}}
\newcommand{\hlfancy}[2]{\sethlcolor{#1}\hl{#2}}
\setbeamercolor{frametitle}{parent=subtitle}
% `title separator` is the one on the title page
% `progress bar in head/foot` is the line on each frame
\setbeamercolor{progress bar in head/foot}{bg=normal text.bg,fg=normal text.bg}
\usepackage{appendixnumberbeamer}
\usepackage{booktabs}
\usepackage[scale=2]{ccicons}
\usepackage{fontawesome}
\usepackage{pgfplots}
\usepgfplotslibrary{dateplot}
%% tikzit
%\usepackage{tikzit}
%\input{blipp.tikzstyles}
%\usetikzlibrary{trees}
%\usetikzlibrary{tikzmark}
%\usetikzlibrary{arrows.meta}
\usepackage{xspace}
\newcommand{\themename}{\textbf{\textsc{metropolis}}\xspace}
\usepackage{stackengine}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amsthm}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage{environ}
\usepackage{array}
\usepackage[
n,advantage,operators,sets,adversary,landau,
probability,
notions,logic,ff,mm,primitives,events,complexity,asymptotics
%keys
]{cryptocode}
\usetikzlibrary{positioning,shapes,arrows,matrix, calc,external,fit,decorations.pathreplacing,arrows.meta,patterns,tikzmark}
\usepackage{mathtools}
\usepackage{comment}
\excludecomment{commentEnv}
\usepackage{array}
\newcolumntype{C}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
\def\makeuppercase#1{
\expandafter\newcommand\csname cal#1\endcsname{\mathcal{#1}}
\expandafter\newcommand\csname adv#1\endcsname{\mathcal{#1}}
\expandafter\newcommand\csname frak#1\endcsname{\mathfrak{#1}}
\expandafter\newcommand\csname bb#1\endcsname{\mathbb{#1}}
\expandafter\newcommand\csname bf#1\endcsname{\textbf{#1}}
}
\def\makelowercase#1{
\expandafter\newcommand\csname frak#1\endcsname{\mathfrak{#1}}
\expandafter\newcommand\csname bf#1\endcsname{\textbf{#1}}
}
\newcounter{char}
\setcounter{char}{1}
\loop
\edef\letter{\alph{char}}
\edef\Letter{\Alph{char}}
\expandafter\makelowercase\letter
\expandafter\makeuppercase\Letter
\stepcounter{char}
\unless\ifnum\thechar>26
\repeat
\newcommand{\quotes}[1]{``#1''}
\newcommand{\filename}[1]{\texttt{#1}}
\newcommand{\cryptoverif}{Crypto\-Verif}
\newcommand{\cv}{\cryptoverif}
\newcommand{\bottom}{\ensuremath{\perp}}
%\usepackage{pgfpages}
%\setbeameroption{show notes on second screen=right}
\usepackage{multicol}
\usepackage{qrcode}
%% msc message diagrams
%\usepackage{msc5}
%\newcommand{\laction}[2]{$\begin{array}{c}\mbox{\textrm{#1}}\\#2\end{array}$}
%\newcommand{\poormanshead}[1]{\textcolor{darkgray}{#1}}
%\newcommand{\poormansline}[2]{\textcolor{gray}{\phantom{-- }\texttt{-- -- -- -- -- -- -- --\phantom{ --}}}\poormanshead{#1}\textcolor{gray}{\texttt{\phantom{-- }#2}}}
\definecolor{light-gray}{gray}{0.5}
% https://gamedev.stackexchange.com/questions/133078/what-colors-to-choose-for-colorblind-people
\definecolor{keyOne}{rgb}{.9,.6,0} % orange
\definecolor{keyTwo}{rgb}{.35,.7,.9} % sky blue
\definecolor{keyThree}{rgb}{0,.6,.5} % bluish green
\definecolor{keyFour}{rgb}{.8,.4,0} % vermilion
%\definecolor{keyFour}{rgb}{.8,.6,.7} % reddish purple
%\definecolor{keyFour}{rgb}{0,.45,.7} % blue
\newcommand{\screenshotframe}[2]{%
\begin{frame}{#1}
\vfill
\begin{center}
\includegraphics[width=.95\textwidth,height=.95\textheight,keepaspectratio]{#2}
\end{center}
\vfill
\end{frame}
}
\usepackage{listings}
\lstdefinelanguage{cryptoverif}
{morekeywords={collision, const, crypto, define, defined, do, else, end, equation, equiv,
event, event_abort, expand, find, forall, foreach, fun, get, implementation, in,
if, inj, insert, length, let, letfun, max, maxlength, newOracle, orfind, otheruses,
param, proba, public_vars, process, proof, query, return, secret, secret1, set, suchthat, success, simplify, then,
table, time, type},
otherkeywords={<-, <-R, &&},
sensitive=true,
morecomment=[s]{(*}{*)},
morestring=[b]",
}
\lstdefinelanguage{cvoutput}
{morekeywords={},
otherkeywords={},
sensitive=true,
morecomment=[s]{(*}{*)},
morestring=[b]",
}
\lstset{
language=cvoutput,
basicstyle=\ttfamily,
commentstyle=\color{black!55},
keywordstyle=\bfseries\color{green!40!black}
}
\lstset{
language=cryptoverif,
basicstyle=\ttfamily,
commentstyle=\color{black!55},
keywordstyle=\bfseries\color{green!40!black}
}
\usepackage{bbding}
\newcommand*\itemtick{\item[\Checkmark]}
\newcommand*\itemfail{\item[\XSolidBrush]}
\title{%
Rosenpass
}
\subtitle{%
Securing \& Deploying Post-Quantum WireGuard
}
\author{\textbf{Karolin Varner}, with Benjamin Lipp, Wanja Zaeske, Lisa Schmidt}
\institute{RWPQC23 | \url{https://rosenpass.eu/whitepaper.pdf}}
\titlegraphic{\hfill\includegraphics[height=2.5cm]{tex/RosenPass-Logo.pdf}}
\usepackage[autostyle]{csquotes}
\begin{document}
\maketitle
\input{rwpqc23-slides-content}
\end{document}

View File

@@ -79,8 +79,6 @@
letter-csv .initial:n = ,
letter-content .tl_set:N = \l_letter_csv_content_tl,
letter-content .initial:n=,
tableofcontents .bool_gset:N = \g__ptxcd_tableofcontents_bool,
tableofcontents .initial:n = true,
}
\tl_new:N \l__markdown_sequence_tl

View File

@@ -171,12 +171,7 @@ version={4.0},
\ExplSyntaxOn
\SetTemplatePreamble{
\hypersetup{pdftitle=\inserttitle,pdfauthor=The~Rosenpass~Project}
\exp_args:NV\tl_if_eq:nnTF \inserttitle{Rosenpass} {
\title{\vspace*{-2.5cm}\includegraphics[width=4cm]{RosenPass-Logo}}
} {
\titlehead{\centerline{\includegraphics[width=4cm]{RosenPass-Logo}}}
\title{\inserttitle}
}
\title{\vspace*{-2.5cm}\includegraphics[width=4cm]{RosenPass-Logo}}
\author{\csname insertauthor\endcsname}
\subject{\csname insertsubject\endcsname}
\date{\vspace{-1cm}}
@@ -379,28 +374,29 @@ version={4.0},
}
}
}
\makeatother
\ExplSyntaxOff
% end of namepartpicturesetup
\newcommand{\captionbox}[1]{{\setlength{\fboxsep}{.5ex}\colorbox{rosenpass-gray}{#1}}}
\makeatletter
\renewenvironment{abstract}{
\small
\begin{center}\normalfont\sectfont\nobreak\abstractname\@endparpenalty\@M\end{center}%
}{
\par
}
\makeatother
\SetTemplateBegin{
\maketitle
\begin{abstract}
\noindent\csname insertabstract\endcsname
\end{abstract}
\bool_if:NT \g__ptxcd_tableofcontents_bool \tableofcontents
\tableofcontents
\clearpage
}
\makeatother
\ExplSyntaxOff
\SetTemplateEnd{}
\SetTemplateEnd{
}

View File

@@ -7,13 +7,13 @@ author:
- Wanja Zaeske
- Lisa Schmidt = {Scientific Illustrator \\url{mullana.de}}
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.
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 some other 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.
The WireGuard implementation enjoys great trust from the cryptography community and has excellent performance characteristics. To preserve these features, the Rosenpass application runs side-by-side with WireGuard and supplies a new post-quantum-secure pre-shared key (PSK) every two minutes. WireGuard itself still performs the pre-quantum-secure key exchange and transfers any transport data with no involvement from Rosenpass at all.
The Rosenpass project consists of a protocol description, an implementation written in Rust, and a symbolic analysis of the protocols security using ProVerif [@proverif]. We are working on a cryptographic security proof using CryptoVerif [@cryptoverif].
This document is a guide for engineers and researchers implementing the protocol; a scientific paper discussing the security properties of Rosenpass is work in progress.
This document is a guide to engineers and researchers implementing the protocol; a scientific paper discussing the security properties of Rosenpass is work in progress.
---
\enlargethispage{5mm}
@@ -169,7 +169,7 @@ Rosenpass uses a cryptographic hash function for multiple purposes:
* Computing the cookie to guard against denial of service attacks. This is a feature adopted from WireGuard, but not yet included in the implementation of Rosenpass.
* Computing the peer ID
* Key derivation during and after the handshake
* Computing the additional data for the biscuit encryption, to provide some privacy for its contents
* Computing the additional data for the biscuit encryption, to prove some privacy for its contents
Using one hash function for multiple purposes can cause real-world security issues and even key recovery attacks [@oraclecloning]. We choose a tree-based domain separation scheme based on a keyed hash function the previously introduced primitive `hash` to make sure all our hash function calls can be seen as distinct.
@@ -237,7 +237,7 @@ For each peer, the server stores:
The initiator stores the following local state for each ongoing handshake:
* A reference to the peer structure
* A state indicator to keep track of the next message expected from the responder
* A state indicator to keep track of the message expected from the responder next
* `sidi` Initiator session ID
* `sidr` Responder session ID
* `ck` The chaining key

View File

@@ -1,300 +0,0 @@
\begin{frame}{Structure of the talk}
\begin{itemize}
\item Problem statement: Post-quantum WireGuard % 4m
\item Post-quantum WireGuard\footnote{
Andreas Hülsing, Kai-Chun Ning, Peter Schwabe, Florian Weber, and Philip R. Zimmermann. “Post-quantum WireGuard”. In: 42nd IEEE Symposium on Security and Privacy, SP 2021, San Francisco, CA, USA, 24-27 May 2021. Full version: https://eprint.iacr.org/2020/379
}: How to build an interactive key exchange from KEMs % 8m
\item Attack we found: State Disruption Attacks %12m
\item Real-World Concerns % 3m
\item Biscuits as a defense against State Disruption Attacks
\end{itemize}
\end{frame}
\begin{frame}{What needs to be done to deploy Post-Quantum WireGuard}
\begin{itemize}
\item Updating the WireGuard protocol to support post-quantum security
\item Updating the (post quantum) WireGuard protocol to be secure against state disruption attacks
\item Reference implementation of the Rosenpass protocol in Rust
\item A way to create hybrid post-quantum secure WireGuard VPNs
\item Stand-alone key exchange app
\item A Sci-Comm project teaching people about post-quantum security
\end{itemize}
\end{frame}
\begin{frame}{WireGuard\footnote{Jason A. Donenfeld. “WireGuard: Next Generation Kernel Network Tunnel”. In: 24th Annual Network and Distributed System Security Symposium, NDSS 2017, San Diego, California, USA, February 26 - March 1, 2017. Whitepaper: https: //www.wireguard.com/papers/wireguard.pdf.}}
\begin{itemize}
\item VPN protocol in the linux kernel
\item Based on Noise IKpsk1 from the Noise Protocol Framework\footnote{Trevor Perrin. The Noise Protocol Framework. 2016. url: http://noiseprotocol.org/noise.pdf}
\item Small, fast, open source crypto
\end{itemize}
\end{frame}
\begin{frame}{WireGuard/Noise IKpsk security properties}
\begin{itemize}
\itemtick Session-key secrecy
\itemtick Forward-secrecy
\itemtick Mutual authentication
\itemtick Session-key Uniqueness
\itemtick Identity Hiding
\itemtick (DoS Mitigation First packet is authenticated\footnote{Based on the unrealistic assumption of a monotonic counter We found a practical attack})
\end{itemize}
\end{frame}
\begin{frame}{Security of Rosenpass}
\begin{columns}
\begin{column}{.30\textwidth}
WireGuard
\begin{itemize}
\itemtick Session-key secrecy
\itemtick Forward-secrecy
\itemtick Mutual authentication
\itemtick Session-key Uniqueness
\itemtick Identity Hiding
\itemtick (DoS Mitigation)
\end{itemize}
\end{column}
\begin{column}{.30\textwidth}
Post-Quantum WireGuard
\begin{itemize}
\itemfail Identity Hiding \footnote{Based on a Identity Hiding/ANON-CCA security of McEliece; unclear whether that holds.}
\itemfail DoS Mitigation \footnote{PQWG provides DoS mitigation under the assumption of a secret PSK, which quite frankly is cheating.}
\end{itemize}
\end{column}
\begin{column}{.30\textwidth}
Rosenpass
\begin{itemize}
\itemtick DoS Mitigation
\itemtick Hybrid Post-Quantum security\footnote{In deployments using WireGuard + Rosenpass; Rosenpass on its own provides post-quantum security.}
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}{Building post-quantum WireGuard: NIKE vs KEM}
NIKE:
$(\texttt{sk}_1, \texttt{pk}_1) \leftarrow \texttt{NIKE.KeyGen}$ \\
$(\texttt{sk}_2, \texttt{pk}_2) \leftarrow \texttt{NIKE.KeyGen}$ \\
$\texttt{NIKE.SharedKey}(\texttt{sk}_1, \texttt{pk}_2) = \texttt{NIKE.SharedKey}(\texttt{sk}_2, \texttt{pk}_1)$
KEM:
$(\texttt{sk}, \texttt{pk}) \leftarrow \texttt{KEM.KeyGen}$ \\
$(\texttt{shk}, \texttt{ct}) \leftarrow \texttt{KEM.Encaps}(\texttt{pk})$ \\
$\texttt{shk} = \texttt{KEM.Decaps}(\texttt{sk}, \texttt{ct})$
\end{frame}
\begin{frame}{Minimal key exchange using KEMs}
\begin{tikzpicture}[shorten > = 1pt,shorten < = 1pt]
\draw (-3,0) node[above](initiator){Initiator\strut} -- coordinate[pos=.2](pk-y) coordinate[pos=.6](ct-y)coordinate[pos=.8](ack-y)+(0,-5);
\draw (3,0) node[above](responder){Responder}-- +(0,-5);
\draw[<-](pk-y-|initiator) -- node[above]{pk} (pk-y-|responder);
\draw[->](ct-y-|initiator) -- node[above] {ct} (ct-y-|responder);
\draw[<-](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
\end{frame}
\begin{frame}{Three encapsulations: Achieving mutual authentication \& forward secrecy}
\tikzset{shorten > = 1pt,shorten < = 1pt}
\begin{columns}
\begin{column}{.30\textwidth}
\begin{tikzpicture}
\draw (-1,0) node[above](initiator){Initiator\strut} --
coordinate[pos=.2](spkr-y)
coordinate[pos=.6](sctr-y)
coordinate[pos=.76](ack-y)+(0,-5);
\draw (1,0) node[above](responder){Responder}-- +(0,-5);
\draw[<-](spkr-y-|initiator) -- node[above]{spkr} (spkr-y-|responder);
\draw[->](sctr-y-|initiator) -- node[above] {sctr} (sctr-y-|responder);
\draw[<-](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Responder Auth
\end{column}
\begin{column}{.30\textwidth}
\begin{tikzpicture}
\draw (-1,0) node[above](initiator){Initiator\strut} --
coordinate[pos=.2](spki-y)
coordinate[pos=.6](Hspki-y)
coordinate[pos=.76] (scti-y)
coordinate[pos=.92](ack-y)+(0,-5);
\draw (1,0) node[above](responder){Responder}-- +(0,-5);
\draw[->](spki-y-|initiator) -- node[above]{spki} (spki-y-|responder);
\draw[->](Hspki-y-|initiator) -- node[above] {H(spki)} (Hspki-y-|responder);
\draw[<-](scti-y-|initiator) -- node[above]{scti} (scti-y-|responder);
\draw[->](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Initiator Auth
\end{column}
\begin{column}{.30\textwidth}
\begin{tikzpicture}
\draw (-1,0) node[above](initiator){Initiator\strut} --
coordinate[pos=.6](epki-y)
coordinate[pos=.76] (ecti-y)
coordinate[pos=.92](ack-y)+(0,-5);
\draw (1,0) node[above](responder){Responder}-- +(0,-5);
\draw[->](epki-y-|initiator) -- node[above]{epki} (epki-y-|responder);
\draw[<-](ecti-y-|initiator) -- node[above]{ecti} (ecti-y-|responder);
\draw[->](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Forward secrecy
\end{column}
\end{columns}
\end{frame}
\begin{frame}{Combining the three encapsulations in one protocol}
\begin{tikzpicture}[shorten > = 1pt,shorten < = 1pt]
\draw (-3,0) node[above](initiator){Initiator\strut} -- coordinate[pos=.2](spki-y)
coordinate[pos=.35](spkr-y)
coordinate[pos=.6](epki-y)
coordinate[pos=.75](scti-y)
coordinate[pos=.9](ack-y)+(0,-5);
\draw (3,0) node[above](responder){Responder}-- +(0,-5);
\draw[->](spki-y-|initiator) -- node[above] {spki} (spki-y-|responder);
\draw[<-](spkr-y-|initiator) -- node[above] {spkr} (spkr-y-|responder);
\draw[->](epki-y-|initiator) -- node[above] {epki, sctr, H(spki)} (epki-y-|responder);
\draw[<-](scti-y-|initiator) -- node[above] {scti,ecti} (scti-y-|responder);
\draw[->](ack-y-|initiator) -- node[above] {(ack)} (ack-y-|responder);
\end{tikzpicture}
Note that the initiator is not authenticated until they send `(ack)`.
\end{frame}
\begin{frame}{In Rosenpasss specifically}
\includegraphics[height=.80\textheight]{graphics/rosenpass-wp-key-exchange-protocol-rgb.pdf}
\end{frame}
\begin{frame}{In Rosenpasss specifically}
\includegraphics[height=.80\textheight]{graphics/rosenpass-wp-message-types-rgb.pdf}
\end{frame}
\begin{frame}{State Disruption Attacks}
\begin{itemize}
\item Use the fact that the initiator is not authenticated until their last message
\item Send faux initiations, overwriting and thus erasing the responder's handshake state
\item Erasing the state aborts protocol execution
\item PQWG argues: The first package is authenticated using the PSK, therefor sending faux initiations works
\item Attacker could replay a legitimate message, but…
\end{itemize}
\end{frame}
\begin{frame}{State Disruption Attacks on authenticated initial package}
\begin{itemize}
\item In Classic WireGuard the initial message (InitHello) is authenticated through static-static Diffie-Hellman
\item Replay protection uses monotonic counter
\item WireGuard stores the time of the last initiator $t_i$
\item When WireGuard receives legitimate initiaton with timestamp $t$, it stores that time $t_i \leftarrow t$a
\item All InitHello messages with a stale timestamp ($t \le t_i$) get rejected
\end{itemize}
\end{frame}
\begin{frame}{CVE-2021-46873 Attacking WireGuard through NTP}
\begin{itemize}
\item The replay protection in classic WireGuard assumes a monotonic counter
\item But the system time is attacker controlled because NTP is insecure
\item This generates a kill packet that can be used to render WireGuard keys useless
\item Attack is possible in the real world!
\end{itemize}
\end{frame}
\begin{frame}{State disruption in Post-Quantum WireGuard}
\begin{itemize}
\item This mechanism needs an authenticated InitHello message
\item Post-Quantum WireGuard relies on the $\texttt{psk}$ to provide InitHello authentication
\item PQWG sets $\texttt{psk} = H(\texttt{spki} \oplus \texttt{spkr})$ to achieve a secret psk.a
\item Relying on private public keys is absurd
\item[$\Rightarrow$] With InitHello effectively unauthenticated, attacker can just generate their own kill packet
\end{itemize}
Solution: Store the responder state in a biscuit (cookie), so there is no state to override.
\end{frame}
\begin{frame}{Biscuits in the protocol flow}
\includegraphics[height=.80\textheight]{graphics/rosenpass-wp-key-exchange-protocol-rgb.pdf}
\end{frame}
\begin{frame}{Biscuits in the messages}
\includegraphics[height=.80\textheight]{graphics/rosenpass-wp-message-types-rgb.pdf}
\end{frame}
\begin{frame}{Biscuits}
\begin{itemize}
\item Assumptions such as a monotonic counter are perilous in the real world
\item Giving the adversary access to state is dangerous
\item In noise protocols the handshake state is very small (32-64 bytes)
\item Sending the state to the protocol peer is a viable course of action!
\item Formalization of State Disruption Attacks covers many attacks of this style
\end{itemize}
\end{frame}
\begin{frame}{Security proof of rosenpass}
\begin{itemize}
\item CryptoVerif in progress (Benjamin Lipp)
\item Really fast symbolic analysis using ProVerif
\end{itemize}
\end{frame}
\begin{frame}{Deployment}
\begin{itemize}
\item Rust implementation in userspace
\item Integrates with WireGuard through the PSK feature to provide Hybrid security
\end{itemize}
\end{frame}
\begin{frame}{Final statements}
\begin{itemize}
\item Post-quantum crypto can be deployed now
\item There are real complexities in protocol design
\item DoS-Resistance needs formalization work
\item Availability needs love and attention from cryptographers
\item Try it out! \url{https://rosenpass.eu/}
\end{itemize}
\end{frame}
%* Problem introduction
% * Post-quantum-secure authenticated key exchange
% * Interactive key exchange
% * No DH
% * But with KEMs
% * KEM is non interactive
% * Gives you secrecy
% * One-sided auth
%* PQWG
% * Doing one key encapsulation in both directions
% * Adding an ephemeral one for forward secrecy
%* State interruption attack
% * Assumption of monotonic counter is not realistic/broken/…
% * static keys become essentially useless
% * Leads to state disruption
% * Case: WG ohne replay protection (motivation for monotonic counter)
% * Case: WG mit replay protection
%* Biscuits
% * No state
% * Provably avoids state disruption
% * State machine WG/PQWG: ini
%*

View File

@@ -1,232 +0,0 @@
\documentclass[10pt,aspectratio=169]{beamer}
%\documentclass[10pt]{beamer}
\usetheme[sectionpage=none] % TODO solve the arithmetic error problem later (on section pages)
{metropolis}
\usepackage{appendixnumberbeamer}
\usepackage{verbatim}
% strikethrough
% - normalem: do not redefine emph to do underline
\usepackage[normalem]{ulem}
% bold math
\usepackage{bm}
\usepackage{xcolor,soul}
\definecolor{lightblue}{rgb}{.90,.95,1}
\definecolor{lightred}{rgb}{1,.80,.80}
\definecolor{lightgreen}{rgb}{.80,1.,.80}
%\definecolor{lightred}{red!40}
\sethlcolor{lightblue}
\definecolor{rosenpass-pink}{RGB}{247, 4, 132}
\definecolor{rosenpass-orange}{RGB}{255, 166, 48}
\definecolor{rosenpass-gray}{RGB}{64, 63, 76}
\definecolor{rosenpass-lightblue}{RGB}{211, 243, 238}
\definecolor{rosenpass-blue}{RGB}{114, 161, 229}
\setbeamercolor{progress bar}{fg=rosenpass-pink,bg=blue}
\setbeamercolor{title separator}{fg=rosenpass-pink,bg=blue}
\renewcommand<>{\hl}[1]{\only#2{\beameroriginal{\hl}}{#1}}
\usepackage[beamer]{hf-tikz}
\usetikzlibrary{arrows.meta}
\tikzset{
>=Latex[round]
}
\urlstyle{same}
% https://tex.stackexchange.com/questions/41683/why-is-it-that-coloring-in-soul-in-beamer-is-not-visible
\makeatletter
\newcommand\SoulColor{%
\let\set@color\beamerorig@set@color
\let\reset@color\beamerorig@reset@color}
\makeatother
\SoulColor
\setlength{\fboxsep}{0pt}
\newcommand{\mathcolorbox}[2]{\colorbox{#1}{$\displaystyle #2$}}
\newcommand{\ah}[1]{\colorbox{lightblue}{$\displaystyle #1$}}
\newcommand{\bh}[1]{\colorbox{lightred}{$\displaystyle #1$}}
\newcommand{\ch}[1]{\colorbox{lightgreen}{$\displaystyle #1$}}
\newcommand{\hlfancy}[2]{\sethlcolor{#1}\hl{#2}}
\setbeamercolor{frametitle}{parent=subtitle}
% `title separator` is the one on the title page
% `progress bar in head/foot` is the line on each frame
\setbeamercolor{progress bar in head/foot}{bg=normal text.bg,fg=normal text.bg}
\usepackage{appendixnumberbeamer}
\usepackage{booktabs}
\usepackage[scale=2]{ccicons}
\usepackage{fontawesome}
\usepackage{pgfplots}
\usepgfplotslibrary{dateplot}
%% tikzit
%\usepackage{tikzit}
%\input{blipp.tikzstyles}
%\usetikzlibrary{trees}
%\usetikzlibrary{tikzmark}
%\usetikzlibrary{arrows.meta}
\usepackage{xspace}
\newcommand{\themename}{\textbf{\textsc{metropolis}}\xspace}
\usepackage{stackengine}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amsthm}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage{environ}
\usepackage{array}
\usepackage[
n,advantage,operators,sets,adversary,landau,
probability,
notions,logic,ff,mm,primitives,events,complexity,asymptotics
%keys
]{cryptocode}
\usetikzlibrary{positioning,shapes,arrows,matrix, calc,external,fit,decorations.pathreplacing,arrows.meta,patterns,tikzmark}
\usepackage{mathtools}
\usepackage{comment}
\excludecomment{commentEnv}
\usepackage{array}
\newcolumntype{C}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
\def\makeuppercase#1{
\expandafter\newcommand\csname cal#1\endcsname{\mathcal{#1}}
\expandafter\newcommand\csname adv#1\endcsname{\mathcal{#1}}
\expandafter\newcommand\csname frak#1\endcsname{\mathfrak{#1}}
\expandafter\newcommand\csname bb#1\endcsname{\mathbb{#1}}
\expandafter\newcommand\csname bf#1\endcsname{\textbf{#1}}
}
\def\makelowercase#1{
\expandafter\newcommand\csname frak#1\endcsname{\mathfrak{#1}}
\expandafter\newcommand\csname bf#1\endcsname{\textbf{#1}}
}
\newcounter{char}
\setcounter{char}{1}
\loop
\edef\letter{\alph{char}}
\edef\Letter{\Alph{char}}
\expandafter\makelowercase\letter
\expandafter\makeuppercase\Letter
\stepcounter{char}
\unless\ifnum\thechar>26
\repeat
\newcommand{\quotes}[1]{``#1''}
\newcommand{\filename}[1]{\texttt{#1}}
\newcommand{\cryptoverif}{Crypto\-Verif}
\newcommand{\cv}{\cryptoverif}
\newcommand{\bottom}{\ensuremath{\perp}}
%\usepackage{pgfpages}
%\setbeameroption{show notes on second screen=right}
\usepackage{multicol}
\usepackage{qrcode}
%% msc message diagrams
%\usepackage{msc5}
%\newcommand{\laction}[2]{$\begin{array}{c}\mbox{\textrm{#1}}\\#2\end{array}$}
%\newcommand{\poormanshead}[1]{\textcolor{darkgray}{#1}}
%\newcommand{\poormansline}[2]{\textcolor{gray}{\phantom{-- }\texttt{-- -- -- -- -- -- -- --\phantom{ --}}}\poormanshead{#1}\textcolor{gray}{\texttt{\phantom{-- }#2}}}
\definecolor{light-gray}{gray}{0.5}
% https://gamedev.stackexchange.com/questions/133078/what-colors-to-choose-for-colorblind-people
\definecolor{keyOne}{rgb}{.9,.6,0} % orange
\definecolor{keyTwo}{rgb}{.35,.7,.9} % sky blue
\definecolor{keyThree}{rgb}{0,.6,.5} % bluish green
\definecolor{keyFour}{rgb}{.8,.4,0} % vermilion
%\definecolor{keyFour}{rgb}{.8,.6,.7} % reddish purple
%\definecolor{keyFour}{rgb}{0,.45,.7} % blue
\newcommand{\screenshotframe}[2]{%
\begin{frame}{#1}
\vfill
\begin{center}
\includegraphics[width=.95\textwidth,height=.95\textheight,keepaspectratio]{#2}
\end{center}
\vfill
\end{frame}
}
\usepackage{listings}
\lstdefinelanguage{cryptoverif}
{morekeywords={collision, const, crypto, define, defined, do, else, end, equation, equiv,
event, event_abort, expand, find, forall, foreach, fun, get, implementation, in,
if, inj, insert, length, let, letfun, max, maxlength, newOracle, orfind, otheruses,
param, proba, public_vars, process, proof, query, return, secret, secret1, set, suchthat, success, simplify, then,
table, time, type},
otherkeywords={<-, <-R, &&},
sensitive=true,
morecomment=[s]{(*}{*)},
morestring=[b]",
}
\lstdefinelanguage{cvoutput}
{morekeywords={},
otherkeywords={},
sensitive=true,
morecomment=[s]{(*}{*)},
morestring=[b]",
}
\lstset{
language=cvoutput,
basicstyle=\ttfamily,
commentstyle=\color{black!55},
keywordstyle=\bfseries\color{green!40!black}
}
\lstset{
language=cryptoverif,
basicstyle=\ttfamily,
commentstyle=\color{black!55},
keywordstyle=\bfseries\color{green!40!black}
}
\usepackage{bbding}
\newcommand*\itemtick{\item[\Checkmark]}
\newcommand*\itemfail{\item[\XSolidBrush]}
\title{%
Rosenpass
}
\subtitle{%
Securing \& Deploying Post-Quantum WireGuard
}
\author{\textbf{Karolin Varner}, with Benjamin Lipp, Wanja Zaeske, Lisa Schmidt}
\institute{\url{https://rosenpass.eu/whitepaper.pdf}}
\titlegraphic{\hfill\includegraphics[height=2.5cm]{tex/RosenPass-Logo.pdf}}
\begin{document}
\maketitle
\input{yrcs-talk-content}
\end{document}

View File

@@ -1,10 +1,7 @@
//! Types types for dealing with (secret-) values
//! This module contains various types for dealing with secrets
//!
//! These types use type level coloring to make accidential leackage of secrets extra hard.
//!
//! These types use type level coloring to make accidential leackage of secrets extra hard. Both [Secret] and [Public] own their data, but the memory backing
//! [Secret] is special:
//! - as it is heap allocated, we can actively zeroize the memory before freeing it.
//! - 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 crate::{
sodium::{rng, zeroize},

View File

@@ -1,6 +1,3 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use {
crate::{prftree::PrfTree, sodium::KEY_SIZE},
anyhow::Result,

View File

@@ -3,7 +3,6 @@ pub mod util;
#[macro_use]
pub mod sodium;
pub mod coloring;
#[rustfmt::skip]
pub mod labeled_prf;
pub mod msgs;
pub mod pqkem;

View File

@@ -3,8 +3,9 @@ use log::{error, info};
use rosenpass::{
attempt,
coloring::{Public, Secret},
pqkem::{StaticKEM, KEM},
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
multimatch,
pqkem::{SKEM, KEM},
protocol::{SPk, SSk, MsgBuf, PeerPtr, Server as CryptoServer, SymKey, Timing},
sodium::sodium_init,
util::{b64_reader, b64_writer, fmt_b64},
};
@@ -312,7 +313,7 @@ pub fn cmd_keygen(mut args: ArgsWalker) -> Result<()> {
// Cmd
let (mut ssk, mut spk) = (SSk::random(), SPk::random());
unsafe {
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
SKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(sf.unwrap())?;
spk.store_secret(pf.unwrap())?;
}
@@ -439,7 +440,12 @@ pub fn cmd_exchange(mut args: ArgsWalker) -> Result<()> {
}
impl AppServer {
pub fn new<A: ToSocketAddrs>(sk: SSk, pk: SPk, addr: A, verbosity: Verbosity) -> Result<Self> {
pub fn new<A: ToSocketAddrs>(
sk: SSk,
pk: SPk,
addr: A,
verbosity: Verbosity,
) -> Result<Self> {
Ok(Self {
crypt: CryptoServer::new(sk, pk),
sock: UdpSocket::bind(addr)?,
@@ -508,10 +514,6 @@ impl AppServer {
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
/// assumes that closure leaves a message in `tx`
/// assumes that closure returns the length of message in bytes
macro_rules! tx_maybe_with {
($peer:expr, $fn:expr) => {
attempt!({
@@ -539,36 +541,22 @@ impl AppServer {
DeleteKey(peer) => self.output_key(peer, Stale, &SymKey::random())?,
ReceivedMessage(len, addr) => {
match self.crypt.handle_msg(&rx[..len], &mut *tx) {
Err(ref e) => {
self.verbose().then(|| {
info!(
"error processing incoming message from {:?}: {:?} {}",
addr,
e,
e.backtrace()
);
});
multimatch!(self.crypt.handle_msg(&rx[..len], &mut *tx),
Err(ref e) =>
self.verbose().then(||
info!("error processing incoming message from {:?}: {:?} {}", addr, e, e.backtrace())),
Ok(HandleMsgResult { resp: Some(len), .. }) => {
self.sock.send_to(&tx[0..len], addr)?
},
Ok(HandleMsgResult { exchanged_with: Some(p), .. }) => {
let ap = AppPeerPtr::lift(p);
ap.get_app_mut(self).tx_addr = Some(addr);
// TODO: Maybe we should rather call the key "rosenpass output"?
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
}
Ok(HandleMsgResult {
resp,
exchanged_with,
..
}) => {
if let Some(len) = resp {
self.sock.send_to(&tx[0..len], addr)?;
}
if let Some(p) = exchanged_with {
let ap = AppPeerPtr::lift(p);
ap.get_app_mut(self).tx_addr = Some(addr);
// TODO: Maybe we should rather call the key "rosenpass output"?
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
}
}
}
);
}
};
}

View File

@@ -1,8 +1,9 @@
//! Data structures representing the messages going over the wire
//! # Messages
//!
//! This module contains de-/serialization of the protocol's messages. Thats kind
//! of a lie, since no actual ser/de happens. Instead, the structures offer views
//! into mutable byte slices (`&mut [u8]`), allowing to modify the fields of an
//! This module contains data structures that help in the
//! serialization/deserialization (ser/de) of messages. Thats kind of a lie,
//! since no actual ser/de happens. Instead, the structures offer views into
//! mutable byte slices (`&mut [u8]`), allowing to modify the fields of an
//! always serialized instance of the data in question. This is closely related
//! to the concept of lenses in function programming; more on that here:
//! [https://sinusoid.es/misc/lager/lenses.pdf](https://sinusoid.es/misc/lager/lenses.pdf)
@@ -243,9 +244,9 @@ data_lense! { InitHello :=
/// Randomly generated connection id
sidi: 4,
/// Kyber 512 Ephemeral Public Key
epki: EphemeralKEM::PK_LEN,
epki: EKEM::PK_LEN,
/// Classic McEliece Ciphertext
sctr: StaticKEM::CT_LEN,
sctr: SKEM::CT_LEN,
/// Encryped: 16 byte hash of McEliece initiator static key
pidic: sodium::AEAD_TAG_LEN + 32,
/// Encrypted TAI64N Time Stamp (against replay attacks)
@@ -258,9 +259,9 @@ data_lense! { RespHello :=
/// Copied from InitHello
sidi: 4,
/// Kyber 512 Ephemeral Ciphertext
ecti: EphemeralKEM::CT_LEN,
ecti: EKEM::CT_LEN,
/// Classic McEliece Ciphertext
scti: StaticKEM::CT_LEN,
scti: SKEM::CT_LEN,
/// Empty encrypted message (just an auth tag)
auth: sodium::AEAD_TAG_LEN,
/// Responders handshake state in encrypted form

View File

@@ -1,6 +1,5 @@
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
//!
//! KEMs are the interface provided by almost all post-quantum
//! This module contains Traits and implementations for Key Encapsulation
//! Mechanisms (KEM). KEMs are the interface provided by almost all post-quantum
//! secure key exchange mechanisms.
//!
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
@@ -8,7 +7,7 @@
//!
//! encapsulation.
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
//! Mechanism. Two implementations for it are provided, [SKEM] and [EKEM].
use crate::{RosenpassError, RosenpassMaybeError};
@@ -51,7 +50,7 @@ pub trait KEM {
/// Classic McEliece is chosen because of its high security margin and its small
/// ciphertexts. The public keys are humongous, but (being static keys) the are never transmitted over
/// the wire so this is not a big problem.
pub struct StaticKEM;
pub struct SKEM;
/// # Safety
///
@@ -66,7 +65,7 @@ pub struct StaticKEM;
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl KEM for StaticKEM {
impl KEM for SKEM {
const SK_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_secret_key as usize;
const PK_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_public_key as usize;
const CT_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_ciphertext as usize;
@@ -120,7 +119,7 @@ impl KEM for StaticKEM {
/// wireguard paper claimed that CPA security would be sufficient. Nonetheless we choose kyber
/// which provides CCA security since there are no publicly vetted KEMs out there which provide
/// only CPA security.
pub struct EphemeralKEM;
pub struct EKEM;
/// # Safety
///
@@ -135,7 +134,7 @@ pub struct EphemeralKEM;
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl KEM for EphemeralKEM {
impl KEM for EKEM {
const SK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_secret_key as usize;
const PK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_public_key as usize;
const CT_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_ciphertext as usize;
@@ -144,7 +143,8 @@ impl KEM for EphemeralKEM {
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_kyber_512_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()).to_rg_error()
oqs_sys::kem::OQS_KEM_kyber_512_keypair(pk.as_mut_ptr(), sk.as_mut_ptr())
.to_rg_error()
}
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
@@ -152,8 +152,12 @@ impl KEM for EphemeralKEM {
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_kyber_512_encaps(ct.as_mut_ptr(), shk.as_mut_ptr(), pk.as_ptr())
.to_rg_error()
oqs_sys::kem::OQS_KEM_kyber_512_encaps(
ct.as_mut_ptr(),
shk.as_mut_ptr(),
pk.as_ptr(),
)
.to_rg_error()
}
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError> {
@@ -161,8 +165,12 @@ impl KEM for EphemeralKEM {
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_kyber_512_decaps(shk.as_mut_ptr(), ct.as_ptr(), sk.as_ptr())
.to_rg_error()
oqs_sys::kem::OQS_KEM_kyber_512_decaps(
shk.as_mut_ptr(),
ct.as_ptr(),
sk.as_ptr(),
)
.to_rg_error()
}
}
}

View File

@@ -1,4 +1,3 @@
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
use {
crate::{
coloring::Secret,

View File

@@ -1,27 +1,21 @@
//! 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
//! succesfull.
//! The most important types in this module probably are [PollResult] & [Server].
//! Once a [Server] was created, the server is provided with new messages via
//! the [Server::handle_msg] method. The [Server::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.
//!
//! TODO explain briefly the role of epki
//!
//! # Example Handshake
//!
//! This example illustrates a minimal setup for a key-exchange between two
//! [CryptoServer].
//! TODO finish doctest example
//!
//! ```
//! use rosenpass::{
//! pqkem::{StaticKEM, KEM},
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
//! pqkem::{SKEM, KEM},
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, Server, SymKey},
//! };
//! # fn main() -> Result<(), rosenpass::RosenpassError> {
//!
@@ -30,38 +24,27 @@
//!
//! // initialize public and private 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.secret_mut())?;
//! SKEM::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_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.secret_mut())?;
//! SKEM::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_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());
//! let mut a = Server::new(peer_a_sk, peer_a_pk.clone());
//! let mut b = Server::new(peer_b_sk, peer_b_pk.clone());
//!
//! // introduce peers to each other
//! a.add_peer(Some(psk.clone()), peer_b_pk).unwrap();
//! b.add_peer(Some(psk), peer_a_pk).unwrap();
//!
//! // declare buffers for message exchange
//! // let them talk
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
//!
//! // let a initiate a handshake
//! let length = a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice());
//!
//! // let b respond to a and a respond to b, in two rounds
//! for _ in 0..2 {
//! b.handle_msg(&a_buf[..], &mut b_buf[..]);
//! a.handle_msg(&b_buf[..], &mut a_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.unwrap().secret(), b_key.unwrap().secret(),
//! "the key exchanged failed to establish a shared secret");
//! let sz = a.initiate_handshake(PeerPtr(0), &mut *a_buf).unwrap();
//! //let (a_key, b_key) = handle(a, &mut a_buf, sz, b, &mut b_buf).unwrap();
//! //assert_eq!(a_key.unwrap().secret(), b_key.unwrap().secret(),
//! // "the key exchanged failed to establish a shared secret");
//! # Ok(())
//! # }
//! ```
@@ -90,7 +73,6 @@ pub const RTX_BUFFER_SIZE: usize = max_usize(
);
/// A type for time, e.g. for backoff before re-tries
// TODO replace by core::time::Duration
pub type Timing = f64;
/// Before Common Era (or more practically: Definitely so old it needs refreshing)
@@ -138,10 +120,10 @@ pub fn has_happened(ev: Timing, now: Timing) -> bool {
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
pub type SPk = Secret<{ StaticKEM::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
pub type SSk = Secret<{ StaticKEM::SK_LEN }>;
pub type EPk = Public<{ EphemeralKEM::PK_LEN }>;
pub type ESk = Secret<{ EphemeralKEM::SK_LEN }>;
pub type SPk = Secret<{ SKEM::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
pub type SSk = Secret<{ SKEM::SK_LEN }>;
pub type EPk = Public<{ EKEM::PK_LEN }>;
pub type ESk = Secret<{ EKEM::SK_LEN }>;
pub type SymKey = Secret<KEY_SIZE>;
pub type SymHash = Public<KEY_SIZE>;
@@ -156,19 +138,9 @@ pub type MsgBuf = Public<MAX_MESSAGE_LEN>;
pub type PeerNo = usize;
/// Implementation of the cryptographic protocol
///
/// The scope of this is:
///
/// - logical protocol flow
/// - timeout handling
/// - key exchange
///
/// Not in scope of this struct:
///
/// - handling of external IO (like sockets etc.)
/// Implementation of the actual cryptographic server
#[derive(Debug)]
pub struct CryptoServer {
pub struct Server {
pub timebase: Timebase,
// Server Crypto
@@ -210,7 +182,6 @@ pub struct Peer {
pub session: Option<Session>,
pub handshake: Option<InitiatorHandshake>,
pub initiation_requested: bool,
pub th: TransmissionHandler,
}
impl Peer {
@@ -222,7 +193,6 @@ impl Peer {
session: None,
initiation_requested: false,
handshake: None,
th: TransmissionHandler::default(),
}
}
}
@@ -283,36 +253,6 @@ pub struct InitiatorHandshake {
pub tx_buf: MsgBuf,
}
#[derive(Debug)]
pub struct TransmissionHandler {
// when to attempt the first transmission
tx_at: Timing,
// average interval between transmission and subsequent retransmissions
tx_interval: Timing,
// how often to attemp (re-) transmission
tx_count: u8,
// length of message in `tx_buf`
tx_len: usize,
// buffer for the message
tx_buf: MsgBuf,
}
impl Default for TransmissionHandler {
fn default() -> Self {
Self {
tx_at: 0.0,
tx_interval: 1.0,
tx_count: 0,
tx_len: 0,
tx_buf: Public::zero(),
}
}
}
#[derive(Debug)]
pub struct Session {
// Metadata
@@ -352,37 +292,37 @@ enum Lifecycle {
/// Implemented for information (secret and public) that has an expire date
trait Mortal {
/// Time of creation, when [Lifecycle::Void] -> [Lifecycle::Young]
fn created_at(&self, srv: &CryptoServer) -> Option<Timing>;
fn created_at(&self, srv: &Server) -> Option<Timing>;
/// The time where [Lifecycle::Young] -> [Lifecycle::Retired]
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing>;
fn retire_at(&self, srv: &Server) -> Option<Timing>;
/// The time where [Lifecycle::Retired] -> [Lifecycle::Dead]
fn die_at(&self, srv: &CryptoServer) -> Option<Timing>;
fn die_at(&self, srv: &Server) -> Option<Timing>;
}
// BUSINESS LOGIC DATA STRUCTURES ////////////////
/// Valid index to [CryptoServer::peers]
/// Valid index to [Server::peers]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct PeerPtr(pub usize);
/// Valid index to [CryptoServer::peers]
/// Valid index to [Server::peers]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct IniHsPtr(pub usize);
/// Valid index to [CryptoServer::peers]
/// Valid index to [Server::peers]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct SessionPtr(pub usize);
/// Valid index to [CryptoServer::biscuit_keys]
/// Valid index to [Server::biscuit_keys]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct BiscuitKeyPtr(pub usize);
impl PeerPtr {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a Peer {
pub fn get<'a>(&self, srv: &'a Server) -> &'a Peer {
&srv.peers[self.0]
}
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut Peer {
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Peer {
&mut srv.peers[self.0]
}
@@ -396,11 +336,11 @@ impl PeerPtr {
}
impl IniHsPtr {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a Option<InitiatorHandshake> {
pub fn get<'a>(&self, srv: &'a Server) -> &'a Option<InitiatorHandshake> {
&srv.peers[self.0].handshake
}
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut Option<InitiatorHandshake> {
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Option<InitiatorHandshake> {
&mut srv.peers[self.0].handshake
}
@@ -410,7 +350,7 @@ impl IniHsPtr {
pub fn insert<'a>(
&self,
srv: &'a mut CryptoServer,
srv: &'a mut Server,
hs: InitiatorHandshake,
) -> Result<&'a mut InitiatorHandshake> {
srv.register_session(hs.core.sidi, self.peer())?;
@@ -419,7 +359,7 @@ impl IniHsPtr {
Ok(self.peer().get_mut(srv).handshake.insert(hs))
}
pub fn take(&self, srv: &mut CryptoServer) -> Option<InitiatorHandshake> {
pub fn take(&self, srv: &mut Server) -> Option<InitiatorHandshake> {
let r = self.peer().get_mut(srv).handshake.take();
if let Some(ref stale) = r {
srv.unregister_session_if_vacant(stale.core.sidi, self.peer());
@@ -429,11 +369,11 @@ impl IniHsPtr {
}
impl SessionPtr {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a Option<Session> {
pub fn get<'a>(&self, srv: &'a Server) -> &'a Option<Session> {
&srv.peers[self.0].session
}
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut Option<Session> {
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Option<Session> {
&mut srv.peers[self.0].session
}
@@ -441,13 +381,13 @@ impl SessionPtr {
PeerPtr(self.0)
}
pub fn insert<'a>(&self, srv: &'a mut CryptoServer, ses: Session) -> Result<&'a mut Session> {
pub fn insert<'a>(&self, srv: &'a mut Server, ses: Session) -> Result<&'a mut Session> {
self.take(srv);
srv.register_session(ses.sidm, self.peer())?;
Ok(self.peer().get_mut(srv).session.insert(ses))
}
pub fn take(&self, srv: &mut CryptoServer) -> Option<Session> {
pub fn take(&self, srv: &mut Server) -> Option<Session> {
let r = self.peer().get_mut(srv).session.take();
if let Some(ref stale) = r {
srv.unregister_session_if_vacant(stale.sidm, self.peer());
@@ -457,23 +397,23 @@ impl SessionPtr {
}
impl BiscuitKeyPtr {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a BiscuitKey {
pub fn get<'a>(&self, srv: &'a Server) -> &'a BiscuitKey {
&srv.biscuit_keys[self.0]
}
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut BiscuitKey {
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut BiscuitKey {
&mut srv.biscuit_keys[self.0]
}
}
// DATABASE //////////////////////////////////////
impl CryptoServer {
/// Initiate a new [CryptoServer] based on a secret key (`sk`) and a public key
impl Server {
/// Initiate a new [Server] based on a secret key (`sk`) and a public key
/// (`pk`)
pub fn new(sk: SSk, pk: SPk) -> CryptoServer {
pub fn new(sk: SSk, pk: SPk) -> Server {
let tb = Timebase::default();
CryptoServer {
Server {
sskm: sk,
spkm: pk,
@@ -492,11 +432,10 @@ impl CryptoServer {
(0..self.biscuit_keys.len()).map(BiscuitKeyPtr)
}
#[rustfmt::skip]
pub fn pidm(spkm: &[u8]) -> Result<PeerId> {
pub fn pidm(&self) -> Result<PeerId> {
Ok(Public::new(
lprf::peerid()?
.mix(spkm)?
.mix(self.spkm.secret())?
.into_value()))
}
@@ -517,7 +456,6 @@ impl CryptoServer {
session: None,
handshake: None,
initiation_requested: false,
th: TransmissionHandler::default(),
};
let peerid = peer.pidt()?;
let peerno = self.peers.len();
@@ -553,7 +491,7 @@ impl CryptoServer {
/// handshake phase
pub fn unregister_session_if_vacant(&mut self, id: SessionId, peer: PeerPtr) {
match (peer.session().get(self), peer.hs().get(self)) {
(Some(ses), _) if ses.sidm == id => {} /* nop */
(Some(ses), _) if ses.sidm == id => {} /* nop */
(_, Some(hs)) if hs.core.sidi == id => {} /* nop */
_ => self.unregister_session(id),
};
@@ -620,11 +558,9 @@ impl Peer {
session: None,
handshake: None,
initiation_requested: false,
th: TransmissionHandler::default(),
}
}
#[rustfmt::skip]
pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new(
lprf::peerid()?
@@ -678,35 +614,35 @@ impl BiscuitKey {
// LIFECYCLE MANAGEMENT //////////////////////////
impl Mortal for IniHsPtr {
fn created_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn created_at(&self, srv: &Server) -> Option<Timing> {
self.get(srv).as_ref().map(|hs| hs.created_at)
}
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn retire_at(&self, srv: &Server) -> Option<Timing> {
self.die_at(srv)
}
fn die_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn die_at(&self, srv: &Server) -> Option<Timing> {
self.created_at(srv).map(|t| t + REJECT_AFTER_TIME)
}
}
impl Mortal for SessionPtr {
fn created_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn created_at(&self, srv: &Server) -> Option<Timing> {
self.get(srv).as_ref().map(|p| p.created_at)
}
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn retire_at(&self, srv: &Server) -> Option<Timing> {
self.created_at(srv).map(|t| t + REKEY_AFTER_TIME)
}
fn die_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn die_at(&self, srv: &Server) -> Option<Timing> {
self.created_at(srv).map(|t| t + REJECT_AFTER_TIME)
}
}
impl Mortal for BiscuitKeyPtr {
fn created_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn created_at(&self, srv: &Server) -> Option<Timing> {
let t = self.get(srv).created_at;
if t < 0.0 {
None
@@ -715,11 +651,11 @@ impl Mortal for BiscuitKeyPtr {
}
}
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn retire_at(&self, srv: &Server) -> Option<Timing> {
self.created_at(srv).map(|t| t + BISCUIT_EPOCH)
}
fn die_at(&self, srv: &CryptoServer) -> Option<Timing> {
fn die_at(&self, srv: &Server) -> Option<Timing> {
self.retire_at(srv).map(|t| t + BISCUIT_EPOCH)
}
}
@@ -727,21 +663,21 @@ impl Mortal for BiscuitKeyPtr {
/// Trait extension to the [Mortal] Trait, that enables nicer access to timing
/// information
trait MortalExt: Mortal {
fn life_left(&self, srv: &CryptoServer) -> Option<Timing>;
fn youth_left(&self, srv: &CryptoServer) -> Option<Timing>;
fn lifecycle(&self, srv: &CryptoServer) -> Lifecycle;
fn life_left(&self, srv: &Server) -> Option<Timing>;
fn youth_left(&self, srv: &Server) -> Option<Timing>;
fn lifecycle(&self, srv: &Server) -> Lifecycle;
}
impl<T: Mortal> MortalExt for T {
fn life_left(&self, srv: &CryptoServer) -> Option<Timing> {
fn life_left(&self, srv: &Server) -> Option<Timing> {
self.die_at(srv).map(|t| t - srv.timebase.now())
}
fn youth_left(&self, srv: &CryptoServer) -> Option<Timing> {
fn youth_left(&self, srv: &Server) -> Option<Timing> {
self.retire_at(srv).map(|t| t - srv.timebase.now())
}
fn lifecycle(&self, srv: &CryptoServer) -> Lifecycle {
fn lifecycle(&self, srv: &Server) -> Lifecycle {
match (self.youth_left(srv), self.life_left(srv)) {
(_, Some(t)) if has_happened(t, 0.0) => Lifecycle::Dead,
(Some(t), _) if has_happened(t, 0.0) => Lifecycle::Retired,
@@ -753,22 +689,14 @@ impl<T: Mortal> MortalExt for T {
// MESSAGE HANDLING //////////////////////////////
impl CryptoServer {
impl Server {
/// Initiate a new handshake, put it to the `tx_buf` __and__ to the
/// retransmission storage
// NOTE retransmission? yes if initiator, no if responder
// TODO remove unecessary copying between global tx_buf and per-peer buf
// TODO move retransmission storage to io server
pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result<usize> {
let mut msg = tx_buf.envelope::<InitHello<()>>()?;
// Envelope::<InitHello>::default(); // TODO
// let mut msg = peer
// .get_mut(self)
// .th
// .tx_buf
// .as_mut_slice()
// .envelope::<InitHello<()>>()?;
let mut msg = tx_buf.envelope::<InitHello<()>>()?; // Envelope::<InitHello>::default(); // TODO
self.handle_initiation(peer, msg.payload_mut().init_hello()?)?;
let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?;
peer.hs()
@@ -783,8 +711,8 @@ pub struct HandleMsgResult {
pub resp: Option<usize>,
}
impl CryptoServer {
/// Respond to an incoming message
impl Server {
/// Repsond to an incoming message
///
/// # Overview
///
@@ -818,12 +746,11 @@ impl CryptoServer {
let mut len = 0;
let mut exchanged = false;
ensure!(!rx_buf.is_empty(), "received empty message, ignoring it");
let peer = match rx_buf[0].try_into() {
Ok(MsgType::InitHello) => {
let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = tx_buf.envelope::<RespHello<&mut [u8]>>()?;
let peer = self.handle_init_hello(
msg_in.payload().init_hello()?,
@@ -958,9 +885,6 @@ pub enum PollResult {
DeleteKey(PeerPtr),
SendInitiation(PeerPtr),
SendRetransmission(PeerPtr),
// Transmit(PeerPtr),
// /// implicitly respond to peerptr with msg in scratch_buf
// Respond
}
impl Default for PollResult {
@@ -1005,11 +929,11 @@ impl PollResult {
}
}
pub fn poll_child<P: Pollable>(&self, srv: &mut CryptoServer, p: &P) -> Result<PollResult> {
pub fn poll_child<P: Pollable>(&self, srv: &mut Server, p: &P) -> Result<PollResult> {
self.try_fold_with(|| p.poll(srv))
}
pub fn poll_children<P, I>(&self, srv: &mut CryptoServer, iter: I) -> Result<PollResult>
pub fn poll_children<P, I>(&self, srv: &mut Server, iter: I) -> Result<PollResult>
where
P: Pollable,
I: Iterator<Item = P>,
@@ -1075,10 +999,10 @@ pub fn void_poll<T, F: FnOnce() -> T>(f: F) -> impl FnOnce() -> PollResult {
}
pub trait Pollable {
fn poll(&self, srv: &mut CryptoServer) -> Result<PollResult>;
fn poll(&self, srv: &mut Server) -> Result<PollResult>;
}
impl CryptoServer {
impl Server {
/// Implements something like [Pollable::poll] for the server, with a
/// notable difference: since `self` already is the server, the signature
/// has to be different; `self` must be a `&mut` and already is a borrow to
@@ -1096,7 +1020,7 @@ impl CryptoServer {
}
impl Pollable for BiscuitKeyPtr {
fn poll(&self, srv: &mut CryptoServer) -> Result<PollResult> {
fn poll(&self, srv: &mut Server) -> Result<PollResult> {
begin_poll()
.sched(self.life_left(srv), void_poll(|| self.get_mut(srv).erase())) // Erase stale biscuits
.ok()
@@ -1104,7 +1028,7 @@ impl Pollable for BiscuitKeyPtr {
}
impl Pollable for PeerPtr {
fn poll(&self, srv: &mut CryptoServer) -> Result<PollResult> {
fn poll(&self, srv: &mut Server) -> Result<PollResult> {
let (ses, hs) = (self.session(), self.hs());
begin_poll()
.sched(hs.life_left(srv), void_poll(|| hs.take(srv))) // Silently erase old handshakes
@@ -1132,7 +1056,7 @@ impl Pollable for PeerPtr {
}
impl Pollable for IniHsPtr {
fn poll(&self, srv: &mut CryptoServer) -> Result<PollResult> {
fn poll(&self, srv: &mut Server) -> Result<PollResult> {
begin_poll().try_sched(self.retransmission_in(srv), || {
// Registering retransmission even if app does not retransmit.
// This explicitly permits applications to ignore the event.
@@ -1144,14 +1068,14 @@ impl Pollable for IniHsPtr {
// MESSAGE RETRANSMISSION ////////////////////////
impl CryptoServer {
impl Server {
pub fn retransmit_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result<usize> {
peer.hs().apply_retransmission(self, tx_buf)
}
}
impl IniHsPtr {
pub fn store_msg_for_retransmission(&self, srv: &mut CryptoServer, msg: &[u8]) -> Result<()> {
pub fn store_msg_for_retransmission(&self, srv: &mut Server, msg: &[u8]) -> Result<()> {
let ih = self
.get_mut(srv)
.as_mut()
@@ -1163,7 +1087,7 @@ impl IniHsPtr {
Ok(())
}
pub fn apply_retransmission(&self, srv: &mut CryptoServer, tx_buf: &mut [u8]) -> Result<usize> {
pub fn apply_retransmission(&self, srv: &mut Server, tx_buf: &mut [u8]) -> Result<usize> {
let ih = self
.get_mut(srv)
.as_mut()
@@ -1172,7 +1096,7 @@ impl IniHsPtr {
Ok(ih.tx_len)
}
pub fn register_retransmission(&self, srv: &mut CryptoServer) -> Result<()> {
pub fn register_retransmission(&self, srv: &mut Server) -> Result<()> {
let tb = srv.timebase.clone();
let ih = self
.get_mut(srv)
@@ -1192,7 +1116,7 @@ impl IniHsPtr {
Ok(())
}
pub fn retransmission_in(&self, srv: &mut CryptoServer) -> Option<Timing> {
pub fn retransmission_in(&self, srv: &mut Server) -> Option<Timing> {
self.get(srv)
.as_ref()
.map(|hs| hs.tx_retry_at - srv.timebase.now())
@@ -1206,7 +1130,7 @@ where
M: LenseView,
{
/// Calculate the message authentication code (`mac`)
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
pub fn seal(&mut self, peer: PeerPtr, srv: &Server) -> Result<()> {
let mac = lprf::mac()?
.mix(peer.get(srv).spkt.secret())?
.mix(self.until_mac())?;
@@ -1221,14 +1145,14 @@ where
M: LenseView,
{
/// Check the message authentication code
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
pub fn check_seal(&self, srv: &Server) -> Result<bool> {
let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?;
Ok(sodium_memcmp(self.mac(), &expected.into_value()[..16]))
}
}
impl InitiatorHandshake {
pub fn zero_with_timestamp(srv: &CryptoServer) -> Self {
pub fn zero_with_timestamp(srv: &Server) -> Self {
InitiatorHandshake {
created_at: srv.timebase.now(),
next: HandshakeStateMachine::RespHello,
@@ -1303,7 +1227,7 @@ impl HandshakeState {
pub fn store_biscuit(
&mut self,
srv: &mut CryptoServer,
srv: &mut Server,
peer: PeerPtr,
biscuit_ct: &mut [u8],
) -> Result<&mut Self> {
@@ -1346,7 +1270,7 @@ impl HandshakeState {
/// Takes an encrypted biscuit and tries to decrypt the contained
/// information
pub fn load_biscuit(
srv: &CryptoServer,
srv: &Server,
biscuit_ct: &[u8],
sidi: SessionId,
sidr: SessionId,
@@ -1397,7 +1321,7 @@ impl HandshakeState {
Ok((peer, no, hs))
}
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> {
pub fn enter_live(self, srv: &Server, role: HandshakeRole) -> Result<Session> {
let HandshakeState { ck, sidi, sidr } = self;
let tki = ck.mix(&lprf::ini_enc()?)?.into_secret();
let tkr = ck.mix(&lprf::res_enc()?)?.into_secret();
@@ -1420,10 +1344,7 @@ impl HandshakeState {
}
}
impl CryptoServer {
/// Get the shared key that was established with given peer
///
/// Fail if no session is available with the peer
impl Server {
pub fn osk(&self, peer: PeerPtr) -> Result<SymKey> {
let session = peer
.session()
@@ -1434,56 +1355,37 @@ impl CryptoServer {
}
}
impl CryptoServer {
impl Server {
/// Implementation of the cryptographic protocol using the already
/// established primitives
pub fn handle_initiation(
&mut self,
peer_ptr: PeerPtr,
_ih: InitHello<&mut [u8]>,
peer: PeerPtr,
mut ih: InitHello<&mut [u8]>,
) -> Result<PeerPtr> {
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
let peer = &mut self.peers[peer_ptr.0];
let mut tx_buf = peer.th.tx_buf;
let mut msg = tx_buf.as_mut_slice().envelope::<InitHello<()>>()?;
peer.th.tx_len = msg.all_bytes().len();
let mut ih = msg.payload_mut().init_hello()?;
// IHI1
hs.core.init(peer.spkt.secret())?;
// IHI2
hs.core.sidi.randomize();
hs.core.init(peer.get(self).spkt.secret())?; // IHI1
hs.core.sidi.randomize(); // IHI2
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
// IHI3
EphemeralKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
EKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?; // IHI3
ih.epki_mut().copy_from_slice(&hs.epki.value);
hs.core.mix(ih.sidi())?.mix(ih.epki())?; // IHI4
hs.core.encaps_and_mix::<SKEM, { SKEM::SHK_LEN }>( // IHI5
ih.sctr_mut(),
peer.get(self).spkt.secret(),
)?;
hs.core // IHI6
.encrypt_and_mix(ih.pidic_mut(), self.pidm()?.as_ref())?;
hs.core // IHI7
.mix(self.spkm.secret())?
.mix(peer.get(self).psk.secret())?;
hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?; // IHI8
// IHI4
hs.core.mix(ih.sidi())?.mix(ih.epki())?;
// Update the handshake hash last (not changing any state on prior error)
peer.hs().insert(self, hs)?;
// IHI5
hs.core
.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
ih.sctr_mut(),
peer.spkt.secret(),
)?;
// IHI6
hs.core
.encrypt_and_mix(ih.pidic_mut(), Self::pidm(self.spkm.secret())?.as_ref())?;
// IHI7
hs.core.mix(self.spkm.secret())?.mix(peer.psk.secret())?;
// IHI8
hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?;
// Update the handshake hash last (not changing any state on prior error
peer.handshake.insert(hs);
Ok(peer_ptr)
Ok(peer)
}
pub fn handle_init_hello(
@@ -1495,58 +1397,36 @@ impl CryptoServer {
core.sidi = SessionId::from_slice(ih.sidi());
// IHR1
core.init(self.spkm.secret())?;
// IHR4
core.mix(ih.sidi())?.mix(ih.epki())?;
// IHR5
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
core.init(self.spkm.secret())?; // IHR1
core.mix(ih.sidi())?.mix(ih.epki())?; // IHR4
core.decaps_and_mix::<SKEM, { SKEM::SHK_LEN }>( // IHR5
self.sskm.secret(),
self.spkm.secret(),
ih.sctr(),
)?;
// IHR6
let peer = {
let peer = { // IHR6
let mut peerid = PeerId::zero();
core.decrypt_and_mix(&mut *peerid, ih.pidic())?;
self.find_peer(peerid)
.with_context(|| format!("No such peer {peerid:?}."))?
};
// IHR7
core.mix(peer.get(self).spkt.secret())?
core.mix(peer.get(self).spkt.secret())? // IHR7
.mix(peer.get(self).psk.secret())?;
core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?; // IHR8
// IHR8
core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?;
// TODO access this peers transmission_handler, bind it as `rh`
// RHR1
core.sidr.randomize();
core.sidr.randomize(); // RHR1
rh.sidi_mut().copy_from_slice(core.sidi.as_ref());
rh.sidr_mut().copy_from_slice(core.sidr.as_ref());
// RHR3
core.mix(rh.sidr())?.mix(rh.sidi())?;
// RHR4
core.encaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>(rh.ecti_mut(), ih.epki())?;
// RHR5
core.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
core.mix(rh.sidr())?.mix(rh.sidi())?; // RHR3
core.encaps_and_mix::<EKEM, { EKEM::SHK_LEN }>( // RHR4
rh.ecti_mut(), ih.epki())?;
core.encaps_and_mix::<SKEM, { SKEM::SHK_LEN }>( // RHR5
rh.scti_mut(),
peer.get(self).spkt.secret(),
)?;
// RHR6
core.store_biscuit(self, peer, rh.biscuit_mut())?;
// RHR7
core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?;
core.store_biscuit(self, peer, rh.biscuit_mut())?; // RHR6
core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?; // RHR7
Ok(peer)
}
@@ -1556,8 +1436,7 @@ impl CryptoServer {
rh: RespHello<&[u8]>,
mut ic: InitConf<&mut [u8]>,
) -> Result<PeerPtr> {
// RHI2
let peer = self
let peer = self // RHI2
.lookup_handshake(SessionId::from_slice(rh.sidi()))
.with_context(|| {
format!(
@@ -1598,28 +1477,19 @@ impl CryptoServer {
// TODO: decaps_and_mix should take Secret<> directly
// to save us from the repetitive secret unwrapping
// RHI3
core.mix(rh.sidr())?.mix(rh.sidi())?;
// RHI4
core.decaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>(
core.mix(rh.sidr())?.mix(rh.sidi())?; // RHI3
core.decaps_and_mix::<EKEM, { EKEM::SHK_LEN }>( // RHI4
hs!().eski.secret(),
&*hs!().epki,
rh.ecti(),
)?;
// RHI5
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
core.decaps_and_mix::<SKEM, { SKEM::SHK_LEN }>( // RHI5
self.sskm.secret(),
self.spkm.secret(),
rh.scti(),
)?;
// RHI6
core.mix(rh.biscuit())?;
// RHI7
core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?;
core.mix(rh.biscuit())?; // RHI6
core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?; // RHI7
// TODO: We should just authenticate the entire network package up to the auth
// tag as a pattern instead of mixing in fields separately
@@ -1627,19 +1497,14 @@ impl CryptoServer {
ic.sidi_mut().copy_from_slice(rh.sidi());
ic.sidr_mut().copy_from_slice(rh.sidr());
// ICI3
core.mix(ic.sidi())?.mix(ic.sidr())?;
core.mix(ic.sidi())?.mix(ic.sidr())?; // ICI3
ic.biscuit_mut().copy_from_slice(rh.biscuit());
// ICI4
core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?;
core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?; // ICI4
// Split() We move the secrets into the session; we do not
// delete the InitiatorHandshake, just clear it's secrets because
// we still need it for InitConf message retransmission to function.
// ICI7
peer.session()
peer.session() // ICI7
.insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?;
hs_mut!().core.erase();
hs_mut!().next = HandshakeStateMachine::RespConf;
@@ -1653,30 +1518,19 @@ impl CryptoServer {
mut rc: EmptyData<&mut [u8]>,
) -> Result<PeerPtr> {
// (peer, bn) ← LoadBiscuit(InitConf.biscuit)
// ICR1
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit(
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( // ICR1
self,
ic.biscuit(),
SessionId::from_slice(ic.sidi()),
SessionId::from_slice(ic.sidr()),
)?;
core.encrypt_and_mix(&mut [0u8; AEAD_TAG_LEN], &NOTHING)?; // ICR2
core.mix(ic.sidi())?.mix(ic.sidr())?; // ICR3
core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?; // ICR4
// ICR2
core.encrypt_and_mix(&mut [0u8; AEAD_TAG_LEN], &NOTHING)?;
// ICR3
core.mix(ic.sidi())?.mix(ic.sidr())?;
// ICR4
core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?;
// ICR5
if sodium_bigint_cmp(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 {
// ICR6
peer.get_mut(self).biscuit_used = biscuit_no;
// ICR7
peer.session()
if sodium_bigint_cmp(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 { // ICR5
peer.get_mut(self).biscuit_used = biscuit_no; // ICR6
peer.session() // ICR7
.insert(self, core.enter_live(self, HandshakeRole::Responder)?)?;
}
@@ -1745,32 +1599,3 @@ impl CryptoServer {
Ok(hs.peer())
}
}
#[cfg(test)]
mod test {
use super::*;
fn init_crypto_server() -> CryptoServer {
// always init libsodium before anything
crate::sodium::sodium_init().unwrap();
// initialize public and private key for the crypto server
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys");
CryptoServer::new(sk, pk)
}
/// The determination of the message type relies on reading the first byte of the message. Only
/// after that the length of the message is checked against the specified message type. This
/// test ensures that nothing breaks in the case of an empty message.
#[test]
#[should_panic = "called `Result::unwrap()` on an `Err` value: received empty message, ignoring it"]
fn handle_empty_message() {
let mut crypt = init_crypto_server();
let empty_rx_buf = [0u8; 0];
let mut tx_buf = [0u8; 0];
crypt.handle_msg(&empty_rx_buf, &mut tx_buf).unwrap();
}
}

View File

@@ -1,5 +1,3 @@
//! Bindings and helpers for accessing libsodium functions
use crate::util::*;
use anyhow::{ensure, Result};
use libsodium_sys as libsodium;

View File

@@ -11,6 +11,9 @@ DESCRIPTION
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.
This is a research project and quantum computers are not thought to become 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.
COMMANDS
keygen private-key <file-path> public-key <file-path>
@@ -32,11 +35,11 @@ COMMANDS
You must either specify the outfile or wireguard output option.
endpoint <ip>[:<port>]
Specifies the address where the peer can be reached. This will be automatically updated after the first successful
Specifies the address where the peer can be reached. This will be automatically updated after the first sucessfull
key exchange with the peer. If this is unspecified, the peer must initiate the connection.
preshared-key <file-path>
You may specify a pre-shared key which will be mixed into the final secret.
You may specifie a pre-shared key which will be mixied into the final secret.
outfile <file-path>
You may specify a file to write the exchanged keys to. If this option is specified, {0} will

View File

@@ -1,5 +1,3 @@
//! Helper functions and macros
use base64::{
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
write::EncoderWriter as B64Writer,
@@ -19,8 +17,8 @@ pub fn xor_into(a: &mut [u8], b: &[u8]) {
}
}
/// Concatenate two byte arrays
// TODO: Zeroize result?
/** Concatenate two byte arrays */
#[macro_export]
macro_rules! cat {
($len:expr; $($toks:expr),+) => {{
@@ -42,10 +40,9 @@ pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst:
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) {
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, to: &mut T) {
let src = src.borrow();
let dst = dst.borrow_mut();
let dst = to.borrow_mut();
let len = min(src.len(), dst.len());
dst[..len].copy_from_slice(&src[..len]);
}
@@ -100,6 +97,15 @@ impl Timebase {
}
}
#[macro_export]
macro_rules! multimatch {
($val:expr) => {{ () }};
($val:expr, $($p:pat => $thn:expr),*) => {{
let v = $val;
($(if let $p = v { Some($thn) } else { None }),*)
}};
}
pub fn mutating<T, F>(mut v: T, f: F) -> T
where
F: Fn(&mut T),