···11# WHARRGARBL
2233-A WIP Strobe+AKE protocol with a whimsical name. Based off the [reference rust implementation here](https://github.com/rozbb/strobe-rs), though with diverging API choices for experimentation.
33+A WIP Strobe inspired HPKE protocol with a whimsical name. Inspired by the [reference rust implementation here](https://github.com/rozbb/strobe-rs), though with diverging API + Spec choices for experimentation.
44+55+## NEKO Encryption
66+77+NEKO is a modification of the STROBE spec, to remove redundancies in the spec and to simplify further its API surface. By doing so, it is possible to improve the throughput of the encryption and also tighten some of its APIs to be stronger against misuse. For more deets, look at the NEKO crate for further explanation.
4859☢️ **WARNING: DO NOT USE IN PRODUCTION. THIS CRATE IS NOT AUDITED AND IS WIP, MAYBE EVEN NOT AS SECURE AS THOUGHT. AAAAAAAAAA** ☢️
610
+8-7
benches/garbl_bench.rs
···1212 text.repeat(128).as_bytes().to_vec()
1313}
14141515-fn wharrgarbl_benchmark(c: &mut Criterion) {
1616- use wharrgarbl::{Sec128, Sec256, transport::AeadStrobe};
1515+fn wharrgarbl_neko_benchmark(c: &mut Criterion) {
1616+ use wharrgarbl::transport::AeadNeko;
1717+ use wharrgarbl_neko::{Neko128, Neko256};
17181819 let key = Array([77u8; 32]);
1920 let nonce = Array([12u8; 16]);
20212121- let transport_128 = AeadStrobe::<Sec128>::new(&key);
2222- let transport_256 = AeadStrobe::<Sec256>::new(&key);
2222+ let transport_128 = AeadNeko::<Neko128>::new(&key);
2323+ let transport_256 = AeadNeko::<Neko256>::new(&key);
23242424- c.bench_function("WHGL-128 TRANSPORT ROUNDTRIP", |b| {
2525+ c.bench_function("NEKO-128 TRANSPORT ROUNDTRIP", |b| {
2526 b.iter_batched_ref(
2627 generate_text,
2728 |text| {
···3233 );
3334 });
34353535- c.bench_function("WHGL-256 TRANSPORT ROUNDTRIP", |b| {
3636+ c.bench_function("NEKO-256 TRANSPORT ROUNDTRIP", |b| {
3637 b.iter_batched_ref(
3738 generate_text,
3839 |text| {
···6768pub fn benches() {
6869 let mut criterion: Criterion<_> = Criterion::default().configure_from_args();
69707070- wharrgarbl_benchmark(&mut criterion);
7171+ wharrgarbl_neko_benchmark(&mut criterion);
7172 chacha20poly1305_benchmark(&mut criterion);
7273}
7374
+80-76
src/handshake.rs
···11use core::marker::PhantomData;
2233-use aead::Buffer;
33+use aead::{
44+ Buffer,
55+ consts::{U32, U64},
66+};
47use alloc::boxed::Box;
55-use hybrid_array::{Array, typenum::Unsigned};
66-use ml_kem::{Encapsulate, Kem, KeyExport, ParameterSet, TryKeyInit, kem::Decapsulate};
88+use hybrid_array::{Array, SliceExt, typenum::Unsigned};
99+use ml_kem::{Encapsulate, Kem, KeyExport, ParameterSet, SharedKey, TryKeyInit, kem::Decapsulate};
710use rand_core::CryptoRng;
88-use wharrgarbl_strobe::{StrobeRole, StrobeState, traits::StrobeSecurity};
1111+use wharrgarbl_neko::{NekoSec, NekoState};
1212+use zeroize::Zeroize;
9131014use crate::{
1111- WHARRGHARBL_PROTO,
1212- transport::{AeadState, AeadStrobe},
1515+ Role, WHARRGHARBL_PROTO,
1616+ transport::{AeadNeko, AeadTranport},
1317};
14181515-pub struct ClientHandshake<S: StrobeSecurity, K: Kem + ParameterSet> {
1919+pub struct ClientHandshake<S: NekoSec, K: Kem + ParameterSet> {
1620 kem_sec: PhantomData<K>,
1717- strobe: StrobeState<S>,
2121+ neko: NekoState<S>,
1822 decap: Option<Box<K::DecapsulationKey>>,
1923}
20242121-impl<S: StrobeSecurity, K: Kem + ParameterSet> ClientHandshake<S, K>
2525+impl<S: NekoSec, K: Kem<SharedKeySize = U32> + ParameterSet> ClientHandshake<S, K>
2226where
2327 K::DecapsulationKey: Decapsulate,
2428{
2529 pub fn new(psk: Option<&[u8; 32]>) -> Self {
2626- let mut strobe = StrobeState::<S>::new(WHARRGHARBL_PROTO.as_bytes(), StrobeRole::Sender);
3030+ let mut neko = NekoState::<S>::new(WHARRGHARBL_PROTO.as_bytes());
27312828- if let Some(psk) = psk {
2929- strobe.key(psk);
3232+ if let Some(psk) = psk.and_then(|psk| psk.as_hybrid_array()) {
3333+ neko.key(psk);
3034 }
31353232- strobe.meta_ad(&K::K::to_u8().to_le_bytes());
3333- strobe.meta_ad(&S::to_bytes());
3636+ neko.ad(&K::K::to_u8().to_le_bytes());
3737+ neko.ad(&S::to_bytes());
3838+ neko.ratchet();
34393540 Self {
3641 kem_sec: PhantomData,
3737- strobe,
4242+ neko,
3843 decap: None,
3944 }
4045 }
41464247 pub fn send(&mut self, rng: &mut impl CryptoRng, buf: &mut dyn Buffer) -> aead::Result<()> {
4343- let mut tag: aead::Tag<AeadStrobe<S>> = Default::default();
4448 let (decap, encap) = K::generate_keypair_from_rng(rng);
45494650 self.decap = Some(Box::new(decap));
47514852 buf.extend_from_slice(&encap.to_bytes())?;
49535050- self.strobe.send_clr(buf.as_ref());
5151- self.strobe.send_mac(&mut tag);
5252- self.strobe.ratchet(S::ratchet_bytes());
5454+ self.neko.cleartext(buf.as_ref());
53555454- buf.extend_from_slice(&tag)?;
5656+ buf.extend_from_slice(&self.neko.create_mac())?;
55575658 Ok(())
5759 }
58605959- pub fn receive(&mut self, ciphertext: &[u8]) -> aead::Result<AeadState<S>> {
6161+ pub fn receive(&mut self, ciphertext: &[u8]) -> aead::Result<AeadTranport<S>> {
6062 let decap = self.decap.as_ref().ok_or(aead::Error)?;
61636264 let tag = ciphertext
6365 .len()
6464- .checked_sub(<AeadStrobe<S> as aead::AeadCore>::TagSize::to_usize())
6666+ .checked_sub(<AeadNeko<S> as aead::AeadCore>::TagSize::to_usize())
6567 .ok_or(aead::Error)?;
66686769 let (ciphertext, tag) = ciphertext.split_at(tag);
68706969- let tag: aead::Tag<AeadStrobe<S>> = tag.try_into().unwrap();
7171+ let tag: aead::Tag<AeadNeko<S>> = tag.try_into().unwrap();
70727171- self.strobe.recv_clr(ciphertext);
7272- self.strobe.recv_mac(&tag)?;
7373- self.strobe.ratchet(S::ratchet_bytes());
7373+ self.neko.cleartext(ciphertext);
7474+ self.neko.verify_mac(&tag)?;
74757576 let shared = decap
7677 .decapsulate_slice(ciphertext)
···7980 Ok(self.finish(shared))
8081 }
81828282- fn finish(&mut self, shared: Array<u8, K::SharedKeySize>) -> AeadState<S> {
8383- self.strobe.ad(&shared);
8383+ fn finish(&mut self, mut shared: SharedKey) -> AeadTranport<S> {
8484+ self.neko.key(&shared);
8585+ shared.zeroize();
8686+8787+ self.neko.ratchet();
8888+8989+ let mut entropy: Array<u8, U64> = Default::default();
84908585- let mut key: aead::Key<AeadStrobe<S>> = Default::default();
8686- let mut inbound: aead::Nonce<AeadStrobe<S>> = Default::default();
8787- let mut outbound: aead::Nonce<AeadStrobe<S>> = Default::default();
9191+ self.neko.prf(&mut entropy);
9292+9393+ let (key, rest) = entropy.split();
9494+ let (outbound, inbound) = rest.split();
88958989- self.strobe.prf(&mut key);
9090- self.strobe.prf(&mut inbound);
9191- self.strobe.prf(&mut outbound);
9696+ entropy.zeroize();
92979393- AeadState::new(key, outbound, inbound, StrobeRole::Sender)
9898+ AeadTranport::new(key, outbound, inbound, Role::Sender)
9499 }
95100}
961019797-pub struct ServerHandshake<S: StrobeSecurity, K: Kem + ParameterSet> {
102102+pub struct ServerHandshake<S: NekoSec, K: Kem + ParameterSet> {
98103 kem_sec: PhantomData<K>,
9999- strobe: StrobeState<S>,
104104+ neko: NekoState<S>,
100105}
101106102102-impl<S: StrobeSecurity, K: Kem + ParameterSet> ServerHandshake<S, K> {
107107+impl<S: NekoSec, K: Kem<SharedKeySize = U32> + ParameterSet> ServerHandshake<S, K> {
103108 pub fn new(psk: Option<&[u8; 32]>) -> Self {
104104- let mut strobe = StrobeState::<S>::new(WHARRGHARBL_PROTO.as_bytes(), StrobeRole::Receiver);
109109+ let mut neko = NekoState::<S>::new(WHARRGHARBL_PROTO.as_bytes());
105110106106- if let Some(psk) = psk {
107107- strobe.key(psk);
111111+ if let Some(psk) = psk.and_then(|psk| psk.as_hybrid_array()) {
112112+ neko.key(psk);
108113 }
109114110110- strobe.meta_ad(&K::K::to_u8().to_le_bytes());
111111- strobe.meta_ad(&S::to_bytes());
115115+ neko.ad(&K::K::to_u8().to_le_bytes());
116116+ neko.ad(&S::to_bytes());
117117+ neko.ratchet();
112118113119 Self {
114120 kem_sec: PhantomData,
115115- strobe,
121121+ neko,
116122 }
117123 }
118124···120126 &mut self,
121127 rng: &mut impl CryptoRng,
122128 buf: &mut dyn Buffer,
123123- ) -> aead::Result<AeadState<S>> {
129129+ ) -> aead::Result<AeadTranport<S>> {
124130 let slice = buf.as_ref();
125131126132 let tag = slice
127133 .len()
128128- .checked_sub(<AeadStrobe<S> as aead::AeadCore>::TagSize::to_usize())
134134+ .checked_sub(<AeadNeko<S> as aead::AeadCore>::TagSize::to_usize())
129135 .ok_or(aead::Error)?;
130136131137 let (encap, tag) = buf.as_ref().split_at(tag);
132138133133- let tag: aead::Tag<AeadStrobe<S>> = tag.try_into().unwrap();
139139+ let tag: aead::Tag<AeadNeko<S>> = tag.try_into().unwrap();
134140135135- self.strobe.recv_clr(encap);
136136- self.strobe.recv_mac(&tag)?;
137137- self.strobe.ratchet(S::ratchet_bytes());
141141+ self.neko.cleartext(encap);
142142+ self.neko.verify_mac(&tag)?;
138143139144 let encap = K::EncapsulationKey::new_from_slice(encap).map_err(|_| aead::Error)?;
140145141146 let (cipher, shared) = encap.encapsulate_with_rng(rng);
142147143143- let mut tag: aead::Tag<AeadStrobe<S>> = Default::default();
144144-145148 buf.truncate(0);
146149147147- self.strobe.send_clr(cipher.as_ref());
148148- self.strobe.send_mac(&mut tag);
149149- self.strobe.ratchet(S::ratchet_bytes());
150150-151150 buf.extend_from_slice(cipher.as_ref())?;
152152- buf.extend_from_slice(&tag)?;
151151+ self.neko.cleartext(cipher.as_ref());
152152+ buf.extend_from_slice(&self.neko.create_mac())?;
153153154154 Ok(self.finish(shared))
155155 }
156156157157- fn finish(&mut self, shared: Array<u8, K::SharedKeySize>) -> AeadState<S> {
158158- self.strobe.ad(&shared);
157157+ fn finish(&mut self, mut shared: SharedKey) -> AeadTranport<S> {
158158+ self.neko.key(&shared);
159159+ shared.zeroize();
159160160160- let mut key: aead::Key<AeadStrobe<S>> = Default::default();
161161- let mut inbound: aead::Nonce<AeadStrobe<S>> = Default::default();
162162- let mut outbound: aead::Nonce<AeadStrobe<S>> = Default::default();
161161+ self.neko.ratchet();
163162164164- self.strobe.prf(&mut key);
165165- self.strobe.prf(&mut inbound);
166166- self.strobe.prf(&mut outbound);
163163+ let mut entropy: Array<u8, U64> = Default::default();
167164168168- AeadState::new(key, outbound, inbound, StrobeRole::Receiver)
165165+ self.neko.prf(&mut entropy);
166166+167167+ let (key, rest) = entropy.split();
168168+ let (outbound, inbound) = rest.split();
169169+170170+ entropy.zeroize();
171171+172172+ AeadTranport::new(key, outbound, inbound, Role::Receiver)
169173 }
170174}
171175172176#[cfg(test)]
173177mod tests {
174178 use ml_kem::{MlKem512, MlKem768};
175175- use wharrgarbl_strobe::{Sec128, Sec256};
179179+ use wharrgarbl_neko::{Neko128, Neko256};
176180177181 use crate::utils::BufferSlice;
178182···185189 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251,
186190 ];
187191188188- let mut alice = ClientHandshake::<Sec128, MlKem512>::new(Some(&psk));
189189- let mut bob = ServerHandshake::<Sec128, MlKem512>::new(Some(&psk));
192192+ let mut alice = ClientHandshake::<Neko128, MlKem512>::new(Some(&psk));
193193+ let mut bob = ServerHandshake::<Neko128, MlKem512>::new(Some(&psk));
190194191195 // BufferSlice acts as our transport across the webz
192196 let mut buf = alloc::vec![0u8; 2048];
···216220217221 #[test]
218222 #[should_panic]
219219- fn handshake_fails_with_strobe_security_mismatch() {
223223+ fn handshake_fails_with_neko_security_mismatch() {
220224 let psk: [u8; 32] = [
221225 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62,
222226 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251,
223227 ];
224228225225- let mut alice = ClientHandshake::<Sec128, MlKem512>::new(Some(&psk));
226226- let mut bob = ServerHandshake::<Sec256, MlKem512>::new(Some(&psk));
229229+ let mut alice = ClientHandshake::<Neko128, MlKem512>::new(Some(&psk));
230230+ let mut bob = ServerHandshake::<Neko256, MlKem512>::new(Some(&psk));
227231228232 // BufferSlice acts as our transport across the webz
229233 let mut buf = alloc::vec![0u8; 2048];
···245249 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251,
246250 ];
247251248248- let mut alice = ClientHandshake::<Sec128, MlKem512>::new(Some(&psk));
249249- let mut bob = ServerHandshake::<Sec128, MlKem768>::new(Some(&psk));
252252+ let mut alice = ClientHandshake::<Neko128, MlKem512>::new(Some(&psk));
253253+ let mut bob = ServerHandshake::<Neko128, MlKem768>::new(Some(&psk));
250254251255 // BufferSlice acts as our transport across the webz
252256 let mut buf = alloc::vec![0u8; 2048];
···268272 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251,
269273 ];
270274271271- let mut alice = ClientHandshake::<Sec128, MlKem512>::new(None);
272272- let mut bob = ServerHandshake::<Sec128, MlKem512>::new(Some(&psk));
275275+ let mut alice = ClientHandshake::<Neko128, MlKem512>::new(None);
276276+ let mut bob = ServerHandshake::<Neko128, MlKem512>::new(Some(&psk));
273277274278 // BufferSlice acts as our transport across the webz
275279 let mut buf = alloc::vec![0u8; 2048];
+22-7
src/lib.rs
···22#![forbid(unsafe_code)]
3344use ml_kem::{MlKem512, MlKem768};
55-pub use wharrgarbl_strobe::{Sec128, Sec256};
55+pub use wharrgarbl_neko::{Neko128, Neko256};
6677pub mod handshake;
88pub mod transport;
···1010extern crate alloc;
11111212/// Version of WHARRGARBL that this crate implements.
1313-pub static WHARRGHARBL_PROTO: &str = "WGBL-v0.1-STv1.0.2";
1313+pub static WHARRGHARBL_PROTO: &str = "WGBLv0.1";
1414+1515+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1616+#[repr(u8)]
1717+enum Role {
1818+ Sender = 0,
1919+ Receiver = 1,
2020+}
2121+2222+impl core::ops::BitXor for Role {
2323+ fn bitxor(self, rhs: Self) -> Self::Output {
2424+ (self as u8) ^ (rhs as u8)
2525+ }
2626+2727+ type Output = u8;
2828+}
14291530pub mod utils {
1616- pub use wharrgarbl_utils::BufferSlice;
3131+ pub use wharrgarbl_utils::{Buffer, BufferSlice};
1732}
18331919-pub type ClientHandshake128 = handshake::ClientHandshake<Sec128, MlKem512>;
2020-pub type ServerHandshake128 = handshake::ServerHandshake<Sec128, MlKem512>;
2121-pub type ClientHandshake256 = handshake::ClientHandshake<Sec256, MlKem768>;
2222-pub type ServerHandshake256 = handshake::ServerHandshake<Sec256, MlKem768>;
3434+pub type NekoClientHandshake128 = handshake::ClientHandshake<Neko128, MlKem512>;
3535+pub type NekoServerHandshake128 = handshake::ServerHandshake<Neko128, MlKem512>;
3636+pub type NekoClientHandshake256 = handshake::ClientHandshake<Neko256, MlKem768>;
3737+pub type NekoServerHandshake256 = handshake::ServerHandshake<Neko256, MlKem768>;
···11+# NEKO
22+33+A whimsical encryption crate that is inspired by the STROBE spec, though it takes diverging choices with regards to its internals and API surface for better performance while retaining security guarantees.
44+55+## SPEC
66+77+WIP
88+99+☢️ **WARNING: DO NOT USE IN PRODUCTION. THIS CRATE IS NOT AUDITED AND IS WIP, MAYBE EVEN NOT AS SECURE AS THOUGHT. AAAAAAAAAA** ☢️
1010+1111+## License
1212+1313+Code dual-licensed under Apache 2.0 or MIT.
···11+use hybrid_array::Array;
22+use zerocopy::IntoBytes;
33+44+use crate::{Neko128, Neko256, NekoState};
55+66+extern crate alloc;
77+88+#[test]
99+fn neko_128_init_state() {
1010+ let neko = NekoState::<Neko128>::new(b"test");
1111+1212+ let first = neko.state[0].to_le_bytes();
1313+ let second = neko.state[1].to_le_bytes();
1414+ let third = neko.state[2].to_le_bytes();
1515+1616+ assert_eq!(&first, b"\x01\x16\x07\x60NEKO");
1717+ // Values that don't fill the block entirely leave padding
1818+ assert_eq!(&second, b"v0.1.0\0\0");
1919+ assert_eq!(&third[..4], b"test");
2020+ // The rest of the state is zeroed
2121+ assert_eq!(&neko.state[3..], &[0; 22]);
2222+}
2323+2424+#[test]
2525+fn neko_256_init_state() {
2626+ let neko = NekoState::<Neko256>::new(b"test");
2727+2828+ let first = neko.state[0].to_le_bytes();
2929+ let second = neko.state[1].to_le_bytes();
3030+ let third = neko.state[2].to_le_bytes();
3131+3232+ assert_eq!(&first, b"\x01\x14\x07\x60NEKO");
3333+ // Values that don't fill the block entirely leave padding
3434+ assert_eq!(&second, b"v0.1.0\0\0");
3535+ assert_eq!(&third[..4], b"test");
3636+ // The rest of the state is zeroed
3737+ assert_eq!(&neko.state[3..], &[0; 22]);
3838+}
3939+4040+#[test]
4141+fn happy_path() {
4242+ let mut tx = NekoState::<Neko128>::new(b"test");
4343+ let mut rx = NekoState::<Neko128>::new(b"test");
4444+4545+ let key = Array::from([13u8; 32]);
4646+4747+ let nonce = Array::from([42u8; 16]);
4848+4949+ let ad = b"guuhhh";
5050+5151+ tx.key(&key);
5252+ rx.key(&key);
5353+5454+ tx.nonce(&nonce);
5555+ rx.nonce(&nonce);
5656+5757+ tx.ad(ad);
5858+ rx.ad(ad);
5959+6060+ let orig = b"Test message, please ignore.";
6161+6262+ let mut msg = orig.to_vec();
6363+6464+ tx.encrypt(&mut msg);
6565+ let mac = tx.create_mac();
6666+6767+ assert_ne!(&msg, orig);
6868+ assert_ne!(&mac, &[0u8; 16]);
6969+7070+ rx.decrypt(&mut msg);
7171+ let verification = rx.verify_mac(&mac);
7272+7373+ assert_eq!(&msg, orig);
7474+ assert!(verification.is_ok());
7575+}
7676+7777+#[test]
7878+fn non_cipher_flag_ops_dont_permute_by_default() {
7979+ let key = Array([1u8; 32]);
8080+ let nonce = Array([2u8; 16]);
8181+ let ad = b"addz";
8282+8383+ let cleartext = b"Message.";
8484+8585+ let mut neko = NekoState::<Neko128>::new(b"test");
8686+8787+ neko.key(&key);
8888+ neko.nonce(&nonce);
8989+ neko.ad(ad);
9090+9191+ let op = neko.state[3].as_bytes();
9292+ let state = neko.state[4..8].as_bytes();
9393+9494+ // OPs are XOR'd onto the state, but when the state is zeroed, it
9595+ // is the same as overwriting, so the ops are visible.
9696+ assert_eq!(op, b"\x00\x08nyaan~");
9797+ assert_eq!(state, key.as_slice());
9898+9999+ let op = neko.state[8].as_bytes();
100100+ let state = neko.state[9..11].as_bytes();
101101+102102+ assert_eq!(op, b"\x04\x09nyaan~");
103103+ assert_eq!(state, nonce.as_slice());
104104+105105+ let op = neko.state[11].as_bytes();
106106+ let state = neko.state[12].as_bytes();
107107+108108+ assert_eq!(op, b"\x09\x0Anyaan~");
109109+ assert_eq!(state, b"addz\0\0\0\0");
110110+111111+ assert_eq!(&neko.state[13..], &[0; 12]);
112112+113113+ // Cleartext will permute the state
114114+ neko.cleartext(cleartext);
115115+116116+ for block in neko.state[0..22].iter().map(IntoBytes::as_bytes) {
117117+ // The permuted state means the message is mixed, so it can no longer be "read"
118118+ // directly.
119119+ assert_ne!(block, b"\x0c\x03nyaan~");
120120+ assert_ne!(block, cleartext);
121121+ }
122122+ let orig_text = u64::from_le_bytes(*cleartext);
123123+ let orig_state = neko.state[0] ^ orig_text;
124124+ let recreated = neko.state[0] ^ orig_state;
125125+126126+ // After XOR'ing from the state, we can reconstruct the cleartext, so we can confirm
127127+ // it is there.
128128+ assert_eq!(orig_text, recreated);
129129+}
130130+131131+#[test]
132132+fn cipher_ops_permute_state_by_default() {
133133+ let key = Array([1u8; 32]);
134134+135135+ let cleartext = b"MESSAGE ENCRYPT.";
136136+137137+ let mut message = cleartext.to_vec();
138138+139139+ let mut neko = NekoState::<Neko128>::new(b"test");
140140+141141+ neko.key(&key);
142142+143143+ // This op will cause the state to be permuted.
144144+ // Encrypted data will be mixed into the permuted state.
145145+ neko.encrypt(&mut message);
146146+147147+ // The encrypted message no longer matches the cleartext
148148+ assert_ne!(cleartext, message.as_slice());
149149+ // The message now is the same as the internal state.
150150+ // This means that on a receiving state, the message can
151151+ // be decrypted via exchanging the state with the ciphertext.
152152+ assert_eq!(neko.state[0..2].as_bytes(), &message);
153153+154154+ let expected_state = [
155155+ 0x14bf7a55ecbfef9f,
156156+ 0xbf881650acfa3419,
157157+ 0x19e0cf77f80c9b9c,
158158+ 0x926cb071dcc542bf,
159159+ 0xb0c2ff19b2a1088a,
160160+ 0x28e1786b57d258b5,
161161+ 0xa10f75b4a49c5830,
162162+ 0xe0d2a4c88992ddab,
163163+ 0xf26af8755e5d7ce8,
164164+ 0xa130fa26d8697da0,
165165+ 0x2d6266942d30bb73,
166166+ 0x0c47cacae106048e,
167167+ 0x1f78293be88890d7,
168168+ 0xcf24691274e96761,
169169+ 0x0501d35fa5a7db34,
170170+ 0xd242e3a40bf885f8,
171171+ 0x717c3e620ea21bdf,
172172+ 0x12af362391a15895,
173173+ 0x8df18b22be842ea5,
174174+ 0xa32fd58b7771026c,
175175+ 0x4a61af391e658ec5,
176176+ 0x189f225280d6d986,
177177+ 0x2718bc59d75e8d60,
178178+ 0x0be0f806c2086f5a,
179179+ 0xd5f2731cc9c0fed0,
180180+ ];
181181+182182+ assert_eq!(&neko.state, &expected_state);
183183+184184+ // Ratchet permutes the state, and then zeros out 128 bits or 256 bits
185185+ // depending on its security strength to ensure forward secrecy.
186186+ neko.ratchet();
187187+188188+ let ratched_expected_state = [
189189+ 0x0000000000000000,
190190+ 0x0000000000000000,
191191+ 0xaeaae19feedba131,
192192+ 0x142c658c8c5d4d41,
193193+ 0x5b15a77c99a73daa,
194194+ 0xe8060d814eb54fe2,
195195+ 0xaf03a57bc4107f06,
196196+ 0x20a98fb94a745e23,
197197+ 0x311dd2be1529baad,
198198+ 0x4d50f23f34057523,
199199+ 0xea89ea72449476d5,
200200+ 0x82457373a57d4062,
201201+ 0x0aad58af56986ea2,
202202+ 0x8548e7cb9b7907c3,
203203+ 0xca95c689bed5fbbb,
204204+ 0x426b6a66b930e5ad,
205205+ 0x514c8865b88f89ea,
206206+ 0x361e3d8a73cd12f1,
207207+ 0xbcd3e3c6949cec98,
208208+ 0x6b210034af33aeac,
209209+ 0x2d06ca5415ba4459,
210210+ 0x57dfabd826619f1e,
211211+ 0x69f4dd80c2791cc4,
212212+ 0xd9086f9fde008f52,
213213+ 0xd71233e8001d2c80,
214214+ ];
215215+216216+ assert_eq!(&neko.state, &ratched_expected_state);
217217+ // The rest of the non-zeroed state should not match the previous state
218218+ assert_ne!(&expected_state[2..], &ratched_expected_state[2..]);
219219+}
220220+221221+#[test]
222222+#[should_panic]
223223+fn mac_correctly_fails_when_it_doesnt_match() {
224224+ let key = Array([1u8; 32]);
225225+226226+ let cleartext = b"MESSAGE ENCRYPT.";
227227+228228+ let mut message = cleartext.to_vec();
229229+230230+ let mut tx = NekoState::<Neko128>::new(b"test");
231231+ let mut rx = NekoState::<Neko128>::new(b"test");
232232+233233+ tx.key(&key);
234234+ rx.key(&key);
235235+236236+ tx.encrypt(&mut message);
237237+ let mut tag = tx.create_mac();
238238+239239+ #[rustfmt::skip]
240240+ let expected_tag = [
241241+ 0xc7, 0xd3, 0xa9, 0x29, 0x61, 0x4a, 0x95, 0xdb,
242242+ 0x39, 0xc3, 0xe2, 0x3f, 0x7e, 0xe1, 0x2e, 0x9f,
243243+ ];
244244+245245+ assert_eq!(&tag, &expected_tag);
246246+247247+ // Modify the tag so that it always fails
248248+ tag[5] ^= 3;
249249+250250+ rx.decrypt(&mut message);
251251+ let verification = rx.verify_mac(&tag);
252252+ assert!(verification.is_ok());
253253+}
254254+255255+#[test]
256256+#[should_panic]
257257+fn mac_correctly_fails_when_state_doesnt_match() {
258258+ let key = Array([1u8; 32]);
259259+260260+ let cleartext = b"MESSAGE ENCRYPT.";
261261+262262+ let mut message = cleartext.to_vec();
263263+264264+ let mut tx = NekoState::<Neko128>::new(b"test");
265265+ let mut rx = NekoState::<Neko128>::new(b"test");
266266+267267+ tx.key(&key);
268268+ rx.key(&key);
269269+270270+ tx.encrypt(&mut message);
271271+ let tag = tx.create_mac();
272272+273273+ #[rustfmt::skip]
274274+ let expected_tag = [
275275+ 0xc7, 0xd3, 0xa9, 0x29, 0x61, 0x4a, 0x95, 0xdb,
276276+ 0x39, 0xc3, 0xe2, 0x3f, 0x7e, 0xe1, 0x2e, 0x9f,
277277+ ];
278278+279279+ message.extend_from_slice(&[0, 0, 0]);
280280+281281+ assert_eq!(&tag, &expected_tag);
282282+ // Decrypt a message that has had its length extended
283283+ rx.decrypt(&mut message);
284284+ let verification = rx.verify_mac(&tag);
285285+ assert!(verification.is_ok());
286286+}
+300
wharrgarbl-neko/src/lib.rs
···11+#![no_std]
22+#![forbid(unsafe_code)]
33+44+mod flags;
55+#[cfg(test)]
66+mod kats;
77+mod operators;
88+mod ops;
99+mod traits;
1010+1111+use core::marker::PhantomData;
1212+1313+use aead::{
1414+ KeySizeUser,
1515+ consts::{U4, U10, U16, U25, U32, U128, U256},
1616+};
1717+use ctutils::CtEq;
1818+use hybrid_array::Array;
1919+use wharrgarbl_utils::OpFlags;
2020+2121+use crate::operators::{NekoOperate, NekoOperateMut};
2222+pub use crate::traits::NekoSec;
2323+2424+pub type Neko128 = U128;
2525+pub type Neko256 = U256;
2626+pub type NekoNonce = Array<u8, U16>;
2727+pub type NekoKey<S> = Array<u8, <NekoState<S> as KeySizeUser>::KeySize>;
2828+pub type NekoTag = Array<u8, U16>;
2929+3030+pub static NEKO_VERSION: &str = "NEKOv0.1.0";
3131+3232+impl<S: NekoSec> KeySizeUser for NekoState<S> {
3333+ type KeySize = U32;
3434+}
3535+3636+#[derive(Clone)]
3737+pub struct NekoState<S: NekoSec> {
3838+ pub(crate) state: Array<u64, U25>,
3939+ position: usize,
4040+ start: usize,
4141+ sec: PhantomData<S>,
4242+}
4343+4444+impl<S: NekoSec> zeroize::Zeroize for NekoState<S> {
4545+ fn zeroize(&mut self) {
4646+ self.state.zeroize();
4747+ self.position.zeroize();
4848+ self.position.zeroize();
4949+ }
5050+}
5151+5252+impl<S: NekoSec> zeroize::ZeroizeOnDrop for NekoState<S> {}
5353+5454+impl<S: NekoSec> Drop for NekoState<S> {
5555+ fn drop(&mut self) {
5656+ zeroize::Zeroize::zeroize(self);
5757+ }
5858+}
5959+6060+impl<Sec: NekoSec> NekoState<Sec> {
6161+ pub fn new(protocol: &[u8]) -> Self {
6262+ let mut strobe = Self {
6363+ state: Array([0u64; keccak::PLEN]),
6464+ position: 0,
6565+ start: 0,
6666+ sec: PhantomData,
6767+ };
6868+6969+ let preamble: Array<u8, U4> = Array::from([0x01, Sec::rate() as u8, 0x07, 0x60]);
7070+7171+ // This is safe because the static string is always 10 bytes long.
7272+ let version: Array<u8, U10> = Array::try_from(NEKO_VERSION.as_bytes()).unwrap();
7373+7474+ let combined = preamble.concat(version);
7575+7676+ strobe.overwrite(&combined);
7777+ strobe.overwrite(protocol);
7878+7979+ strobe
8080+ }
8181+8282+ #[inline]
8383+ fn begin_op(&mut self, red_flags: OpFlags) {
8484+ let old_start = self.start;
8585+ self.start = self.position + 1;
8686+8787+ self.absorb(&[
8888+ old_start as u8,
8989+ red_flags.bits(),
9090+ b'n',
9191+ b'y',
9292+ b'a',
9393+ b'a',
9494+ b'n',
9595+ b'~',
9696+ ]);
9797+ }
9898+9999+ fn permutation_f(&mut self) {
100100+ let permuter = [
101101+ self.start as u8,
102102+ self.position as u8,
103103+ 0x04,
104104+ 0x80,
105105+ b'M',
106106+ b'E',
107107+ b'O',
108108+ b'W',
109109+ ];
110110+111111+ // XOR the permuter into first entropy block
112112+ self.state[Sec::rate()] ^= u64::from_le_bytes(permuter);
113113+114114+ keccak::Keccak::new().with_f1600(|permute| permute(&mut self.state.0));
115115+116116+ self.position = 0;
117117+ self.start = 0;
118118+ }
119119+120120+ #[inline]
121121+ fn absorb(&mut self, data: &[u8]) {
122122+ NekoOperate::new(self, data).operate(|(state, byte)| *state ^= *byte);
123123+ }
124124+125125+ #[inline]
126126+ fn absorb_and_set(&mut self, data: &mut [u8]) {
127127+ NekoOperateMut::new(self, data).operate_mut(|(state, byte)| {
128128+ *state ^= *byte;
129129+ *byte = *state;
130130+ });
131131+ }
132132+133133+ #[inline]
134134+ fn copy_state(&mut self, data: &mut [u8]) {
135135+ NekoOperateMut::new(self, data).operate_mut(|(state, byte)| *byte = *state);
136136+ }
137137+138138+ #[inline]
139139+ fn exchange(&mut self, data: &mut [u8]) {
140140+ NekoOperateMut::new(self, data).operate_mut(|(state, byte)| {
141141+ *byte ^= *state;
142142+ *state ^= *byte;
143143+ });
144144+ }
145145+146146+ #[inline]
147147+ fn overwrite(&mut self, data: &[u8]) {
148148+ NekoOperate::new(self, data).operate(|(state, byte)| *state = *byte);
149149+ }
150150+151151+ #[inline]
152152+ fn squeeze(&mut self, data: &mut [u8]) {
153153+ NekoOperateMut::new(self, data).operate_mut(|(state, byte)| {
154154+ *byte = *state;
155155+ *state = 0;
156156+ });
157157+ }
158158+159159+ #[inline]
160160+ fn zero_state(&mut self) {
161161+ let ratchet_bytes = Sec::ratchet_bytes();
162162+163163+ self.state[0..ratchet_bytes].iter_mut().for_each(|block| {
164164+ *block = 0;
165165+ });
166166+167167+ self.position += ratchet_bytes;
168168+ }
169169+170170+ pub fn key(&mut self, data: &NekoKey<Sec>) {
171171+ self.begin_op(ops::KEY);
172172+173173+ self.overwrite(data);
174174+ }
175175+176176+ pub fn nonce(&mut self, data: &NekoNonce) {
177177+ self.begin_op(ops::NONCE);
178178+179179+ self.absorb(data);
180180+ }
181181+182182+ /// Absorb associated data into the cipher. This should include a nonce at least,
183183+ /// along with other data coming from the app.
184184+ pub fn ad(&mut self, data: &[u8]) {
185185+ self.begin_op(ops::AD);
186186+187187+ self.absorb(data);
188188+ }
189189+190190+ pub fn prf(&mut self, data: &mut [u8]) {
191191+ self.begin_op(ops::PRF);
192192+193193+ self.permutation_f();
194194+195195+ self.squeeze(data);
196196+ }
197197+198198+ pub fn create_mac(&mut self) -> NekoTag {
199199+ self.begin_op(ops::MAC);
200200+201201+ self.permutation_f();
202202+203203+ let mut tag: NekoTag = Default::default();
204204+205205+ self.copy_state(&mut tag);
206206+207207+ tag
208208+ }
209209+210210+ pub fn verify_mac(&mut self, data: &NekoTag) -> aead::Result<()> {
211211+ self.begin_op(ops::MAC);
212212+213213+ self.permutation_f();
214214+215215+ let mut mac_copy = *data;
216216+217217+ self.exchange(&mut mac_copy);
218218+219219+ let all_zero: NekoTag = Default::default();
220220+221221+ if mac_copy.ct_eq(&all_zero).to_bool() {
222222+ Ok(())
223223+ } else {
224224+ Err(aead::Error)
225225+ }
226226+ }
227227+228228+ pub fn encrypt(&mut self, data: &mut [u8]) {
229229+ self.begin_op(ops::ENC);
230230+231231+ self.permutation_f();
232232+233233+ self.absorb_and_set(data);
234234+ }
235235+236236+ pub fn decrypt(&mut self, data: &mut [u8]) {
237237+ self.begin_op(ops::ENC);
238238+239239+ self.permutation_f();
240240+241241+ self.exchange(data);
242242+ }
243243+244244+ /// This is for mixing a cleartext message into the state. This is meant to be used
245245+ /// both for when you send the cleartext message on one side, then when you receive the
246246+ /// message on the other side.
247247+ pub fn cleartext(&mut self, data: &[u8]) {
248248+ self.begin_op(ops::CLR);
249249+250250+ self.permutation_f();
251251+252252+ self.absorb(data);
253253+ }
254254+255255+ pub fn ratchet(&mut self) {
256256+ self.begin_op(ops::RATCHET);
257257+258258+ self.permutation_f();
259259+260260+ self.zero_state();
261261+ }
262262+}
263263+264264+impl<S: NekoSec> core::fmt::Display for NekoState<S> {
265265+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266266+ f.write_str(NEKO_VERSION)?;
267267+ write!(f, "/1600-{}", S::to_usize())
268268+ }
269269+}
270270+271271+impl<S: NekoSec> core::fmt::Debug for NekoState<S> {
272272+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273273+ // Do not reveal internal state of StrobeState, other than its security level
274274+ f.debug_struct("NekoState")
275275+ .field("sec", &S::to_usize())
276276+ .field("version", &NEKO_VERSION)
277277+ .finish_non_exhaustive()
278278+ }
279279+}
280280+281281+#[cfg(test)]
282282+mod tests {
283283+ use super::*;
284284+285285+ extern crate std;
286286+287287+ #[test]
288288+ fn version_formatting() {
289289+ let s = NekoState::<Neko128>::new(b"");
290290+291291+ let display = std::format!("{s}");
292292+ let debug = std::format!("{s:?}");
293293+294294+ assert_eq!(&display, "NEKOv0.1.0/1600-128");
295295+ assert_eq!(
296296+ &debug,
297297+ "NekoState { sec: 128, version: \"NEKOv0.1.0\", .. }"
298298+ );
299299+ }
300300+}