use blake2::digest::{consts::U16, KeyInit as MacKeyInit, Mac, Update};
use blake2::{Blake2s256, Blake2sMac, Digest};
use chacha20poly1305::aead::{Aead, AeadInPlace, Payload};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, XChaCha20Poly1305, XNonce};
use curve25519_dalek::montgomery::MontgomeryPoint;
use hmac::SimpleHmac;
use std::io;
use zeroize::Zeroize;
use crate::wg::constants::{NoisePresharedKey, NoisePrivateKey, NoisePublicKey};
use crate::wg::constants::{
BLAKE2S_128_SIZE, BLAKE2S_256_SIZE, CHACHAPOLY_KEY_SIZE, NOISE_PUBLIC_KEY_SIZE, WG_IDENTIFIER,
WG_LABEL_COOKIE, WG_LABEL_MAC1,
};
use crate::Result;
type Blake2sMac128 = Blake2sMac<U16>;
type HmacBlake2s = SimpleHmac<Blake2s256>;
pub(crate) fn blake2s_256(msg: &[u8]) -> [u8; BLAKE2S_256_SIZE] {
let mut h = Blake2s256::new();
Digest::update(&mut h, msg);
let out = h.finalize();
let mut buf = [0u8; BLAKE2S_256_SIZE];
buf.copy_from_slice(&out);
buf
}
pub(crate) fn mix_hash(dst: &mut [u8; BLAKE2S_256_SIZE], h: &[u8; BLAKE2S_256_SIZE], data: &[u8]) {
let mut hasher = Blake2s256::new();
Digest::update(&mut hasher, h);
Digest::update(&mut hasher, data);
let out = hasher.finalize();
dst.copy_from_slice(&out);
}
pub(crate) fn hmac1(sum: &mut [u8; BLAKE2S_256_SIZE], key: &[u8], in0: &[u8]) {
let mut mac = <HmacBlake2s as Mac>::new_from_slice(key).expect("HMAC accepts any key length");
Mac::update(&mut mac, in0);
let out = mac.finalize().into_bytes();
sum.copy_from_slice(&out);
}
pub(crate) fn hmac2(sum: &mut [u8; BLAKE2S_256_SIZE], key: &[u8], in0: &[u8], in1: &[u8]) {
let mut mac = <HmacBlake2s as Mac>::new_from_slice(key).expect("HMAC accepts any key length");
Mac::update(&mut mac, in0);
Mac::update(&mut mac, in1);
let out = mac.finalize().into_bytes();
sum.copy_from_slice(&out);
}
pub(crate) fn kdf1(t0: &mut [u8; BLAKE2S_256_SIZE], key: &[u8], input: &[u8]) {
hmac1(t0, key, input);
let prk = *t0;
hmac1(t0, &prk, &[0x01]);
}
pub(crate) fn kdf2(
t0: &mut [u8; BLAKE2S_256_SIZE],
t1: &mut [u8; BLAKE2S_256_SIZE],
key: &[u8],
input: &[u8],
) {
let mut prk = [0u8; BLAKE2S_256_SIZE];
hmac1(&mut prk, key, input);
hmac1(t0, &prk, &[0x01]);
hmac2(t1, &prk, t0.as_slice(), &[0x02]);
prk.zeroize();
}
pub(crate) fn kdf3(
t0: &mut [u8; BLAKE2S_256_SIZE],
t1: &mut [u8; BLAKE2S_256_SIZE],
t2: &mut [u8; BLAKE2S_256_SIZE],
key: &[u8],
input: &[u8],
) {
let mut prk = [0u8; BLAKE2S_256_SIZE];
hmac1(&mut prk, key, input);
hmac1(t0, &prk, &[0x01]);
let mut data2 = [0u8; BLAKE2S_256_SIZE + 1];
data2[..BLAKE2S_256_SIZE].copy_from_slice(t0.as_slice());
data2[BLAKE2S_256_SIZE] = 0x02;
hmac1(t1, &prk, &data2);
let mut data3 = [0u8; BLAKE2S_256_SIZE + 1];
data3[..BLAKE2S_256_SIZE].copy_from_slice(t1.as_slice());
data3[BLAKE2S_256_SIZE] = 0x03;
hmac1(t2, &prk, &data3);
prk.zeroize();
data2.zeroize();
data3.zeroize();
}
pub(crate) fn mix_psk(
chaining_key: &mut [u8; BLAKE2S_256_SIZE],
hash: &mut [u8; BLAKE2S_256_SIZE],
key: &mut [u8; CHACHAPOLY_KEY_SIZE],
psk: &NoisePresharedKey,
) {
let mut tau = [0u8; BLAKE2S_256_SIZE];
let mut new_key = [0u8; BLAKE2S_256_SIZE];
let saved_c = *chaining_key;
kdf3(
chaining_key,
&mut tau,
&mut new_key,
&saved_c,
psk.as_bytes(),
);
key.copy_from_slice(&new_key);
let h_copy = *hash;
mix_hash(hash, &h_copy, &tau);
tau.zeroize();
new_key.zeroize();
}
pub(crate) fn mix_key(dst: &mut [u8; BLAKE2S_256_SIZE], c: &[u8; BLAKE2S_256_SIZE], data: &[u8]) {
kdf1(dst, c, data);
}
pub(crate) fn clamp(sk: &mut [u8; 32]) {
sk[0] &= 248;
sk[31] = (sk[31] & 127) | 64;
}
pub(crate) fn x25519_public(sk: &NoisePrivateKey) -> NoisePublicKey {
NoisePublicKey(MontgomeryPoint::mul_base_clamped(sk.0).to_bytes())
}
pub(crate) fn x25519_dh(sk: &NoisePrivateKey, pk: &NoisePublicKey) -> [u8; 32] {
let point = MontgomeryPoint(pk.0);
point.mul_clamped(sk.0).to_bytes()
}
pub fn generate_private_key() -> Result<NoisePrivateKey> {
let mut buf = [0u8; 32];
getrandom::getrandom(&mut buf).map_err(|e| io::Error::other(format!("getrandom: {}", e)))?;
clamp(&mut buf);
Ok(NoisePrivateKey(buf))
}
pub fn generate_preshared_key() -> Result<NoisePresharedKey> {
let mut buf = [0u8; 32];
getrandom::getrandom(&mut buf).map_err(|e| io::Error::other(format!("getrandom: {}", e)))?;
Ok(NoisePresharedKey(buf))
}
pub(crate) fn fill_random(buf: &mut [u8]) -> Result<()> {
getrandom::getrandom(buf).map_err(|e| io::Error::other(format!("getrandom: {}", e)))
}
pub(crate) fn calculate_mac1_key(pk: &NoisePublicKey) -> [u8; 32] {
let mut hasher = Blake2s256::new();
Digest::update(&mut hasher, WG_LABEL_MAC1);
Digest::update(&mut hasher, pk.0);
let out = hasher.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(&out);
key
}
#[allow(dead_code)] pub(crate) fn calculate_cookie_key(pk: &NoisePublicKey) -> [u8; 32] {
let mut hasher = Blake2s256::new();
Digest::update(&mut hasher, WG_LABEL_COOKIE);
Digest::update(&mut hasher, pk.0);
let out = hasher.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(&out);
key
}
pub(crate) fn blake2s_mac_128(key: &[u8], data: &[u8]) -> [u8; BLAKE2S_128_SIZE] {
let mut mac =
<Blake2sMac128 as MacKeyInit>::new_from_slice(key).expect("Blake2sMac accepts <=32B keys");
Update::update(&mut mac, data);
let out = mac.finalize().into_bytes();
let mut buf = [0u8; BLAKE2S_128_SIZE];
buf.copy_from_slice(&out);
buf
}
pub(crate) fn ct_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}
#[allow(dead_code)]
pub(crate) fn is_zero(arr: &[u8]) -> bool {
arr.iter().all(|&b| b == 0)
}
pub(crate) fn aead_seal(
key: &[u8; CHACHAPOLY_KEY_SIZE],
nonce_counter: u64,
pt: &[u8],
ad: &[u8],
) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
let mut nonce_bytes = [0u8; 12];
nonce_bytes[4..].copy_from_slice(&nonce_counter.to_le_bytes());
let nonce = Nonce::from_slice(&nonce_bytes);
cipher
.encrypt(nonce, Payload { msg: pt, aad: ad })
.expect("AEAD encrypt cannot fail for valid inputs")
}
pub(crate) fn aead_seal_zero(key: &[u8; CHACHAPOLY_KEY_SIZE], pt: &[u8], ad: &[u8]) -> Vec<u8> {
aead_seal(key, 0, pt, ad)
}
pub(crate) fn aead_open_zero(
key: &[u8; CHACHAPOLY_KEY_SIZE],
ct: &[u8],
ad: &[u8],
) -> Result<Vec<u8>> {
aead_open(key, 0, ct, ad)
}
pub(crate) fn aead_open(
key: &[u8; CHACHAPOLY_KEY_SIZE],
nonce_counter: u64,
ct: &[u8],
ad: &[u8],
) -> Result<Vec<u8>> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
let mut nonce_bytes = [0u8; 12];
nonce_bytes[4..].copy_from_slice(&nonce_counter.to_le_bytes());
let nonce = Nonce::from_slice(&nonce_bytes);
cipher
.decrypt(nonce, Payload { msg: ct, aad: ad })
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "aead open failed"))
}
pub(crate) fn aead_seal_in_place(
key: &[u8; CHACHAPOLY_KEY_SIZE],
nonce_counter: u64,
pt: &[u8],
ad: &[u8],
dst: &mut [u8],
) -> usize {
let needed = pt.len() + 16;
debug_assert!(dst.len() >= needed);
let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
let mut nonce_bytes = [0u8; 12];
nonce_bytes[4..].copy_from_slice(&nonce_counter.to_le_bytes());
let nonce = Nonce::from_slice(&nonce_bytes);
let mut buf: Vec<u8> = pt.to_vec();
cipher
.encrypt_in_place(nonce, ad, &mut buf)
.expect("AEAD encrypt_in_place cannot fail for valid inputs");
dst[..buf.len()].copy_from_slice(&buf);
buf.len()
}
#[allow(dead_code)] pub(crate) fn xaead_seal(key: &[u8; 32], nonce: &[u8; 24], pt: &[u8], ad: &[u8]) -> Vec<u8> {
let cipher = XChaCha20Poly1305::new(Key::from_slice(key));
cipher
.encrypt(XNonce::from_slice(nonce), Payload { msg: pt, aad: ad })
.expect("XChaCha encrypt cannot fail for valid inputs")
}
#[allow(dead_code)] pub(crate) fn xaead_open(
key: &[u8; 32],
nonce: &[u8; 24],
ct: &[u8],
ad: &[u8],
) -> Result<Vec<u8>> {
let cipher = XChaCha20Poly1305::new(Key::from_slice(key));
cipher
.decrypt(XNonce::from_slice(nonce), Payload { msg: ct, aad: ad })
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "xaead open failed"))
}
pub(crate) fn initial_chain_key() -> [u8; BLAKE2S_256_SIZE] {
blake2s_256(NOISE_CONSTRUCTION_BYTES)
}
pub(crate) fn initial_hash() -> [u8; BLAKE2S_256_SIZE] {
let ck = initial_chain_key();
let mut h = [0u8; BLAKE2S_256_SIZE];
mix_hash(&mut h, &ck, WG_IDENTIFIER);
h
}
const NOISE_CONSTRUCTION_BYTES: &[u8] = crate::wg::constants::NOISE_CONSTRUCTION;
const _: () = {
assert!(NOISE_PUBLIC_KEY_SIZE == 32);
};
#[cfg(test)]
mod tests {
use super::*;
use crate::wg::constants::NOISE_PRIVATE_KEY_SIZE;
#[test]
fn x25519_roundtrip() {
let sk_a = generate_private_key().unwrap();
let sk_b = generate_private_key().unwrap();
let pk_a = x25519_public(&sk_a);
let pk_b = x25519_public(&sk_b);
let shared_ab = x25519_dh(&sk_a, &pk_b);
let shared_ba = x25519_dh(&sk_b, &pk_a);
assert_eq!(shared_ab, shared_ba);
assert!(!shared_ab.iter().all(|&b| b == 0));
}
#[test]
fn clamp_zeroes_low_bits_and_sets_high_bits() {
let mut sk = [0xFFu8; NOISE_PRIVATE_KEY_SIZE];
clamp(&mut sk);
assert_eq!(sk[0] & 0b111, 0, "low 3 bits must be cleared");
assert_eq!(sk[31] & 0x80, 0, "top bit must be cleared");
assert_eq!(sk[31] & 0x40, 0x40, "bit 254 must be set");
}
#[test]
fn kdf1_matches_kdf2_first_output() {
let key = b"some chain key bytes...........";
let input = b"diffie-hellman output";
let mut a = [0u8; 32];
let mut b0 = [0u8; 32];
let mut b1 = [0u8; 32];
kdf1(&mut a, key, input);
kdf2(&mut b0, &mut b1, key, input);
assert_eq!(a, b0);
assert_ne!(b0, b1, "two outputs must differ");
}
#[test]
fn mix_psk_does_not_panic_and_writes_key() {
let mut ck = [0x11u8; 32];
let mut h = [0x22u8; 32];
let mut k = [0u8; 32];
let psk = NoisePresharedKey([0xAB; 32]);
mix_psk(&mut ck, &mut h, &mut k, &psk);
assert!(!is_zero(&k));
assert!(!is_zero(&ck));
}
#[test]
fn aead_roundtrip_zero_nonce() {
let key = [0x42u8; 32];
let pt = b"hello noise IK";
let ad = b"associated";
let ct = aead_seal_zero(&key, pt, ad);
let recovered = aead_open_zero(&key, &ct, ad).expect("decrypt");
assert_eq!(recovered, pt);
}
#[test]
fn aead_open_rejects_tampered() {
let key = [0x55u8; 32];
let pt = b"payload";
let ad = b"ad";
let mut ct = aead_seal_zero(&key, pt, ad);
let last = ct.len() - 1;
ct[last] ^= 1;
assert!(aead_open_zero(&key, &ct, ad).is_err());
}
#[test]
fn mac1_key_derivation_is_deterministic() {
let pk = NoisePublicKey([0x77; 32]);
let a = calculate_mac1_key(&pk);
let b = calculate_mac1_key(&pk);
assert_eq!(a, b);
let pk2 = NoisePublicKey([0x88; 32]);
assert_ne!(a, calculate_mac1_key(&pk2));
}
#[test]
fn ct_eq_basic() {
assert!(ct_eq(b"hello", b"hello"));
assert!(!ct_eq(b"hello", b"world"));
assert!(!ct_eq(b"hello", b"hello!"));
}
}