diff --git a/src/transport.rs b/src/transport.rs index 271dd72..06a3b14 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -3,7 +3,7 @@ use core::marker::PhantomData; use aead::{AeadInOut, Buffer, Key, KeySizeUser, TagPosition, common::IvSizeUser}; use ctutils::{CtEq, CtSelect}; use hybrid_array::AssocArraySize; -use wharrgarbl_neko::{NekoSec, NekoState, NekoTag}; +use wharrgarbl_neko::{NekoMilkConsumer, NekoMilkProducer, NekoSec, NekoState, NekoTag}; use crate::{Role, WHARRGHARBL_PROTO}; @@ -161,6 +161,24 @@ pub struct SendState<'a, S: NekoSec> { } impl SendState<'_, S> { + pub fn as_stream(&mut self, ad: &[u8]) -> aead::Result> { + match self.counter.checked_add(1) { + Some(inc) => self.counter = inc, + None => return Err(aead::Error), + } + let state = NekoState::new(WHARRGHARBL_PROTO.as_bytes()); + let stream = NekoMilkProducer::new( + state, + &self.transport.aead.key, + &self + .transport + .mix_nonce(self.counter.to_be_bytes(), Role::Sender), + ad, + ); + + Ok(stream) + } + pub fn encrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> { match self.counter.checked_add(1) { Some(inc) => self.counter = inc, @@ -197,6 +215,24 @@ pub struct RecvState<'a, S: NekoSec> { } impl RecvState<'_, S> { + pub fn as_stream(&mut self, ad: &[u8]) -> aead::Result> { + match self.counter.checked_add(1) { + Some(inc) => self.counter = inc, + None => return Err(aead::Error), + } + let state = NekoState::new(WHARRGHARBL_PROTO.as_bytes()); + let stream = NekoMilkConsumer::new( + state, + &self.transport.aead.key, + &self + .transport + .mix_nonce(self.counter.to_be_bytes(), Role::Receiver), + ad, + ); + + Ok(stream) + } + pub fn decrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> { match self.counter.checked_add(1) { Some(inc) => self.counter = inc, @@ -306,6 +342,71 @@ mod tests { Ok(()) } + #[test] + fn two_way_transport_streaming_works() -> aead::Result<()> { + let shared_secret = [ + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, + ]; + + let outbound = 123u128.to_ne_bytes(); + let inbound = 234u128.to_ne_bytes(); + + let alice = AeadTransport::::new( + shared_secret.into(), + outbound.into(), + inbound.into(), + Role::Sender, + ); + let bob = AeadTransport::::new( + shared_secret.into(), + outbound.into(), + inbound.into(), + Role::Receiver, + ); + + let (mut alice_send, mut alice_recv) = alice.split(); + let (mut bob_send, mut bob_recv) = bob.split(); + + let mut alice_send_stream = alice_send.as_stream(b"strem")?; + let mut bob_recv_stream = bob_recv.as_stream(b"strem")?; + + let mut message = b"OnePacket.TwoPacket.".to_vec(); + let orig = message.clone(); + + alice_send_stream.produce(&mut message[..10]); + alice_send_stream.produce(&mut message[10..]); + let tag = alice_send_stream.finalize(); + + assert_ne!(&message, &orig); + let first_encrypted_msg = message.clone(); + + bob_recv_stream.consume(&mut message[..10]); + bob_recv_stream.consume(&mut message[10..]); + bob_recv_stream.finalize(&tag)?; + + assert_eq!(&message, &orig); + + let mut bob_send_stream = bob_send.as_stream(b"strem")?; + let mut alice_recv_stream = alice_recv.as_stream(b"strem")?; + + bob_send_stream.produce(&mut message[..10]); + bob_send_stream.produce(&mut message[10..]); + let tag = bob_send_stream.finalize(); + + assert_ne!(&message, &orig); + assert_ne!(&message, &first_encrypted_msg); + + alice_recv_stream.consume(&mut message[..10]); + alice_recv_stream.consume(&mut message[10..]); + alice_recv_stream.finalize(&tag)?; + + assert_eq!(&message, &orig); + + Ok(()) + } + #[test] fn two_way_transport_fails_with_security_level_mismatch() { let shared_secret = [ diff --git a/wharrgarbl-neko/src/kats.rs b/wharrgarbl-neko/src/kats.rs index 7afbba3..ee04c26 100644 --- a/wharrgarbl-neko/src/kats.rs +++ b/wharrgarbl-neko/src/kats.rs @@ -436,3 +436,69 @@ fn omitting_ops_with_empty_data_should_fail() { assert!(verification.is_err()); } + +#[test] +fn streaming_encryption_produces_and_consumes_as_expected() { + use crate::milk::{NekoMilkConsumer, NekoMilkProducer}; + + let key = Array([1u8; 32]); + let nonce = Array([2u8; 16]); + + let mut tx = NekoMilkProducer::new(NekoState::::new(b"test"), &key, &nonce, b"buh"); + let mut rx = NekoMilkConsumer::new(NekoState::::new(b"test"), &key, &nonce, b"buh"); + + let mut message = b"First Message. Second Message.".to_vec(); + let orig = message.clone(); + + tx.produce(&mut message[..15]); + tx.produce(&mut message[15..]); + + let tag = tx.finalize(); + + #[rustfmt::skip] + let expected_tag = [ + 0x75, 0x68, 0xf7, 0xe3, 0x59, 0x83, 0x9e, 0x6d, + 0xcc, 0xfb, 0xfa, 0xd8, 0x33, 0x4a, 0x71, 0xa2, + ]; + + assert_ne!(&message, &orig); + assert_eq!(&tag, &expected_tag); + + rx.consume(&mut message[..15]); + rx.consume(&mut message[15..]); + + assert!(rx.finalize(&tag).is_ok()); +} + +#[test] +fn streaming_encryption_fails_if_messages_are_not_replayed_correctly() { + use crate::milk::{NekoMilkConsumer, NekoMilkProducer}; + + let key = Array([1u8; 32]); + let nonce = Array([2u8; 16]); + + let mut tx = NekoMilkProducer::new(NekoState::::new(b"test"), &key, &nonce, b"buh"); + let mut rx = NekoMilkConsumer::new(NekoState::::new(b"test"), &key, &nonce, b"buh"); + + let mut message = b"First Message. Second Message.".to_vec(); + let orig = message.clone(); + + tx.produce(&mut message[..15]); + tx.produce(&mut message[15..]); + + let tag = tx.finalize(); + + #[rustfmt::skip] + let expected_tag = [ + 0x75, 0x68, 0xf7, 0xe3, 0x59, 0x83, 0x9e, 0x6d, + 0xcc, 0xfb, 0xfa, 0xd8, 0x33, 0x4a, 0x71, 0xa2, + ]; + + assert_ne!(&message, &orig); + assert_eq!(&tag, &expected_tag); + + rx.consume(&mut message[..9]); + rx.consume(&mut message[9..]); + + assert!(rx.finalize(&tag).is_err()); +} diff --git a/wharrgarbl-neko/src/lib.rs b/wharrgarbl-neko/src/lib.rs index e468941..234b695 100644 --- a/wharrgarbl-neko/src/lib.rs +++ b/wharrgarbl-neko/src/lib.rs @@ -3,6 +3,7 @@ mod flags; #[cfg(test)] mod kats; +mod milk; mod operators; mod ops; mod traits; @@ -19,6 +20,7 @@ use hybrid_array::Array; use wharrgarbl_utils::OpFlags; use zerocopy::IntoBytes; +pub use crate::milk::*; use crate::operators::{NekoOperate, NekoOperateMut}; pub use crate::traits::NekoSec; diff --git a/wharrgarbl-neko/src/milk.rs b/wharrgarbl-neko/src/milk.rs new file mode 100644 index 0000000..5bb62ed --- /dev/null +++ b/wharrgarbl-neko/src/milk.rs @@ -0,0 +1,75 @@ +//! Neko Milk, or streaming constructs that produce milk (encrypted streamed data), and also +//! consume said milk. +//! +//! A streaming process REQUIRES that once a Neko is initialised for streaming, whether to produce +//! or to consume, the stream MUST be finalised. The producer finalises to produce a TAG, that +//! verifies that this milk came from a genuine Neko, so that when the consumer finalises, they can +//! take the tag and verify the consumed milk. This then allows both Nekos to be happy. + +use crate::{NekoKey, NekoNonce, NekoSec, NekoState, NekoTag, operators::NekoOperateMut, ops}; + +/// A Neko that produces milk, for other Nekos to consume. +pub struct NekoMilkProducer { + stream: NekoState, +} + +impl NekoMilkProducer { + pub fn new( + mut stream: NekoState, + key: &NekoKey, + nonce: &NekoNonce, + ad: &[u8], + ) -> Self { + stream.key(key); + stream.nonce(nonce); + stream.ad(ad); + stream.permutation_p12(ops::RESET); + stream.begin_op(ops::ENCRYPTION); + Self { stream } + } + + /// Produce milk, by taking a slice of bytes and encrypting them. + pub fn produce(&mut self, data: &mut [u8]) { + NekoOperateMut::absorb_and_set(&mut self.stream, data); + } + + /// Neko is now tired of producing milk, so make the Neko provide a + /// seal/tag of authenticity, so that the milk can be certified as 100% + /// Neko made. + pub fn finalize(mut self) -> NekoTag { + self.stream.create_mac() + } +} + +/// A Neko that consumes only Neko-made milk. +pub struct NekoMilkConsumer { + stream: NekoState, +} + +impl NekoMilkConsumer { + pub fn new( + mut stream: NekoState, + key: &NekoKey, + nonce: &NekoNonce, + ad: &[u8], + ) -> Self { + stream.key(key); + stream.nonce(nonce); + stream.ad(ad); + stream.permutation_p12(ops::RESET); + stream.begin_op(ops::ENCRYPTION); + Self { stream } + } + + /// Consume milk, by taking a slice of bytes and decrypting it. + pub fn consume(&mut self, data: &mut [u8]) { + NekoOperateMut::exchange(&mut self.stream, data); + } + + /// Finalize the consumption. If you fed the Neko milk that didn't + /// come from another Neko, or if the milk was messed with prior to + /// consuming, you will make the Neko sad and angry >:[ + pub fn finalize(mut self, mac: &NekoTag) -> aead::Result<()> { + self.stream.verify_mac(mac) + } +}