Skip to main content

dryoc/
onetimeauth.rs

1//! # One-time authentication
2//!
3//! [`OnetimeAuth`] implements libsodium's one-time authentication, based on the
4//! Poly1305 message authentication code.
5//!
6//! Use [`OnetimeAuth`] to authenticate messages when:
7//!
8//! * you need to authenticate a message with a one-time Poly1305 key
9//! * your protocol derives a unique key for every distinct message
10//!
11//! Never use the same key to authenticate two different messages. Poly1305 key
12//! reuse can let an attacker forge authentication codes. Reusing the key to
13//! verify the authentication code for the same message is safe.
14//!
15//! # Rustaceous API example, single-part interface
16//!
17//! ```
18//! use dryoc::onetimeauth::*;
19//! use dryoc::types::*;
20//!
21//! // Generate a random key
22//! let key = Key::generate();
23//!
24//! // Compute the MAC. Use the key only for this message.
25//! let mac: Mac = OnetimeAuth::compute(&key, b"Data to authenticate");
26//!
27//! // Verify the MAC
28//! OnetimeAuth::compute_and_verify(&mac, &key, b"Data to authenticate").expect("verify failed");
29//! ```
30//!
31//! # Rustaceous API example, incremental interface
32//!
33//! ```
34//! use dryoc::onetimeauth::*;
35//! use dryoc::types::*;
36//!
37//! // Generate a random key
38//! let key = Key::generate();
39//!
40//! // Initialize the MAC. Use the key only for this message.
41//! let mut mac = OnetimeAuth::new(&key);
42//! mac.update(b"Multi-part");
43//! mac.update(b"data");
44//! let mac: Mac = mac.finalize();
45//!
46//! // Verify the MAC for the same message
47//! let mut verify_mac = OnetimeAuth::new(&key);
48//! verify_mac.update(b"Multi-part");
49//! verify_mac.update(b"data");
50//! verify_mac.verify(&mac).expect("verify failed");
51//!
52//! // Check that a modified MAC fails for the same message
53//! let mut modified_mac = mac.clone();
54//! modified_mac[0] ^= 1;
55//! let mut verify_mac = OnetimeAuth::new(&key);
56//! verify_mac.update(b"Multi-part");
57//! verify_mac.update(b"data");
58//! verify_mac
59//!     .verify(&modified_mac)
60//!     .expect_err("verify should have failed");
61//! ```
62
63#[cfg(feature = "alloc")]
64use alloc::vec::Vec;
65
66use crate::classic::crypto_onetimeauth::{
67    OnetimeauthState, crypto_onetimeauth, crypto_onetimeauth_final, crypto_onetimeauth_init,
68    crypto_onetimeauth_update, crypto_onetimeauth_verify,
69};
70use crate::constants::{CRYPTO_ONETIMEAUTH_BYTES, CRYPTO_ONETIMEAUTH_KEYBYTES};
71use crate::error::Error;
72use crate::types::*;
73use crate::utils::verify_ct;
74
75/// Stack-allocated key for one-time authentication.
76pub type Key = StackByteArray<CRYPTO_ONETIMEAUTH_KEYBYTES>;
77/// Stack-allocated message authentication code for one-time authentication.
78pub type Mac = StackByteArray<CRYPTO_ONETIMEAUTH_BYTES>;
79
80#[cfg(any(
81    all(feature = "protected", any(unix, windows)),
82    all(doc, not(doctest), feature = "std")
83))]
84#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
85pub mod protected {
86    //! # Protected memory type aliases for [`OnetimeAuth`]
87    //!
88    //! Protected-memory aliases for one-time authentication keys and codes.
89    //!
90    //! ## Example
91    //!
92    //! ```
93    //! use dryoc::onetimeauth::OnetimeAuth;
94    //! use dryoc::onetimeauth::protected::*;
95    //!
96    //! // Create a randomly generated key, lock it, protect it as read-only
97    //! let key = Key::generate_readonly_locked().expect("generate failed");
98    //! let input =
99    //!     HeapBytes::from_slice_into_readonly_locked(b"super secret input").expect("input failed");
100    //! // Compute the message authentication code
101    //! let mac: Locked<Mac> = OnetimeAuth::compute(&key, &input);
102    //! ```
103    use super::*;
104    pub use crate::protected::*;
105
106    /// Heap-allocated, page-aligned key for one-time authentication with
107    /// protected memory.
108    pub type Key = HeapByteArray<CRYPTO_ONETIMEAUTH_KEYBYTES>;
109    /// Heap-allocated, page-aligned one-time authentication code for use with
110    /// protected memory.
111    pub type Mac = HeapByteArray<CRYPTO_ONETIMEAUTH_BYTES>;
112}
113
114/// One-time authentication implementation based on Poly1305, compatible with
115/// libsodium's `crypto_onetimeauth_*` functions.
116pub struct OnetimeAuth {
117    state: OnetimeauthState,
118}
119
120impl OnetimeAuth {
121    /// Computes the message authentication code for `input` using `key`.
122    ///
123    /// The key must not be used to authenticate any other message. It may be
124    /// retained to verify the authentication code for this same message.
125    #[must_use]
126    pub fn compute<
127        Output: NewByteArray<CRYPTO_ONETIMEAUTH_BYTES>,
128        Key: ByteArray<CRYPTO_ONETIMEAUTH_KEYBYTES>,
129        Input: Bytes + ?Sized,
130    >(
131        key: &Key,
132        input: &Input,
133    ) -> Output {
134        let mut output = Output::new_byte_array();
135        crypto_onetimeauth(output.as_mut_array(), input.as_slice(), key.as_array());
136        output
137    }
138
139    /// Computes the message authentication code and returns it as a [`Vec`].
140    ///
141    /// This is a convenience wrapper around [`OnetimeAuth::compute`].
142    #[cfg(feature = "alloc")]
143    #[must_use]
144    pub fn compute_to_vec<Key: ByteArray<CRYPTO_ONETIMEAUTH_KEYBYTES>, Input: Bytes + ?Sized>(
145        key: &Key,
146        input: &Input,
147    ) -> Vec<u8> {
148        Self::compute::<Mac, _, _>(key, input).to_vec()
149    }
150
151    /// Verifies that `other_mac` authenticates `input` under `key`.
152    ///
153    /// # Errors
154    ///
155    /// Returns an error if `other_mac` does not match the authentication code
156    /// computed from `key` and `input`.
157    pub fn compute_and_verify<
158        OtherMac: ByteArray<CRYPTO_ONETIMEAUTH_BYTES>,
159        Key: ByteArray<CRYPTO_ONETIMEAUTH_KEYBYTES>,
160        Input: Bytes + ?Sized,
161    >(
162        other_mac: &OtherMac,
163        key: &Key,
164        input: &Input,
165    ) -> Result<(), Error> {
166        crypto_onetimeauth_verify(other_mac.as_array(), input.as_slice(), key.as_array())
167    }
168
169    /// Returns a new incremental one-time authenticator for `key`.
170    ///
171    /// The key must not be used to authenticate any other message. It may be
172    /// retained to verify the authentication code for this same message.
173    #[must_use]
174    pub fn new<Key: ByteArray<CRYPTO_ONETIMEAUTH_KEYBYTES>>(key: &Key) -> Self {
175        Self {
176            state: crypto_onetimeauth_init(key.as_array()),
177        }
178    }
179
180    /// Updates the one-time authenticator at `self` with `input`.
181    pub fn update<Input: Bytes + ?Sized>(&mut self, input: &Input) {
182        crypto_onetimeauth_update(&mut self.state, input.as_slice())
183    }
184
185    /// Finalizes this one-time authenticator, returning the message
186    /// authentication code.
187    #[must_use]
188    pub fn finalize<Output: NewByteArray<CRYPTO_ONETIMEAUTH_BYTES>>(self) -> Output {
189        let mut output = Output::new_byte_array();
190        crypto_onetimeauth_final(self.state, output.as_mut_array());
191        output
192    }
193
194    /// Finalizes this one-time authenticator, returning the message
195    /// authentication code as a [`Vec`]. Convenience wrapper around
196    /// [`OnetimeAuth::finalize`].
197    #[cfg(feature = "alloc")]
198    #[must_use]
199    pub fn finalize_to_vec(self) -> Vec<u8> {
200        self.finalize::<Mac>().to_vec()
201    }
202
203    /// Finalizes this authenticator, and verifies that the computed code
204    /// matches `other_mac` using a constant-time comparison.
205    ///
206    /// # Errors
207    ///
208    /// Returns an error if `other_mac` does not match the authentication code
209    /// computed from the data passed to [`OnetimeAuth::update`].
210    pub fn verify<OtherMac: ByteArray<CRYPTO_ONETIMEAUTH_BYTES>>(
211        self,
212        other_mac: &OtherMac,
213    ) -> Result<(), Error> {
214        let computed_mac: Mac = self.finalize();
215
216        verify_ct(other_mac.as_array(), computed_mac.as_array())
217    }
218}
219
220#[cfg(all(test, feature = "alloc"))]
221mod tests {
222    use super::*;
223
224    /// RFC 8439 section 2.5.2 Poly1305 vector.
225    const KEY: &str = "85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b";
226    const MESSAGE: &[u8] = b"Cryptographic Forum Research Group";
227    const TAG: &str = "a8061dc1305136c6c22b8baf0c0127a9";
228
229    fn vector() -> (Key, Vec<u8>) {
230        (
231            Key::try_from(hex::decode(KEY).expect("hex").as_slice()).expect("key"),
232            hex::decode(TAG).expect("hex"),
233        )
234    }
235
236    #[test]
237    fn rfc8439_vector_through_single_and_multi_part_interfaces() {
238        let (key, expected) = vector();
239
240        assert_eq!(OnetimeAuth::compute_to_vec(&key, &MESSAGE), expected);
241        let fixed: Mac = OnetimeAuth::compute(&key, &MESSAGE);
242        assert_eq!(fixed.as_slice(), expected.as_slice());
243        OnetimeAuth::compute_and_verify(&fixed, &key, &MESSAGE).expect("verify failed");
244
245        // Splits at and around the 16-byte Poly1305 block boundary, plus empty
246        // chunks, must all reproduce the one-shot tag.
247        for parts in [
248            vec![MESSAGE],
249            vec![&MESSAGE[..16], &MESSAGE[16..]],
250            vec![&MESSAGE[..15], &MESSAGE[15..17], &MESSAGE[17..]],
251            vec![
252                &[][..],
253                &MESSAGE[..1],
254                &[][..],
255                &MESSAGE[1..33],
256                &MESSAGE[33..],
257                &[][..],
258            ],
259        ] {
260            let mut auth = OnetimeAuth::new(&key);
261            for part in &parts {
262                auth.update(part);
263            }
264            assert_eq!(auth.finalize_to_vec(), expected);
265
266            let mut verifier = OnetimeAuth::new(&key);
267            for part in &parts {
268                verifier.update(part);
269            }
270            verifier.verify(&fixed).expect("incremental verify failed");
271        }
272
273        for index in [0, CRYPTO_ONETIMEAUTH_BYTES - 1] {
274            let mut flipped = fixed.clone();
275            flipped[index] ^= 1;
276            assert!(matches!(
277                OnetimeAuth::compute_and_verify(&flipped, &key, &MESSAGE),
278                Err(Error::AuthenticationFailed)
279            ));
280            let mut verifier = OnetimeAuth::new(&key);
281            verifier.update(&MESSAGE);
282            assert!(matches!(
283                verifier.verify(&flipped),
284                Err(Error::AuthenticationFailed)
285            ));
286        }
287
288        let mut wrong_key = key.clone();
289        wrong_key[CRYPTO_ONETIMEAUTH_KEYBYTES - 1] ^= 1;
290        assert!(matches!(
291            OnetimeAuth::compute_and_verify(&fixed, &wrong_key, &MESSAGE),
292            Err(Error::AuthenticationFailed)
293        ));
294        let mut verifier = OnetimeAuth::new(&key);
295        verifier.update(&MESSAGE[..MESSAGE.len() - 1]);
296        assert!(matches!(
297            verifier.verify(&fixed),
298            Err(Error::AuthenticationFailed)
299        ));
300    }
301
302    #[test]
303    fn rustaceous_and_classic_macs_verify_each_other() {
304        let (key, _) = vector();
305        for len in [0, 1, 15, 16, 17, 31, 32, MESSAGE.len()] {
306            let message = &MESSAGE[..len];
307            let mac = OnetimeAuth::compute_to_vec(&key, &message);
308            crypto_onetimeauth_verify(
309                mac.as_slice().try_into().expect("MAC length"),
310                message,
311                key.as_array(),
312            )
313            .expect("classic verify");
314
315            let mut classic = [0u8; CRYPTO_ONETIMEAUTH_BYTES];
316            crypto_onetimeauth(&mut classic, message, key.as_array());
317            OnetimeAuth::compute_and_verify(&classic, &key, &message).expect("rustaceous verify");
318            let mut verifier = OnetimeAuth::new(&key);
319            verifier.update(&message);
320            verifier.verify(&classic).expect("incremental verify");
321        }
322    }
323
324    #[cfg(all(feature = "protected", any(unix, windows)))]
325    #[test]
326    fn locked_key_and_input_produce_the_rfc8439_tag() {
327        use crate::onetimeauth::protected::*;
328
329        let (key, expected) = vector();
330        let key =
331            protected::Key::from_slice_into_readonly_locked(key.as_slice()).expect("lock key");
332        let input = HeapBytes::from_slice_into_readonly_locked(MESSAGE).expect("lock input");
333
334        let mac: Locked<protected::Mac> = OnetimeAuth::compute(&key, &input);
335        assert_eq!(mac.as_slice(), expected.as_slice());
336        OnetimeAuth::compute_and_verify(&mac, &key, &input).expect("verify failed");
337        let mut verifier = OnetimeAuth::new(&key);
338        verifier.update(&input);
339        verifier.verify(&mac).expect("incremental verify failed");
340    }
341
342    #[cfg(dryoc_native_tests)]
343    #[test]
344    fn rfc8439_vector_matches_libsodium() {
345        use crate::native_test_util::onetimeauth_poly1305;
346
347        let (key, expected) = vector();
348        let so_tag = onetimeauth_poly1305(MESSAGE, key.as_slice());
349        assert_eq!(so_tag.as_slice(), expected.as_slice());
350        OnetimeAuth::compute_and_verify(&so_tag, &key, &MESSAGE).expect("verify sodium tag");
351    }
352}