From 77cd8a9fd107f6fa11bb945a3111d0bd08bb7e85 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Thu, 30 Nov 2023 17:52:41 +0100 Subject: [PATCH] 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 --- Cargo.lock | 1 + ciphers/Cargo.toml | 1 + .../prftree.rs => ciphers/src/hash_domain.rs | 71 ++++++++++--------- ciphers/src/lib.rs | 8 +-- rosenpass/src/hash_domains.rs | 46 ++++++++++++ rosenpass/src/labeled_prf.rs | 48 ------------- rosenpass/src/lib.rs | 4 +- rosenpass/src/protocol.rs | 48 ++++++------- 8 files changed, 111 insertions(+), 116 deletions(-) rename rosenpass/src/prftree.rs => ciphers/src/hash_domain.rs (50%) create mode 100644 rosenpass/src/hash_domains.rs delete mode 100644 rosenpass/src/labeled_prf.rs diff --git a/Cargo.lock b/Cargo.lock index 7c2f43c..9b3cab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1096,6 +1096,7 @@ version = "0.1.0" dependencies = [ "anyhow", "rosenpass-constant-time", + "rosenpass-secret-memory", "rosenpass-sodium", "rosenpass-to", "static_assertions", diff --git a/ciphers/Cargo.toml b/ciphers/Cargo.toml index 2663cab..4d739d4 100644 --- a/ciphers/Cargo.toml +++ b/ciphers/Cargo.toml @@ -14,5 +14,6 @@ anyhow = { workspace = true } rosenpass-sodium = { workspace = true } rosenpass-to = { workspace = true } rosenpass-constant-time = { workspace = true } +rosenpass-secret-memory = { workspace = true } static_assertions = { workspace = true } zeroize = { workspace = true } diff --git a/rosenpass/src/prftree.rs b/ciphers/src/hash_domain.rs similarity index 50% rename from rosenpass/src/prftree.rs rename to ciphers/src/hash_domain.rs index 289646c..856e7bd 100644 --- a/rosenpass/src/prftree.rs +++ b/ciphers/src/hash_domain.rs @@ -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 rosenpass_ciphers::{hash, KEY_LEN}; +use rosenpass_secret_memory::Secret; use rosenpass_to::To; +use crate::subtle::incorrect_hmac_blake2b as hash; + +pub use hash::KEY_LEN; + // TODO Use a proper Dec interface #[derive(Clone, Debug)] -pub struct PrfTree([u8; KEY_LEN]); +pub struct HashDomain([u8; KEY_LEN]); #[derive(Clone, Debug)] -pub struct PrfTreeBranch([u8; KEY_LEN]); +pub struct HashDomainNamespace([u8; KEY_LEN]); #[derive(Clone, Debug)] -pub struct SecretPrfTree(Secret); +pub struct SecretHashDomain(Secret); #[derive(Clone, Debug)] -pub struct SecretPrfTreeBranch(Secret); +pub struct SecretHashDomainNamespace(Secret); -impl PrfTree { +impl HashDomain { pub fn zero() -> Self { Self([0u8; KEY_LEN]) } - pub fn dup(self) -> PrfTreeBranch { - PrfTreeBranch(self.0) + pub fn dup(self) -> HashDomainNamespace { + HashDomainNamespace(self.0) } - pub fn into_secret_prf_tree(self) -> SecretPrfTree { - SecretPrfTree(Secret::from_slice(&self.0)) + pub fn turn_secret(self) -> SecretHashDomain { + SecretHashDomain(Secret::from_slice(&self.0)) } // 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]>()?)) } - pub fn mix_secret(self, v: Secret) -> Result { - SecretPrfTree::prf_invoc(&self.0, v.secret()) + pub fn mix_secret(self, v: Secret) -> Result { + SecretHashDomain::invoke_primitive(&self.0, v.secret()) } pub fn into_value(self) -> [u8; KEY_LEN] { @@ -42,19 +43,21 @@ impl PrfTree { } } -impl PrfTreeBranch { - pub fn mix(&self, v: &[u8]) -> Result { - Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?)) +impl HashDomainNamespace { + pub fn mix(&self, v: &[u8]) -> Result { + Ok(HashDomain( + hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?, + )) } - pub fn mix_secret(&self, v: Secret) -> Result { - SecretPrfTree::prf_invoc(&self.0, v.secret()) + pub fn mix_secret(&self, v: Secret) -> Result { + SecretHashDomain::invoke_primitive(&self.0, v.secret()) } } -impl SecretPrfTree { - pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result { - let mut r = SecretPrfTree(Secret::zero()); +impl SecretHashDomain { + pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result { + let mut r = SecretHashDomain(Secret::zero()); hash::hash(k, d).to(r.0.secret_mut())?; Ok(r) } @@ -63,20 +66,20 @@ impl SecretPrfTree { Self(Secret::zero()) } - pub fn dup(self) -> SecretPrfTreeBranch { - SecretPrfTreeBranch(self.0) + pub fn dup(self) -> SecretHashDomainNamespace { + SecretHashDomainNamespace(self.0) } pub fn danger_from_secret(k: Secret) -> Self { Self(k) } - pub fn mix(self, v: &[u8]) -> Result { - Self::prf_invoc(self.0.secret(), v) + pub fn mix(self, v: &[u8]) -> Result { + Self::invoke_primitive(self.0.secret(), v) } - pub fn mix_secret(self, v: Secret) -> Result { - Self::prf_invoc(self.0.secret(), v.secret()) + pub fn mix_secret(self, v: Secret) -> Result { + Self::invoke_primitive(self.0.secret(), v.secret()) } pub fn into_secret(self) -> Secret { @@ -88,13 +91,13 @@ impl SecretPrfTree { } } -impl SecretPrfTreeBranch { - pub fn mix(&self, v: &[u8]) -> Result { - SecretPrfTree::prf_invoc(self.0.secret(), v) +impl SecretHashDomainNamespace { + pub fn mix(&self, v: &[u8]) -> Result { + SecretHashDomain::invoke_primitive(self.0.secret(), v) } - pub fn mix_secret(&self, v: Secret) -> Result { - SecretPrfTree::prf_invoc(self.0.secret(), v.secret()) + pub fn mix_secret(&self, v: Secret) -> Result { + SecretHashDomain::invoke_primitive(self.0.secret(), v.secret()) } // TODO: This entire API is not very nice; we need this for biscuits, but diff --git a/ciphers/src/lib.rs b/ciphers/src/lib.rs index b791db2..f0040ea 100644 --- a/ciphers/src/lib.rs +++ b/ciphers/src/lib.rs @@ -5,7 +5,7 @@ pub mod subtle; pub const KEY_LEN: usize = 32; const_assert!(KEY_LEN == aead::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 pub mod aead { @@ -21,8 +21,4 @@ pub mod xaead { }; } -pub mod hash { - pub use crate::subtle::incorrect_hmac_blake2b::{ - hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN, - }; -} +pub mod hash_domain; diff --git a/rosenpass/src/hash_domains.rs b/rosenpass/src/hash_domains.rs new file mode 100644 index 0000000..988dc21 --- /dev/null +++ b/rosenpass/src/hash_domains.rs @@ -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 { + 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::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"); diff --git a/rosenpass/src/labeled_prf.rs b/rosenpass/src/labeled_prf.rs deleted file mode 100644 index 87090f4..0000000 --- a/rosenpass/src/labeled_prf.rs +++ /dev/null @@ -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::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 { - 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"); diff --git a/rosenpass/src/lib.rs b/rosenpass/src/lib.rs index ee49bb6..a6cf82c 100644 --- a/rosenpass/src/lib.rs +++ b/rosenpass/src/lib.rs @@ -1,11 +1,9 @@ -#[rustfmt::skip] -pub mod labeled_prf; pub mod app_server; pub mod cli; pub mod config; +pub mod hash_domains; pub mod msgs; pub mod pqkem; -pub mod prftree; pub mod protocol; #[derive(thiserror::Error, Debug)] diff --git a/rosenpass/src/protocol.rs b/rosenpass/src/protocol.rs index 0207f0a..5f50b01 100644 --- a/rosenpass/src/protocol.rs +++ b/rosenpass/src/protocol.rs @@ -67,13 +67,9 @@ //! # } //! ``` -use crate::{ - labeled_prf as lprf, - msgs::*, - pqkem::*, - prftree::{SecretPrfTree, SecretPrfTreeBranch}, -}; +use crate::{hash_domains, msgs::*, pqkem::*}; use anyhow::{bail, ensure, Context, Result}; +use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace}; use rosenpass_ciphers::{aead, xaead, KEY_LEN}; use rosenpass_secret_memory::{Public, Secret}; use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase}; @@ -233,7 +229,7 @@ pub struct HandshakeState { /// Session ID of Responder pub sidr: SessionId, /// Chaining Key - pub ck: SecretPrfTreeBranch, + pub ck: SecretHashDomainNamespace, // TODO: We should probably add an abstr } #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] @@ -285,7 +281,7 @@ pub struct Session { pub sidt: SessionId, pub handshake_role: HandshakeRole, // Crypto - pub ck: SecretPrfTreeBranch, + pub ck: SecretHashDomainNamespace, /// Key for Transmission ("transmission key mine") pub txkm: SymKey, /// Key for Reception ("transmission key theirs") @@ -460,7 +456,7 @@ impl CryptoServer { #[rustfmt::skip] pub fn pidm(&self) -> Result { Ok(Public::new( - lprf::peerid()? + hash_domains::peerid()? .mix(self.spkm.secret())? .into_value())) } @@ -590,7 +586,7 @@ impl Peer { #[rustfmt::skip] pub fn pidt(&self) -> Result { Ok(Public::new( - lprf::peerid()? + hash_domains::peerid()? .mix(self.spkt.secret())? .into_value())) } @@ -603,7 +599,7 @@ impl Session { sidm: SessionId::zero(), sidt: SessionId::zero(), handshake_role: HandshakeRole::Initiator, - ck: SecretPrfTree::zero().dup(), + ck: SecretHashDomain::zero().dup(), txkm: SymKey::zero(), txkt: SymKey::zero(), txnm: 0, @@ -1174,7 +1170,7 @@ where { /// Calculate the message authentication code (`mac`) 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(self.until_mac())?; self.mac_mut() @@ -1189,7 +1185,9 @@ where { /// Check the message authentication code pub fn check_seal(&self, srv: &CryptoServer) -> Result { - 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( self.mac(), &expected.into_value()[..16], @@ -1219,32 +1217,32 @@ impl HandshakeState { Self { sidi: SessionId::zero(), sidr: SessionId::zero(), - ck: SecretPrfTree::zero().dup(), + ck: SecretHashDomain::zero().dup(), } } 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> { - self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup(); + self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup(); Ok(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) } 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)?; self.mix(ct) } 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)?; self.mix(ct) } @@ -1290,7 +1288,7 @@ impl HandshakeState { .copy_from_slice(self.ck.clone().danger_into_secret().secret()); // calculate ad contents - let ad = lprf::biscuit_ad()? + let ad = hash_domains::biscuit_ad()? .mix(srv.spkm.secret())? .mix(self.sidi.as_slice())? .mix(self.sidr.as_slice())? @@ -1325,7 +1323,7 @@ impl HandshakeState { let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize); // Calculate additional data fields - let ad = lprf::biscuit_ad()? + let ad = hash_domains::biscuit_ad()? .mix(srv.spkm.secret())? .mix(sidi.as_slice())? .mix(sidr.as_slice())? @@ -1343,7 +1341,7 @@ impl HandshakeState { // Reconstruct the biscuit fields 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()); // Reconstruct the handshake state @@ -1370,8 +1368,8 @@ impl HandshakeState { pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result { let HandshakeState { ck, sidi, sidr } = self; - let tki = ck.mix(&lprf::ini_enc()?)?.into_secret(); - let tkr = ck.mix(&lprf::res_enc()?)?.into_secret(); + let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret(); + let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret(); let created_at = srv.timebase.now(); let (ntx, nrx) = (0, 0); let (mysid, peersid, ktx, krx) = match role { @@ -1402,7 +1400,7 @@ impl CryptoServer { .get(self) .as_ref() .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()) } }