Compare commits

...

30 Commits

Author SHA1 Message Date
wucke13
94d57f2f87 chore: Release rosenpass version 0.1.2-rc.4 2023-04-13 19:52:09 +02:00
Emil Engler
279b3c49fc doc: add rosenpass.1 manual page
This commit adds a manual page for the rosenpass(1) utility written in
mdoc(7).
2023-04-11 20:00:02 +02:00
wucke13
9c40c77f71 Merge pull request #42 from rosenpass/dev/fix-#41
fix #41
2023-04-09 18:18:19 +02:00
wucke13
c79dffa627 fix #41
Adds a check for empty messages as well as unit test verifying that
empty messages are handled as desired.
2023-04-09 17:54:51 +02:00
wucke13
b8f19c5510 remove multimatch macro and fix typo 2023-04-09 17:52:41 +02:00
wucke13
f459b91abf fix documentation 2023-04-09 17:52:41 +02:00
wucke13
801ce4cd34 add check for broken documentation to qc workflow 2023-04-09 17:52:41 +02:00
wucke13
a36da78bc8 Merge pull request #38 from rosenpass/dev/fix-small-todos
improve documentation
2023-04-05 16:54:05 +02:00
wucke13
df02f616bf remove code format snowflakes
this also enables the `cargo fmt` check in the flake
2023-04-05 16:35:31 +02:00
wucke13
87b08bcee1 rename SKEM -> StaticKEM & EKEM -> EphemeralKEM 2023-04-05 16:35:26 +02:00
wucke13
897fa3daf6 improve documentation
- fix key-exchange doctest example
- add more info on the CryptoServer struct
- add more doc-strings
2023-04-04 22:13:23 +02:00
wucke13
953b861b4c add rustfmt::skip attributes on _special_ code
related to https://github.com/rust-lang/rustfmt/issues/4306
2023-04-04 22:13:23 +02:00
wucke13
1a61a99575 rename protocol::Server -> protocol::CryptoServer 2023-04-04 22:13:12 +02:00
Karolin Varner
25a7a0736b feat(papers): Reorder RWPQC slides 2023-03-24 18:09:21 +09:00
Marei (peiTeX)
844e9b3c7e support abstract only documents 2023-03-22 15:39:54 +09:00
Karolin Varner
a723951c71 feat(papers): CrossFyre 2023 Submission abstract 2023-03-22 15:39:54 +09:00
Marei (peiTeX)
be9ac58bf9 enlarge images 2023-03-20 23:49:02 +09:00
Marei (peiTeX)
75853159fe fix enquote 2023-03-20 23:49:02 +09:00
Marei (peiTeX)
95aba257fd fix node alignment 2023-03-20 23:49:02 +09:00
Karolin Varner
34d0bab5c5 feat(papers): Add RWPQC 23 slides 2023-03-20 23:49:02 +09:00
Mullana
91d1986126 transparent background for key exchange CMYK PDF 2023-03-20 11:58:32 +01:00
Mullana
319785cf6e Transparent Background für key exchange RGB PDF 2023-03-20 11:50:29 +01:00
Marei (peiTeX)
df5a6125cd small layout adjustments 2023-03-17 17:44:04 +01:00
Marei (peiTeX)
80697e6189 relative postioning in tikzpictures 2023-03-17 17:44:04 +01:00
Marei (peiTeX)
6212153c48 choose rgb images for slides 2023-03-17 17:44:04 +01:00
Marei (peiTeX)
4645ed5569 rule to rosenpass-pink 2023-03-17 17:44:04 +01:00
Karolin Varner
2aeb9067e2 feat(papers): Add YRCS talk slides 2023-03-17 17:44:04 +01:00
Benjamin Lipp
c64917fe2e Add LaTeX beamer template for talk 2023-03-17 17:44:04 +01:00
Karolin Varner
a011cc1e1c fix(whitepaper): Rollback adding an article to state, acknowledgement and replay
All of these are abstract so these are – in my view – zero articles.
https://www.toppr.com/guides/english/articles/omission-of-the-article
2023-03-09 07:57:31 +01:00
timothy mellor
ad75d2218c Lektorat für whitepaper 2023-03-09 07:57:31 +01:00
28 changed files with 1420 additions and 231 deletions

View File

@@ -12,15 +12,23 @@ jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- 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@v2
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
@@ -31,17 +39,27 @@ jobs:
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- run: rustup component add clippy
- name: Install xmllint
- name: Install libsodium
run: sudo apt-get install -y libsodium-dev
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
cargo-audit:
cargo-doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
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

2
Cargo.lock generated
View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "rosenpass"
version = "0.1.1"
version = "0.1.2-rc.4"
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::{CCAKEM, KEM},
protocol::{CcaPk, CcaSk, HandleMsgResult, MsgBuf, PeerPtr, Server, SymKey},
pqkem::{EphemeralKEM, CCAKEM},
protocol::{CcaPk, CcaSk, CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SymKey},
sodium::sodium_init,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn handle(
tx: &mut Server,
tx: &mut CryptoServer,
msgb: &mut MsgBuf,
msgl: usize,
rx: &mut Server,
rx: &mut CryptoServer,
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 Server, res: &mut Server) -> Result<()> {
fn hs(ini: &mut CryptoServer, res: &mut CryptoServer) -> Result<()> {
let (mut inib, mut resb) = (MsgBuf::zero(), MsgBuf::zero());
let sz = ini.initiate_handshake(PeerPtr(0), &mut *inib)?;
let (kini, kres) = handle(ini, &mut inib, sz, res, &mut resb)?;
@@ -44,10 +44,13 @@ fn keygen() -> Result<(CcaSk, CcaPk)> {
Ok((sk, pk))
}
fn make_server_pair() -> Result<(Server, Server)> {
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
let psk = SymKey::random();
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
let (mut a, mut b) = (Server::new(ska, pka.clone()), Server::new(skb, pkb.clone()));
let (mut a, mut b) = (
CryptoServer::new(ska, pka.clone()),
CryptoServer::new(skb, pkb.clone()),
);
a.add_peer(Some(psk.clone()), pkb)?;
b.add_peer(Some(psk), pka)?;
Ok((a, b))

105
doc/rosenpass.1 Normal file
View File

@@ -0,0 +1,105 @@
.Dd $Mdocdate$
.Dt ROSENPASS 1
.Os
.Sh NAME
.Nm rosenpass
.Nd builds post-quantum-secure VPNs
.Sh SYNOPSIS
.Nm
.Op COMMAND
.Op Ar OPTIONS ...
.Op Ar ARGS ...
.Sh DESCRIPTION
.Nm
performs cryptographic key exchanges that are secure against quantum-computers
and outputs the keys.
These keys can then be passed to various services such as wireguard or other
vpn services as pre-shared-keys to achieve security against attackers with
quantum computers.
.Pp
This is a research project and quantum computers are not thought to become
practical in less than ten years.
If you are not specifically tasked with developing post-quantum secure systems,
you probably do not need this tool.
.Ss COMMANDS
.Bl -tag -width Ds
.It Ar keygen private-key <file-path> public-key <file-path>
Generate a keypair to use in the exchange command later.
Send the public-key file to your communication partner and keep the private-key
file secret!
.It Ar exchange private-key <file-path> public-key <file-path> [ OPTIONS ] PEERS
Start a process to exchange keys with the specified peers.
You should specify at least one peer.
.Pp
It's
.Ar OPTIONS
are as follows:
.Bl -tag -width Ds
.It Ar listen <ip>[:<port>]
Instructs
.Nm
to listen on the specified interface and port.
By default
.Nm
will listen on all interfaces and select a random port.
.It Ar verbose
Extra logging.
.El
.El
.Ss PEER
Each
.Ar PEER
is defined as follows:
.Qq peer public-key <file-path> [endpoint <ip>[:<port>]] [preshared-key <file-path>] [outfile <file-path>] [wireguard <dev> <peer> <extra_params>]
.Pp
Providing a
.Ar PEER
instructs
.Nm
to exchange keys with the given peer and write the resulting PSK into the given
output file.
You must either specify the outfile or wireguard output option.
.Pp
The parameters of
.Ar PEER
are as follows:
.Bl -tag -width Ds
.It Ar endpoint <ip>[:<port>]
Specifies the address where the peer can be reached.
This will be automatically updated after the first successful key exchange with
the peer.
If this is unspecified, the peer must initiate the connection.
.It Ar preshared-key <file-path>
You may specify a pre-shared key which will be mixed into the final secret.
.It Ar outfile <file-path>
You may specify a file to write the exchanged keys to.
If this option is specified,
.Nm
will write a notification to standard out every time the key is updated.
.It Ar wireguard <dev> <peer> <extra_params>
This allows you to directly specify a wireguard peer to deploy the
pre-shared-key to.
You may specify extra parameters you would pass to
.Qq wg set
besides the preshared-key parameter which is used by
.Nm .
This makes it possible to add peers entirely from
.Nm .
.El
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
.Xr rp 1 ,
.Xr wg 1
.Sh STANDARDS
This tool is the reference implementation of the Rosenpass protocol, written
by Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt.
.Sh AUTHORS
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
.Pp
This manual page was written by
.An Emil Engler
.Sh BUGS
The bugs are tracked at
.Lk https://github.com/rosenpass/rosenpass/issues .

View File

@@ -62,6 +62,7 @@
gawk
wireguard-tools
];
# a function to generate a nix derivation for rosenpass against any
# given set of nixpkgs
rpDerivation = p:
@@ -89,6 +90,11 @@
# 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 =
@@ -241,6 +247,7 @@
#
devShells.default = pkgs.mkShell {
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
inherit (packages.rosenpass) RUST_MIN_STACK;
inputsFrom = [ packages.default ];
nativeBuildInputs = with pkgs; [
cargo-release
@@ -252,17 +259,16 @@
};
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 = {
# 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
# '';
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
cargo fmt --manifest-path=${./.}/Cargo.toml --check > $out
'';
nixpkgs-fmt = pkgs.runCommand "check-nixpkgs-fmt"
{ nativeBuildInputs = [ pkgs.nixpkgs-fmt ]; } ''
nixpkgs-fmt --check ${./.} && touch $out
@@ -272,6 +278,8 @@
cd ${./.} && prettier --check . && touch $out
'';
};
formatter = pkgs.nixpkgs-fmt;
}))
];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

View File

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

@@ -0,0 +1,137 @@
%
\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}

233
papers/rwpqc23-slides.tex Normal file
View File

@@ -0,0 +1,233 @@
\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,6 +79,8 @@
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,7 +171,12 @@ version={4.0},
\ExplSyntaxOn
\SetTemplatePreamble{
\hypersetup{pdftitle=\inserttitle,pdfauthor=The~Rosenpass~Project}
\title{\vspace*{-2.5cm}\includegraphics[width=4cm]{RosenPass-Logo}}
\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}
}
\author{\csname insertauthor\endcsname}
\subject{\csname insertsubject\endcsname}
\date{\vspace{-1cm}}
@@ -374,29 +379,28 @@ 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}
\tableofcontents
\bool_if:NT \g__ptxcd_tableofcontents_bool \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 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.
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.
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 to engineers and researchers implementing the protocol; a scientific paper discussing the security properties of Rosenpass is work in progress.
This document is a guide for engineers and researchers implementing the protocol; a scientific paper discussing the security properties of Rosenpass is work in progress.
---
\enlargethispage{5mm}
@@ -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 prove some privacy for its contents
* Computing the additional data for the biscuit encryption, to provide 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 message expected from the responder next
* A state indicator to keep track of the next message expected from the responder
* `sidi` Initiator session ID
* `sidr` Responder session ID
* `ck` The chaining key

View File

@@ -0,0 +1,300 @@
\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
%*

232
papers/yrcs-talk.tex Normal file
View File

@@ -0,0 +1,232 @@
\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,7 +1,10 @@
//! This module contains various types for dealing with secrets
//!
//! These types use type level coloring to make accidential leackage of secrets extra hard.
//! Types types for dealing with (secret-) values
//!
//! 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,3 +1,6 @@
//! 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,6 +3,7 @@ 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,9 +3,8 @@ use log::{error, info};
use rosenpass::{
attempt,
coloring::{Public, Secret},
multimatch,
pqkem::{SKEM, KEM},
protocol::{SPk, SSk, MsgBuf, PeerPtr, Server as CryptoServer, SymKey, Timing},
pqkem::{StaticKEM, KEM},
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
sodium::sodium_init,
util::{b64_reader, b64_writer, fmt_b64},
};
@@ -313,7 +312,7 @@ pub fn cmd_keygen(mut args: ArgsWalker) -> Result<()> {
// Cmd
let (mut ssk, mut spk) = (SSk::random(), SPk::random());
unsafe {
SKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(sf.unwrap())?;
spk.store_secret(pf.unwrap())?;
}
@@ -440,12 +439,7 @@ 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)?,
@@ -514,6 +508,10 @@ 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!({
@@ -541,22 +539,36 @@ impl AppServer {
DeleteKey(peer) => self.output_key(peer, Stale, &SymKey::random())?,
ReceivedMessage(len, addr) => {
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)?)?;
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()
);
});
}
);
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,9 +1,8 @@
//! # Messages
//! Data structures representing the messages going over the wire
//!
//! 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
//! 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
//! 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)
@@ -244,9 +243,9 @@ data_lense! { InitHello :=
/// Randomly generated connection id
sidi: 4,
/// Kyber 512 Ephemeral Public Key
epki: EKEM::PK_LEN,
epki: EphemeralKEM::PK_LEN,
/// Classic McEliece Ciphertext
sctr: SKEM::CT_LEN,
sctr: StaticKEM::CT_LEN,
/// Encryped: 16 byte hash of McEliece initiator static key
pidic: sodium::AEAD_TAG_LEN + 32,
/// Encrypted TAI64N Time Stamp (against replay attacks)
@@ -259,9 +258,9 @@ data_lense! { RespHello :=
/// Copied from InitHello
sidi: 4,
/// Kyber 512 Ephemeral Ciphertext
ecti: EKEM::CT_LEN,
ecti: EphemeralKEM::CT_LEN,
/// Classic McEliece Ciphertext
scti: SKEM::CT_LEN,
scti: StaticKEM::CT_LEN,
/// Empty encrypted message (just an auth tag)
auth: sodium::AEAD_TAG_LEN,
/// Responders handshake state in encrypted form

View File

@@ -1,5 +1,6 @@
//! This module contains Traits and implementations for Key Encapsulation
//! Mechanisms (KEM). KEMs are the interface provided by almost all post-quantum
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
//!
//! KEMs are the interface provided by almost all post-quantum
//! secure key exchange mechanisms.
//!
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
@@ -7,7 +8,7 @@
//!
//! encapsulation.
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [SKEM] and [EKEM].
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
use crate::{RosenpassError, RosenpassMaybeError};
@@ -50,7 +51,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 SKEM;
pub struct StaticKEM;
/// # Safety
///
@@ -65,7 +66,7 @@ pub struct SKEM;
/// 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 SKEM {
impl KEM for StaticKEM {
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;
@@ -119,7 +120,7 @@ impl KEM for SKEM {
/// 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 EKEM;
pub struct EphemeralKEM;
/// # Safety
///
@@ -134,7 +135,7 @@ pub struct EKEM;
/// 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 EKEM {
impl KEM for EphemeralKEM {
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;
@@ -143,8 +144,7 @@ impl KEM for EKEM {
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,12 +152,8 @@ impl KEM for EKEM {
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> {
@@ -165,12 +161,8 @@ impl KEM for EKEM {
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,3 +1,4 @@
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
use {
crate::{
coloring::Secret,

View File

@@ -1,21 +1,27 @@
//! Module containing the cryptographic protocol implementation
//!
//! # Overview
//!
//! 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.
//! 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.
//!
//! TODO explain briefly the role of epki
//!
//! # Example Handshake
//!
//! TODO finish doctest example
//! This example illustrates a minimal setup for a key-exchange between two
//! [CryptoServer].
//!
//! ```
//! use rosenpass::{
//! pqkem::{SKEM, KEM},
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, Server, SymKey},
//! pqkem::{StaticKEM, KEM},
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
//! };
//! # fn main() -> Result<(), rosenpass::RosenpassError> {
//!
@@ -24,27 +30,38 @@
//!
//! // initialize public and private key for peer a ...
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
//! SKEM::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
//! StaticKEM::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());
//! SKEM::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?;
//! StaticKEM::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 = Server::new(peer_a_sk, peer_a_pk.clone());
//! let mut b = Server::new(peer_b_sk, peer_b_pk.clone());
//! 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).unwrap();
//! b.add_peer(Some(psk), peer_a_pk).unwrap();
//!
//! // let them talk
//! // declare buffers for message exchange
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
//! 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");
//!
//! // 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");
//! # Ok(())
//! # }
//! ```
@@ -120,10 +137,10 @@ pub fn has_happened(ev: Timing, now: Timing) -> bool {
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
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 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 SymKey = Secret<KEY_SIZE>;
pub type SymHash = Public<KEY_SIZE>;
@@ -138,9 +155,19 @@ pub type MsgBuf = Public<MAX_MESSAGE_LEN>;
pub type PeerNo = usize;
/// Implementation of the actual cryptographic server
/// 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.)
#[derive(Debug)]
pub struct Server {
pub struct CryptoServer {
pub timebase: Timebase,
// Server Crypto
@@ -292,37 +319,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: &Server) -> Option<Timing>;
fn created_at(&self, srv: &CryptoServer) -> Option<Timing>;
/// The time where [Lifecycle::Young] -> [Lifecycle::Retired]
fn retire_at(&self, srv: &Server) -> Option<Timing>;
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing>;
/// The time where [Lifecycle::Retired] -> [Lifecycle::Dead]
fn die_at(&self, srv: &Server) -> Option<Timing>;
fn die_at(&self, srv: &CryptoServer) -> Option<Timing>;
}
// BUSINESS LOGIC DATA STRUCTURES ////////////////
/// Valid index to [Server::peers]
/// Valid index to [CryptoServer::peers]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct PeerPtr(pub usize);
/// Valid index to [Server::peers]
/// Valid index to [CryptoServer::peers]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct IniHsPtr(pub usize);
/// Valid index to [Server::peers]
/// Valid index to [CryptoServer::peers]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct SessionPtr(pub usize);
/// Valid index to [Server::biscuit_keys]
/// Valid index to [CryptoServer::biscuit_keys]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct BiscuitKeyPtr(pub usize);
impl PeerPtr {
pub fn get<'a>(&self, srv: &'a Server) -> &'a Peer {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a Peer {
&srv.peers[self.0]
}
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Peer {
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut Peer {
&mut srv.peers[self.0]
}
@@ -336,11 +363,11 @@ impl PeerPtr {
}
impl IniHsPtr {
pub fn get<'a>(&self, srv: &'a Server) -> &'a Option<InitiatorHandshake> {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a Option<InitiatorHandshake> {
&srv.peers[self.0].handshake
}
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Option<InitiatorHandshake> {
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut Option<InitiatorHandshake> {
&mut srv.peers[self.0].handshake
}
@@ -350,7 +377,7 @@ impl IniHsPtr {
pub fn insert<'a>(
&self,
srv: &'a mut Server,
srv: &'a mut CryptoServer,
hs: InitiatorHandshake,
) -> Result<&'a mut InitiatorHandshake> {
srv.register_session(hs.core.sidi, self.peer())?;
@@ -359,7 +386,7 @@ impl IniHsPtr {
Ok(self.peer().get_mut(srv).handshake.insert(hs))
}
pub fn take(&self, srv: &mut Server) -> Option<InitiatorHandshake> {
pub fn take(&self, srv: &mut CryptoServer) -> 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());
@@ -369,11 +396,11 @@ impl IniHsPtr {
}
impl SessionPtr {
pub fn get<'a>(&self, srv: &'a Server) -> &'a Option<Session> {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a Option<Session> {
&srv.peers[self.0].session
}
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Option<Session> {
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut Option<Session> {
&mut srv.peers[self.0].session
}
@@ -381,13 +408,13 @@ impl SessionPtr {
PeerPtr(self.0)
}
pub fn insert<'a>(&self, srv: &'a mut Server, ses: Session) -> Result<&'a mut Session> {
pub fn insert<'a>(&self, srv: &'a mut CryptoServer, 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 Server) -> Option<Session> {
pub fn take(&self, srv: &mut CryptoServer) -> 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());
@@ -397,23 +424,23 @@ impl SessionPtr {
}
impl BiscuitKeyPtr {
pub fn get<'a>(&self, srv: &'a Server) -> &'a BiscuitKey {
pub fn get<'a>(&self, srv: &'a CryptoServer) -> &'a BiscuitKey {
&srv.biscuit_keys[self.0]
}
pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut BiscuitKey {
pub fn get_mut<'a>(&self, srv: &'a mut CryptoServer) -> &'a mut BiscuitKey {
&mut srv.biscuit_keys[self.0]
}
}
// DATABASE //////////////////////////////////////
impl Server {
/// Initiate a new [Server] based on a secret key (`sk`) and a public key
impl CryptoServer {
/// Initiate a new [CryptoServer] based on a secret key (`sk`) and a public key
/// (`pk`)
pub fn new(sk: SSk, pk: SPk) -> Server {
pub fn new(sk: SSk, pk: SPk) -> CryptoServer {
let tb = Timebase::default();
Server {
CryptoServer {
sskm: sk,
spkm: pk,
@@ -432,6 +459,7 @@ impl Server {
(0..self.biscuit_keys.len()).map(BiscuitKeyPtr)
}
#[rustfmt::skip]
pub fn pidm(&self) -> Result<PeerId> {
Ok(Public::new(
lprf::peerid()?
@@ -491,7 +519,7 @@ impl Server {
/// 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),
};
@@ -561,6 +589,7 @@ impl Peer {
}
}
#[rustfmt::skip]
pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new(
lprf::peerid()?
@@ -614,35 +643,35 @@ impl BiscuitKey {
// LIFECYCLE MANAGEMENT //////////////////////////
impl Mortal for IniHsPtr {
fn created_at(&self, srv: &Server) -> Option<Timing> {
fn created_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.get(srv).as_ref().map(|hs| hs.created_at)
}
fn retire_at(&self, srv: &Server) -> Option<Timing> {
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.die_at(srv)
}
fn die_at(&self, srv: &Server) -> Option<Timing> {
fn die_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.created_at(srv).map(|t| t + REJECT_AFTER_TIME)
}
}
impl Mortal for SessionPtr {
fn created_at(&self, srv: &Server) -> Option<Timing> {
fn created_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.get(srv).as_ref().map(|p| p.created_at)
}
fn retire_at(&self, srv: &Server) -> Option<Timing> {
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.created_at(srv).map(|t| t + REKEY_AFTER_TIME)
}
fn die_at(&self, srv: &Server) -> Option<Timing> {
fn die_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.created_at(srv).map(|t| t + REJECT_AFTER_TIME)
}
}
impl Mortal for BiscuitKeyPtr {
fn created_at(&self, srv: &Server) -> Option<Timing> {
fn created_at(&self, srv: &CryptoServer) -> Option<Timing> {
let t = self.get(srv).created_at;
if t < 0.0 {
None
@@ -651,11 +680,11 @@ impl Mortal for BiscuitKeyPtr {
}
}
fn retire_at(&self, srv: &Server) -> Option<Timing> {
fn retire_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.created_at(srv).map(|t| t + BISCUIT_EPOCH)
}
fn die_at(&self, srv: &Server) -> Option<Timing> {
fn die_at(&self, srv: &CryptoServer) -> Option<Timing> {
self.retire_at(srv).map(|t| t + BISCUIT_EPOCH)
}
}
@@ -663,21 +692,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: &Server) -> Option<Timing>;
fn youth_left(&self, srv: &Server) -> Option<Timing>;
fn lifecycle(&self, srv: &Server) -> Lifecycle;
fn life_left(&self, srv: &CryptoServer) -> Option<Timing>;
fn youth_left(&self, srv: &CryptoServer) -> Option<Timing>;
fn lifecycle(&self, srv: &CryptoServer) -> Lifecycle;
}
impl<T: Mortal> MortalExt for T {
fn life_left(&self, srv: &Server) -> Option<Timing> {
fn life_left(&self, srv: &CryptoServer) -> Option<Timing> {
self.die_at(srv).map(|t| t - srv.timebase.now())
}
fn youth_left(&self, srv: &Server) -> Option<Timing> {
fn youth_left(&self, srv: &CryptoServer) -> Option<Timing> {
self.retire_at(srv).map(|t| t - srv.timebase.now())
}
fn lifecycle(&self, srv: &Server) -> Lifecycle {
fn lifecycle(&self, srv: &CryptoServer) -> 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,
@@ -689,7 +718,7 @@ impl<T: Mortal> MortalExt for T {
// MESSAGE HANDLING //////////////////////////////
impl Server {
impl CryptoServer {
/// Initiate a new handshake, put it to the `tx_buf` __and__ to the
/// retransmission storage
// NOTE retransmission? yes if initiator, no if responder
@@ -711,8 +740,8 @@ pub struct HandleMsgResult {
pub resp: Option<usize>,
}
impl Server {
/// Repsond to an incoming message
impl CryptoServer {
/// Respond to an incoming message
///
/// # Overview
///
@@ -746,6 +775,8 @@ impl Server {
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]>>()?;
@@ -929,11 +960,11 @@ impl PollResult {
}
}
pub fn poll_child<P: Pollable>(&self, srv: &mut Server, p: &P) -> Result<PollResult> {
pub fn poll_child<P: Pollable>(&self, srv: &mut CryptoServer, p: &P) -> Result<PollResult> {
self.try_fold_with(|| p.poll(srv))
}
pub fn poll_children<P, I>(&self, srv: &mut Server, iter: I) -> Result<PollResult>
pub fn poll_children<P, I>(&self, srv: &mut CryptoServer, iter: I) -> Result<PollResult>
where
P: Pollable,
I: Iterator<Item = P>,
@@ -999,10 +1030,10 @@ pub fn void_poll<T, F: FnOnce() -> T>(f: F) -> impl FnOnce() -> PollResult {
}
pub trait Pollable {
fn poll(&self, srv: &mut Server) -> Result<PollResult>;
fn poll(&self, srv: &mut CryptoServer) -> Result<PollResult>;
}
impl Server {
impl CryptoServer {
/// 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
@@ -1020,7 +1051,7 @@ impl Server {
}
impl Pollable for BiscuitKeyPtr {
fn poll(&self, srv: &mut Server) -> Result<PollResult> {
fn poll(&self, srv: &mut CryptoServer) -> Result<PollResult> {
begin_poll()
.sched(self.life_left(srv), void_poll(|| self.get_mut(srv).erase())) // Erase stale biscuits
.ok()
@@ -1028,7 +1059,7 @@ impl Pollable for BiscuitKeyPtr {
}
impl Pollable for PeerPtr {
fn poll(&self, srv: &mut Server) -> Result<PollResult> {
fn poll(&self, srv: &mut CryptoServer) -> 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
@@ -1056,7 +1087,7 @@ impl Pollable for PeerPtr {
}
impl Pollable for IniHsPtr {
fn poll(&self, srv: &mut Server) -> Result<PollResult> {
fn poll(&self, srv: &mut CryptoServer) -> 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.
@@ -1068,14 +1099,14 @@ impl Pollable for IniHsPtr {
// MESSAGE RETRANSMISSION ////////////////////////
impl Server {
impl CryptoServer {
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 Server, msg: &[u8]) -> Result<()> {
pub fn store_msg_for_retransmission(&self, srv: &mut CryptoServer, msg: &[u8]) -> Result<()> {
let ih = self
.get_mut(srv)
.as_mut()
@@ -1087,7 +1118,7 @@ impl IniHsPtr {
Ok(())
}
pub fn apply_retransmission(&self, srv: &mut Server, tx_buf: &mut [u8]) -> Result<usize> {
pub fn apply_retransmission(&self, srv: &mut CryptoServer, tx_buf: &mut [u8]) -> Result<usize> {
let ih = self
.get_mut(srv)
.as_mut()
@@ -1096,7 +1127,7 @@ impl IniHsPtr {
Ok(ih.tx_len)
}
pub fn register_retransmission(&self, srv: &mut Server) -> Result<()> {
pub fn register_retransmission(&self, srv: &mut CryptoServer) -> Result<()> {
let tb = srv.timebase.clone();
let ih = self
.get_mut(srv)
@@ -1116,7 +1147,7 @@ impl IniHsPtr {
Ok(())
}
pub fn retransmission_in(&self, srv: &mut Server) -> Option<Timing> {
pub fn retransmission_in(&self, srv: &mut CryptoServer) -> Option<Timing> {
self.get(srv)
.as_ref()
.map(|hs| hs.tx_retry_at - srv.timebase.now())
@@ -1130,7 +1161,7 @@ where
M: LenseView,
{
/// Calculate the message authentication code (`mac`)
pub fn seal(&mut self, peer: PeerPtr, srv: &Server) -> Result<()> {
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = lprf::mac()?
.mix(peer.get(srv).spkt.secret())?
.mix(self.until_mac())?;
@@ -1145,14 +1176,14 @@ where
M: LenseView,
{
/// Check the message authentication code
pub fn check_seal(&self, srv: &Server) -> Result<bool> {
pub fn check_seal(&self, srv: &CryptoServer) -> 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: &Server) -> Self {
pub fn zero_with_timestamp(srv: &CryptoServer) -> Self {
InitiatorHandshake {
created_at: srv.timebase.now(),
next: HandshakeStateMachine::RespHello,
@@ -1227,7 +1258,7 @@ impl HandshakeState {
pub fn store_biscuit(
&mut self,
srv: &mut Server,
srv: &mut CryptoServer,
peer: PeerPtr,
biscuit_ct: &mut [u8],
) -> Result<&mut Self> {
@@ -1270,7 +1301,7 @@ impl HandshakeState {
/// Takes an encrypted biscuit and tries to decrypt the contained
/// information
pub fn load_biscuit(
srv: &Server,
srv: &CryptoServer,
biscuit_ct: &[u8],
sidi: SessionId,
sidr: SessionId,
@@ -1321,7 +1352,7 @@ impl HandshakeState {
Ok((peer, no, hs))
}
pub fn enter_live(self, srv: &Server, role: HandshakeRole) -> Result<Session> {
pub fn enter_live(self, srv: &CryptoServer, 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();
@@ -1344,7 +1375,10 @@ impl HandshakeState {
}
}
impl Server {
impl CryptoServer {
/// Get the shared key that was established with given peer
///
/// Fail if no session is available with the peer
pub fn osk(&self, peer: PeerPtr) -> Result<SymKey> {
let session = peer
.session()
@@ -1355,7 +1389,7 @@ impl Server {
}
}
impl Server {
impl CryptoServer {
/// Implementation of the cryptographic protocol using the already
/// established primitives
pub fn handle_initiation(
@@ -1365,24 +1399,40 @@ impl Server {
) -> Result<PeerPtr> {
let mut hs = InitiatorHandshake::zero_with_timestamp(self);
hs.core.init(peer.get(self).spkt.secret())?; // IHI1
hs.core.sidi.randomize(); // IHI2
// IHI1
hs.core.init(peer.get(self).spkt.secret())?;
// IHI2
hs.core.sidi.randomize();
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
EKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?; // IHI3
// IHI3
EphemeralKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
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
// IHI4
hs.core.mix(ih.sidi())?.mix(ih.epki())?;
// IHI5
hs.core
.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
ih.sctr_mut(),
peer.get(self).spkt.secret(),
)?;
// IHI6
hs.core
.encrypt_and_mix(ih.pidic_mut(), self.pidm()?.as_ref())?;
hs.core // IHI7
// IHI7
hs.core
.mix(self.spkm.secret())?
.mix(peer.get(self).psk.secret())?;
hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?; // IHI8
// Update the handshake hash last (not changing any state on prior error)
// IHI8
hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?;
// Update the handshake hash last (not changing any state on prior error
peer.hs().insert(self, hs)?;
Ok(peer)
@@ -1397,36 +1447,56 @@ impl Server {
core.sidi = SessionId::from_slice(ih.sidi());
core.init(self.spkm.secret())?; // IHR1
core.mix(ih.sidi())?.mix(ih.epki())?; // IHR4
core.decaps_and_mix::<SKEM, { SKEM::SHK_LEN }>( // IHR5
// IHR1
core.init(self.spkm.secret())?;
// IHR4
core.mix(ih.sidi())?.mix(ih.epki())?;
// IHR5
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
self.sskm.secret(),
self.spkm.secret(),
ih.sctr(),
)?;
let peer = { // IHR6
// IHR6
let peer = {
let mut peerid = PeerId::zero();
core.decrypt_and_mix(&mut *peerid, ih.pidic())?;
self.find_peer(peerid)
.with_context(|| format!("No such peer {peerid:?}."))?
};
core.mix(peer.get(self).spkt.secret())? // IHR7
.mix(peer.get(self).psk.secret())?;
core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?; // IHR8
core.sidr.randomize(); // RHR1
// IHR7
core.mix(peer.get(self).spkt.secret())?
.mix(peer.get(self).psk.secret())?;
// IHR8
core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?;
// RHR1
core.sidr.randomize();
rh.sidi_mut().copy_from_slice(core.sidi.as_ref());
rh.sidr_mut().copy_from_slice(core.sidr.as_ref());
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
// 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 }>(
rh.scti_mut(),
peer.get(self).spkt.secret(),
)?;
core.store_biscuit(self, peer, rh.biscuit_mut())?; // RHR6
core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?; // RHR7
// RHR6
core.store_biscuit(self, peer, rh.biscuit_mut())?;
// RHR7
core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?;
Ok(peer)
}
@@ -1436,7 +1506,8 @@ impl Server {
rh: RespHello<&[u8]>,
mut ic: InitConf<&mut [u8]>,
) -> Result<PeerPtr> {
let peer = self // RHI2
// RHI2
let peer = self
.lookup_handshake(SessionId::from_slice(rh.sidi()))
.with_context(|| {
format!(
@@ -1477,19 +1548,28 @@ impl Server {
// TODO: decaps_and_mix should take Secret<> directly
// to save us from the repetitive secret unwrapping
core.mix(rh.sidr())?.mix(rh.sidi())?; // RHI3
core.decaps_and_mix::<EKEM, { EKEM::SHK_LEN }>( // RHI4
// RHI3
core.mix(rh.sidr())?.mix(rh.sidi())?;
// RHI4
core.decaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>(
hs!().eski.secret(),
&*hs!().epki,
rh.ecti(),
)?;
core.decaps_and_mix::<SKEM, { SKEM::SHK_LEN }>( // RHI5
// RHI5
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
self.sskm.secret(),
self.spkm.secret(),
rh.scti(),
)?;
core.mix(rh.biscuit())?; // RHI6
core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?; // RHI7
// RHI6
core.mix(rh.biscuit())?;
// RHI7
core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?;
// TODO: We should just authenticate the entire network package up to the auth
// tag as a pattern instead of mixing in fields separately
@@ -1497,14 +1577,19 @@ impl Server {
ic.sidi_mut().copy_from_slice(rh.sidi());
ic.sidr_mut().copy_from_slice(rh.sidr());
core.mix(ic.sidi())?.mix(ic.sidr())?; // ICI3
// ICI3
core.mix(ic.sidi())?.mix(ic.sidr())?;
ic.biscuit_mut().copy_from_slice(rh.biscuit());
core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?; // ICI4
// ICI4
core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?;
// 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.
peer.session() // ICI7
// ICI7
peer.session()
.insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?;
hs_mut!().core.erase();
hs_mut!().next = HandshakeStateMachine::RespConf;
@@ -1518,19 +1603,30 @@ impl Server {
mut rc: EmptyData<&mut [u8]>,
) -> Result<PeerPtr> {
// (peer, bn) ← LoadBiscuit(InitConf.biscuit)
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( // ICR1
// ICR1
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit(
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
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
// 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()
.insert(self, core.enter_live(self, HandshakeRole::Responder)?)?;
}
@@ -1599,3 +1695,32 @@ impl Server {
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,3 +1,5 @@
//! Bindings and helpers for accessing libsodium functions
use crate::util::*;
use anyhow::{ensure, Result};
use libsodium_sys as libsodium;

View File

@@ -1,3 +1,5 @@
//! Helper functions and macros
use base64::{
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
write::EncoderWriter as B64Writer,
@@ -17,8 +19,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),+) => {{
@@ -40,9 +42,10 @@ pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst:
dst.borrow_mut().copy_from_slice(src.borrow());
}
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, to: &mut T) {
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
let src = src.borrow();
let dst = to.borrow_mut();
let dst = dst.borrow_mut();
let len = min(src.len(), dst.len());
dst[..len].copy_from_slice(&src[..len]);
}
@@ -97,15 +100,6 @@ 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),