A whimsical STROBE based encryption protocol
1use core::marker::PhantomData;
2
3use aead::{AeadInOut, Buffer, Key, KeySizeUser, TagPosition, common::IvSizeUser};
4use ctutils::{CtEq, CtSelect};
5use hybrid_array::AssocArraySize;
6use wharrgarbl_neko::{NekoSec, NekoState, NekoTag};
7
8use crate::{Role, WHARRGHARBL_PROTO};
9
10pub struct AeadNeko<S: NekoSec> {
11 pub(crate) key: Key<Self>,
12 param: PhantomData<S>,
13}
14
15impl<S: NekoSec> aead::KeySizeUser for AeadNeko<S> {
16 type KeySize = <NekoState<S> as KeySizeUser>::KeySize;
17}
18
19impl<S: NekoSec> IvSizeUser for AeadNeko<S> {
20 type IvSize = <NekoState<S> as IvSizeUser>::IvSize;
21}
22
23impl<S: NekoSec> aead::AeadCore for AeadNeko<S> {
24 type NonceSize = <AeadNeko<S> as IvSizeUser>::IvSize;
25 type TagSize = <NekoTag as AssocArraySize>::Size;
26 const TAG_POSITION: TagPosition = TagPosition::Postfix;
27}
28
29impl<S: NekoSec> aead::KeyInit for AeadNeko<S> {
30 fn new(key: &Key<Self>) -> Self {
31 Self {
32 key: *key,
33 param: PhantomData,
34 }
35 }
36}
37
38impl<S: NekoSec> aead::AeadInOut for AeadNeko<S> {
39 fn encrypt_inout_detached(
40 &self,
41 nonce: &aead::Nonce<Self>,
42 associated_data: &[u8],
43 mut buffer: aead::inout::InOutBuf<'_, '_, u8>,
44 ) -> aead::Result<aead::Tag<Self>> {
45 let mut neko = NekoState::<S>::new(WHARRGHARBL_PROTO.as_bytes());
46
47 neko.key(&self.key);
48 neko.nonce(nonce);
49 neko.ad(associated_data);
50 neko.encrypt(buffer.get_out());
51
52 Ok(neko.create_mac())
53 }
54
55 fn decrypt_inout_detached(
56 &self,
57 nonce: &aead::Nonce<Self>,
58 associated_data: &[u8],
59 mut buffer: aead::inout::InOutBuf<'_, '_, u8>,
60 tag: &aead::Tag<Self>,
61 ) -> aead::Result<()> {
62 let mut neko = NekoState::<S>::new(WHARRGHARBL_PROTO.as_bytes());
63
64 neko.key(&self.key);
65 neko.nonce(nonce);
66 neko.ad(associated_data);
67 neko.decrypt(buffer.get_out());
68
69 neko.verify_mac(tag)
70 }
71}
72
73impl<S: NekoSec> zeroize::Zeroize for AeadNeko<S> {
74 fn zeroize(&mut self) {
75 self.key.zeroize();
76 }
77}
78
79pub struct AeadTransport<S: NekoSec> {
80 pub(crate) aead: AeadNeko<S>,
81 pub(crate) epstein: aead::Nonce<AeadNeko<S>>,
82 pub(crate) trump: aead::Nonce<AeadNeko<S>>,
83 handshake_role: Role,
84}
85
86impl<S: NekoSec> zeroize::Zeroize for AeadTransport<S> {
87 fn zeroize(&mut self) {
88 self.aead.zeroize();
89 self.epstein.zeroize();
90 self.trump.zeroize();
91 }
92}
93
94impl<S: NekoSec> zeroize::ZeroizeOnDrop for AeadTransport<S> {}
95
96impl<S: NekoSec> Drop for AeadTransport<S> {
97 fn drop(&mut self) {
98 zeroize::Zeroize::zeroize(self);
99 }
100}
101
102impl<S: NekoSec> AeadTransport<S> {
103 pub(crate) fn new(
104 key: Key<AeadNeko<S>>,
105 outbound: aead::Nonce<AeadNeko<S>>,
106 inbound: aead::Nonce<AeadNeko<S>>,
107 role: Role,
108 ) -> Self {
109 assert_ne!(
110 &inbound, &outbound,
111 "The Base Nonces MUST NOT equal to each other"
112 );
113
114 Self {
115 aead: AeadNeko {
116 key,
117 param: PhantomData,
118 },
119 epstein: outbound,
120 trump: inbound,
121 handshake_role: role,
122 }
123 }
124
125 fn select_nonce(&self, sending_role: Role) -> aead::Nonce<AeadNeko<S>> {
126 let role_context = self.handshake_role ^ sending_role;
127
128 self.epstein.ct_select(&self.trump, role_context.ct_eq(&1))
129 }
130
131 fn mix_nonce(&self, position: [u8; 8], sending_role: Role) -> aead::Nonce<AeadNeko<S>> {
132 let mut nonce = self.select_nonce(sending_role);
133
134 let mid = nonce.len() - position.len();
135
136 nonce[mid..]
137 .iter_mut()
138 .zip(position)
139 .for_each(|(n, p)| *n ^= p);
140
141 nonce
142 }
143
144 pub fn split(&self) -> (SendState<'_, S>, RecvState<'_, S>) {
145 (
146 SendState {
147 transport: self,
148 counter: 0,
149 },
150 RecvState {
151 transport: self,
152 counter: 0,
153 },
154 )
155 }
156}
157
158pub struct SendState<'a, S: NekoSec> {
159 transport: &'a AeadTransport<S>,
160 counter: u64,
161}
162
163impl<S: NekoSec> SendState<'_, S> {
164 pub fn encrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> {
165 match self.counter.checked_add(1) {
166 Some(inc) => self.counter = inc,
167 None => return Err(aead::Error),
168 }
169
170 self.transport.aead.encrypt_in_place(
171 &self
172 .transport
173 .mix_nonce(self.counter.to_be_bytes(), Role::Sender),
174 ad,
175 buffer,
176 )
177 }
178}
179
180pub struct RecvState<'a, S: NekoSec> {
181 transport: &'a AeadTransport<S>,
182 counter: u64,
183}
184
185impl<S: NekoSec> RecvState<'_, S> {
186 pub fn decrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> {
187 match self.counter.checked_add(1) {
188 Some(inc) => self.counter = inc,
189 None => return Err(aead::Error),
190 }
191
192 // If the message's MAC doesn't evaluate successfully, this op will fail.
193 self.transport.aead.decrypt_in_place(
194 &self
195 .transport
196 .mix_nonce(self.counter.to_be_bytes(), Role::Receiver),
197 ad,
198 buffer,
199 )
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use wharrgarbl_neko::{Neko128, Neko256};
206
207 use super::*;
208
209 #[test]
210 fn two_way_transport_sync_works() -> aead::Result<()> {
211 let shared_secret = [
212 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
213 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
214 0x9c, 0x9d, 0x9e, 0x9f,
215 ];
216
217 let outbound = 123u128.to_ne_bytes();
218 let inbound = 234u128.to_ne_bytes();
219
220 let alice = AeadTransport::<Neko128>::new(
221 shared_secret.into(),
222 outbound.into(),
223 inbound.into(),
224 Role::Sender,
225 );
226 let bob = AeadTransport::<Neko128>::new(
227 shared_secret.into(),
228 outbound.into(),
229 inbound.into(),
230 Role::Receiver,
231 );
232
233 let (mut alice_send, mut alice_recv) = alice.split();
234 let (mut bob_send, mut bob_recv) = bob.split();
235
236 let orig = b"Test Message, Please ignore. AAAAAAAAAAAAAAAAAAaaaaaaaaaaa | B .";
237
238 let ad = b"random";
239
240 let mut msg = orig.to_vec();
241
242 // a -> b
243 alice_send.encrypt(&mut msg, ad)?;
244
245 assert_ne!(orig.as_slice(), msg.as_slice());
246 let ct1 = msg.clone();
247
248 bob_recv.decrypt(&mut msg, ad)?;
249
250 // a -> b
251 alice_send.encrypt(&mut msg, b"")?;
252
253 assert_ne!(msg.as_slice(), ct1.as_slice());
254 let ct2 = msg.clone();
255
256 bob_recv.decrypt(&mut msg, b"")?;
257
258 // b -> a
259 bob_send.encrypt(&mut msg, ad)?;
260
261 // None of the ciphertexts should match each other
262 assert_ne!(msg.as_slice(), ct1.as_slice());
263 assert_ne!(msg.as_slice(), ct2.as_slice());
264 assert_ne!(ct1.as_slice(), ct2.as_slice());
265
266 alice_recv.decrypt(&mut msg, ad)?;
267
268 assert_eq!(orig.as_slice(), msg.as_slice());
269
270 // Counters are tracked from sender to receiver
271 assert_eq!(alice_send.counter, bob_recv.counter);
272 assert_eq!(bob_send.counter, alice_recv.counter);
273
274 // Counters are not linked on the same side
275 assert_ne!(alice_send.counter, alice_recv.counter);
276 assert_ne!(bob_send.counter, bob_recv.counter);
277
278 Ok(())
279 }
280
281 #[test]
282 #[should_panic]
283 fn two_way_transport_fails_with_security_level_mismatch() {
284 let shared_secret = [
285 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
286 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
287 0x9c, 0x9d, 0x9e, 0x9f,
288 ];
289
290 let outbound = 123u128.to_ne_bytes();
291 let inbound = 234u128.to_ne_bytes();
292
293 let alice = AeadTransport::<Neko128>::new(
294 shared_secret.into(),
295 outbound.into(),
296 inbound.into(),
297 Role::Sender,
298 );
299 let bob = AeadTransport::<Neko256>::new(
300 shared_secret.into(),
301 outbound.into(),
302 inbound.into(),
303 Role::Receiver,
304 );
305
306 let (mut alice_send, mut _alice_recv) = alice.split();
307 let (mut _bob_send, mut bob_recv) = bob.split();
308
309 let orig = b"Test Message, Please ignore.";
310
311 let ad = b"random";
312
313 let mut msg = orig.to_vec();
314
315 // a -> b
316 alice_send.encrypt(&mut msg, ad).unwrap();
317
318 assert_ne!(orig.as_slice(), msg.as_slice());
319
320 bob_recv.decrypt(&mut msg, ad).unwrap();
321 }
322}