strobe_rs/strobe.rs
1use crate::keccak::{keccakf_u8, AlignedKeccakState, KECCAK_BLOCK_BITLEN_STR, KECCAK_BLOCK_SIZE};
2
3use bitflags::bitflags;
4use subtle::{self, ConstantTimeEq};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7// With this feature on, a user can serialize and deserialize the state of a STROBE session
8#[cfg(feature = "serialize_secret_state")]
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11/// Version of Strobe that this crate implements.
12pub const STROBE_VERSION: &[u8] = b"1.0.2";
13
14/// A placeholder for STROBE version strings. This is the length of the real version strings, for
15/// Keccak-f[1600]
16const TEMPLATE_VERSION_STR: [u8; 29] = *b"Strobe-Keccak-sss/bbbb-vX.Y.Z";
17
18bitflags! {
19 /// Operation flags defined in the Strobe paper. This is defined as a bitflags struct.
20 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21 pub(crate) struct OpFlags: u8 {
22 /// Is data being moved inbound
23 const I = 1<<0;
24 /// Is data being sent to the application
25 const A = 1<<1;
26 /// Does this operation use cipher output
27 const C = 1<<2;
28 /// Is data being sent for transport
29 const T = 1<<3;
30 /// Use exclusively for metadata operations
31 const M = 1<<4;
32 /// Reserved and currently unimplemented. Using this will cause a panic.
33 const K = 1<<5;
34 }
35}
36
37#[cfg(feature = "serialize_secret_state")]
38impl Serialize for OpFlags {
39 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
40 bitflags_serde_legacy::serialize(self, "OpFlags", serializer)
41 }
42}
43
44#[cfg(feature = "serialize_secret_state")]
45impl<'de> Deserialize<'de> for OpFlags {
46 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
47 bitflags_serde_legacy::deserialize("OpFlags", deserializer)
48 }
49}
50
51impl Zeroize for OpFlags {
52 fn zeroize(&mut self) {
53 self.0 .0.zeroize();
54 }
55}
56
57/// Security parameter. Choice of 128 or 256 bits.
58#[derive(Clone, Copy)]
59#[cfg_attr(feature = "serialize_secret_state", derive(Serialize, Deserialize))]
60#[repr(usize)]
61pub enum SecParam {
62 B128 = 128,
63 B256 = 256,
64}
65
66/// An empty struct that just indicates that MAC verification failed
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct AuthError;
69
70impl core::fmt::Display for AuthError {
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 f.write_str("MAC verification failed")
73 }
74}
75
76/// The main Strobe object. This is currently limited to using Keccak-f\[1600\] (the highest
77/// security level) as the internal permutation function. For more information on this object, the
78/// [protocol specification][spec] is a great resource.
79///
80/// [spec]: https://strobe.sourceforge.io/specs/
81///
82/// Description of method input
83/// ---------------------------
84/// Most operations exposed by `Strobe` take the same set of inputs. The arguments are
85///
86/// * `data` - The input data to the operation.
87/// * `more` - For streaming purposes. Specifies whether you're trying to add more input / get more
88/// output to/from the previous operation. For example:
89///
90/// ```rust
91/// # extern crate strobe_rs;
92/// # use strobe_rs::{SecParam, Strobe};
93/// # let mut s = Strobe::new(b"example-of-more", SecParam::B128);
94/// s.ad(b"hello world", false);
95/// ```
96/// is equivalent to
97/// ```rust
98/// # extern crate strobe_rs;
99/// # use strobe_rs::{SecParam, Strobe};
100/// # let mut s = Strobe::new(b"example-of-more", SecParam::B128);
101/// s.ad(b"hello ", false);
102/// s.ad(b"world", true);
103/// ```
104///
105/// **NOTE:** If you try to set the `more` flag for an operation that is not preceded by the same
106/// operation (e.g., if you try `ad` followed by `send_enc` with `more=true`), then **the function
107/// will panic**, since that is an invalid use of the `more` flag.
108///
109/// Finally, `ratchet` and `meta_ratchet` take a `usize` argument instead of bytes. These functions
110/// are individually commented below.
111#[derive(Clone, Zeroize, ZeroizeOnDrop)]
112#[cfg_attr(feature = "serialize_secret_state", derive(Serialize, Deserialize))]
113pub struct Strobe {
114 /// Internal Keccak state
115 pub(crate) st: AlignedKeccakState,
116 /// Security parameter (128 or 256 bits)
117 #[zeroize(skip)]
118 sec: SecParam,
119 /// This is the `R` parameter in the Strobe spec
120 rate: usize,
121 /// Index into `st`
122 pos: usize,
123 /// Index into `st`
124 pos_begin: usize,
125 /// Represents whether we're a sender or a receiver or uninitialized
126 is_receiver: Option<bool>,
127 /// The last operation performed. This is to verify that the `more` flag is only used across
128 /// identical operations.
129 prev_flags: Option<OpFlags>,
130}
131
132// This defines an operation and meta-operation that mutates its input
133macro_rules! def_op_mut {
134 ($name:ident, $meta_name:ident, $flags:expr, $doc_str:expr) => {
135 #[doc = $doc_str]
136 pub fn $name(&mut self, data: &mut [u8], more: bool) {
137 let flags = $flags;
138 self.operate(flags, data, more);
139 }
140
141 #[doc = $doc_str]
142 pub fn $meta_name(&mut self, data: &mut [u8], more: bool) {
143 let flags = $flags | OpFlags::M;
144 self.operate(flags, data, more);
145 }
146 };
147}
148
149// This defines an operation and meta-operation that does not mutate its input
150macro_rules! def_op_no_mut {
151 ($name:ident, $meta_name:ident, $flags:expr, $doc_str:expr) => {
152 #[doc = $doc_str]
153 pub fn $name(&mut self, data: &[u8], more: bool) {
154 let flags = $flags;
155 self.operate_no_mutate(flags, data, more);
156 }
157
158 #[doc = $doc_str]
159 pub fn $meta_name(&mut self, data: &[u8], more: bool) {
160 let flags = $flags | OpFlags::M;
161 self.operate_no_mutate(flags, data, more);
162 }
163 };
164}
165
166impl Strobe {
167 /// Makes a new `Strobe` object with a given protocol byte string and security parameter.
168 pub fn new(proto: &[u8], sec: SecParam) -> Strobe {
169 let rate = KECCAK_BLOCK_SIZE * 8 - (sec as usize) / 4 - 2;
170 assert!(rate >= 1);
171 assert!(rate < 254);
172
173 // Initialize state: st = F([0x01, R+2, 0x01, 0x00, 0x01, 0x60] + b"STROBEvX.Y.Z")
174 let mut st_buf = [0u8; KECCAK_BLOCK_SIZE * 8];
175 st_buf[0..6].copy_from_slice(&[0x01, (rate as u8) + 2, 0x01, 0x00, 0x01, 0x60]);
176 st_buf[6..13].copy_from_slice(b"STROBEv");
177 st_buf[13..18].copy_from_slice(STROBE_VERSION);
178
179 let mut st = AlignedKeccakState(st_buf);
180 keccakf_u8(&mut st);
181
182 let mut strobe = Strobe {
183 st,
184 sec,
185 rate,
186 pos: 0,
187 pos_begin: 0,
188 is_receiver: None,
189 prev_flags: None,
190 };
191
192 // Mix the protocol into the state
193 strobe.meta_ad(proto, false);
194
195 strobe
196 }
197
198 /// Returns a bytestring of the form `Strobe-Keccak-SEC/B-vVER` where `SEC` is the
199 /// bits of security (128 or 256), `B` is the block size (in bits) of the Keccak
200 /// permutation function (currently this can only be 1600), and `VER` is the protocol
201 /// version.
202 pub fn version_str(&self) -> [u8; TEMPLATE_VERSION_STR.len()] {
203 let mut buf = TEMPLATE_VERSION_STR;
204
205 match self.sec {
206 SecParam::B128 => buf[14..17].copy_from_slice(b"128"),
207 SecParam::B256 => buf[14..17].copy_from_slice(b"256"),
208 }
209 buf[18..22].copy_from_slice(KECCAK_BLOCK_BITLEN_STR);
210 buf[24..29].copy_from_slice(STROBE_VERSION);
211
212 buf
213 }
214
215 /// Validates that the `more` flag is being used correctly. Panics when validation fails.
216 fn validate_streaming(&mut self, flags: OpFlags, more: bool) {
217 // Streaming only makes sense if this operation is the same as last. For example you can do
218 // s.ad("hello", false);
219 // s.ad(" world", true).
220 // But you can't do
221 // s.ad("hello", false);
222 // s.key(" world", true).
223 if more {
224 assert_eq!(
225 self.prev_flags,
226 Some(flags),
227 "`more` can only be used when this operation is the same as the previous operation"
228 );
229 }
230
231 // Update the last-performed operation (i.e., the one we're about to perform)
232 self.prev_flags = Some(flags);
233 }
234
235 // Runs the permutation function on the internal state
236 fn run_f(&mut self) {
237 self.st.0[self.pos] ^= self.pos_begin as u8;
238 self.st.0[self.pos + 1] ^= 0x04;
239 self.st.0[self.rate + 1] ^= 0x80;
240
241 keccakf_u8(&mut self.st);
242 self.pos = 0;
243 self.pos_begin = 0;
244 }
245
246 /// XORs the given data into the state. This is a special case of the `duplex` code in the
247 /// STROBE paper.
248 fn absorb(&mut self, data: &[u8]) {
249 for b in data {
250 self.st.0[self.pos] ^= *b;
251
252 self.pos += 1;
253 if self.pos == self.rate {
254 self.run_f();
255 }
256 }
257 }
258
259 /// XORs the given data into the state, then sets the data equal the state. This is a special
260 /// case of the `duplex` code in the STROBE paper.
261 fn absorb_and_set(&mut self, data: &mut [u8]) {
262 for b in data {
263 let state_byte = self.st.0.get_mut(self.pos).unwrap();
264 *state_byte ^= *b;
265 *b = *state_byte;
266
267 self.pos += 1;
268 if self.pos == self.rate {
269 self.run_f();
270 }
271 }
272 }
273
274 /// Copies the internal state into the given buffer. This is a special case of `absorb_and_set`
275 /// where `data` is all zeros.
276 fn copy_state(&mut self, data: &mut [u8]) {
277 for b in data {
278 *b = self.st.0[self.pos];
279
280 self.pos += 1;
281 if self.pos == self.rate {
282 self.run_f();
283 }
284 }
285 }
286
287 /// Overwrites the state with the given data while XORing the given data with the old state.
288 /// This is a special case of the `duplex` code in the STROBE paper.
289 fn exchange(&mut self, data: &mut [u8]) {
290 for b in data {
291 let state_byte = self.st.0.get_mut(self.pos).unwrap();
292 *b ^= *state_byte;
293 *state_byte ^= *b;
294
295 self.pos += 1;
296 if self.pos == self.rate {
297 self.run_f();
298 }
299 }
300 }
301
302 /// Overwrites the state with the given data. This is a special case of `Strobe::exchange`,
303 /// where we do not want to mutate the input data.
304 fn overwrite(&mut self, data: &[u8]) {
305 for b in data {
306 self.st.0[self.pos] = *b;
307
308 self.pos += 1;
309 if self.pos == self.rate {
310 self.run_f();
311 }
312 }
313 }
314
315 /// Copies the state into the given buffer and sets the state to 0. This is a special case of
316 /// `Strobe::exchange`, where `data` is assumed to be the all-zeros string. This is precisely
317 /// the case when the current operation is PRF.
318 fn squeeze(&mut self, data: &mut [u8]) {
319 for b in data {
320 let state_byte = self.st.0.get_mut(self.pos).unwrap();
321 *b = *state_byte;
322 *state_byte = 0;
323
324 self.pos += 1;
325 if self.pos == self.rate {
326 self.run_f();
327 }
328 }
329 }
330
331 /// Overwrites the state with a specified number of zeros. This is a special case of
332 /// `Strobe::exchange`. More specifically, it's a special case of `Strobe::overwrite` and
333 /// `Strobe::squeeze`. It's like `squeeze` in that we assume we've been given all zeros as
334 /// input, and like `overwrite` in that we do not mutate (or take) any input.
335 fn zero_state(&mut self, mut bytes_to_zero: usize) {
336 static ZEROS: [u8; 8 * KECCAK_BLOCK_SIZE] = [0u8; 8 * KECCAK_BLOCK_SIZE];
337
338 // Do the zero-writing in chunks
339 while bytes_to_zero > 0 {
340 let slice_len = core::cmp::min(self.rate - self.pos, bytes_to_zero);
341 self.st.0[self.pos..(self.pos + slice_len)].copy_from_slice(&ZEROS[..slice_len]);
342
343 self.pos += slice_len;
344 bytes_to_zero -= slice_len;
345
346 if self.pos == self.rate {
347 self.run_f();
348 }
349 }
350 }
351
352 /// Mixes the current state index and flags into the state, accounting for whether we are
353 /// sending or receiving
354 fn begin_op(&mut self, mut flags: OpFlags) {
355 if flags.contains(OpFlags::T) {
356 let is_op_receiving = flags.contains(OpFlags::I);
357 // If uninitialized, take on the direction of the first directional operation we get
358 if self.is_receiver.is_none() {
359 self.is_receiver = Some(is_op_receiving);
360 }
361
362 // So that the sender and receiver agree, toggle the I flag as necessary
363 // This is equivalent to flags ^= is_receiver
364 flags.set(OpFlags::I, self.is_receiver.unwrap() != is_op_receiving);
365 }
366
367 let old_pos_begin = self.pos_begin;
368 self.pos_begin = self.pos + 1;
369
370 // Mix in the position and flags
371 let to_mix = &mut [old_pos_begin as u8, flags.bits()];
372 self.absorb(&to_mix[..]);
373
374 let force_f = flags.contains(OpFlags::C) || flags.contains(OpFlags::K);
375 if force_f && self.pos != 0 {
376 self.run_f();
377 }
378 }
379
380 /// Performs the state / data transformation that corresponds to the given flags. If `more` is
381 /// given, this will treat `data` as a continuation of the data given in the previous
382 /// call to `operate`.
383 pub(crate) fn operate(&mut self, flags: OpFlags, data: &mut [u8], more: bool) {
384 // Make sure the K opflag isn't being used, and that the `more` flag isn't being misused
385 assert!(!flags.contains(OpFlags::K), "Op flag K not implemented");
386 self.validate_streaming(flags, more);
387
388 // If `more` isn't set, this is a new operation. Do the begin_op sequence
389 if !more {
390 self.begin_op(flags);
391 }
392
393 // Meta-ness is only relevant for `begin_op`. Remove it to simplify the below logic.
394 let flags = flags & !OpFlags::M;
395
396 // TODO?: Assert that input is empty under some flag conditions
397 if flags.contains(OpFlags::C) && flags.contains(OpFlags::T) && !flags.contains(OpFlags::I) {
398 // This is equivalent to the `duplex` operation in the Python implementation, with
399 // `cafter = True`
400 if flags == OpFlags::C | OpFlags::T {
401 // This is `send_mac`. Pretend the input is all zeros
402 self.copy_state(data)
403 } else {
404 self.absorb_and_set(data);
405 }
406 } else if flags == OpFlags::I | OpFlags::A | OpFlags::C {
407 // Special case of case below. This is PRF. Use `squeeze` instead of `exchange`.
408 self.squeeze(data);
409 } else if flags.contains(OpFlags::C) {
410 // This is equivalent to the `duplex` operation in the Python implementation, with
411 // `cbefore = True`
412 self.exchange(data);
413 } else {
414 // This should normally call `absorb`, but `absorb` does not mutate, so the implementor
415 // should have used operate_no_mutate instead
416 panic!("operate should not be called for operations that do not require mutation");
417 }
418 }
419
420 /// Performs the state transformation that corresponds to the given flags. If `more` is given,
421 /// this will treat `data` as a continuation of the data given in the previous call to
422 /// `operate`. This uses non-mutating variants of the specializations of the `duplex` function.
423 pub(crate) fn operate_no_mutate(&mut self, flags: OpFlags, data: &[u8], more: bool) {
424 // Make sure the K opflag isn't being used, and that the `more` flag isn't being misused
425 assert!(!flags.contains(OpFlags::K), "Op flag K not implemented");
426 self.validate_streaming(flags, more);
427
428 // If `more` isn't set, this is a new operation. Do the begin_op sequence
429 if !more {
430 self.begin_op(flags);
431 }
432
433 // There are no non-mutating variants of things with flags & (C | T | I) == C | T
434 if flags.contains(OpFlags::C) && flags.contains(OpFlags::T) && !flags.contains(OpFlags::I) {
435 panic!("operate_no_mutate called on something that requires mutation");
436 } else if flags.contains(OpFlags::C) {
437 // This is equivalent to a non-mutating form of the `duplex` operation in the Python
438 // implementation, with `cbefore = True`
439 self.overwrite(data);
440 } else {
441 // This is equivalent to the `duplex` operation in the Python implementation, with
442 // `cbefore = cafter = False`
443 self.absorb(data);
444 };
445 }
446
447 // This is separately defined because it's the only method that can return a `Result`. See docs
448 // for recv_mac and meta_recv_mac.
449 fn generalized_recv_mac<const N: usize>(
450 &mut self,
451 mac: &[u8; N],
452 is_meta: bool,
453 ) -> Result<(), AuthError> {
454 // Make a temp buffer for the MAC. This is because operate() mutates the buffer
455 let mut mac_copy = *mac;
456
457 // These are the (meta_)recv_mac flags
458 let flags = if is_meta {
459 OpFlags::I | OpFlags::C | OpFlags::T | OpFlags::M
460 } else {
461 OpFlags::I | OpFlags::C | OpFlags::T
462 };
463 // recv_mac can never be streamed
464 self.operate(flags, &mut mac_copy, /* more */ false);
465
466 // Constant-time MAC check. This accumulates the truth values of byte == 0
467 let mut all_zero = subtle::Choice::from(1u8);
468 for b in mac_copy {
469 all_zero &= b.ct_eq(&0u8);
470 }
471
472 // Zeroize the temp buffer
473 mac_copy.zeroize();
474
475 // If the buffer isn't all zeros, that's an invalid MAC
476 if !bool::from(all_zero) {
477 Err(AuthError)
478 } else {
479 Ok(())
480 }
481 }
482
483 /// Attempts to authenticate the current state against the given MAC. On failure, it returns an
484 /// `AuthError`.
485 pub fn recv_mac<const N: usize>(&mut self, mac: &[u8; N]) -> Result<(), AuthError> {
486 self.generalized_recv_mac(mac, /* is_meta */ false)
487 }
488
489 /// Attempts to authenticate the current state against the given MAC. On failure, it returns an
490 /// `AuthError`.
491 pub fn meta_recv_mac<const N: usize>(&mut self, mac: &[u8; N]) -> Result<(), AuthError> {
492 self.generalized_recv_mac(mac, /* is_meta */ true)
493 }
494
495 // This is separately defined because it's the only method that takes an integer and mutates
496 // its input
497 fn generalized_ratchet(&mut self, num_bytes_to_zero: usize, more: bool, is_meta: bool) {
498 // These are the (meta_)ratchet flags
499 let flags = if is_meta {
500 OpFlags::C | OpFlags::M
501 } else {
502 OpFlags::C
503 };
504
505 // We don't make an `operate` call, since this is a super special case. That means we have
506 // to validate the flags and make the `begin_op` call manually.
507 self.validate_streaming(flags, more);
508 if !more {
509 self.begin_op(flags);
510 }
511
512 self.zero_state(num_bytes_to_zero);
513 }
514
515 /// Ratchets the internal state forward in an irreversible way by zeroing bytes. If
516 /// the security parameter is 128, then `num_bytes_to_zero = 16` is sufficient. If
517 /// it's 256, then `num_bytes_to_zero = 32` is sufficient.
518 ///
519 /// Takes a `usize` argument specifying the number of bytes of public state to zero. If the
520 /// size exceeds `self.rate`, Keccak-f will be called before more bytes are zeroed.
521 pub fn ratchet(&mut self, num_bytes_to_zero: usize, more: bool) {
522 self.generalized_ratchet(num_bytes_to_zero, more, /* is_meta */ false)
523 }
524
525 /// Ratchets the internal state forward in an irreversible way by zeroing bytes.
526 ///
527 /// Takes a `usize` argument specifying the number of bytes of public state to zero. If the
528 /// size exceeds `self.rate`, Keccak-f will be called before more bytes are zeroed.
529 pub fn meta_ratchet(&mut self, num_bytes_to_zero: usize, more: bool) {
530 self.generalized_ratchet(num_bytes_to_zero, more, /* is_meta */ true)
531 }
532
533 //
534 // These operations mutate their inputs
535 //
536
537 def_op_mut!(
538 send_enc,
539 meta_send_enc,
540 OpFlags::A | OpFlags::C | OpFlags::T,
541 "Sends an encrypted message."
542 );
543 def_op_mut!(
544 recv_enc,
545 meta_recv_enc,
546 OpFlags::I | OpFlags::A | OpFlags::C | OpFlags::T,
547 "Receives an encrypted message."
548 );
549 def_op_mut!(
550 send_mac,
551 meta_send_mac,
552 OpFlags::C | OpFlags::T,
553 "Sends a MAC of the internal state. \
554 The output is independent of the initial contents of the input buffer."
555 );
556 def_op_mut!(
557 prf,
558 meta_prf,
559 OpFlags::I | OpFlags::A | OpFlags::C,
560 "Extracts pseudorandom data as a function of the internal state. \
561 The output is independent of the initial contents of the input buffer."
562 );
563
564 //
565 // These operations do not mutate their inputs
566 //
567
568 def_op_no_mut!(
569 send_clr,
570 meta_send_clr,
571 OpFlags::A | OpFlags::T,
572 "Sends a plaintext message."
573 );
574 def_op_no_mut!(
575 recv_clr,
576 meta_recv_clr,
577 OpFlags::I | OpFlags::A | OpFlags::T,
578 "Receives a plaintext message."
579 );
580 def_op_no_mut!(
581 ad,
582 meta_ad,
583 OpFlags::A,
584 "Mixes associated data into the internal state."
585 );
586 def_op_no_mut!(
587 key,
588 meta_key,
589 OpFlags::A | OpFlags::C,
590 "Sets a symmetric cipher key."
591 );
592}
593
594#[test]
595fn version_str() {
596 let s128 = Strobe::new(b"version_str test", SecParam::B128);
597 assert_eq!(&s128.version_str(), b"Strobe-Keccak-128/1600-v1.0.2");
598
599 let s256 = Strobe::new(b"version_str test", SecParam::B256);
600 assert_eq!(&s256.version_str(), b"Strobe-Keccak-256/1600-v1.0.2");
601}