Skip to main content

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}