pub(crate) mod buffers;
pub mod cipher;
pub mod cipher_state;
pub mod curve;
pub mod datagram;
pub mod error;
pub(crate) mod handshake;
pub mod hash;
pub mod pattern;
pub(crate) mod process;
pub mod role;
#[cfg(any(target_os = "macos", target_os = "ios", test))]
pub(crate) mod seal;
pub mod session_id;
#[doc(hidden)]
pub mod support;
pub mod symmetric_state;
pub mod tokens;
pub mod transport;
pub mod well_formed;
pub use self::cipher::{AesGcm, AesGcmKey, ChaChaPoly, ChaChaPolyKey, Cipher};
pub use self::cipher_state::CipherState;
pub use self::curve::{Curve, DhCurve, P256, X448, X25519};
pub use self::datagram::{DatagramRecv, DatagramSend};
pub use self::error::HandshakeError;
pub use self::hash::{Blake2b, Blake2s, Hash, Sha256, Sha512};
pub use self::pattern::Pattern;
pub use self::role::{Initiator, Responder, Role};
pub use self::session_id::SessionId;
pub use self::symmetric_state::SymmetricState;
pub use self::tokens::*;
pub use self::transport::{Transport, TransportRecv, TransportSend};
pub use self::well_formed::WellFormed;
use std::fmt;
use std::marker::PhantomData;
pub struct Noise<P, Cu, Ci, H> {
_pattern: PhantomData<fn() -> P>,
_curve: PhantomData<fn() -> Cu>,
_cipher: PhantomData<fn() -> Ci>,
_hash: PhantomData<fn() -> H>,
}
impl<P, Cu, Ci, H> Noise<P, Cu, Ci, H> {
pub const fn new() -> Self {
Self {
_pattern: PhantomData,
_curve: PhantomData,
_cipher: PhantomData,
_hash: PhantomData,
}
}
}
impl<P: Pattern, Cu: DhCurve, Ci: Cipher, H: Hash> Noise<P, Cu, Ci, H> {
pub const DHLEN: usize = Cu::DHLEN;
pub const PUBLIC_KEY_SIZE: usize = Cu::PUBLIC_KEY_SIZE;
pub const TAG_SIZE: usize = Ci::TAG_SIZE;
pub const HASH_LEN: usize = H::HASH_LEN;
pub const NUM_MESSAGES: usize = P::NUM_MESSAGES;
}
impl<P: Pattern, Cu: Curve, Ci: Cipher, H: Hash> fmt::Display for Noise<P, Cu, Ci, H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Noise_{}_{}_{}_{}", P::NAME, Cu::NAME, Ci::NAME, H::NAME)
}
}
impl<P, Cu, Ci, H> Default for Noise<P, Cu, Ci, H> {
fn default() -> Self {
Self::new()
}
}
pub trait Protocol {
type Pattern: Pattern;
type Curve: DhCurve;
type Cipher: Cipher;
type Hash: Hash;
}
impl<P: WellFormed, Cu: DhCurve, Ci: Cipher, H: Hash> Protocol for Noise<P, Cu, Ci, H> {
type Pattern = P;
type Curve = Cu;
type Cipher = Ci;
type Hash = H;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::curve::p256::{P256r1PrivateKey, P256r1PublicKey};
use crate::noise_message_size;
use crate::provider::EphemeralOnly;
use crate::provider::ProviderExt;
use crate::psk::Psk;
use rand::rngs::StdRng;
use std::num::NonZeroU64;
type Channel = Noise<pattern::IKpsk1, P256, ChaChaPoly, Blake2b>;
type NoiseSeal = Noise<pattern::N, P256, ChaChaPoly, Blake2b>;
type NoiseK = Noise<pattern::K, P256, ChaChaPoly, Blake2b>;
type NoiseKpsk0 = Noise<pattern::Kpsk0, P256, ChaChaPoly, Blake2b>;
hiss::noise! { pub N<P256, ChaChaPoly, Blake2b> { <- s ... -> e, es } }
hiss::noise! { pub K<P256, ChaChaPoly, Blake2b> { -> s <- s ... -> e, es, ss } }
hiss::noise! { pub Kpsk0<P256, ChaChaPoly, Blake2b> { -> s <- s ... -> psk, e, es, ss } }
hiss::noise! {
pub IKpsk1<P256, ChaChaPoly, Blake2b> { <- s ... -> e, es, s, ss, psk <- e, ee, se }
}
hiss::noise! {
pub IkAes<P256, AesGcm, Sha256> { <- s ... -> e, es, s, ss <- e, ee, se }
}
#[test]
fn session_types_are_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Transport<IkAes>>();
assert_send_sync::<DatagramRecv<IkAes>>();
assert_send_sync::<DatagramSend<IkAes>>();
assert_send_sync::<CipherState<AesGcm>>();
assert_send_sync::<Transport<IKpsk1>>();
assert_send_sync::<DatagramRecv<IKpsk1>>();
assert_send_sync::<DatagramSend<IKpsk1>>();
assert_send_sync::<CipherState<ChaChaPoly>>();
}
fn complete_ikpsk1(psk: &Psk) -> (Transport<IKpsk1>, Transport<IKpsk1>) {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let (msg1, i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, psk)
.unwrap();
let r_hs = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, psk)
.unwrap();
let (msg2, r_transport) = r_hs.write_message_2().unwrap();
let i_transport = i_hs.read_message_2(&msg2).unwrap();
(i_transport, r_transport)
}
#[test]
fn descriptor_string() {
let proto = Channel::new();
assert_eq!(proto.to_string(), "Noise_IKpsk1_P256_ChaChaPoly_BLAKE2b");
}
#[test]
fn n_descriptor_string() {
let proto = NoiseSeal::new();
assert_eq!(proto.to_string(), "Noise_N_P256_ChaChaPoly_BLAKE2b");
}
#[test]
fn sizes() {
assert_eq!(Channel::DHLEN, 32);
assert_eq!(Channel::PUBLIC_KEY_SIZE, 65);
assert_eq!(Channel::TAG_SIZE, 16);
assert_eq!(Channel::HASH_LEN, 64);
assert_eq!(Channel::NUM_MESSAGES, 2);
}
#[test]
fn n_sizes() {
assert_eq!(NoiseSeal::NUM_MESSAGES, 1);
assert_eq!(size_of::<NoiseSeal>(), 0);
}
#[test]
fn zero_sized() {
assert_eq!(size_of::<Channel>(), 0);
}
#[test]
fn noise_n_seal_open() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let recipient_static = provider.generate::<P256>().unwrap();
let recipient_pub = provider.public(&recipient_static).unwrap();
let psk_to_seal = Psk::from_bytes([0x42; 32]);
let (msg, mut transport) = N::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
recipient_pub,
)
.write_message_1()
.unwrap();
assert_eq!(N::MSG1_SIZE, 81);
let mut sealed = [0u8; 64]; let sealed_len = transport.send(psk_to_seal.as_bytes(), &mut sealed).unwrap();
let mut transport = N::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
recipient_static,
)
.unwrap()
.read_message_1(&msg)
.unwrap();
let mut opened = [0u8; 32];
let opened_len = transport
.receive(&sealed[..sealed_len], &mut opened)
.unwrap();
assert_eq!(opened_len, 32);
assert_eq!(opened, *psk_to_seal.as_bytes());
}
#[test]
fn noise_n_tampered_ephemeral_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let recipient_static = provider.generate::<P256>().unwrap();
let recipient_pub = provider.public(&recipient_static).unwrap();
let (mut tampered, _transport) = N::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
recipient_pub,
)
.write_message_1()
.unwrap();
tampered[1] ^= 0xFF;
let outcome = N::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
recipient_static,
)
.unwrap()
.read_message_1(&tampered);
assert!(outcome.is_err());
}
#[test]
fn noise_n_tampered_tag_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let recipient_static = provider.generate::<P256>().unwrap();
let recipient_pub = provider.public(&recipient_static).unwrap();
let (mut tampered, _transport) = N::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
recipient_pub,
)
.write_message_1()
.unwrap();
tampered[N::MSG1_SIZE - 1] ^= 0xFF;
let outcome = N::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
recipient_static,
)
.unwrap()
.read_message_1(&tampered);
assert!(matches!(outcome, Err(HandshakeError::DecryptionFailed)));
}
#[test]
fn k_descriptor_string() {
let proto = NoiseK::new();
assert_eq!(proto.to_string(), "Noise_K_P256_ChaChaPoly_BLAKE2b");
}
#[test]
fn kpsk0_descriptor_string() {
let proto = NoiseKpsk0::new();
assert_eq!(proto.to_string(), "Noise_Kpsk0_P256_ChaChaPoly_BLAKE2b");
}
#[test]
fn k_sizes() {
assert_eq!(NoiseK::NUM_MESSAGES, 1);
assert_eq!(size_of::<NoiseK>(), 0);
}
#[test]
fn kpsk0_sizes() {
assert_eq!(NoiseKpsk0::NUM_MESSAGES, 1);
assert_eq!(size_of::<NoiseKpsk0>(), 0);
}
#[test]
fn noise_k_seal_open() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let alice_static = provider.generate::<P256>().unwrap();
let alice_pub = provider.public(&alice_static).unwrap();
let bob_static = provider.generate::<P256>().unwrap();
let bob_pub = provider.public(&bob_static).unwrap();
let payload: [u8; 32] = [0x42; 32];
let (msg, mut transport) = K::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
alice_static,
bob_pub,
)
.unwrap()
.write_message_1()
.unwrap();
assert_eq!(K::MSG1_SIZE, 81);
let mut sealed = [0u8; 64]; let sealed_len = transport.send(&payload, &mut sealed).unwrap();
let mut transport = K::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
alice_pub,
bob_static,
)
.unwrap()
.read_message_1(&msg)
.unwrap();
let mut opened = [0u8; 32];
let opened_len = transport
.receive(&sealed[..sealed_len], &mut opened)
.unwrap();
assert_eq!(opened_len, 32);
assert_eq!(opened, payload);
}
#[test]
fn noise_kpsk0_seal_open() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let alice_static = provider.generate::<P256>().unwrap();
let alice_pub = provider.public(&alice_static).unwrap();
let bob_static = provider.generate::<P256>().unwrap();
let bob_pub = provider.public(&bob_static).unwrap();
let psk = Psk::from_bytes([0xBB; 32]);
let payload: [u8; 32] = [0x42; 32];
let (msg, mut transport) = Kpsk0::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
alice_static,
bob_pub,
)
.unwrap()
.write_message_1(&psk)
.unwrap();
assert_eq!(Kpsk0::MSG1_SIZE, 81);
let mut sealed = [0u8; 64];
let sealed_len = transport.send(&payload, &mut sealed).unwrap();
let mut transport = Kpsk0::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
alice_pub,
bob_static,
)
.unwrap()
.read_message_1(&msg, &psk)
.unwrap();
let mut opened = [0u8; 32];
let opened_len = transport
.receive(&sealed[..sealed_len], &mut opened)
.unwrap();
assert_eq!(opened_len, 32);
assert_eq!(opened, payload);
}
#[test]
fn noise_kpsk0_wrong_psk_fails() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let alice_static = provider.generate::<P256>().unwrap();
let alice_pub = provider.public(&alice_static).unwrap();
let bob_static = provider.generate::<P256>().unwrap();
let bob_pub = provider.public(&bob_static).unwrap();
let psk = Psk::from_bytes([0xBB; 32]);
let wrong_psk = Psk::from_bytes([0xCC; 32]);
let payload: [u8; 32] = [0x42; 32];
let (msg, mut transport) = Kpsk0::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
alice_static,
bob_pub,
)
.unwrap()
.write_message_1(&psk)
.unwrap();
let mut sealed = [0u8; 64];
let _sealed_len = transport.send(&payload, &mut sealed).unwrap();
let result = Kpsk0::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
alice_pub,
bob_static,
)
.unwrap()
.read_message_1(&msg, &wrong_psk);
assert!(result.is_err());
}
#[test]
fn ikpsk1_round_trip() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let initiator_pub = provider.public(&initiator_static).unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let psk = Psk::from_bytes([0xAA; 32]);
let (msg1, i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
assert_eq!(IKpsk1::MSG1_SIZE, 162);
let initiator_e_from_wire =
P256r1PublicKey::from_bytes(&msg1[..65]).expect("valid ephemeral in msg1");
let r_hs = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &psk)
.unwrap();
assert_eq!(*r_hs.remote_ephemeral(), initiator_e_from_wire);
assert_eq!(*r_hs.remote_static(), initiator_pub);
let (msg2, mut r_transport) = r_hs.write_message_2().unwrap();
assert_eq!(IKpsk1::MSG2_SIZE, 81);
P256r1PublicKey::from_bytes(&msg2[..65]).expect("valid ephemeral in msg2");
let mut i_transport = i_hs.read_message_2(&msg2).unwrap();
assert_eq!(i_transport.session_id(), r_transport.session_id());
let plaintext = b"hello from initiator";
let mut ct_buf = [0u8; 256];
let ct_len = i_transport.send(plaintext, &mut ct_buf).unwrap();
let mut pt_buf = [0u8; 256];
let pt_len = r_transport.receive(&ct_buf[..ct_len], &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], plaintext);
let plaintext = b"hello from responder";
let ct_len = r_transport.send(plaintext, &mut ct_buf).unwrap();
let pt_len = i_transport.receive(&ct_buf[..ct_len], &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], plaintext);
}
#[test]
fn chacha_encrypt_decrypt_round_trip() {
let key = [0x42u8; 32];
let plaintext = b"the quick brown fox";
let ad = b"associated data";
let mut ct = [0u8; 128];
let ct_len =
ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, ad, plaintext, &mut ct).unwrap();
assert_eq!(ct_len, plaintext.len() + 16);
let mut pt = [0u8; 128];
let pt_len =
ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, ad, &ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], plaintext);
}
#[test]
fn chacha_decrypt_corrupted_tag() {
let key = [0x42u8; 32];
let plaintext = b"test data";
let mut ct = [0u8; 64];
let ct_len =
ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], plaintext, &mut ct).unwrap();
ct[ct_len - 1] ^= 0xFF;
let mut pt = [0u8; 64];
let err = ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, &[], &ct[..ct_len], &mut pt)
.unwrap_err();
assert!(
matches!(err, error::HandshakeError::DecryptionFailed),
"expected DecryptionFailed, got {err:?}"
);
}
#[test]
fn chacha_decrypt_too_short() {
let key = [0u8; 32];
let mut pt = [0u8; 64];
let err =
ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, &[], &[0u8; 15], &mut pt).unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn chacha_wrong_nonce_fails() {
let key = [0x42u8; 32];
let plaintext = b"nonce matters";
let mut ct = [0u8; 64];
let ct_len =
ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], plaintext, &mut ct).unwrap();
let mut pt = [0u8; 64];
let err = ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 1, &[], &ct[..ct_len], &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn chacha_wrong_key_fails() {
let key = [0x42u8; 32];
let wrong_key = [0x43u8; 32];
let plaintext = b"key matters";
let mut ct = [0u8; 64];
let ct_len =
ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], plaintext, &mut ct).unwrap();
let mut pt = [0u8; 64];
let err = ChaChaPoly::decrypt(&ChaChaPoly::key(&wrong_key), 0, &[], &ct[..ct_len], &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn chacha_wrong_ad_fails() {
let key = [0x42u8; 32];
let plaintext = b"ad matters";
let mut ct = [0u8; 64];
let ct_len =
ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, b"correct", plaintext, &mut ct).unwrap();
let mut pt = [0u8; 64];
let err = ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, b"wrong", &ct[..ct_len], &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn chacha_empty_plaintext() {
let key = [0x42u8; 32];
let mut ct = [0u8; 16]; let ct_len = ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], &[], &mut ct).unwrap();
assert_eq!(ct_len, 16);
let mut pt = [0u8; 0];
let pt_len =
ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, &[], &ct[..ct_len], &mut pt).unwrap();
assert_eq!(pt_len, 0);
}
#[test]
fn cipher_state_unkeyed_passthrough() {
let mut cs = cipher_state::CipherState::<ChaChaPoly>::empty();
assert!(!cs.has_key());
let plaintext = b"plaintext passthrough";
let mut out = [0u8; 64];
let len = cs.encrypt_with_ad(b"ad", plaintext, &mut out).unwrap();
assert_eq!(&out[..len], plaintext);
let mut pt = [0u8; 64];
let len = cs.decrypt_with_ad(b"ad", &out[..len], &mut pt).unwrap();
assert_eq!(&pt[..len], plaintext);
}
#[test]
fn blake2b_hash_deterministic() {
let a = Blake2b::hash(b"test data");
let b = Blake2b::hash(b"test data");
assert_eq!(a, b);
assert_eq!(a.len(), 64);
}
#[test]
fn blake2b_hash_different_inputs() {
let a = Blake2b::hash(b"input one");
let b = Blake2b::hash(b"input two");
assert_ne!(a, b);
}
#[test]
fn blake2b_hash_two_equals_concat() {
let a = b"first part";
let b = b"second part";
let h1 = Blake2b::hash_two(a, b);
let mut concat = a.to_vec();
concat.extend_from_slice(b);
let h2 = Blake2b::hash(&concat);
assert_eq!(h1, h2);
}
#[test]
fn blake2b_hmac_deterministic() {
let a = Blake2b::hmac(b"key", b"data");
let b = Blake2b::hmac(b"key", b"data");
assert_eq!(a, b);
assert_eq!(a.len(), 64);
}
#[test]
fn blake2b_hmac_different_keys() {
let a = Blake2b::hmac(b"key1", b"data");
let b = Blake2b::hmac(b"key2", b"data");
assert_ne!(a, b);
}
#[test]
fn blake2b_hmac_cross_checked() {
assert_eq!(
hex::encode(Blake2b::hmac(&[0x0b; 20], b"Hi There")),
"358a6a184924894fc34bee5680eedf57d84a37bb38832f288e3b27dc63a98cc8\
c91e76da476b508bc6b2d408a248857452906e4a20b48c6b4b55d2df0fe1dd24"
);
assert_eq!(
hex::encode(Blake2b::hmac(b"Jefe", b"what do ya want for nothing?")),
"6ff884f8ddc2a6586b3c98a4cd6ebdf14ec10204b6710073eb5865ade37a2643\
b8807c1335d107ecdb9ffeaeb6828c4625ba172c66379efcd222c2de11727ab4"
);
assert_eq!(
hex::encode(Blake2b::hmac(&[0xaa; 20], &[0xdd; 50])),
"f43bc62c7a99353c3b2c60e8ef24fbbd42e9547866dc9c5be4edc6f4a7d4bc0a\
c620c2c60034d040f0dbaf86f9e9cd7891a095595eed55e2a996215f0c15c018"
);
assert_eq!(
hex::encode(Blake2b::hmac(
&[0xaa; 131],
b"Test Using Larger Than Block-Size Key - Hash Key First"
)),
"a54b2943b2a20227d41ca46c0945af09bc1faefb2f49894c23aebc557fb79c48\
89dca74408dc865086667aedee4a3185c53a49c80b814c4c5813ea0c8b38a8f8"
);
}
#[test]
fn sha256_matches_nist_vectors() {
assert_eq!(
hex::encode(Sha256::hash(b"")),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
assert_eq!(
hex::encode(Sha256::hash(b"abc")),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
assert_eq!(
hex::encode(Sha256::hash(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
)),
"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
);
assert_eq!(Sha256::hash(b"abc").len(), 32);
}
#[test]
fn sha256_hash_two_equals_concat() {
let a = b"first part";
let b = b"second part";
let h1 = Sha256::hash_two(a, b);
let mut concat = a.to_vec();
concat.extend_from_slice(b);
let h2 = Sha256::hash(&concat);
assert_eq!(h1, h2);
}
#[test]
fn sha256_hmac_rfc4231() {
assert_eq!(
hex::encode(Sha256::hmac(&[0x0b; 20], b"Hi There")),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
assert_eq!(
hex::encode(Sha256::hmac(b"Jefe", b"what do ya want for nothing?")),
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
);
assert_eq!(
hex::encode(Sha256::hmac(&[0xaa; 20], &[0xdd; 50])),
"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
);
assert_eq!(
hex::encode(Sha256::hmac(
&[0xaa; 131],
b"Test Using Larger Than Block-Size Key - Hash Key First"
)),
"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
);
}
#[test]
fn sha256_hmac_different_keys() {
let a = Sha256::hmac(b"key1", b"data");
let b = Sha256::hmac(b"key2", b"data");
assert_ne!(a, b);
}
#[test]
fn blake2s_matches_rfc7693() {
assert_eq!(
hex::encode(Blake2s::hash(b"abc")),
"508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982"
);
assert_eq!(Blake2s::hash(b"abc").len(), 32);
}
#[test]
fn blake2s_hash_two_equals_concat() {
let a = b"first part";
let b = b"second part";
let h1 = Blake2s::hash_two(a, b);
let mut concat = a.to_vec();
concat.extend_from_slice(b);
let h2 = Blake2s::hash(&concat);
assert_eq!(h1, h2);
}
#[test]
fn blake2s_hmac_cross_checked() {
assert_eq!(
hex::encode(Blake2s::hmac(&[0x0b; 20], b"Hi There")),
"65a8b7c5cc9136d424e82c37e2707e74e913c0655b99c75f40edf387453a3260"
);
assert_eq!(
hex::encode(Blake2s::hmac(b"Jefe", b"what do ya want for nothing?")),
"90b6281e2f3038c9056af0b4a7e763cae6fe5d9eb4386a0ec95237890c104ff0"
);
assert_eq!(
hex::encode(Blake2s::hmac(&[0xaa; 20], &[0xdd; 50])),
"fcc4f59529502e34c3d8da3ffdab82966a2cb637ff5e9bd701135c2e9469e790"
);
assert_eq!(
hex::encode(Blake2s::hmac(
&[0xaa; 131],
b"Test Using Larger Than Block-Size Key - Hash Key First"
)),
"d23d79394f53d536a096e6514447eeaabb05ded01be32c1937da6a8f7103bc4e"
);
}
#[test]
fn blake2s_hmac_different_keys() {
let a = Blake2s::hmac(b"key1", b"data");
let b = Blake2s::hmac(b"key2", b"data");
assert_ne!(a, b);
}
#[test]
fn sha512_matches_nist_vectors() {
assert_eq!(
hex::encode(Sha512::hash(b"")),
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\
47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
);
assert_eq!(
hex::encode(Sha512::hash(b"abc")),
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
);
assert_eq!(
hex::encode(Sha512::hash(
b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn\
hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
)),
"8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018\
501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
);
assert_eq!(Sha512::hash(b"abc").len(), 64);
}
#[test]
fn sha512_hash_two_equals_concat() {
let a = b"first part";
let b = b"second part";
let h1 = Sha512::hash_two(a, b);
let mut concat = a.to_vec();
concat.extend_from_slice(b);
let h2 = Sha512::hash(&concat);
assert_eq!(h1, h2);
}
#[test]
fn sha512_hmac_rfc4231() {
assert_eq!(
hex::encode(Sha512::hmac(&[0x0b; 20], b"Hi There")),
"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde\
daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
);
assert_eq!(
hex::encode(Sha512::hmac(b"Jefe", b"what do ya want for nothing?")),
"164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554\
9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
);
assert_eq!(
hex::encode(Sha512::hmac(&[0xaa; 20], &[0xdd; 50])),
"fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39\
bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
);
assert_eq!(
hex::encode(Sha512::hmac(
&[0xaa; 131],
b"Test Using Larger Than Block-Size Key - Hash Key First"
)),
"80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352\
6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
);
}
#[test]
fn sha512_hmac_different_keys() {
let a = Sha512::hmac(b"key1", b"data");
let b = Sha512::hmac(b"key2", b"data");
assert_ne!(a, b);
}
#[test]
fn generated_message_size_matches_the_size_macro() {
assert_eq!(
IKpsk1::MSG1_SIZE,
noise_message_size!(curve: P256, cipher: ChaChaPoly, has_psk: true, keyed: false, tokens: [E, Es, S, Ss, Psk],),
);
}
#[test]
fn expected_message_size_reports_correctly() {
assert_eq!(
noise_message_size!(curve: P256, cipher: ChaChaPoly, has_psk: true, keyed: false, tokens: [E, Es, S, Ss, Psk],),
162
);
}
#[test]
fn corrupted_encrypted_static_in_msg1_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let psk = Psk::from_bytes([0xBB; 32]);
let (mut corrupted, _i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
corrupted[70] ^= 0xFF;
let outcome = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&corrupted, &psk);
assert!(matches!(outcome, Err(HandshakeError::DecryptionFailed)));
}
#[test]
fn mismatched_psk_fails() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let i_psk = Psk::from_bytes([0xAA; 32]);
let r_psk = Psk::from_bytes([0xBB; 32]);
let (msg1, _i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &i_psk)
.unwrap();
let result = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &r_psk);
assert!(result.is_err());
}
#[test]
fn transport_corrupted_ciphertext_rejected() {
let psk = Psk::from_bytes([0xCC; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
let mut ct_buf = [0u8; 256];
let ct_len = i_transport.send(b"secret", &mut ct_buf).unwrap();
ct_buf[0] ^= 0xFF;
let mut pt_buf = [0u8; 256];
let err = r_transport
.receive(&ct_buf[..ct_len], &mut pt_buf)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn transport_multiple_messages_nonce_advances() {
let psk = Psk::from_bytes([0xDD; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
let mut ct_buf = [0u8; 256];
let mut pt_buf = [0u8; 256];
for i in 0u32..10 {
let msg = format!("message {i}");
let ct_len = i_transport.send(msg.as_bytes(), &mut ct_buf).unwrap();
let pt_len = r_transport.receive(&ct_buf[..ct_len], &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], msg.as_bytes());
let reply = format!("reply {i}");
let ct_len = r_transport.send(reply.as_bytes(), &mut ct_buf).unwrap();
let pt_len = i_transport.receive(&ct_buf[..ct_len], &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], reply.as_bytes());
}
}
#[test]
fn symmetric_state_long_protocol_name() {
let long_name = "A".repeat(100);
let ss = symmetric_state::SymmetricState::<ChaChaPoly, Blake2b>::initialize(&long_name);
assert_eq!(ss.handshake_hash().len(), 64);
}
#[test]
fn symmetric_state_short_protocol_name_sha256() {
let name = "Noise_XX_P256_ChaChaPoly_SHA256";
let ss = symmetric_state::SymmetricState::<ChaChaPoly, Sha256>::initialize(name);
let mut want = vec![0u8; 32];
want[..name.len()].copy_from_slice(name.as_bytes());
assert_eq!(ss.handshake_hash(), want.as_slice());
}
#[test]
fn ikpsk1_wrong_responder_key_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let wrong_static = provider.generate::<P256>().unwrap();
let wrong_pub = provider.public(&wrong_static).unwrap();
let psk = Psk::from_bytes([0xCC; 32]);
let (msg1, _i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
wrong_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
let result = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &psk);
assert!(result.is_err());
}
#[test]
fn transport_keys_are_directional() {
let psk = Psk::from_bytes([0xFF; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
let mut ct_buf = [0u8; 256];
let ct_len = i_transport.send(b"hello", &mut ct_buf).unwrap();
let mut pt_buf = [0u8; 256];
let err = i_transport
.receive(&ct_buf[..ct_len], &mut pt_buf)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
let pt_len = r_transport.receive(&ct_buf[..ct_len], &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], b"hello");
}
#[test]
fn two_sessions_produce_different_handshake_hashes() {
let responder_bytes = [0xBB_u8; 32];
let psk = Psk::from_bytes([0xAA; 32]);
let mut hashes = Vec::new();
for _ in 0..2 {
let responder_static =
P256r1PrivateKey::from_bytes(responder_bytes).expect("valid test scalar");
let responder_pub = responder_static.public();
let initiator_static = EphemeralOnly::new(rand::make_rng::<StdRng>())
.generate::<P256>()
.unwrap();
let (msg1, i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
let r_hs = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &psk)
.unwrap();
let (msg2, r_transport) = r_hs.write_message_2().unwrap();
let i_transport = i_hs.read_message_2(&msg2).unwrap();
assert_eq!(i_transport.session_id(), r_transport.session_id());
hashes.push(i_transport.session_id().as_ref().to_vec());
}
assert_ne!(hashes[0], hashes[1]);
}
#[tokio::test]
async fn ikpsk1_wrong_initiator_static_in_msg1() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let initiator_pub = provider.public(&initiator_static).unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let psk = Psk::from_bytes([0xCC; 32]);
let wrong_static = provider.generate::<P256>().unwrap();
let (msg1, i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(wrong_static, &psk)
.unwrap();
let r_hs = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &psk)
.unwrap();
assert_ne!(*r_hs.remote_static(), initiator_pub);
let (msg2, r_transport) = r_hs.write_message_2().unwrap();
let i_transport = i_hs.read_message_2(&msg2).unwrap();
assert_eq!(i_transport.session_id(), r_transport.session_id());
drop(i_transport);
drop(r_transport);
}
#[test]
fn ikpsk1_corrupted_msg1_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let psk = Psk::from_bytes([0xDD; 32]);
let (mut corrupted, _i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
corrupted[5] ^= 0xFF;
let outcome = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&corrupted, &psk);
assert!(outcome.is_err());
}
#[test]
fn ikpsk1_corrupted_msg2_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let initiator_static = provider.generate::<P256>().unwrap();
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let psk = Psk::from_bytes([0xDD; 32]);
let (msg1, i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
let r_hs = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &psk)
.unwrap();
let (mut corrupted, _r_transport) = r_hs.write_message_2().unwrap();
corrupted[3] ^= 0xFF;
assert!(i_hs.read_message_2(&corrupted).is_err());
}
#[test]
fn transport_replayed_message_rejected() {
let psk = Psk::from_bytes([0xEE; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
let mut ct_buf = [0u8; 256];
let ct_len = i_transport.send(b"first message", &mut ct_buf).unwrap();
let captured = ct_buf[..ct_len].to_vec();
let mut pt_buf = [0u8; 256];
let pt_len = r_transport.receive(&captured, &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], b"first message");
let err = r_transport.receive(&captured, &mut pt_buf).unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn transport_enforces_max_message_length() {
let psk = Psk::from_bytes([0xFF; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
let max_payload: Vec<u8> = (0..65519).map(|i| (i % 256) as u8).collect();
let mut ct_buf = vec![0u8; max_payload.len() + 16];
let ct_len = i_transport.send(&max_payload, &mut ct_buf).unwrap();
assert_eq!(ct_len, 65535);
let mut pt_buf = vec![0u8; max_payload.len()];
let pt_len = r_transport.receive(&ct_buf[..ct_len], &mut pt_buf).unwrap();
assert_eq!(&pt_buf[..pt_len], &max_payload[..]);
let over_payload = vec![0u8; 65520];
let mut ct_buf = vec![0u8; over_payload.len() + 16];
let err = i_transport.send(&over_payload, &mut ct_buf).unwrap_err();
assert!(matches!(
err,
error::HandshakeError::MessageTooLong { len: 65536 }
));
let oversize = vec![0u8; 65536];
let mut pt_buf = vec![0u8; oversize.len()];
let err = r_transport.receive(&oversize, &mut pt_buf).unwrap_err();
assert!(matches!(
err,
error::HandshakeError::MessageTooLong { len: 65536 }
));
}
#[test]
fn transport_rekey_then_communicate() {
let psk = Psk::from_bytes([0x11; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
let mut ct = [0u8; 256];
let mut pt = [0u8; 256];
let ct_len = i_transport.send(b"before rekey", &mut ct).unwrap();
let pt_len = r_transport.receive(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"before rekey");
i_transport.rekey().unwrap();
r_transport.rekey().unwrap();
let ct_len = i_transport.send(b"after rekey", &mut ct).unwrap();
let pt_len = r_transport.receive(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"after rekey");
let ct_len = r_transport.send(b"reply after rekey", &mut ct).unwrap();
let pt_len = i_transport.receive(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"reply after rekey");
}
#[test]
fn transport_split_round_trip() {
let psk = Psk::from_bytes([0x5A; 32]);
let (i_transport, r_transport) = complete_ikpsk1(&psk);
assert!(i_transport.local_ephemeral().is_some());
assert!(i_transport.remote_ephemeral().is_some());
assert!(r_transport.local_ephemeral().is_some());
assert!(r_transport.remote_ephemeral().is_some());
assert!(i_transport.session_id() == r_transport.session_id());
let (mut i_send, mut i_recv) = i_transport.split();
let (mut r_send, mut r_recv) = r_transport.split();
assert!(i_send.session_id() == i_recv.session_id());
assert!(i_send.session_id() == r_recv.session_id());
assert!(i_send.local_ephemeral().is_some());
assert!(i_send.remote_ephemeral().is_some());
assert!(r_recv.local_ephemeral().is_some());
assert!(r_recv.remote_ephemeral().is_some());
let mut ct = [0u8; 256];
let mut pt = [0u8; 256];
let ct_len = i_send.encrypt(b"ping", &mut ct).unwrap();
assert_eq!(ct_len, b"ping".len() + TransportSend::<Channel>::OVERHEAD);
let pt_len = r_recv.decrypt(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"ping");
let ct_len = r_send.encrypt(b"pong", &mut ct).unwrap();
let pt_len = i_recv.decrypt(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"pong");
i_send.rekey().unwrap();
r_recv.rekey().unwrap();
r_send.rekey().unwrap();
i_recv.rekey().unwrap();
let ct_len = i_send.encrypt(b"after rekey", &mut ct).unwrap();
let pt_len = r_recv.decrypt(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"after rekey");
let ct_len = r_send.encrypt(b"reply after rekey", &mut ct).unwrap();
let pt_len = i_recv.decrypt(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"reply after rekey");
}
fn ikpsk1_transport_pair() -> (Transport<IKpsk1>, Transport<IKpsk1>) {
complete_ikpsk1(&Psk::from_bytes([0x7A; 32]))
}
#[test]
fn datagram_shuffled_delivery_all_open() {
let (i_transport, r_transport) = ikpsk1_transport_pair();
let (mut i_send, _i_recv) = i_transport.into_datagram();
let (_r_send, mut r_recv) = r_transport.into_datagram();
assert!(i_send.session_id() == r_recv.session_id());
let mut ct = [0u8; 256];
let mut sealed: Vec<(u64, Vec<u8>)> = Vec::new();
for i in 0..8u64 {
let msg = format!("datagram {i}");
let (counter, ct_len) = i_send.encrypt_next(&[], msg.as_bytes(), &mut ct).unwrap();
assert_eq!(counter, i); sealed.push((counter, ct[..ct_len].to_vec()));
}
sealed.swap(0, 7);
sealed.swap(1, 4);
sealed.swap(2, 6);
sealed.swap(3, 5);
let mut pt = [0u8; 256];
for (counter, packet) in &sealed {
let pt_len = r_recv.decrypt_at(*counter, &[], packet, &mut pt).unwrap();
assert_eq!(&pt[..pt_len], format!("datagram {counter}").as_bytes());
}
}
#[test]
fn datagram_gap_later_counters_still_open() {
let (i_transport, r_transport) = ikpsk1_transport_pair();
let (mut i_send, _) = i_transport.into_datagram();
let (_, mut r_recv) = r_transport.into_datagram();
let mut ct = [0u8; 256];
let mut sealed: Vec<(u64, Vec<u8>)> = Vec::new();
for i in 0..5u64 {
let msg = format!("packet {i}");
let (counter, ct_len) = i_send.encrypt_next(&[], msg.as_bytes(), &mut ct).unwrap();
sealed.push((counter, ct[..ct_len].to_vec()));
}
let mut pt = [0u8; 256];
for (counter, packet) in sealed.iter().filter(|(c, _)| *c != 2) {
let pt_len = r_recv.decrypt_at(*counter, &[], packet, &mut pt).unwrap();
assert_eq!(&pt[..pt_len], format!("packet {counter}").as_bytes());
}
}
#[test]
fn datagram_replay_opens_and_counter_is_monotonic() {
let (i_transport, r_transport) = ikpsk1_transport_pair();
let (mut i_send, _) = i_transport.into_datagram();
let (_, mut r_recv) = r_transport.into_datagram();
let mut ct = [0u8; 256];
let mut seen: Vec<u64> = Vec::new();
for _ in 0..4 {
let (counter, _) = i_send.encrypt_next(&[], b"tick", &mut ct).unwrap();
assert!(seen.iter().all(|c| *c != counter));
seen.push(counter);
}
assert_eq!(seen, vec![0, 1, 2, 3]);
let (counter, ct_len) = i_send.encrypt_next(&[], b"payload", &mut ct).unwrap();
let mut pt = [0u8; 256];
let first = r_recv
.decrypt_at(counter, &[], &ct[..ct_len], &mut pt)
.unwrap();
assert_eq!(&pt[..first], b"payload");
let second = r_recv
.decrypt_at(counter, &[], &ct[..ct_len], &mut pt)
.unwrap();
assert_eq!(&pt[..second], b"payload");
}
#[test]
fn datagram_bad_inputs_error_without_poisoning_state() {
let (i_transport, r_transport) = ikpsk1_transport_pair();
let (mut i_send, _) = i_transport.into_datagram();
let (_, mut r_recv) = r_transport.into_datagram();
let mut ct = [0u8; 256];
let (counter, ct_len) = i_send.encrypt_next(b"ad", b"honest", &mut ct).unwrap();
let good = ct[..ct_len].to_vec();
let mut pt = [0u8; 256];
let mut tampered = good.clone();
tampered[0] ^= 0xFF;
let err = r_recv
.decrypt_at(counter, b"ad", &tampered, &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
let err = r_recv
.decrypt_at(counter, b"other", &good, &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
let err = r_recv
.decrypt_at(counter + 1, b"ad", &good, &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
let pt_len = r_recv.decrypt_at(counter, b"ad", &good, &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"honest");
}
#[test]
fn datagram_encrypt_next_guards_nonce_exhaustion() {
let (i_transport, _r_transport) = ikpsk1_transport_pair();
let (mut i_send, _) = i_transport.into_datagram();
i_send.set_counter_for_test(u64::MAX - 1);
let mut ct = [0u8; 64];
let (counter, _) = i_send.encrypt_next(&[], b"x", &mut ct).unwrap();
assert_eq!(counter, u64::MAX - 1);
let mut ct2 = [0xABu8; 64];
let err = i_send.encrypt_next(&[], b"x", &mut ct2).unwrap_err();
assert!(matches!(err, error::HandshakeError::NonceOverflow));
assert_eq!(ct2, [0xABu8; 64]);
}
#[test]
fn datagram_next_counter_matches_next_seal() {
let (i_transport, _) = ikpsk1_transport_pair();
let (mut i_send, _) = i_transport.into_datagram();
let mut ct = [0u8; 256];
for _ in 0..8 {
let promised = i_send.next_counter();
let (counter, _) = i_send.encrypt_next(&[], b"payload", &mut ct).unwrap();
assert_eq!(counter, promised);
}
let before = i_send.next_counter();
let oversize = vec![0u8; 65535];
let mut big_out = vec![0u8; 65535 + 64];
let err = i_send
.encrypt_next(&[], &oversize, &mut big_out)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::MessageTooLong { .. }));
assert_eq!(i_send.next_counter(), before);
i_send.set_counter_for_test(u64::MAX);
assert_eq!(i_send.next_counter(), u64::MAX);
let err = i_send.encrypt_next(&[], b"x", &mut ct).unwrap_err();
assert!(matches!(err, error::HandshakeError::NonceOverflow));
assert_eq!(i_send.next_counter(), u64::MAX);
}
#[test]
fn datagram_and_stream_interoperate_in_order() {
let (i_transport, r_transport) = ikpsk1_transport_pair();
let (mut i_dg_send, mut i_dg_recv) = i_transport.into_datagram();
let (mut r_send, mut r_recv) = r_transport.split();
let mut ct = [0u8; 256];
let mut pt = [0u8; 256];
for i in 0..4u64 {
let msg = format!("dg->stream {i}");
let (counter, ct_len) = i_dg_send
.encrypt_next(&[], msg.as_bytes(), &mut ct)
.unwrap();
assert_eq!(counter, i);
let pt_len = r_recv.decrypt(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], msg.as_bytes());
}
for i in 0..4u64 {
let msg = format!("stream->dg {i}");
let ct_len = r_send.encrypt(msg.as_bytes(), &mut ct).unwrap();
let pt_len = i_dg_recv
.decrypt_at(i, &[], &ct[..ct_len], &mut pt)
.unwrap();
assert_eq!(&pt[..pt_len], msg.as_bytes());
}
assert!(i_dg_send.session_id() == r_recv.session_id());
assert!(i_dg_recv.session_id() == r_send.session_id());
}
fn epoch(n: u64) -> NonZeroU64 {
NonZeroU64::new(n).expect("epoch size must be non-zero")
}
fn fixed_epoch_transport() -> Transport<IKpsk1> {
let key = [0x24u8; 32];
Transport::<IKpsk1>::new(
CipherState::from_key(key),
CipherState::from_key(key),
SessionId::from(vec![0xEE; 8]),
None,
None,
None,
)
}
#[test]
fn rekey_kat() {
use super::cipher_state::rekey_key;
const PROBE: &[u8] = b"probe payload for the rekey KAT";
const PROBE_AD: &[u8] = b"rekey-kat";
fn seal(key: &<ChaChaPoly as Cipher>::Key) -> Vec<u8> {
let mut out = [0u8; 64];
let n = ChaChaPoly::encrypt(key, 7, PROBE_AD, PROBE, &mut out).unwrap();
out[..n].to_vec()
}
let k = [0u8; 32];
let mut scratch = [0u8; 48];
ChaChaPoly::encrypt(
&ChaChaPoly::key(&k),
u64::MAX,
&[],
&[0u8; 32],
&mut scratch,
)
.unwrap();
let mut definition = [0u8; 32];
definition.copy_from_slice(&scratch[..32]);
assert_eq!(
hex::encode(definition),
"25ce5d37df19f3783185f2ffd5ab17fa3397c212f02d62fb1733e0b875b74c58",
"REKEY next-key KAT drifted"
);
let expected = seal(&ChaChaPoly::key(&definition));
let derived = rekey_key::<ChaChaPoly>(&ChaChaPoly::key(&k)).unwrap();
assert_eq!(
seal(&derived),
expected,
"rekey_key does not derive the REKEY definition"
);
let mut cs = CipherState::<ChaChaPoly>::from_key(k);
cs.rekey().unwrap();
let mut after = [0u8; 64];
let n = cs.encrypt_with_ad(PROBE_AD, PROBE, &mut after).unwrap();
let mut reference = CipherState::<ChaChaPoly>::from_key(definition);
let mut want = [0u8; 64];
let m = reference
.encrypt_with_ad(PROBE_AD, PROBE, &mut want)
.unwrap();
assert_eq!(
&after[..n],
&want[..m],
"CipherState::rekey installed a key other than the REKEY definition"
);
}
#[test]
fn datagram_no_ratchet_byte_identity() {
let size = epoch(4);
let (mut plain_send, _) = fixed_epoch_transport().into_datagram();
let (mut epoch_send, _) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut a = [0u8; 64];
let mut b = [0u8; 64];
for _ in 0..4u64 {
let (ca, na) = plain_send
.encrypt_next(b"ad", b"identical payload", &mut a)
.unwrap();
let (cb, nb) = epoch_send
.encrypt_next(b"ad", b"identical payload", &mut b)
.unwrap();
assert_eq!(ca, cb);
assert_eq!(a[..na], b[..nb]);
}
let (cp, np) = plain_send.encrypt_next(b"ad", b"x", &mut a).unwrap();
let (ce, ne) = epoch_send.encrypt_next(b"ad", b"x", &mut b).unwrap();
assert_eq!(cp, 4);
assert_eq!(ce, 4);
assert_ne!(a[..np], b[..ne]);
let (_, mut plain_recv) = fixed_epoch_transport().into_datagram();
let mut pt = [0u8; 64];
assert!(plain_recv.decrypt_at(4, b"ad", &a[..np], &mut pt).is_ok());
let err = plain_recv
.decrypt_at(4, b"ad", &b[..ne], &mut pt)
.unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn datagram_epoch_boundary_roundtrip_and_reorder() {
let size = epoch(4);
let (mut send, _) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut ctbuf = [0u8; 64];
let mut sealed: Vec<Vec<u8>> = Vec::new();
for i in 0..6u64 {
let (c, n) = send
.encrypt_next(&[], format!("m{i}").as_bytes(), &mut ctbuf)
.unwrap();
assert_eq!(c, i);
sealed.push(ctbuf[..n].to_vec());
}
let mut pt = [0u8; 64];
let (_, mut ra) = fixed_epoch_transport().into_datagram_with_epoch(size);
for i in [3u64, 4, 5] {
let n = ra.decrypt_at(i, &[], &sealed[i as usize], &mut pt).unwrap();
assert_eq!(&pt[..n], format!("m{i}").as_bytes());
}
let (_, mut rb) = fixed_epoch_transport().into_datagram_with_epoch(size);
for i in [5u64, 3, 4] {
let n = rb.decrypt_at(i, &[], &sealed[i as usize], &mut pt).unwrap();
assert_eq!(&pt[..n], format!("m{i}").as_bytes());
}
}
#[test]
fn datagram_next_counter_spans_epoch_boundary() {
let size = epoch(4);
let (mut send, _) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut ct = [0u8; 64];
for i in 0..6u64 {
assert_eq!(send.next_counter(), i);
let (c, _) = send.encrypt_next(&[], b"m", &mut ct).unwrap();
assert_eq!(c, i);
}
assert_eq!(send.next_counter(), 6);
}
#[test]
fn datagram_old_epoch_refused() {
let size = epoch(4);
let (mut send, _) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut ctbuf = [0u8; 64];
let mut sealed: Vec<Vec<u8>> = Vec::new();
for i in 0..9u64 {
let (c, n) = send
.encrypt_next(&[], format!("m{i}").as_bytes(), &mut ctbuf)
.unwrap();
assert_eq!(c, i);
sealed.push(ctbuf[..n].to_vec());
}
let (_, mut recv) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut pt = [0u8; 64];
let n = recv.decrypt_at(8, &[], &sealed[8], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m8");
let n = recv.decrypt_at(4, &[], &sealed[4], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m4");
let err = recv.decrypt_at(0, &[], &sealed[0], &mut pt).unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn datagram_forward_jump_cap_refuses_without_derivation() {
let size = epoch(4);
let (mut send, _) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut ctbuf = [0u8; 64];
let mut sealed: Vec<Vec<u8>> = Vec::new();
for i in 0..13u64 {
let (_, n) = send
.encrypt_next(&[], format!("m{i}").as_bytes(), &mut ctbuf)
.unwrap();
sealed.push(ctbuf[..n].to_vec());
}
let (_, mut recv) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut pt = [0u8; 64];
let err = recv.decrypt_at(12, &[], &sealed[12], &mut pt).unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
let n = recv.decrypt_at(0, &[], &sealed[0], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m0");
let n = recv.decrypt_at(8, &[], &sealed[8], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m8");
}
#[test]
fn datagram_forged_future_tag_does_not_advance() {
let size = epoch(4);
let (mut send, _) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut ctbuf = [0u8; 64];
let mut sealed: Vec<Vec<u8>> = Vec::new();
for i in 0..6u64 {
let (_, n) = send
.encrypt_next(&[], format!("m{i}").as_bytes(), &mut ctbuf)
.unwrap();
sealed.push(ctbuf[..n].to_vec());
}
let (_, mut recv) = fixed_epoch_transport().into_datagram_with_epoch(size);
let mut pt = [0u8; 64];
let mut forged = sealed[4].clone();
forged[0] ^= 0xFF;
let err = recv.decrypt_at(4, &[], &forged, &mut pt).unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
let n = recv.decrypt_at(0, &[], &sealed[0], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m0");
let n = recv.decrypt_at(4, &[], &sealed[4], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m4");
let n = recv.decrypt_at(5, &[], &sealed[5], &mut pt).unwrap();
assert_eq!(&pt[..n], b"m5");
}
#[test]
fn datagram_epoch_seal_refuses_nonce_exhaustion() {
let big = NonZeroU64::new(u64::MAX).unwrap();
let (mut send, _) = fixed_epoch_transport().into_datagram_with_epoch(big);
send.set_counter_for_test(u64::MAX - 1);
let mut ct = [0u8; 64];
let (c, _) = send.encrypt_next(&[], b"x", &mut ct).unwrap();
assert_eq!(c, u64::MAX - 1);
let mut ct2 = [0xABu8; 64];
let err = send.encrypt_next(&[], b"x", &mut ct2).unwrap_err();
assert!(matches!(err, error::HandshakeError::NonceOverflow));
assert_eq!(ct2, [0xABu8; 64]);
}
#[test]
fn transport_rekey_desync_rejected() {
let psk = Psk::from_bytes([0x22; 32]);
let (mut i_transport, mut r_transport) = complete_ikpsk1(&psk);
i_transport.rekey().unwrap();
let mut ct = [0u8; 256];
let ct_len = i_transport.send(b"desynced", &mut ct).unwrap();
let mut pt = [0u8; 256];
let err = r_transport.receive(&ct[..ct_len], &mut pt).unwrap_err();
assert!(matches!(err, error::HandshakeError::DecryptionFailed));
}
#[test]
fn rekey_without_key_fails() {
let mut cs = cipher_state::CipherState::<ChaChaPoly>::empty();
let err = cs.rekey().unwrap_err();
assert!(matches!(err, error::HandshakeError::RekeyWithoutKey));
}
#[test]
fn matching_prologue_succeeds() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let prologue = b"hiss/v1";
let (msg, mut i_transport) = N::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
prologue,
responder_pub,
)
.write_message_1()
.unwrap();
let mut r_transport = N::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
prologue,
responder_static,
)
.unwrap()
.read_message_1(&msg)
.unwrap();
let mut ct = [0u8; 64];
let mut pt = [0u8; 64];
let ct_len = i_transport.send(b"hello", &mut ct).unwrap();
let pt_len = r_transport.receive(&ct[..ct_len], &mut pt).unwrap();
assert_eq!(&pt[..pt_len], b"hello");
}
#[test]
fn mismatched_prologue_rejected() {
let mut provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let responder_static = provider.generate::<P256>().unwrap();
let responder_pub = provider.public(&responder_static).unwrap();
let (msg, _i_transport) = N::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
b"v1",
responder_pub,
)
.write_message_1()
.unwrap();
let outcome = N::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
b"v2",
responder_static,
)
.unwrap()
.read_message_1(&msg);
assert!(matches!(outcome, Err(HandshakeError::DecryptionFailed)));
}
#[test]
fn chacha_encrypt_output_buffer_too_small() {
let key = [0u8; 32];
let plaintext = b"hello world";
let mut output = [0u8; 10]; let err = ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], plaintext, &mut output)
.unwrap_err();
assert!(matches!(
err,
error::HandshakeError::OutputBufferTooSmall { .. }
));
}
#[test]
fn chacha_decrypt_output_buffer_too_small() {
let key = [0u8; 32];
let plaintext = b"hello world";
let mut ct = [0u8; 64];
let ct_len =
ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], plaintext, &mut ct).unwrap();
let mut output = [0u8; 5]; let err = ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, &[], &ct[..ct_len], &mut output)
.unwrap_err();
assert!(matches!(
err,
error::HandshakeError::OutputBufferTooSmall { .. }
));
}
mod prop {
use super::*;
use crate::psk::Psk;
use proptest::prelude::*;
fn full_ikpsk1_handshake(
initiator_static: P256r1PrivateKey,
responder_static: P256r1PrivateKey,
psk: Psk,
) -> (transport::Transport<IKpsk1>, transport::Transport<IKpsk1>) {
let provider = EphemeralOnly::new(rand::make_rng::<StdRng>());
let responder_pub = provider.public(&responder_static).unwrap();
let (msg1, i_hs) = IKpsk1::initiator(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_pub,
)
.write_message_1(initiator_static, &psk)
.unwrap();
let r_hs = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
responder_static,
)
.unwrap()
.read_message_1(&msg1, &psk)
.unwrap();
let (msg2, r_transport) = r_hs.write_message_2().unwrap();
let i_transport = i_hs.read_message_2(&msg2).unwrap();
(i_transport, r_transport)
}
proptest! {
#[test]
fn transport_any_payload(
plaintext in proptest::collection::vec(any::<u8>(), 0..4096),
psk in any::<[u8; 32]>().prop_map(Psk::from_bytes),
) {
let i_sk = EphemeralOnly::new(rand::make_rng::<StdRng>()).generate::<P256>().unwrap();
let r_sk = EphemeralOnly::new(rand::make_rng::<StdRng>()).generate::<P256>().unwrap();
let (mut i_t, mut r_t) = full_ikpsk1_handshake(i_sk, r_sk, psk);
let mut ct = vec![0u8; plaintext.len() + 16];
let ct_len = i_t.send(&plaintext, &mut ct).unwrap();
let mut pt = vec![0u8; plaintext.len()];
let pt_len = r_t.receive(&ct[..ct_len], &mut pt).unwrap();
prop_assert_eq!(&pt[..pt_len], &plaintext[..]);
let ct_len = r_t.send(&plaintext, &mut ct).unwrap();
let pt_len = i_t.receive(&ct[..ct_len], &mut pt).unwrap();
prop_assert_eq!(&pt[..pt_len], &plaintext[..]);
}
#[test]
fn transport_any_corruption_detected(
plaintext in proptest::collection::vec(any::<u8>(), 1..512),
psk in any::<[u8; 32]>().prop_map(Psk::from_bytes),
corrupt_pos_seed in any::<usize>(),
) {
let i_sk = EphemeralOnly::new(rand::make_rng::<StdRng>()).generate::<P256>().unwrap();
let r_sk = EphemeralOnly::new(rand::make_rng::<StdRng>()).generate::<P256>().unwrap();
let (mut i_t, mut r_t) = full_ikpsk1_handshake(i_sk, r_sk, psk);
let mut ct = vec![0u8; plaintext.len() + 16];
let ct_len = i_t.send(&plaintext, &mut ct).unwrap();
let pos = corrupt_pos_seed % ct_len;
ct[pos] ^= 0x01;
let mut pt = vec![0u8; plaintext.len()];
let result = r_t.receive(&ct[..ct_len], &mut pt);
prop_assert!(result.is_err());
}
#[test]
fn random_msg1_rejected(
garbage in proptest::collection::vec(any::<u8>(), 162..163),
) {
let r_sk = EphemeralOnly::new(rand::make_rng::<StdRng>()).generate::<P256>().unwrap();
let garbage: [u8; IKpsk1::MSG1_SIZE] =
garbage.try_into().expect("generated at the wire size");
let outcome = IKpsk1::responder(
EphemeralOnly::new(rand::make_rng::<StdRng>()),
&[],
r_sk,
)
.unwrap()
.read_message_1(&garbage, &Psk::from_bytes([0u8; 32]));
prop_assert!(outcome.is_err());
}
#[test]
fn chacha_round_trip_any(
key in any::<[u8; 32]>(),
nonce in 0..1000u64,
ad in proptest::collection::vec(any::<u8>(), 0..128),
plaintext in proptest::collection::vec(any::<u8>(), 0..4096),
) {
let mut ct = vec![0u8; plaintext.len() + 16];
let ct_len = ChaChaPoly::encrypt(&ChaChaPoly::key(&key), nonce, &ad, &plaintext, &mut ct).unwrap();
let mut pt = vec![0u8; plaintext.len()];
let pt_len = ChaChaPoly::decrypt(&ChaChaPoly::key(&key), nonce, &ad, &ct[..ct_len], &mut pt).unwrap();
prop_assert_eq!(&pt[..pt_len], &plaintext[..]);
}
#[test]
fn chacha_any_bit_flip_detected(
key in any::<[u8; 32]>(),
plaintext in proptest::collection::vec(any::<u8>(), 1..256),
flip_pos_seed in any::<usize>(),
flip_bit in 0u8..8,
) {
let mut ct = vec![0u8; plaintext.len() + 16];
let ct_len = ChaChaPoly::encrypt(&ChaChaPoly::key(&key), 0, &[], &plaintext, &mut ct).unwrap();
let pos = flip_pos_seed % ct_len;
ct[pos] ^= 1 << flip_bit;
let mut pt = vec![0u8; plaintext.len()];
let result = ChaChaPoly::decrypt(&ChaChaPoly::key(&key), 0, &[], &ct[..ct_len], &mut pt);
prop_assert!(result.is_err());
}
}
}
}