Compare commits

...

22 Commits

Author SHA1 Message Date
Karolin Varner
87cbd1e8ea stash 2023-11-16 17:32:27 +01:00
Karolin Varner
d3834d411c stash 2023-11-16 17:32:25 +01:00
Karolin Varner
acece7d5b4 stash: KMAC as the PRF 2023-11-16 17:32:11 +01:00
Karolin Varner
2fc18ebdf1 feat(whitepaper): Format list of crypto primitives using a table 2023-11-16 17:30:36 +01:00
Karolin Varner
9658687989 chore(whitepaper): Typos 2023-11-16 17:30:36 +01:00
Karolin Varner
f2b1e3cdb4 feat(whitepaper): Add a chart with the cryptographic building blocks 2023-11-16 17:30:36 +01:00
Karolin Varner
031f1b1f66 feat: Use NIST Round 4 Submission Classic McEliece
Issue: #91 (https://github.com/rosenpass/rosenpass/issues/91)

Add version info to whitepaper

This uses an release candidate of liboqs and a patched version of the
oqs-sys rust crate. We should wait until a proper release is done.
2023-11-16 17:30:36 +01:00
Karolin Varner
70cc51ed7a fix: Swap auth and biscuit fields in RespHello
Issue: #68 (https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563655004)
2023-11-16 17:30:36 +01:00
Karolin Varner
cdcdb502fd fix(whitepaper): Mixing order in encaps/decaps_and_mix
Issue: #68 (https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563638396)

pk, shk, ct -> pk, ct, shk
2023-11-16 17:30:36 +01:00
Karolin Varner
ace2e82e2a fix: Typo in ini_enc and res_enc labels
Issue: #68 (https://github.com/rosenpass/rosenpass/issues/68)

initiator handshake encryption -> initiator session encryption
responder handshake encryption -> responder session encryption
2023-11-16 17:30:35 +01:00
Karolin Varner
594786c294 chore(whitepaper): Changelog entry for 303c5a569
Fix a typo "key chaining extract" -> "chaining key extract"; "key chaining init" -> "chaining key init"
2023-11-16 17:28:38 +01:00
Karolin Varner
5d4ecd7210 feat(whitepaper): Typo in biscuit counter comparison in load_biscuit 2023-11-16 17:28:38 +01:00
Karolin Varner
5689990f0d feat(whitepaper): Rename variable: index -> sessions 2023-11-16 17:28:38 +01:00
Karolin Varner
ec2c30f85c feat(whitepaper): Typo ct1 -> sctr in IHR5 2023-11-16 17:28:38 +01:00
Karolin Varner
6e1c1df1d9 feat(whitepaper): Add an extra session for information about timers 2023-11-16 17:28:38 +01:00
Karolin Varner
f94ba3b416 feat(whitepaper): Biscuit rotation interval is not mandatory and every five min 2023-11-16 17:28:38 +01:00
Karolin Varner
5a2202d00d feat(whitepaper): Mandate little-endian format for numbers 2023-11-16 17:28:38 +01:00
Karolin Varner
448ce6b395 feat(whitepaper): Detailed info about when security properties are achieved 2023-11-16 17:28:38 +01:00
Karolin Varner
6c04070184 feat(whitepaper): Explicitly handle erasure of eski to achieve forward secrecy
This is a minor security fix: Before this change the specification left erasing the secret key to the implementation. The reference implementation did erase `eski` but only after receiving the responder confirmation package (EmptyData at the time) instructing the initiator to stop retransmission of the InitConf package. With this change, `eski` is erased before transmission of the InitConf package.
2023-11-16 17:28:38 +01:00
Karolin Varner
5bfa02e7f6 feat(whitepaper): Clarify that these are protocol versions – not software versions 2023-11-16 17:28:38 +01:00
Karolin Varner
7deab6bbd7 fix(whitepaper): Use the PROTOCOL string from the whitepaper 2023-11-16 17:28:38 +01:00
Karolin Varner
6b33848426 feat(whitepaper): Clarify how protocol roles are determined and race condition handling 2023-11-16 17:28:38 +01:00
21 changed files with 700 additions and 224 deletions

10
Cargo.lock generated
View File

@@ -147,11 +147,11 @@ checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
[[package]]
name = "bindgen"
version = "0.65.1"
version = "0.68.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.4.0",
"cexpr",
"clang-sys",
"lazy_static",
@@ -827,13 +827,13 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "oqs-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fa114149fb6e5362b9cf539de9305a6a3cf1556fbfa93b5053b4f70cf3adfb9"
source = "git+https://github.com/koraa/liboqs-rust.git?branch=main#046572faebcbf03c7529bf701d332b3851d5aa79"
dependencies = [
"bindgen",
"build-deps",
"cmake",
"libc",
"pkg-config",
]
[[package]]

View File

@@ -3,5 +3,6 @@ resolver = "2"
members = [
"rosenpass",
"kmac-test-vectors",
]

View File

@@ -0,0 +1,9 @@
[package]
name = "kmac-test-vectors"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rosenpass/rosenpass"
[dependencies]
xkcp-sys = "0.0.3"

View File

@@ -0,0 +1,2 @@
fn main() {
}

View File

@@ -177,22 +177,31 @@
}
@techreport{mceliece,
title = {{C}lassic {M}c{E}liece: conservative code-based cryptography},
title = {{C}lassic {M}c{E}liece: conservative code-based cryptography (NIST Round 4 Submission)},
author = {Martin R. Albrecht and Daniel J. Bernstein and Tung Chou and Carlos Cid and Jan Gilcher and Tanja Lange and Varun Maram and Ingo von Maurich and Rafael Misoczki and Ruben Niederhagen and Kenneth G. Paterson and Edoardo Persichetti and Christiane Peters and Peter Schwabe and Nicolas Sendrier and Jakub Szefer and Cen Jung Tjhai and Martin Tomlinson and Wen Wang},
year = 2022,
month = 10,
day = 23,
type = {NIST Post-Quantum Cryptography Round 4 Submission},
url = {https://classic.mceliece.org/}
url = {https://classic.mceliece.org/mceliece-spec-20221023.pdf}
}
@techreport{kyber,
title = {CRYSTALS-Kyber},
title = {CRYSTALS-Kyber (NIST Round 3 Submission)},
author = {Roberto Avanzi and Joppe Bos and Léo Ducas and Eike Kiltz and Tancrède Lepoint and
Vadim Lyubashevsky and John M. Schanck and Peter Schwabe and Gregor Seiler and Damien Stehlé},
year = 2020,
month = 10,
day = 1,
type = {NIST Post-Quantum Cryptography Selected Algorithm},
url = {https://pq-crystals.org/kyber/}
url = {https://pq-crystals.org/kyber/data/kyber-submission-nist-round3.zip}
}
@article{sha3,
title={SHA-3 derived functions: cSHAKE, KMAC, TupleHash, and ParallelHash},
author={Kelsey, John and Chang, Shu-jen and Perlner, Ray},
journal={NIST special publication},
volume={800},
pages={185},
year={2016}
}

View File

@@ -33,6 +33,7 @@ abstract: |
Rosenpass inherits most security properties from Post-Quantum WireGuard (PQWG). The security properties mentioned here are covered by the symbolic analysis in the Rosenpass repository.
## Secrecy
Three key encapsulations using the keypairs `sski`/`spki`, `sskr`/`spkr`, and `eski`/`epki` provide secrecy (see Section \ref{variables} for an introduction of the variables). Their respective ciphertexts are called `scti`, `sctr`, and `ectr` and the resulting keys are called `spti`, `sptr`, `epti`. A single secure encapsulation is sufficient to provide secrecy. We use two different KEMs (Key Encapsulation Mechanisms; see section \ref{skem}): Kyber and Classic McEliece.
## Authenticity
@@ -60,12 +61,22 @@ Note that while Rosenpass is secure against state disruption, using it does not
## Cryptographic Building Blocks
All symmetric keys and hash values used in Rosenpass are 32 bytes long.
The following cryptographic building blocks are used:
| Use | Scheme | Version | Purpose |
| --- | --- | --- | --- |
| hash | KMAC[@sha3] | | SHA-3/Keccak based message authentication code |
| AEAD | ChaCha20-Poly1305[@rfc_chachapoly] | | Encryption with sequential nonce |
| XAEAD | XChaCha20-Poly1305[@draft_xchachapoly] | | Encryption with random nonce |
| SKEM | Classic McEliece 460896[@mceliece] | NIST Round 4 Submission | Key encapsulation with static keys |
| EKEM | Kyber-512[@kyber] | NIST Round 3 Submission (most recent) | Key encapsulation with ephemeral (random) keys |
All symmetric keys and hash values used in Rosenpass are 32 bytes long.
### Hash
A keyed hash function with one 32-byte input, one variable-size input, and one 32-byte output. As keyed hash function we use the HMAC construction [@rfc_hmac] with BLAKE2s [@rfc_blake2] as the inner hash function.
A keyed hash function with one 32-byte input, one variable-size input, and one 32-byte output. As keyed hash function we use the KMAC[@sha3] with no (i.e. an empty) customization string.
```pseudorust
hash(key, data) -> key
@@ -73,7 +84,7 @@ hash(key, data) -> key
### AEAD
Authenticated encryption with additional data for use with sequential nonces. We use ChaCha20Poly1305 [@rfc_chachapoly] in the implementation.
Authenticated encryption with additional data for use with sequential nonces. We use ChaCha20-Poly1305 [@rfc_chachapoly] in the implementation.
```pseudorust
AEAD::enc(key, nonce, plaintext, additional_data) -> ciphertext
@@ -82,7 +93,7 @@ AEAD::dec(key, nonce, ciphertext, additional_data) -> plaintext
### XAEAD
Authenticated encryption with additional data for use with random nonces. We use XChaCha20Poly1305 [@draft_xchachapoly] in the implementation, a construction also used by WireGuard.
Authenticated encryption with additional data for use with random nonces. We use XChaCha20-Poly1305 [@draft_xchachapoly] in the implementation, a construction also used by WireGuard.
```pseudorust
@@ -92,7 +103,7 @@ XAEAD::dec(key, nonce, ciphertext, additional_data) -> plaintext
### SKEM {#skem}
“Key Encapsulation Mechanism” (KEM) is the name of an interface widely used in post-quantum-secure protocols. KEMs can be seen as asymmetric encryption specifically for symmetric keys. Rosenpass uses two different KEMs. SKEM is the key encapsulation mechanism used with the static keypairs in Rosenpass. The public keys of these keypairs are not transmitted over the wire during the protocol. We use Classic McEliece 460896 [@mceliece] which claims to be as hard to break as 192-bit AES. As one of the oldest post-quantum-secure KEMs, it enjoys wide trust among cryptographers, but it has not been chosen for standardization by NIST. Its ciphertexts and private keys are small (188 bytes and 13568 bytes), and its public keys are large (524160 bytes). This fits our use case: public keys are exchanged out-of-band, and only the small ciphertexts have to be transmitted during the handshake.
“Key Encapsulation Mechanism” (KEM) is the name of an interface widely used in post-quantum-secure protocols. KEMs can be seen as asymmetric encryption specifically for symmetric keys. Rosenpass uses two different KEMs. SKEM is the key encapsulation mechanism used with the static keypairs in Rosenpass. The public keys of these keypairs are not transmitted over the wire during the protocol. We use Classic McEliece 460896 (NIST Round 4 Submission; [@mceliece] which claims to be as hard to break as 192-bit AES. As one of the oldest post-quantum-secure KEMs, it enjoys wide trust among cryptographers, but it has not been chosen for standardization by NIST. Its ciphertexts and private keys are small (188 bytes and 13568 bytes), and its public keys are large (524160 bytes). This fits our use case: public keys are exchanged out-of-band, and only the small ciphertexts have to be transmitted during the handshake.
```pseudorust
SKEM::enc(public_key) -> (ciphertext, shared_key)
@@ -101,7 +112,7 @@ SKEM::dec(secret_key, ciphertext) -> shared_key
### EKEM
Key encapsulation mechanism used with the ephemeral KEM keypairs in Rosenpass. The public keys of these keypairs need to be transmitted over the wire during the protocol. We use Kyber-512 [@kyber], which has been selected in the NIST post-quantum cryptography competition and claims to be as hard to break as 128-bit AES. Its ciphertexts, public keys, and private keys are 768, 800, and 1632 bytes long, respectively, providing a good balance for our use case as both a public key and a ciphertext have to be transmitted during the handshake.
Key encapsulation mechanism used with the ephemeral KEM keypairs in Rosenpass. The public keys of these keypairs need to be transmitted over the wire during the protocol. We use Kyber-512 [@kyber] (NIST Round 3 Submission), which has been selected in the NIST post-quantum cryptography competition and claims to be as hard to break as 128-bit AES. Its ciphertexts, public keys, and private keys are 768, 800, and 1632 bytes long, respectively, providing a good balance for our use case as both a public key and a ciphertext have to be transmitted during the handshake.
```pseudorust
EKEM::enc(public_key) -> (ciphertext, shared_key)
@@ -208,6 +219,30 @@ hs_enc = hash(hash(hash(0, PROTOCOL), "chaining key extract"), "handshake encryp
= lhash("chaining key extract", "handshake encryption")
```
## Protocol roles
There are two handshake roles:
- The initiator
- The responder
The initiator acts as a stateful client, directing the handshake process. The responder acts as a stateless server reacting to the initiator's messages while keeping no local state until the handshake is complete. Instead, the responder pushes their variables into an encrypted session cookie.
The number of concurrent responder-role handshakes with another client is unlimited to account for the possibility of an imposter trying to execute a handshake: Before completion of said handshake, there is no way to figure out which peer is an imposter and which peer is a legitimate client; any attempt to do so might lead to to a state-disruption attack -- denial of service on the protocol level.
There is no mechanism to negotiate which of the peers acts as initiator and responder, instead two parties may be processing separate handshakes in client role and in responder role at the same time.
Implementations must account for this possibility by aborting any ongoing initiator-role handshake upon accepting an InitConf package. Implementations should also use different back-off periods depending on whether the handshake was completed in initiator role or in responder role. The following values are used in the rust reference implementation:
- Initiator rekey interval: 130s
- Responder rekey interval: 120s
In practice these delays cause participants to take turns acting as initiator and acting as responder since the ten seconds difference is usually enough for the handshake with switched roles to complete before the old initiator's rekey timer goes to zero.
## Endianess
All numeric values are in little-endian format unless otherwise noted.
## Server State
### Global
@@ -222,7 +257,7 @@ The server needs to store the following variables:
Not mandated per se, but required in practice:
* `peers` A lookup table mapping the peer ID to the internal peer structure
* `index` A lookup table mapping the session ID to the ongoing initiator handshake or live session
* `sessions` A lookup table mapping the session ID to the ongoing initiator handshake or live session
### Peer
@@ -252,7 +287,7 @@ The responder stores no state. While the responder has access to all of the abov
The biscuit is encrypted with the `XAEAD` primitive and a randomly chosen nonce. The values `sidi` and `sidr` are transmitted publicly as part of InitConf, so they do not need to be present in the biscuit, but they are added to the biscuit's additional data to make sure the correct values are transmitted as part of InitConf.
The `biscuit_key` used to encrypt biscuits should be rotated every two minutes. Implementations should keep two biscuit keys in memory at any given time to avoid having to drop packages when `biscuit_key` is rotated.
The `biscuit_key` used to encrypt biscuits should be rotated frequently. The reference implementation uses a rotation interval of five minutes. Implementations should keep two biscuit keys in memory at any given time to avoid having to drop packages when `biscuit_key` is rotated.
### Live Session State
@@ -382,7 +417,7 @@ fn load_biscuit(nct) {
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
// Find the peer and apply retransmission protection
lookup_peer(pt.peerid);
assert(pt.biscuit_no <= peer.biscuit_used);
assert(pt.biscuit_no >= peer.biscuit_used);
// Restore the chaining key
ck ← pt.ck;
@@ -434,8 +469,77 @@ The initiator deals with packet loss by storing the messages it sends to the res
The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
\printbibliography
## Timers
The Rosenpass protocol uses various timer-triggered events during its operation. This section provides a listing of the timers used and gives the values used in the reference implementation. Other implementations may choose different values.
### Rekeying
Period after which the previous responder starts a new handshake in initiator role; period after which the previous initiator starts a new handshake in initiator role again; period after which a peer rejects an existing shared key.
```pseudorust
REKEY_AFTER_TIME_RESPONDER = 120s
REKEY_AFTER_TIME_INITIATOR = 130s
REJECT_AFTER_TIME = 180s
```
### Biscuits
Period after which the biscuit key is rotated.
```pseudorust
BISCUIT_EPOCH = 300s
```
### Retransmission
Delay after which all retransmission attempts are aborted; exponential backoff factor for retransmission delay; initial (minimum) retransmission delay; final (maximum) retransmission delay; retransmission jitter/variance factor.
```pseudorust
RETRANSMIT_ABORT = 120s
RETRANSMIT_DELAY_GROWTH = 2
RETRANSMIT_DELAY_BEGIN = 500ms
RETRANSMIT_DELAY_END = 10s
RETRANSMIT_DELAY_JITTER = 0.5
```
\setupimage{landscape,fullpage,label=img:HandlingCode}
![Rosenpass Message Handling Code](graphics/rosenpass-wp-message-handling-code-rgb.svg)
\printbibliography
# Version history
## Protocol version 2 -- XXXX-XX-XX
During the implementation of go-rosenpass, Steffen Vogel found a number of problems ([issue #68](https://github.com/rosenpass/rosenpass/issues/68)) with the whitepaper. Version two of the document primarily addresses these issues:
### Features
- Use NIST Round 4 Submission of Classic McEliece
### Security issues
- Explicitly erase `eski` (forward secrecy). This is a minor security fix: Before this change the specification left erasing the secret key to the implementation. The reference implementation did erase `eski` but only after receiving the responder confirmation package (EmptyData at the time) instructing the initiator to stop retransmission of the InitConf package. With this change, `eski` is erased before transmission of the InitConf package.
### Bug fixes
- Handle race conditions when both peers complete concurrent handshakes in switched roles. Backwards compatible. Initially addressed in [397a776](https://github.com/rosenpass/rosenpass/commit/397a776c55b1feae1e8e5aceef01cf06bf56b6ed) "fix: Race condition due to concurrent handshake".
### Clarifications
- Add detailed information about when in the handshake process security properties are achieved.
- Extra section with a list of timers used.
- Rename the session id/session lookup table from `index` to `sessions`
- Indicate which version of Classic McEliece and Kyber is used
- Add a chart with the cryptographic building blocks used
### Mistakes/Inconsistencies
- Old `ct1` name was used for `sctr` (the static responder KEM ciphertext)
- Biscuit number was asserted to be smaller or equal to the peer's biscuit used variable, where it should have been bigger or equal to
- Fix a typo "key chaining extract" -> "chaining key extract"; "key chaining init" -> "chaining key init"
## Protocol version 1 -- 2023-03-04
Initial release.

View File

@@ -19,7 +19,7 @@ base64 = "0.21.1"
static_assertions = "1.1.0"
memoffset = "0.9.0"
libsodium-sys-stable = { version = "1.19.28", features = ["use-pkg-config"] }
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
oqs-sys = { git = "https://github.com/koraa/liboqs-rust.git", branch = "main", default-features = false, features = ['classic_mceliece', 'kyber'] }
lazy_static = "1.4.0"
thiserror = "1.0.40"
paste = "1.0.12"
@@ -29,6 +29,9 @@ serde = { version = "1.0.163", features = ["derive"] }
toml = "0.7.4"
clap = { version = "4.3.0", features = ["derive"] }
mio = { version = "0.8.6", features = ["net", "os-poll"] }
sha3 = "0.10.8"
num-traits = "0.2.17"
digest = "0.10.7"
[build-dependencies]
anyhow = "1.0.71"

View File

@@ -7,7 +7,7 @@
//! - the memory is mlocked, e.g. it is never swapped
use crate::{
sodium::{rng, zeroize},
classical_crypto::{rng, zeroize},
util::{cpy, mutating},
};
use lazy_static::lazy_static;

View File

@@ -6,8 +6,10 @@ use {
anyhow::Result,
};
const PROTOCOL : &str = "rosenpass 1 rosenpass.eu aead=chachapoly1305 hash=blake2s ekem=kyber512 skem=mceliece460896 xaead=xchachapoly1305";
pub fn protocol() -> Result<PrfTree> {
PrfTree::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
PrfTree::zero().mix(PROTOCOL.as_bytes())
}
// TODO Use labels that can serve as identifiers
@@ -40,8 +42,8 @@ macro_rules! prflabel_leaf {
prflabel_leaf!(_ckextract, mix, "mix");
prflabel_leaf!(_ckextract, hs_enc, "handshake encryption");
prflabel_leaf!(_ckextract, ini_enc, "initiator handshake encryption");
prflabel_leaf!(_ckextract, res_enc, "responder handshake encryption");
prflabel_leaf!(_ckextract, ini_enc, "initiator session encryption");
prflabel_leaf!(_ckextract, res_enc, "responder session encryption");
prflabel!(_ckextract, _user, "user");
prflabel!(_user, _rp, "rosenpass.eu");

View File

@@ -1,7 +1,7 @@
#[macro_use]
pub mod util;
#[macro_use]
pub mod sodium;
pub mod symmetric;
pub mod coloring;
#[rustfmt::skip]
pub mod labeled_prf;

View File

@@ -291,10 +291,10 @@ data_lense! { RespHello :=
ecti: EphemeralKEM::CT_LEN,
/// Classic McEliece Ciphertext
scti: StaticKEM::CT_LEN,
/// Empty encrypted message (just an auth tag)
auth: sodium::AEAD_TAG_LEN,
/// Responders handshake state in encrypted form
biscuit: BISCUIT_CT_LEN
biscuit: BISCUIT_CT_LEN,
/// Empty encrypted message (just an auth tag)
auth: sodium::AEAD_TAG_LEN
}
data_lense! { InitConf :=

View File

@@ -1,168 +0,0 @@
//! 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
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
//!
//! encapsulation.
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
use crate::{RosenpassError, RosenpassMaybeError};
/// Key Encapsulation Mechanism
///
/// The KEM interface defines three operations: Key generation, key encapsulation and key
/// decapsulation.
pub trait KEM {
/// Secrete Key length
const SK_LEN: usize;
/// Public Key length
const PK_LEN: usize;
/// Ciphertext length
const CT_LEN: usize;
/// Shared Secret length
const SHK_LEN: usize;
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
///
/// `keygen() -> sk, pk`
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError>;
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
///
/// `encaps(pk) -> shk, ct`
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError>;
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
/// (`shk`)
///
/// `decaps(sk, ct) -> shk`
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError>;
}
/// A KEM that is secure against Chosen Ciphertext Attacks (CCA).
/// In the context of rosenpass this is used for static keys.
/// Uses [Classic McEliece](https://classic.mceliece.org/) 460896 from liboqs.
///
/// 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;
/// # Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
/// slices only identified using raw pointers. It must be ensured that the raw
/// pointers point into byte slices of sufficient length, to avoid UB through
/// overwriting of arbitrary data. This is checked in the following code before
/// the unsafe calls, and an early return with an Err occurs if the byte slice
/// size does not match the required size.
///
/// __Note__: This requirement is stricter than necessary, it would suffice
/// 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 {
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;
const SHK_LEN: usize =
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_shared_secret as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_keypair(pk.as_mut_ptr(), sk.as_mut_ptr())
.to_rg_error()
}
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_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> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
unsafe {
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_decaps(
shk.as_mut_ptr(),
ct.as_ptr(),
sk.as_ptr(),
)
.to_rg_error()
}
}
}
/// Implements a KEM that is secure against Chosen Plaintext Attacks (CPA).
/// In the context of rosenpass this is used for ephemeral keys.
/// Currently the implementation uses
/// [Kyber 512](https://openquantumsafe.org/liboqs/algorithms/kem/kyber) from liboqs.
///
/// This is being used for ephemeral keys; since these are use-once the first post quantum
/// 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;
/// # Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
/// slices only identified using raw pointers. It must be ensured that the raw
/// pointers point into byte slices of sufficient length, to avoid UB through
/// overwriting of arbitrary data. This is checked in the following code before
/// the unsafe calls, and an early return with an Err occurs if the byte slice
/// size does not match the required size.
///
/// __Note__: This requirement is stricter than necessary, it would suffice
/// 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 {
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;
const SHK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_shared_secret as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError> {
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()
}
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
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()
}
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError> {
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
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()
}
}
}

View File

@@ -0,0 +1,204 @@
use std::result::Result;
use digest::{Update, XofReader};
use crate::util::types::Leftright;
use crate::util::io::{WriteSecret, CountAndWriteSecret};
#[cfg(test)]
use crate::util::io::assemble_secret;
/// Variable length encoding for unsigned numbers as specified by in
/// NIST Special Publication 800-185.
///
/// This corresponds to left_encode(…) if `lr == Leftright::Left`
/// and to right_encode(…) if `lr == Leftright::Right`.
///
/// # Panics
///
/// This will panic if the number `v` is greater than $2^{2040}-1$,
/// i.e. if more than 255 bits are required to represent the number.
///
/// For the natively supported integers (u8…u128 and usize), this will not panic
/// unless usize is an u256 now.
///
/// # Example
///
/// ```
/// use crate::util::types::Leftright::*;
///
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0)), assemble_secret(|w| left_encode(w, 0)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 1)), assemble_secret(|w| left_encode(w, 1)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0xff)), assemble_secret(|w| left_encode(w, 0xff)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0x100)), assemble_secret(|w| left_encode(w, 0x100)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0x010203)), assemble_secret(|w| left_encode(w, 0x010203)));
///
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0)), assemble_secret(|w| right_encode(w, 0)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 1)), assemble_secret(|w| right_encode(w, 1)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0xff)), assemble_secret(|w| right_encode(w, 0xff)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0x100)), assemble_secret(|w| right_encode(w, 0x100)));
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0x010203)), assemble_secret(|w| right_encode(w, 0x010203)));
/// ```
pub(crate) fn leftright_encode<W, N>(lr: Leftright, dst: W, v: &N)
-> Result<(), W::Error>
where
W: WriteSecret,
N: ToBytes {
use Leftright::*;
let be = v.to_be_bytes();
let trailing_zeros = be.rev().take_while(|v| v == 0).count();
let num_bytes = std::max(1, be.len() - trailing_zeros);
assert!(num_bytes < 256);
match lr {
Left => {
dst.write(from_ref(num_bytes.into()))?;
dst.write(&be[..num_bytes])?;
},
Right => {
dst.write(&be[..num_bytes])?;
dst.write(from_ref(num_bytes.into()))?;
}
}
Ok(())
}
/// Variable length encoding for unsigned numbers as specified by in
/// NIST Special Publication 800-185.
///
/// First writes a single byte indication the width of the
/// encoded number to `dst` then writes the number in big-endian
/// format.
///
/// # Panics
///
/// This will panic if the number `v` is greater than $2^{2040}-1$,
/// i.e. if more than 255 bits are required to represent the number.
///
/// For the natively supported integers (u8…u128 and usize), this will not panic
/// unless usize is an u256 now.
///
/// # Example
///
/// ```
/// assert_eq!(assemble_secret(|w| left_encode(w, 0)), b"\1\0");
/// assert_eq!(assemble_secret(|w| left_encode(w, 1)), b"\1\1");
/// assert_eq!(assemble_secret(|w| left_encode(w, 0xff)), b"\1\0xff");
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x100)), b"\1\0\1");
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x010203)), b"\3\1\2\3");
/// ```
pub(crate) fn left_encode<W, N>(dst: W, v: &N)
-> Result<(), W::Error>
where
W: Write,
N: ToBytes {
nist_leftright_encode(Leftright::Left, dst, v)
}
/// Variable length encoding for unsigned numbers as specified by in
/// NIST Special Publication 800-185.
///
/// This uses the same format as `left_encode` but swaps length tag
/// and then length of the number itself.
///
/// # Panics
///
/// This will panic if the number `v` is greater than $2^{2040}-1$,
/// i.e. if more than 255 bits are required to represent the number.
///
/// For the natively supported integers (u8…u128 and usize), this will not panic
/// unless usize is an u256 now.
///
/// # Example
///
/// ```
/// assert_eq!(assemble_secret(|w| left_encode(w, 0)), b"\0\1");
/// assert_eq!(assemble_secret(|w| left_encode(w, 1)), b"\1\1");
/// assert_eq!(assemble_secret(|w| left_encode(w, 0xff)), b"\0xff\1");
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x100)), b"\0\1\1");
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x010203)), b"\1\2\3\3");
/// ```
pub(crate) fn right_encode<W, N>(dst: &mut W, v: &N)
-> Result<(), W::Error>
where
W: WriteSecret,
N: ToBytes {
nist_leftright_encode(Leftright::Right, dst, v)
}
/// Serialization for variable length strings as specified by in
/// NIST Special Publication 800-185.
///
/// This first writes the length of the string using `left_encode` to `dst`
/// then copies the string itself to `dst`.
///
/// # Example
///
/// ```
/// assert_eq!(assemble_secret(|w| encode_string(w, b"")), b"\1\0");
/// assert_eq!(assemble_secret(|w| encode_string(w, b"\0")), b"\1\1\0");
/// assert_eq!(assemble_secret(|w| encode_string(w, b"Hello")), b"\1\5Hello");
/// assert_eq!(assemble_secret(|w| encode_string(w, b"Hello World")), b"\1\x0AHello World");
/// ```
pub(crate) fn encode_string<W>(dst: W, str: &[u8])
-> Result<(), W::Error>
where
W: WriteSecret {
/// This will not panic unless `usize` is an u256 now.
left_encode(&mut dst, str.len())?;
w.write_all(str)?;
Ok(())
}
/// Serialize arbitrary data and then pad the output using zero bytes
/// as specified NIST Special Publication 800-185.
///
/// This first writes the width of the padding unit `pad_to` to `dst`
/// and then calls `f` to serialize arbitrary data. Finally, this function
/// writes null bytes until the total number of bytes written is a multiple
/// of `pad_to`.
///
/// # Example
///
/// ```
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| )), b"\1\5\0\0\0");
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| w.write_all(b"Hello"))), b"\1\5Hello\0\0\0");
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| w.write_all(b"Hello dearie!"))), b"\1\5Hello dearie!");
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| w.write_all(b"_"))), b"\1\5_\0\0");
/// ```
pub(crate) fn bytepad<W, Fn>(dst: W, pad_to: u64, f: Fn)
-> Result<(), W::Error>
where
W: WriteSecret,
Fn: FnOnce<CountAndWrite> {
let w = CountAndWrite::new(dst);
nist_left_encode(&mut w, pad_to)?;
f(w)?;
for _ in ..ceiling_remainder(w2.get_count(), pad_to) {
w.write_all(from_ref(0u8.into()))?;
}
Ok(())
}
///
#[inline]
pub(crate) fn kmac256(out: &mut [u8], key: &[u8], data: &[u8]) {
// A proper implementation of KMAC is currently not available in the sha3 crate, but they do
// provide cSHAKE which can be used to implement KMAC
//
// Issue:
// https://github.com/RustCrypto/MACs/issues/133
// "kmac: Towards an implementation"
//
// KMAC Spec:
// https://doi.org/10.6028/NIST.SP.800-185
// "SHA-3 derived functions: cSHAKE, KMAC, TupleHash, and ParallelHash"
// Page 10
use sha3::{CShake256, CShake256Core};
let hasher = CShake256::from_core(
CShake256Core::new_with_function_name(&"", &"KMAC"));
bytepad(hasher, 168, |w| encode_string(w, key)).guaranteed();
hasher.update(data);
right_encode(hasher, out.len()).guaranteed();
hasher.finalize_xof_into(out);
}

View File

@@ -0,0 +1,9 @@
//! Symmetric primitives & Libsodium bindings
pub(crate) mod sodium;
pub(crate) mod kmac;
pub(crate) mod kem;
pub(crate) use sodium::*;
pub(crate) use kmac::kmac256;
pub(crate) use kem::{KEM, StaticKEM, EphemeralKEM};

View File

@@ -1,12 +1,16 @@
//! Bindings and helpers for accessing libsodium functions
//! bindings and helpers for accessing libsodium functions
use crate::util::*;
use anyhow::{ensure, Result};
use libsodium_sys as libsodium;
use log::trace;
use static_assertions::const_assert_eq;
use std::slice::from_ref;
use std::os::raw::{c_ulonglong, c_void};
use std::ptr::{null as nullptr, null_mut as nullptr_mut};
use std::io::Write;
use std::cmp::max;
use num_traits::{Zero, ToBytes};
pub const AEAD_TAG_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_ABYTES as usize;
pub const AEAD_NONCE_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize;
@@ -187,30 +191,6 @@ pub fn xaead_dec_into(
Ok(())
}
#[inline]
fn blake2b_flexible(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
const KEY_MIN: usize = libsodium::crypto_generichash_KEYBYTES_MIN as usize;
const KEY_MAX: usize = libsodium::crypto_generichash_KEYBYTES_MAX as usize;
const OUT_MIN: usize = libsodium::crypto_generichash_BYTES_MIN as usize;
const OUT_MAX: usize = libsodium::crypto_generichash_BYTES_MAX as usize;
assert!(key.is_empty() || (KEY_MIN <= key.len() && key.len() <= KEY_MAX));
assert!(OUT_MIN <= out.len() && out.len() <= OUT_MAX);
let kptr = match key.len() {
// NULL key
0 => nullptr(),
_ => key.as_ptr(),
};
sodium_call!(
crypto_generichash_blake2b,
out.as_mut_ptr(),
out.len(),
data.as_ptr(),
data.len() as c_ulonglong,
kptr,
key.len()
)
}
// TODO: Use proper streaming hash; for mix_hash too.
#[inline]
pub fn hash_into(out: &mut [u8], data: &[u8]) -> Result<()> {

View File

@@ -103,7 +103,7 @@ pub const BCE: Timing = -3600.0 * 24.0 * 356.0 * 10_000.0;
// regarding unexpectedly large numbers in system APIs as this is < i16::MAX
pub const UNENDING: Timing = 3600.0 * 8.0;
// From the wireguard paper; rekey every two minutes,
// Rekey every two minutes,
// discard the key if no rekey is achieved within three
pub const REKEY_AFTER_TIME_RESPONDER: Timing = 120.0;
pub const REKEY_AFTER_TIME_INITIATOR: Timing = 130.0;
@@ -1254,7 +1254,7 @@ impl HandshakeState {
) -> Result<&mut Self> {
let mut shk = Secret::<SHK_LEN>::zero();
T::encaps(shk.secret_mut(), ct, pk)?;
self.mix(pk)?.mix(shk.secret())?.mix(ct)
self.mix(pk)?.mix(ct)?.mix(shk.secret())
}
pub fn decaps_and_mix<T: KEM, const SHK_LEN: usize>(
@@ -1265,7 +1265,7 @@ impl HandshakeState {
) -> Result<&mut Self> {
let mut shk = Secret::<SHK_LEN>::zero();
T::decaps(shk.secret_mut(), sk, ct)?;
self.mix(pk)?.mix(shk.secret())?.mix(ct)
self.mix(pk)?.mix(ct)?.mix(shk.secret())
}
pub fn store_biscuit(
@@ -1604,6 +1604,10 @@ impl CryptoServer {
// ICI7
peer.session()
.insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?;
// RHI8
hs_mut!().eski.zeroize();
hs_mut!().core.erase();
hs_mut!().next = HandshakeStateMachine::RespConf;

186
rosenpass/src/util/io.rs Normal file
View File

@@ -0,0 +1,186 @@
use std::io::Write;
use crate::util::cpy_min;
use crate::util::result::{NeverFails, Guaranteed};
/// Errors
#[derive(Debug, PartialEq, Eq)]
enum BoundedWriteSecretError {
OutOfBounds,
}
/// A version of the Write trait for secret data
///
/// # Examples
///
/// ```
/// let buf = [0u8; 8]
/// assert_eq!(buf.write_secret(b"Hello"), Ok(()));
/// assert_eq!(&buf, b"Hello\0\0\0");
///
/// assert_eq!(buf.write_secret(b"You glorious world"), Err(BoundedWriteSecretError::OutOfBounds));
/// assert_eq!(&buf, b"Hello\0\0\0");
/// ```
pub(crate) trait WriteSecret {
type Error;
/// Atomic write operation: writes `buf` to the underlying container
///
/// The implementation must guarantee that the write operation either completes
/// successfully, otherwise no data must be written.
///
/// The implementation should take care to ensure that any intermediate buffers
/// are zeroized.
fn write_secret(&mut self, buf: &[u8]) -> std::Result<(), Self::Error>;
}
impl<T: AsRef<[u8]>> WriteSecret for T {
type Error = BoundedWriteSecretError;
fn write_secret(&mut self, buf: &[u8]) -> std::Result<(), Self::Error> {
let dst = self.as_ref();
if dst.len() >= buf.len() {
cpy_min(buf.len(), dst.len());
Ok(())
} else {
Err(CursorSecretWriteError::OutOfBounds)
}
}
}
/// Helper for make_write_secret to implement WriteSecret on the fly
#[derive(Debug, Clone)]
pub(crate) struct ClosureWriteSecret<Fn, E>
where
Fn: FnMut(&[u8]) -> std::Result<(), E> {
f: Fn
}
/// Implement WriteSecret on the fly
///
/// # Examples
///
/// ```
/// enum PasswordWriterError {
/// OutOfBounds
/// }
///
/// let password = [0u8; 12];
/// let ptr = 0usize;
/// let password_writer = make_write_secret(mut |buf| {
/// let new_ptr = ptr + buf.len();
/// if new_ptr > password.len() {
/// Err(PasswordWriterError::OutOfBounds)
/// } else {
/// (&mut password[ptr..]).copy_from_slice(buf));
/// ptr = new_ptr;
/// Ok(())
/// }
/// });
///
/// assert_eq!(password_writer.write_secret("This is"), Ok(()));
/// assert_eq!(&password, b"This is\0\0\0\0\0");
///
/// assert_eq!(password_writer.write_secret("a bad password"), Err(PasswordWriterError::OutOfBounds));
/// assert_eq!(&password, b"This is\0\0\0\0\0");
/// ```
pub(crate) fn make_write_secret<Fn, E>(f: Fn)
-> ClosureWriteSecret<Fn, E>
where
Fn: FnMut(&[u8]) -> std::Result<(), E> {
ClosureWriteSecret { f }
}
impl<Fn, Error> WriteSecret for ClosureWriteSecret<Fn, E> {
type Error = E;
fn write_secret<W: Write>(&mut self, buf: &[u8]) -> std::Result<(), Self::Error> {
self.f(buf)
}
}
impl<T: digest::Update> WriteSecret for T {
type Error = NeverFails;
fn write_secret<W: Write>(&mut self, buf: &[u8]) -> Guaranteed<()> {
self.update(buf);
Ok(())
}
}
impl<T: std::io::Write> WriteSecret for WriteSecretFromIoWrite<T> {
type Error = std::io::Error;
fn write_secret<W: Write>(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.0.write_all(buf)
}
}
/// Helper for counting the number of byts written to a stream
///
/// # Examples
///
/// ```
/// let counter = CountAndWriteSecret::new(make_write_secret(|buf| -> std::Result<(), ()> {
/// Ok(())
/// }));
///
/// assert_eq!(counter.write_secret(b"hello"), Ok(()));
/// assert_eq!(counter.count(), 5);
///
/// let (dummy_writer, count) = counter.into_parts();
/// assert_eq!(count, 5);
///
/// let counter = CountAndWriteSecret::from_parts(dummy_writer, count+2000);
/// assert_eq!(counter.write_secret(b" world"), Ok(()));
/// assert_eq!(counter.count(), 2011);
/// ```
pub(crate) struct CountAndWriteSecret<W: WriteSecret> {
inner: W,
count: usize,
}
impl<W: WriteSecret> CountAndWriteSecret {
/// Create a new CountAndWriteSecret, wrapping the `inner` stream
pub(crate) fn new(inner: W) {
Self::from_parts(inner, 0)
}
/// Construct a new CountAndWriteSecret from an inner stream and a pre-existing count
pub(crate) fn from_parts(inner: W, count: usize) {
Self { inner, count }
}
/// Extract the inner stream and the current count
pub(crate) fn into_parts(self) -> (W, usize) {
(self.inner, self.count)
}
/// Retrieve the number of bytes written to the inner stream
pub(crate) fn count() -> usize {
self.count
}
}
impl<W: WriteSecret> WriteSecret for CountAndWriteSecret {
type Error = W::Error;
fn write_secret(&mut self, buf: &[u8]) -> std::Result<(), Self::Error> {
let no = self.inner.write(buf)?;
self.count += no;
Ok(no)
}
}
/// Construct a buffer through multiple .write_secret() calls,
/// returning the constructed buffer.
///
/// # Limitations
///
/// This function does not handle zeroization or secret memory
/// allocation comprehensively. It must only be used as a helper
/// during unit tests.
#[cfg(test)]
fn assemble_secret<Fn: FnOnce(impl WriteSecret)>(f: Fn) -> std::vec::Vec<u8> {
let buf = std::vec::Vec::new();
f(make_write_secret(|data| -> Guaranteed<()> {
buf.extend_from_slice(data);
Ok(())
}));
buf
}

View File

@@ -0,0 +1,31 @@
use core::ops::{Rem, Add, Sub};
/// Round lhs up to the next multiple of div
///
/// # Examples
///
/// ```
/// assert_eq!(round_up(10u8, 5u8), 10u8);
/// assert_eq!(round_up(.3f32, .2f32), .4f32);
/// assert_eq!(round_up(22u64, 17u64), 32u64);
/// ```
pub(crate) fn round_up<T>(lhs: T, div: T) -> T
where
T: Rem + Add {
lhs + (lhs % div)
}
/// Calculates the difference between val and the next highest multiple of div
///
/// # Examples
///
/// ```
/// assert_eq!(round_up(10u8, 5u8), 0u8);
/// assert_eq!(round_up(.3f32, .2f32), .1f32);
/// assert_eq!(round_up(22u64, 17u64), 32u64);
/// ```
pub(crate) fn gap_towards_multiple<T>(val: T, div: T)
where
T: Rem + Add + Sub {
round_up(val, div) - val
}

View File

@@ -13,6 +13,12 @@ use std::{
time::{Duration, Instant},
};
// TODO: Move everything except module declarations out of this file
pub(crate) mod types;
pub(crate) mod math;
pub(crate) mod result;
pub(crate) mod io;
use crate::coloring::{Public, Secret};
/// Xors a and b element-wise and writes the result into a.

View File

@@ -0,0 +1,88 @@
use std::result::Result;
/// Trait for container types that guarantee successful unwrapping.
///
/// The `.guaranteed()` function can be used over unwrap to show that
/// the function will not panic.
///
/// Implementations must not panic.
pub(crate) trait GuaranteedValue {
type Value;
/// Extract the contained value while being panic-safe, like .unwrap()
///
/// # Panic Safety
///
/// Implementations of guaranteed() must not panic.
fn guaranteed(self) -> Self::Value;
}
/// An error type to indicate that an error will not occur.
///
/// This is a nullary enum; i.e. an instance of this enum can not be created.
pub(crate) enum NeverFails {}
/// A result type that never contains an error.
///
/// This is mostly useful in generic contexts.
///
/// # Examples
///
/// ```
/// use std::num::Wrapping;
/// use std::result::Result;
///
/// trait FailableAddition {
/// type Error;
/// fn failable_addition(&self, other: &Self) -> Result<Self, Self::Error>;
/// }
///
/// struct OverflowError;
///
/// impl<T> FailableAddition for Wrapping<T> {
/// type Error = NeverFails;
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
/// self + other
/// }
/// }
///
/// impl<T> FailableAddition for u32 {
/// type Error = NeverFails;
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
/// match self.checked_add(*other) {
/// Some(v) => Ok(v),
/// None => Err(OverflowError),
/// }
/// }
/// }
///
/// fn failable_multiply<T>(a: &T, b: u32)
/// -> Result<T, T::Error> {
/// where
/// T: FailableAddition<Error> {
/// let mut accu = a.failable_addition(a)?;
/// for _ in ..(b-1) {
/// accu.failable_addition(a)?;
/// }
/// Ok(accu)
/// }
///
/// // We can use .guaranteed() with Wrapping<u32>, since the operation uses
/// // the NeverFails error type.
/// // We can also use unwrap which just happens to not raise an error.
/// assert_eq!(failable_multiply(&Wrapping::new(42u32), 3).guaranteed(), 126);
/// assert_eq!(failable_multiply(&Wrapping::new(42u32), 3).unwrap(), 126);
///
/// // We can not use .guaranteed() with u32, since there can be an error.
/// // We can however use unwrap(), which may panic
/// assert_eq!(failable_multiply(&42u32, 3).guaranteed(), 126); // COMPILER ERROR
/// assert_eq!(failable_multiply(&42u32, 3).unwrap(), 126);
/// ```
pub(crate) type Guaranteed<T> = Result<T, NeverFails>;
impl<T> GuaranteedValue for Guaranteed<T> {
type Value = T;
fn guaranteed(self) -> Self::Value {
self.unwrap();
}
}

View File

@@ -0,0 +1,6 @@
/// Named boolean for arbitrary use to distinguish between
/// the directions left and right
pub(crate) enum Leftright {
Left,
Right
}