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 = [
"anyhow",
"rosenpass-constant-time",
"rosenpass-secret-memory",
"rosenpass-sodium",
"rosenpass-to",
"static_assertions",

View File

@@ -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 }

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 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<KEY_LEN>);
pub struct SecretHashDomain(Secret<KEY_LEN>);
#[derive(Clone, Debug)]
pub struct SecretPrfTreeBranch(Secret<KEY_LEN>);
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
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<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(&self.0, v.secret())
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
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<PrfTree> {
Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
impl HashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
Ok(HashDomain(
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
))
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(&self.0, v.secret())
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
}
impl SecretPrfTree {
pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result<SecretPrfTree> {
let mut r = SecretPrfTree(Secret::zero());
impl SecretHashDomain {
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
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<KEY_LEN>) -> Self {
Self(k)
}
pub fn mix(self, v: &[u8]) -> Result<SecretPrfTree> {
Self::prf_invoc(self.0.secret(), v)
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
Self::prf_invoc(self.0.secret(), v.secret())
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v.secret())
}
pub fn into_secret(self) -> Secret<KEY_LEN> {
@@ -88,13 +91,13 @@ impl SecretPrfTree {
}
}
impl SecretPrfTreeBranch {
pub fn mix(&self, v: &[u8]) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(self.0.secret(), v)
impl SecretHashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
SecretPrfTree::prf_invoc(self.0.secret(), v.secret())
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
}
// 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;
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;

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 cli;
pub mod config;
pub mod hash_domains;
pub mod msgs;
pub mod pqkem;
pub mod prftree;
pub mod protocol;
#[derive(thiserror::Error, Debug)]

View File

@@ -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<PeerId> {
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<PeerId> {
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<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(
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<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();
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())
}
}