diff --git a/Cargo.lock b/Cargo.lock index aebadb3..f687edf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,6 +1074,7 @@ dependencies = [ "rosenpass-ciphers", "rosenpass-constant-time", "rosenpass-sodium", + "rosenpass-to", "rosenpass-util", "serde", "stacker", @@ -1087,12 +1088,20 @@ dependencies = [ name = "rosenpass-ciphers" version = "0.1.0" dependencies = [ + "anyhow", + "rosenpass-constant-time", "rosenpass-sodium", + "rosenpass-to", + "static_assertions", + "zeroize", ] [[package]] name = "rosenpass-constant-time" version = "0.1.0" +dependencies = [ + "rosenpass-to", +] [[package]] name = "rosenpass-fuzzing" @@ -1103,6 +1112,7 @@ dependencies = [ "rosenpass", "rosenpass-ciphers", "rosenpass-sodium", + "rosenpass-to", "stacker", ] @@ -1113,6 +1123,7 @@ dependencies = [ "anyhow", "libsodium-sys-stable", "log", + "rosenpass-to", "rosenpass-util", ] @@ -1712,6 +1723,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "zip" version = "0.6.6" diff --git a/ciphers/Cargo.toml b/ciphers/Cargo.toml index a84ba2f..c190957 100644 --- a/ciphers/Cargo.toml +++ b/ciphers/Cargo.toml @@ -10,4 +10,9 @@ repository = "https://github.com/rosenpass/rosenpass" readme = "readme.md" [dependencies] +anyhow = "1.0.75" rosenpass-sodium = { path = "../sodium" } +rosenpass-to = { path = "../to" } +rosenpass-constant-time = { path = "../constant-time" } +static_assertions = "1.1.0" +zeroize = "1.7.0" diff --git a/ciphers/src/lib.rs b/ciphers/src/lib.rs index 14fe2ac..b791db2 100644 --- a/ciphers/src/lib.rs +++ b/ciphers/src/lib.rs @@ -1,11 +1,28 @@ +use static_assertions::const_assert; + +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); + +/// Authenticated encryption with associated data pub mod aead { pub use rosenpass_sodium::aead::chacha20poly1305_ietf::{ decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN, }; } +/// Authenticated encryption with associated data with a constant nonce pub mod xaead { pub use rosenpass_sodium::aead::xchacha20poly1305_ietf::{ decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN, }; } + +pub mod hash { + pub use crate::subtle::incorrect_hmac_blake2b::{ + hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN, + }; +} diff --git a/ciphers/src/subtle/incorrect_hmac_blake2b.rs b/ciphers/src/subtle/incorrect_hmac_blake2b.rs new file mode 100644 index 0000000..802859a --- /dev/null +++ b/ciphers/src/subtle/incorrect_hmac_blake2b.rs @@ -0,0 +1,44 @@ +use anyhow::ensure; +use rosenpass_constant_time::xor; +use rosenpass_sodium::hash::blake2b; +use rosenpass_to::{ops::copy_slice, with_destination, To}; +use zeroize::Zeroizing; + +pub const KEY_LEN: usize = 32; +pub const KEY_MIN: usize = KEY_LEN; +pub const KEY_MAX: usize = KEY_LEN; +pub const OUT_MIN: usize = blake2b::OUT_MIN; +pub const OUT_MAX: usize = blake2b::OUT_MAX; + +/// This is a woefully incorrect implementation of hmac_blake2b. +/// See +/// +/// It accepts 32 byte keys, exclusively. +/// +/// This will be replaced, likely by Kekkac at some point soon. +/// +#[inline] +pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a { + const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN]; + const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN]; + + with_destination(|out: &mut [u8]| { + // Not bothering with padding; the implementation + // uses appropriately sized keys. + ensure!(key.len() == KEY_LEN); + + type Key = Zeroizing<[u8; KEY_LEN]>; + let mut tmp_key = Key::default(); + + copy_slice(key).to(tmp_key.as_mut()); + xor(&IPAD).to(tmp_key.as_mut()); + let mut outer_data = Key::default(); + blake2b::hash(tmp_key.as_ref(), data).to(outer_data.as_mut())?; + + copy_slice(key).to(tmp_key.as_mut()); + xor(&OPAD).to(tmp_key.as_mut()); + blake2b::hash(tmp_key.as_ref(), outer_data.as_ref()).to(out)?; + + Ok(()) + }) +} diff --git a/ciphers/src/subtle/mod.rs b/ciphers/src/subtle/mod.rs new file mode 100644 index 0000000..b4f4456 --- /dev/null +++ b/ciphers/src/subtle/mod.rs @@ -0,0 +1 @@ +pub mod incorrect_hmac_blake2b; diff --git a/constant-time/Cargo.toml b/constant-time/Cargo.toml index 205b0af..3e30b63 100644 --- a/constant-time/Cargo.toml +++ b/constant-time/Cargo.toml @@ -12,3 +12,4 @@ readme = "readme.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rosenpass-to = { path = "../to" } diff --git a/constant-time/src/lib.rs b/constant-time/src/lib.rs index c721e43..f1a8c54 100644 --- a/constant-time/src/lib.rs +++ b/constant-time/src/lib.rs @@ -1,18 +1,26 @@ -/// Xors a and b element-wise and writes the result into a. +use rosenpass_to::{with_destination, To}; + +/// Xors the source into the destination /// /// # Examples /// /// ``` -/// use rosenpass_constant_time::xor_into; -/// let mut a = String::from("hello").into_bytes(); -/// let b = b"world"; -/// xor_into(&mut a, b); -/// assert_eq!(&a, b"\x1f\n\x1e\x00\x0b"); +/// use rosenpass_constant_time::xor; +/// use rosenpass_to::To; +/// assert_eq!( +/// xor(b"world").to_this(|| b"hello".to_vec()), +/// b"\x1f\n\x1e\x00\x0b"); /// ``` +/// +/// # Panics +/// +/// If source and destination are of different sizes. #[inline] -pub fn xor_into(a: &mut [u8], b: &[u8]) { - assert!(a.len() == b.len()); - for (av, bv) in a.iter_mut().zip(b.iter()) { - *av ^= *bv; - } +pub fn xor<'a>(src: &'a [u8]) -> impl To<[u8], ()> + 'a { + with_destination(|dst: &mut [u8]| { + assert!(src.len() == dst.len()); + for (dv, sv) in dst.iter_mut().zip(src.iter()) { + *dv ^= *sv; + } + }) } diff --git a/fuzzing/Cargo.toml b/fuzzing/Cargo.toml index 933f5ee..f9d3e0e 100644 --- a/fuzzing/Cargo.toml +++ b/fuzzing/Cargo.toml @@ -21,6 +21,9 @@ path = "../sodium" [dependencies.rosenpass-ciphers] path = "../ciphers" +[dependencies.rosenpass-to] +path = "../to" + [[bin]] name = "fuzz_handle_msg" path = "fuzz_targets/handle_msg.rs" diff --git a/fuzzing/fuzz_targets/blake2b.rs b/fuzzing/fuzz_targets/blake2b.rs index 2d628b2..807c1a6 100644 --- a/fuzzing/fuzz_targets/blake2b.rs +++ b/fuzzing/fuzz_targets/blake2b.rs @@ -4,8 +4,8 @@ extern crate rosenpass; use libfuzzer_sys::fuzz_target; -use rosenpass::sodium::mac_into; -use rosenpass_sodium::init as sodium_init; +use rosenpass_sodium::{hash::blake2b, init as sodium_init}; +use rosenpass_to::To; #[derive(arbitrary::Arbitrary, Debug)] pub struct Blake2b { @@ -18,5 +18,5 @@ fuzz_target!(|input: Blake2b| { let mut out = [0u8; 32]; - mac_into(&mut out, &input.key, &input.data).unwrap(); + blake2b::hash(&input.key, &input.data).to(&mut out).unwrap(); }); diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index 7fa2c5b..65d9c55 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -18,6 +18,7 @@ rosenpass-util = { path = "../util" } rosenpass-constant-time = { path = "../constant-time" } rosenpass-sodium = { path = "../sodium" } rosenpass-ciphers = { path = "../ciphers" } +rosenpass-to = { path = "../to" } anyhow = { version = "1.0.71", features = ["backtrace"] } static_assertions = "1.1.0" memoffset = "0.9.0" diff --git a/rosenpass/src/labeled_prf.rs b/rosenpass/src/labeled_prf.rs index bbc88bc..87090f4 100644 --- a/rosenpass/src/labeled_prf.rs +++ b/rosenpass/src/labeled_prf.rs @@ -1,10 +1,10 @@ //! Pseudo Random Functions (PRFs) with a tree-like label scheme which //! ensures their uniqueness -use { - crate::{prftree::PrfTree, sodium::KEY_SIZE}, - anyhow::Result, -}; + +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()) @@ -30,7 +30,7 @@ prflabel!(protocol, _ckextract, "chaining key extract"); macro_rules! prflabel_leaf { ($base:ident, $name:ident, $($lbl:expr),* ) => { - pub fn $name() -> Result<[u8; KEY_SIZE]> { + pub fn $name() -> Result<[u8; KEY_LEN]> { let t = $base()?; $( let t = t.mix($lbl.as_bytes())?; )* Ok(t.into_value()) diff --git a/rosenpass/src/lib.rs b/rosenpass/src/lib.rs index 894f65c..b223400 100644 --- a/rosenpass/src/lib.rs +++ b/rosenpass/src/lib.rs @@ -1,5 +1,3 @@ -#[macro_use] -pub mod sodium; pub mod coloring; #[rustfmt::skip] pub mod labeled_prf; diff --git a/rosenpass/src/lprf.rs b/rosenpass/src/lprf.rs index 6dff7a0..765a49d 100644 --- a/rosenpass/src/lprf.rs +++ b/rosenpass/src/lprf.rs @@ -17,23 +17,16 @@ //! //! TODO Base the construction on a proper Dec function -pub struct Iprf([u8; KEY_SIZE]); -pub struct IprfBranch([u8; KEY_SIZE]); -pub struct SecretIprf(Secret); -pub struct SecretIprfBranch(Secret); +use rosenpass_ciphers::{KEY_LEN, hash}; -pub fn prf_into(out: &mut [u8], key: &[u8], data: &[u8]) { - // TODO: The error handling with sodium is a scurge - hmac_into(out, key, data).unwrap() -} - -pub fn prf(key: &[u8], data: &[u8]) -> [u8; KEY_SIZE] { - mutating([0u8; KEY_SIZE], |r| prf_into(r, key, data)) -} +pub struct Iprf([u8; KEY_LEN]); +pub struct IprfBranch([u8; KEY_LEN]); +pub struct SecretIprf(Secret); +pub struct SecretIprfBranch(Secret); impl Iprf { fn zero() -> Self { - Self([0u8; KEY_SIZE]) + Self([0u8; KEY_LEN]) } fn dup(self) -> IprfBranch { @@ -42,25 +35,25 @@ impl Iprf { // TODO: Protocol! Use domain separation to ensure that fn mix(self, v: &[u8]) -> Self { - Self(prf(&self.0, v)) + Self(hash(&self.0, v).collect<[u8; KEY_LEN]>()) } fn mix_secret(self, v: Secret) -> SecretIprf { SecretIprf::prf_invoc(&self.0, v.secret()) } - fn into_value(self) -> [u8; KEY_SIZE] { + fn into_value(self) -> [u8; KEY_LEN] { self.0 } fn extract(self, v: &[u8], dst: &mut [u8]) { - prf_into(&self.0, v, dst) + hash(&self.0, v).to(dst) } } impl IprfBranch { fn mix(&self, v: &[u8]) -> Iprf { - Iprf(prf(self.0, v)) + Iprf(hash(self.0, v).collect<[u8; KEY_LEN]>()) } fn mix_secret(&self, v: Secret) -> SecretIprf { @@ -71,7 +64,7 @@ impl IprfBranch { impl SecretIprf { fn prf_invoc(k: &[u8], d: &[u8]) -> SecretIprf { mutating(SecretIprf(Secret::zero()), |r| { - prf_into(k, d, r.secret_mut()) + hash(k, d).to(r.secret_mut()) }) } @@ -87,12 +80,12 @@ impl SecretIprf { Self::prf_invoc(self.0.secret(), v.secret()) } - fn into_secret(self) -> Secret { + fn into_secret(self) -> Secret { self.0 } fn into_secret_slice(self, v: &[u8], dst: &[u8]) { - prf_into(self.0.secret(), v, dst) + hash(self.0.secret(), v).to(dst) } } diff --git a/rosenpass/src/msgs.rs b/rosenpass/src/msgs.rs index b77af50..eac0369 100644 --- a/rosenpass/src/msgs.rs +++ b/rosenpass/src/msgs.rs @@ -44,8 +44,8 @@ //! ``` use super::RosenpassError; -use crate::{pqkem::*, sodium}; -use rosenpass_ciphers::{aead, xaead}; +use crate::pqkem::*; +use rosenpass_ciphers::{aead, xaead, KEY_LEN}; // Macro magic //////////////////////////////////////////////////////////////// @@ -265,9 +265,9 @@ data_lense! { Envelope := payload: M::LEN, /// Message Authentication Code (mac) over all bytes until (exclusive) /// `mac` itself - mac: sodium::MAC_SIZE, + mac: 16, /// Currently unused, TODO: do something with this - cookie: sodium::MAC_SIZE + cookie: 16 } data_lense! { InitHello := @@ -320,11 +320,11 @@ data_lense! { EmptyData := data_lense! { Biscuit := /// H(spki) – Ident ifies the initiator - pidi: sodium::KEY_SIZE, + pidi: KEY_LEN, /// The biscuit number (replay protection) biscuit_no: 12, /// Chaining key - ck: sodium::KEY_SIZE + ck: KEY_LEN } data_lense! { DataMsg := @@ -389,20 +389,17 @@ pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG #[cfg(test)] mod test_constants { - use crate::{ - msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN}, - sodium, - }; - use rosenpass_ciphers::xaead; + use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN}; + use rosenpass_ciphers::{xaead, KEY_LEN}; #[test] fn sodium_keysize() { - assert_eq!(sodium::KEY_SIZE, 32); + assert_eq!(KEY_LEN, 32); } #[test] fn biscuit_pt_len() { - assert_eq!(BISCUIT_PT_LEN, 2 * sodium::KEY_SIZE + 12); + assert_eq!(BISCUIT_PT_LEN, 2 * KEY_LEN + 12); } #[test] diff --git a/rosenpass/src/prftree.rs b/rosenpass/src/prftree.rs index 8064416..f3956f1 100644 --- a/rosenpass/src/prftree.rs +++ b/rosenpass/src/prftree.rs @@ -1,25 +1,23 @@ //! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf) -use { - crate::{ - coloring::Secret, - sodium::{hmac, hmac_into, KEY_SIZE}, - }, - anyhow::Result, -}; +use crate::coloring::Secret; + +use anyhow::Result; +use rosenpass_ciphers::{hash, KEY_LEN}; +use rosenpass_to::To; // TODO Use a proper Dec interface #[derive(Clone, Debug)] -pub struct PrfTree([u8; KEY_SIZE]); +pub struct PrfTree([u8; KEY_LEN]); #[derive(Clone, Debug)] -pub struct PrfTreeBranch([u8; KEY_SIZE]); +pub struct PrfTreeBranch([u8; KEY_LEN]); #[derive(Clone, Debug)] -pub struct SecretPrfTree(Secret); +pub struct SecretPrfTree(Secret); #[derive(Clone, Debug)] -pub struct SecretPrfTreeBranch(Secret); +pub struct SecretPrfTreeBranch(Secret); impl PrfTree { pub fn zero() -> Self { - Self([0u8; KEY_SIZE]) + Self([0u8; KEY_LEN]) } pub fn dup(self) -> PrfTreeBranch { @@ -32,21 +30,21 @@ impl PrfTree { // TODO: Protocol! Use domain separation to ensure that pub fn mix(self, v: &[u8]) -> Result { - Ok(Self(hmac(&self.0, v)?)) + 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 into_value(self) -> [u8; KEY_SIZE] { + pub fn into_value(self) -> [u8; KEY_LEN] { self.0 } } impl PrfTreeBranch { pub fn mix(&self, v: &[u8]) -> Result { - Ok(PrfTree(hmac(&self.0, v)?)) + Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?)) } pub fn mix_secret(&self, v: Secret) -> Result { @@ -57,7 +55,7 @@ impl PrfTreeBranch { impl SecretPrfTree { pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result { let mut r = SecretPrfTree(Secret::zero()); - hmac_into(r.0.secret_mut(), k, d)?; + hash::hash(k, d).to(r.0.secret_mut())?; Ok(r) } @@ -69,7 +67,7 @@ impl SecretPrfTree { SecretPrfTreeBranch(self.0) } - pub fn danger_from_secret(k: Secret) -> Self { + pub fn danger_from_secret(k: Secret) -> Self { Self(k) } @@ -81,12 +79,12 @@ impl SecretPrfTree { Self::prf_invoc(self.0.secret(), v.secret()) } - pub fn into_secret(self) -> Secret { + pub fn into_secret(self) -> Secret { self.0 } pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> { - hmac_into(self.0.secret_mut(), v, dst) + hash::hash(v, dst).to(self.0.secret_mut()) } } @@ -102,7 +100,7 @@ impl SecretPrfTreeBranch { // TODO: This entire API is not very nice; we need this for biscuits, but // it might be better to extract a special "biscuit" // labeled subkey and reinitialize the chain with this - pub fn danger_into_secret(self) -> Secret { + pub fn danger_into_secret(self) -> Secret { self.0 } } diff --git a/rosenpass/src/protocol.rs b/rosenpass/src/protocol.rs index a68c3ed..7c7556f 100644 --- a/rosenpass/src/protocol.rs +++ b/rosenpass/src/protocol.rs @@ -73,10 +73,9 @@ use crate::{ msgs::*, pqkem::*, prftree::{SecretPrfTree, SecretPrfTreeBranch}, - sodium::*, }; use anyhow::{bail, ensure, Context, Result}; -use rosenpass_ciphers::{aead, xaead}; +use rosenpass_ciphers::{aead, xaead, KEY_LEN}; use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase}; use std::collections::hash_map::{ Entry::{Occupied, Vacant}, @@ -145,10 +144,10 @@ pub type SSk = Secret<{ StaticKEM::SK_LEN }>; pub type EPk = Public<{ EphemeralKEM::PK_LEN }>; pub type ESk = Secret<{ EphemeralKEM::SK_LEN }>; -pub type SymKey = Secret; -pub type SymHash = Public; +pub type SymKey = Secret; +pub type SymHash = Public; -pub type PeerId = Public; +pub type PeerId = Public; pub type SessionId = Public; pub type BiscuitId = Public; @@ -1240,13 +1239,13 @@ impl HandshakeState { 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(); - aead::encrypt(ct, k.secret(), &NONCE0, &NOTHING, pt)?; + 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(); - aead::decrypt(pt, k.secret(), &NONCE0, &NOTHING, ct)?; + aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?; self.mix(ct) } @@ -1448,7 +1447,7 @@ impl CryptoServer { .mix(peer.get(self).psk.secret())?; // IHI8 - hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?; + hs.core.encrypt_and_mix(ih.auth_mut(), &[])?; // Update the handshake hash last (not changing any state on prior error peer.hs().insert(self, hs)?; @@ -1514,7 +1513,7 @@ impl CryptoServer { core.store_biscuit(self, peer, rh.biscuit_mut())?; // RHR7 - core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?; + core.encrypt_and_mix(rh.auth_mut(), &[])?; Ok(peer) } @@ -1600,7 +1599,7 @@ impl CryptoServer { ic.biscuit_mut().copy_from_slice(rh.biscuit()); // ICI4 - core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?; + core.encrypt_and_mix(ic.auth_mut(), &[])?; // Split() – We move the secrets into the session; we do not // delete the InitiatorHandshake, just clear it's secrets because @@ -1630,7 +1629,7 @@ impl CryptoServer { )?; // ICR2 - core.encrypt_and_mix(&mut [0u8; aead::TAG_LEN], &NOTHING)?; + core.encrypt_and_mix(&mut [0u8; aead::TAG_LEN], &[])?; // ICR3 core.mix(ic.sidi())?.mix(ic.sidr())?; @@ -1686,7 +1685,7 @@ impl CryptoServer { let n = cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]); let k = ses.txkm.secret(); - aead::encrypt(rc.auth_mut(), k, &n, &NOTHING, &NOTHING)?; // ct, k, n, ad, pt + aead::encrypt(rc.auth_mut(), k, &n, &[], &[])?; // ct, k, n, ad, pt Ok(peer) } @@ -1723,7 +1722,7 @@ impl CryptoServer { &mut [0u8; 0], s.txkt.secret(), &cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]), - &NOTHING, + &[], rc.auth(), )?; } diff --git a/rosenpass/src/sodium.rs b/rosenpass/src/sodium.rs deleted file mode 100644 index 48e8fb9..0000000 --- a/rosenpass/src/sodium.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Bindings and helpers for accessing libsodium functions - -use anyhow::{ensure, Result}; -use libsodium_sys as libsodium; -use rosenpass_constant_time::xor_into; -use rosenpass_util::attempt; -use static_assertions::const_assert_eq; -use std::os::raw::c_ulonglong; -use std::ptr::null as nullptr; - -pub const NONCE0: [u8; libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize] = - [0u8; libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize]; -pub const NOTHING: [u8; 0] = [0u8; 0]; -pub const KEY_SIZE: usize = 32; -pub const MAC_SIZE: usize = 16; - -const_assert_eq!( - KEY_SIZE, - libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize -); -const_assert_eq!(KEY_SIZE, libsodium::crypto_generichash_BYTES as usize); - -macro_rules! sodium_call { - ($name:ident, $($args:expr),*) => { attempt!({ - ensure!(unsafe{libsodium::$name($($args),*)} > -1, - "Error in libsodium's {}.", stringify!($name)); - Ok(()) - })}; - ($name:ident) => { sodium_call!($name, ) }; -} - -#[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<()> { - assert!(out.len() == KEY_SIZE); - blake2b_flexible(out, &NOTHING, data) -} - -#[inline] -pub fn hash(data: &[u8]) -> Result<[u8; KEY_SIZE]> { - let mut r = [0u8; KEY_SIZE]; - hash_into(&mut r, data)?; - Ok(r) -} - -#[inline] -pub fn mac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> { - assert!(out.len() == KEY_SIZE); - assert!(key.len() == KEY_SIZE); - blake2b_flexible(out, key, data) -} - -#[inline] -pub fn mac(key: &[u8], data: &[u8]) -> Result<[u8; KEY_SIZE]> { - let mut r = [0u8; KEY_SIZE]; - mac_into(&mut r, key, data)?; - Ok(r) -} - -#[inline] -pub fn mac16(key: &[u8], data: &[u8]) -> Result<[u8; 16]> { - assert!(key.len() == KEY_SIZE); - let mut out = [0u8; 16]; - blake2b_flexible(&mut out, key, data)?; - Ok(out) -} - -#[inline] -pub fn hmac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> { - // Not bothering with padding; the implementation - // uses appropriately sized keys. - ensure!(key.len() == KEY_SIZE); - - const IPAD: [u8; KEY_SIZE] = [0x36u8; KEY_SIZE]; - let mut temp_key = [0u8; KEY_SIZE]; - temp_key.copy_from_slice(key); - xor_into(&mut temp_key, &IPAD); - let outer_data = mac(&temp_key, data)?; - - const OPAD: [u8; KEY_SIZE] = [0x5Cu8; KEY_SIZE]; - temp_key.copy_from_slice(key); - xor_into(&mut temp_key, &OPAD); - mac_into(out, &temp_key, &outer_data) -} - -#[inline] -pub fn hmac(key: &[u8], data: &[u8]) -> Result<[u8; KEY_SIZE]> { - let mut r = [0u8; KEY_SIZE]; - hmac_into(&mut r, key, data)?; - Ok(r) -} diff --git a/sodium/Cargo.toml b/sodium/Cargo.toml index 1cd3bea..3108888 100644 --- a/sodium/Cargo.toml +++ b/sodium/Cargo.toml @@ -11,6 +11,7 @@ readme = "readme.md" [dependencies] rosenpass-util = { path = "../util" } +rosenpass-to = { path = "../to" } anyhow = { version = "1.0.71", features = ["backtrace"] } libsodium-sys-stable = { version = "1.19.28", features = ["use-pkg-config"] } log = { version = "0.4.17" } diff --git a/sodium/src/hash/blake2b.rs b/sodium/src/hash/blake2b.rs new file mode 100644 index 0000000..e2f2fe3 --- /dev/null +++ b/sodium/src/hash/blake2b.rs @@ -0,0 +1,31 @@ +use libsodium_sys as libsodium; +use rosenpass_to::{with_destination, To}; +use std::ffi::c_ulonglong; +use std::ptr::null; + +pub const KEY_MIN: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MIN as usize; +pub const KEY_MAX: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MAX as usize; +pub const OUT_MIN: usize = libsodium::crypto_generichash_blake2b_BYTES_MIN as usize; +pub const OUT_MAX: usize = libsodium::crypto_generichash_blake2b_BYTES_MAX as usize; + +#[inline] +pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a { + with_destination(|out: &mut [u8]| { + 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 => null(), + _ => 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() + ) + }) +} diff --git a/sodium/src/hash/mod.rs b/sodium/src/hash/mod.rs new file mode 100644 index 0000000..52d9d26 --- /dev/null +++ b/sodium/src/hash/mod.rs @@ -0,0 +1 @@ +pub mod blake2b; diff --git a/sodium/src/lib.rs b/sodium/src/lib.rs index de43326..606bbd0 100644 --- a/sodium/src/lib.rs +++ b/sodium/src/lib.rs @@ -16,4 +16,5 @@ pub fn init() -> anyhow::Result<()> { } pub mod aead; +pub mod hash; pub mod helpers;