feat: Move prftree into ciphers crate

- Use a new nomenclature for these functions based on the idea of a hash
  domain (as in domain separation); this makes much more sence
- Remove the ciphers::hash export; we did not even export a hash
  function in the purest sence of the word. This gets us around the
  difficulty of figuring out what we should call the underlying
  primitive
This commit is contained in:
Karolin Varner
2023-11-30 17:52:41 +01:00
committed by Karolin Varner
parent 0f89ab7976
commit 77cd8a9fd1
8 changed files with 111 additions and 116 deletions

1
Cargo.lock generated
View File

@@ -1096,6 +1096,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"rosenpass-constant-time", "rosenpass-constant-time",
"rosenpass-secret-memory",
"rosenpass-sodium", "rosenpass-sodium",
"rosenpass-to", "rosenpass-to",
"static_assertions", "static_assertions",

View File

@@ -14,5 +14,6 @@ anyhow = { workspace = true }
rosenpass-sodium = { workspace = true } rosenpass-sodium = { workspace = true }
rosenpass-to = { workspace = true } rosenpass-to = { workspace = true }
rosenpass-constant-time = { workspace = true } rosenpass-constant-time = { workspace = true }
rosenpass-secret-memory = { workspace = true }
static_assertions = { workspace = true } static_assertions = { workspace = true }
zeroize = { workspace = true } zeroize = { workspace = true }

View File

@@ -1,31 +1,32 @@
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
use rosenpass_secret_memory::Secret;
use anyhow::Result; use anyhow::Result;
use rosenpass_ciphers::{hash, KEY_LEN}; use rosenpass_secret_memory::Secret;
use rosenpass_to::To; use rosenpass_to::To;
use crate::subtle::incorrect_hmac_blake2b as hash;
pub use hash::KEY_LEN;
// TODO Use a proper Dec interface // TODO Use a proper Dec interface
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PrfTree([u8; KEY_LEN]); pub struct HashDomain([u8; KEY_LEN]);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PrfTreeBranch([u8; KEY_LEN]); pub struct HashDomainNamespace([u8; KEY_LEN]);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SecretPrfTree(Secret<KEY_LEN>); pub struct SecretHashDomain(Secret<KEY_LEN>);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SecretPrfTreeBranch(Secret<KEY_LEN>); pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
impl PrfTree { impl HashDomain {
pub fn zero() -> Self { pub fn zero() -> Self {
Self([0u8; KEY_LEN]) Self([0u8; KEY_LEN])
} }
pub fn dup(self) -> PrfTreeBranch { pub fn dup(self) -> HashDomainNamespace {
PrfTreeBranch(self.0) HashDomainNamespace(self.0)
} }
pub fn into_secret_prf_tree(self) -> SecretPrfTree { pub fn turn_secret(self) -> SecretHashDomain {
SecretPrfTree(Secret::from_slice(&self.0)) SecretHashDomain(Secret::from_slice(&self.0))
} }
// TODO: Protocol! Use domain separation to ensure that // TODO: Protocol! Use domain separation to ensure that
@@ -33,8 +34,8 @@ impl PrfTree {
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?)) Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
} }
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> { pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretPrfTree::prf_invoc(&self.0, v.secret()) SecretHashDomain::invoke_primitive(&self.0, v.secret())
} }
pub fn into_value(self) -> [u8; KEY_LEN] { pub fn into_value(self) -> [u8; KEY_LEN] {
@@ -42,19 +43,21 @@ impl PrfTree {
} }
} }
impl PrfTreeBranch { impl HashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<PrfTree> { pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?)) Ok(HashDomain(
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
))
} }
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> { pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretPrfTree::prf_invoc(&self.0, v.secret()) SecretHashDomain::invoke_primitive(&self.0, v.secret())
} }
} }
impl SecretPrfTree { impl SecretHashDomain {
pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result<SecretPrfTree> { pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
let mut r = SecretPrfTree(Secret::zero()); let mut r = SecretHashDomain(Secret::zero());
hash::hash(k, d).to(r.0.secret_mut())?; hash::hash(k, d).to(r.0.secret_mut())?;
Ok(r) Ok(r)
} }
@@ -63,20 +66,20 @@ impl SecretPrfTree {
Self(Secret::zero()) Self(Secret::zero())
} }
pub fn dup(self) -> SecretPrfTreeBranch { pub fn dup(self) -> SecretHashDomainNamespace {
SecretPrfTreeBranch(self.0) SecretHashDomainNamespace(self.0)
} }
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self { pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
Self(k) Self(k)
} }
pub fn mix(self, v: &[u8]) -> Result<SecretPrfTree> { pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
Self::prf_invoc(self.0.secret(), v) Self::invoke_primitive(self.0.secret(), v)
} }
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> { pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
Self::prf_invoc(self.0.secret(), v.secret()) Self::invoke_primitive(self.0.secret(), v.secret())
} }
pub fn into_secret(self) -> Secret<KEY_LEN> { pub fn into_secret(self) -> Secret<KEY_LEN> {
@@ -88,13 +91,13 @@ impl SecretPrfTree {
} }
} }
impl SecretPrfTreeBranch { impl SecretHashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<SecretPrfTree> { pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
SecretPrfTree::prf_invoc(self.0.secret(), v) SecretHashDomain::invoke_primitive(self.0.secret(), v)
} }
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> { pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretPrfTree::prf_invoc(self.0.secret(), v.secret()) SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
} }
// TODO: This entire API is not very nice; we need this for biscuits, but // TODO: This entire API is not very nice; we need this for biscuits, but

View File

@@ -5,7 +5,7 @@ pub mod subtle;
pub const KEY_LEN: usize = 32; pub const KEY_LEN: usize = 32;
const_assert!(KEY_LEN == aead::KEY_LEN); const_assert!(KEY_LEN == aead::KEY_LEN);
const_assert!(KEY_LEN == xaead::KEY_LEN); const_assert!(KEY_LEN == xaead::KEY_LEN);
const_assert!(KEY_LEN == hash::KEY_LEN); const_assert!(KEY_LEN == hash_domain::KEY_LEN);
/// Authenticated encryption with associated data /// Authenticated encryption with associated data
pub mod aead { pub mod aead {
@@ -21,8 +21,4 @@ pub mod xaead {
}; };
} }
pub mod hash { pub mod hash_domain;
pub use crate::subtle::incorrect_hmac_blake2b::{
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
};
}

View File

@@ -0,0 +1,46 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use anyhow::Result;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
// TODO Use labels that can serve as identifiers
macro_rules! hash_domain_ns {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<HashDomain> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t)
}
}
}
macro_rules! hash_domain {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())
}
}
}
pub fn protocol() -> Result<HashDomain> {
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
}
hash_domain_ns!(protocol, mac, "mac");
hash_domain_ns!(protocol, cookie, "cookie");
hash_domain_ns!(protocol, peerid, "peer id");
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
hash_domain_ns!(protocol, ckinit, "chaining key init");
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
hash_domain!(_ckextract, mix, "mix");
hash_domain!(_ckextract, hs_enc, "handshake encryption");
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
hash_domain_ns!(_ckextract, _user, "user");
hash_domain_ns!(_user, _rp, "rosenpass.eu");
hash_domain!(_rp, osk, "wireguard psk");

View File

@@ -1,48 +0,0 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use crate::prftree::PrfTree;
use anyhow::Result;
use rosenpass_ciphers::KEY_LEN;
pub fn protocol() -> Result<PrfTree> {
PrfTree::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
}
// TODO Use labels that can serve as identifiers
macro_rules! prflabel {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<PrfTree> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t)
}
}
}
prflabel!(protocol, mac, "mac");
prflabel!(protocol, cookie, "cookie");
prflabel!(protocol, peerid, "peer id");
prflabel!(protocol, biscuit_ad, "biscuit additional data");
prflabel!(protocol, ckinit, "chaining key init");
prflabel!(protocol, _ckextract, "chaining key extract");
macro_rules! prflabel_leaf {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())
}
}
}
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!(_ckextract, _user, "user");
prflabel!(_user, _rp, "rosenpass.eu");
prflabel_leaf!(_rp, osk, "wireguard psk");

View File

@@ -1,11 +1,9 @@
#[rustfmt::skip]
pub mod labeled_prf;
pub mod app_server; pub mod app_server;
pub mod cli; pub mod cli;
pub mod config; pub mod config;
pub mod hash_domains;
pub mod msgs; pub mod msgs;
pub mod pqkem; pub mod pqkem;
pub mod prftree;
pub mod protocol; pub mod protocol;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]

View File

@@ -67,13 +67,9 @@
//! # } //! # }
//! ``` //! ```
use crate::{ use crate::{hash_domains, msgs::*, pqkem::*};
labeled_prf as lprf,
msgs::*,
pqkem::*,
prftree::{SecretPrfTree, SecretPrfTreeBranch},
};
use anyhow::{bail, ensure, Context, Result}; use anyhow::{bail, ensure, Context, Result};
use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace};
use rosenpass_ciphers::{aead, xaead, KEY_LEN}; use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_secret_memory::{Public, Secret}; use rosenpass_secret_memory::{Public, Secret};
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase}; use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
@@ -233,7 +229,7 @@ pub struct HandshakeState {
/// Session ID of Responder /// Session ID of Responder
pub sidr: SessionId, pub sidr: SessionId,
/// Chaining Key /// Chaining Key
pub ck: SecretPrfTreeBranch, pub ck: SecretHashDomainNamespace, // TODO: We should probably add an abstr
} }
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
@@ -285,7 +281,7 @@ pub struct Session {
pub sidt: SessionId, pub sidt: SessionId,
pub handshake_role: HandshakeRole, pub handshake_role: HandshakeRole,
// Crypto // Crypto
pub ck: SecretPrfTreeBranch, pub ck: SecretHashDomainNamespace,
/// Key for Transmission ("transmission key mine") /// Key for Transmission ("transmission key mine")
pub txkm: SymKey, pub txkm: SymKey,
/// Key for Reception ("transmission key theirs") /// Key for Reception ("transmission key theirs")
@@ -460,7 +456,7 @@ impl CryptoServer {
#[rustfmt::skip] #[rustfmt::skip]
pub fn pidm(&self) -> Result<PeerId> { pub fn pidm(&self) -> Result<PeerId> {
Ok(Public::new( Ok(Public::new(
lprf::peerid()? hash_domains::peerid()?
.mix(self.spkm.secret())? .mix(self.spkm.secret())?
.into_value())) .into_value()))
} }
@@ -590,7 +586,7 @@ impl Peer {
#[rustfmt::skip] #[rustfmt::skip]
pub fn pidt(&self) -> Result<PeerId> { pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new( Ok(Public::new(
lprf::peerid()? hash_domains::peerid()?
.mix(self.spkt.secret())? .mix(self.spkt.secret())?
.into_value())) .into_value()))
} }
@@ -603,7 +599,7 @@ impl Session {
sidm: SessionId::zero(), sidm: SessionId::zero(),
sidt: SessionId::zero(), sidt: SessionId::zero(),
handshake_role: HandshakeRole::Initiator, handshake_role: HandshakeRole::Initiator,
ck: SecretPrfTree::zero().dup(), ck: SecretHashDomain::zero().dup(),
txkm: SymKey::zero(), txkm: SymKey::zero(),
txkt: SymKey::zero(), txkt: SymKey::zero(),
txnm: 0, txnm: 0,
@@ -1174,7 +1170,7 @@ where
{ {
/// Calculate the message authentication code (`mac`) /// Calculate the message authentication code (`mac`)
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = lprf::mac()? let mac = hash_domains::mac()?
.mix(peer.get(srv).spkt.secret())? .mix(peer.get(srv).spkt.secret())?
.mix(self.until_mac())?; .mix(self.until_mac())?;
self.mac_mut() self.mac_mut()
@@ -1189,7 +1185,9 @@ where
{ {
/// Check the message authentication code /// Check the message authentication code
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> { pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?; let expected = hash_domains::mac()?
.mix(srv.spkm.secret())?
.mix(self.until_mac())?;
Ok(rosenpass_sodium::helpers::memcmp( Ok(rosenpass_sodium::helpers::memcmp(
self.mac(), self.mac(),
&expected.into_value()[..16], &expected.into_value()[..16],
@@ -1219,32 +1217,32 @@ impl HandshakeState {
Self { Self {
sidi: SessionId::zero(), sidi: SessionId::zero(),
sidr: SessionId::zero(), sidr: SessionId::zero(),
ck: SecretPrfTree::zero().dup(), ck: SecretHashDomain::zero().dup(),
} }
} }
pub fn erase(&mut self) { pub fn erase(&mut self) {
self.ck = SecretPrfTree::zero().dup(); self.ck = SecretHashDomain::zero().dup();
} }
pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> { pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> {
self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup(); self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup();
Ok(self) Ok(self)
} }
pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> { pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> {
self.ck = self.ck.mix(&lprf::mix()?)?.mix(a)?.dup(); self.ck = self.ck.mix(&hash_domains::mix()?)?.mix(a)?.dup();
Ok(self) Ok(self)
} }
pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> { pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret(); let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?; aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?;
self.mix(ct) self.mix(ct)
} }
pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> { pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret(); let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?; aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?;
self.mix(ct) self.mix(ct)
} }
@@ -1290,7 +1288,7 @@ impl HandshakeState {
.copy_from_slice(self.ck.clone().danger_into_secret().secret()); .copy_from_slice(self.ck.clone().danger_into_secret().secret());
// calculate ad contents // calculate ad contents
let ad = lprf::biscuit_ad()? let ad = hash_domains::biscuit_ad()?
.mix(srv.spkm.secret())? .mix(srv.spkm.secret())?
.mix(self.sidi.as_slice())? .mix(self.sidi.as_slice())?
.mix(self.sidr.as_slice())? .mix(self.sidr.as_slice())?
@@ -1325,7 +1323,7 @@ impl HandshakeState {
let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize); let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize);
// Calculate additional data fields // Calculate additional data fields
let ad = lprf::biscuit_ad()? let ad = hash_domains::biscuit_ad()?
.mix(srv.spkm.secret())? .mix(srv.spkm.secret())?
.mix(sidi.as_slice())? .mix(sidi.as_slice())?
.mix(sidr.as_slice())? .mix(sidr.as_slice())?
@@ -1343,7 +1341,7 @@ impl HandshakeState {
// Reconstruct the biscuit fields // Reconstruct the biscuit fields
let no = BiscuitId::from_slice(biscuit.biscuit_no()); let no = BiscuitId::from_slice(biscuit.biscuit_no());
let ck = SecretPrfTree::danger_from_secret(Secret::from_slice(biscuit.ck())).dup(); let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
let pid = PeerId::from_slice(biscuit.pidi()); let pid = PeerId::from_slice(biscuit.pidi());
// Reconstruct the handshake state // Reconstruct the handshake state
@@ -1370,8 +1368,8 @@ impl HandshakeState {
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> { pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> {
let HandshakeState { ck, sidi, sidr } = self; let HandshakeState { ck, sidi, sidr } = self;
let tki = ck.mix(&lprf::ini_enc()?)?.into_secret(); let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret();
let tkr = ck.mix(&lprf::res_enc()?)?.into_secret(); let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret();
let created_at = srv.timebase.now(); let created_at = srv.timebase.now();
let (ntx, nrx) = (0, 0); let (ntx, nrx) = (0, 0);
let (mysid, peersid, ktx, krx) = match role { let (mysid, peersid, ktx, krx) = match role {
@@ -1402,7 +1400,7 @@ impl CryptoServer {
.get(self) .get(self)
.as_ref() .as_ref()
.with_context(|| format!("No current session for peer {:?}", peer))?; .with_context(|| format!("No current session for peer {:?}", peer))?;
Ok(session.ck.mix(&lprf::osk()?)?.into_secret()) Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret())
} }
} }