Skip to main content

dryoc/classic/
crypto_onetimeauth.rs

1//! # One-time authentication
2//!
3//! Implements one-time authentication using the Poly1305 algorithm, compatible
4//! with libsodium's `crypto_onetimeauth_*` functions.
5//!
6//! A Poly1305 key must be used for only one message. Reusing a key for
7//! different messages can allow forgeries. This primitive authenticates data
8//! but does not encrypt it.
9//!
10//! # Classic API single-part example
11//!
12//! ```
13//! use base64::Engine as _;
14//! use base64::engine::general_purpose;
15//! use dryoc::classic::crypto_onetimeauth::{
16//!     Mac, crypto_onetimeauth, crypto_onetimeauth_keygen, crypto_onetimeauth_verify,
17//! };
18//!
19//! let key = crypto_onetimeauth_keygen();
20//! let mut mac = Mac::default();
21//!
22//! crypto_onetimeauth(&mut mac, b"Data to authenticate", &key);
23//!
24//! // This should be valid
25//! crypto_onetimeauth_verify(&mac, b"Data to authenticate", &key).expect("failed to authenticate");
26//!
27//! // This should not be valid
28//! crypto_onetimeauth_verify(&mac, b"Invalid data", &key).expect_err("should not authenticate");
29//! ```
30//!
31//! # Classic API multi-part example
32//!
33//! ```
34//! use base64::Engine as _;
35//! use base64::engine::general_purpose;
36//! use dryoc::classic::crypto_onetimeauth::{
37//!     Mac, crypto_onetimeauth_final, crypto_onetimeauth_init, crypto_onetimeauth_keygen,
38//!     crypto_onetimeauth_update, crypto_onetimeauth_verify,
39//! };
40//!
41//! let key = crypto_onetimeauth_keygen();
42//! let mut mac = Mac::default();
43//!
44//! let mut state = crypto_onetimeauth_init(&key);
45//! crypto_onetimeauth_update(&mut state, b"Multi-part");
46//! crypto_onetimeauth_update(&mut state, b"data");
47//! crypto_onetimeauth_final(state, &mut mac);
48//!
49//! // This should be valid
50//! crypto_onetimeauth_verify(&mac, b"Multi-partdata", &key).expect("failed to authenticate");
51//!
52//! // This should not be valid
53//! crypto_onetimeauth_verify(&mac, b"Invalid data", &key).expect_err("should not authenticate");
54//! ```
55use crate::constants::{
56    CRYPTO_ONETIMEAUTH_BYTES, CRYPTO_ONETIMEAUTH_KEYBYTES, CRYPTO_ONETIMEAUTH_POLY1305_BYTES,
57    CRYPTO_ONETIMEAUTH_POLY1305_KEYBYTES,
58};
59use crate::error::Error;
60use crate::poly1305::Poly1305;
61use crate::types::*;
62use crate::utils::{verify_ct, zeroize_bytes};
63struct OnetimeauthPoly1305State {
64    mac: Poly1305,
65}
66
67/// Key type for use with one-time authentication.
68pub type Key = [u8; CRYPTO_ONETIMEAUTH_POLY1305_KEYBYTES];
69/// Message authentication code type for use with one-time authentication.
70pub type Mac = [u8; CRYPTO_ONETIMEAUTH_POLY1305_BYTES];
71
72fn crypto_onetimeauth_poly1305(output: &mut Mac, message: &[u8], key: &Key) {
73    let mut poly1305 = Poly1305::new(key);
74    poly1305.update(message);
75    poly1305.finalize(output)
76}
77fn crypto_onetimeauth_poly1305_verify(mac: &Mac, input: &[u8], key: &Key) -> Result<(), Error> {
78    let mut poly1305 = Poly1305::new(key);
79    poly1305.update(input);
80    // The computed tag is the valid tag for `input`, so wipe it even when
81    // verification fails.
82    let mut computed_mac = Mac::default();
83    poly1305.finalize(&mut computed_mac);
84
85    let verified = verify_ct(mac, &computed_mac);
86    zeroize_bytes(&mut computed_mac);
87    verified
88}
89
90fn crypto_onetimeauth_poly1305_init(key: &Key) -> OnetimeauthPoly1305State {
91    OnetimeauthPoly1305State {
92        mac: Poly1305::new(key),
93    }
94}
95
96fn crypto_onetimeauth_poly1305_update(state: &mut OnetimeauthPoly1305State, input: &[u8]) {
97    state.mac.update(input)
98}
99fn crypto_onetimeauth_poly1305_final(
100    mut state: OnetimeauthPoly1305State,
101    output: &mut [u8; CRYPTO_ONETIMEAUTH_POLY1305_BYTES],
102) {
103    state.mac.finalize(output)
104}
105
106/// Authenticates `message` using `key`, and places the result into
107/// `mac`. `key` should only be used once.
108///
109/// Equivalent to libsodium's `crypto_onetimeauth`.
110pub fn crypto_onetimeauth(mac: &mut Mac, message: &[u8], key: &Key) {
111    crypto_onetimeauth_poly1305(mac, message, key)
112}
113
114/// Verifies that `mac` is the correct authenticator for `message` using `key`.
115/// Returns `Ok(())` if the message authentication code is valid.
116///
117/// Equivalent to libsodium's `crypto_onetimeauth_verify`.
118///
119/// # Errors
120///
121/// Returns an error if `mac` is not valid for `input` under `key`.
122pub fn crypto_onetimeauth_verify(mac: &Mac, input: &[u8], key: &Key) -> Result<(), Error> {
123    crypto_onetimeauth_poly1305_verify(mac, input, key)
124}
125
126/// Internal state for [`crypto_onetimeauth`].
127pub struct OnetimeauthState {
128    state: OnetimeauthPoly1305State,
129}
130
131/// Generates a random key using
132/// [`copy_randombytes`](crate::rng::copy_randombytes), suitable for use with
133/// [`crypto_onetimeauth_init`] and [`crypto_onetimeauth`]. The key should only
134/// be used once.
135///
136/// Equivalent to libsodium's `crypto_onetimeauth_keygen`.
137#[must_use]
138pub fn crypto_onetimeauth_keygen() -> Key {
139    Key::generate()
140}
141
142/// Initializes the incremental Poly1305-based one-time authentication.
143///
144/// Initialize the incremental interface for Poly1305-based one-time
145/// authentication, using `key`. Returns a state struct which is required for
146/// subsequent calls to [`crypto_onetimeauth_update`] and
147/// [`crypto_onetimeauth_final`]. The key should only be used once.
148///
149/// Equivalent to libsodium's `crypto_onetimeauth_init`.
150#[must_use]
151pub fn crypto_onetimeauth_init(key: &[u8; CRYPTO_ONETIMEAUTH_KEYBYTES]) -> OnetimeauthState {
152    OnetimeauthState {
153        state: crypto_onetimeauth_poly1305_init(key),
154    }
155}
156
157/// Updates `state` for the one-time authentication function, based on `input`.
158///
159/// Equivalent to libsodium's `crypto_onetimeauth_update`.
160pub fn crypto_onetimeauth_update(state: &mut OnetimeauthState, input: &[u8]) {
161    crypto_onetimeauth_poly1305_update(&mut state.state, input)
162}
163
164/// Finalizes the message authentication code for `state`, and places the result
165/// into `output`.
166///
167/// Equivalent to libsodium's `crypto_onetimeauth_final`.
168pub fn crypto_onetimeauth_final(
169    state: OnetimeauthState,
170    output: &mut [u8; CRYPTO_ONETIMEAUTH_BYTES],
171) {
172    crypto_onetimeauth_poly1305_final(state.state, output)
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::test_prelude::*;
179
180    /// RFC 8439 section 2.5.2: key, message and tag.
181    const RFC_KEY: Key = [
182        0x85, 0xd6, 0xbe, 0x78, 0x57, 0x55, 0x6d, 0x33, 0x7f, 0x44, 0x52, 0xfe, 0x42, 0xd5, 0x06,
183        0xa8, 0x01, 0x03, 0x80, 0x8a, 0xfb, 0x0d, 0xb2, 0xfd, 0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49,
184        0xf5, 0x1b,
185    ];
186    const RFC_MESSAGE: &[u8] = b"Cryptographic Forum Research Group";
187    const RFC_TAG: Mac = [
188        0xa8, 0x06, 0x1d, 0xc1, 0x30, 0x51, 0x36, 0xc6, 0xc2, 0x2b, 0x8b, 0xaf, 0x0c, 0x01, 0x27,
189        0xa9,
190    ];
191
192    /// Update boundaries inside, at and just past the first and second
193    /// Poly1305 block of the 34-byte RFC message.
194    const SPLITS: [usize; 8] = [0, 1, 15, 16, 17, 31, 32, 33];
195
196    fn incremental(key: &Key, chunks: &[&[u8]]) -> Mac {
197        let mut state = crypto_onetimeauth_init(key);
198        for chunk in chunks {
199            crypto_onetimeauth_update(&mut state, chunk);
200        }
201        let mut mac = Mac::default();
202        crypto_onetimeauth_final(state, &mut mac);
203        mac
204    }
205
206    /// The RFC 8439 vector one-shot, verified, and incrementally split at
207    /// every boundary in [`SPLITS`] (two updates), and at all of them at
208    /// once (nine updates, one of them empty).
209    #[test]
210    fn test_rfc8439_vector_one_shot_and_incremental() {
211        let mut mac = Mac::default();
212        crypto_onetimeauth(&mut mac, RFC_MESSAGE, &RFC_KEY);
213        assert_eq!(mac, RFC_TAG);
214        crypto_onetimeauth_verify(&RFC_TAG, RFC_MESSAGE, &RFC_KEY).expect("verify");
215
216        for split in SPLITS {
217            let (head, rest) = RFC_MESSAGE.split_at(split);
218            assert_eq!(
219                incremental(&RFC_KEY, &[head, rest]),
220                RFC_TAG,
221                "split {split}"
222            );
223        }
224
225        let mut chunks = Vec::new();
226        let mut cuts = SPLITS.to_vec();
227        cuts.push(RFC_MESSAGE.len());
228        for window in cuts.windows(2) {
229            chunks.push(&RFC_MESSAGE[window[0]..window[1]]);
230        }
231        assert_eq!(incremental(&RFC_KEY, &[&[][..], RFC_MESSAGE]), RFC_TAG);
232        assert_eq!(incremental(&RFC_KEY, &chunks), RFC_TAG);
233    }
234
235    /// Verification rejects a tag with any single byte altered, a message
236    /// with its last byte altered or truncated, and the wrong key.
237    #[test]
238    fn test_verify_rejects_mutations() {
239        for index in 0..CRYPTO_ONETIMEAUTH_BYTES {
240            let mut mac = RFC_TAG;
241            mac[index] ^= 1;
242            assert!(
243                matches!(
244                    crypto_onetimeauth_verify(&mac, RFC_MESSAGE, &RFC_KEY),
245                    Err(Error::AuthenticationFailed)
246                ),
247                "tag byte {index}"
248            );
249        }
250
251        let mut message = RFC_MESSAGE.to_vec();
252        *message.last_mut().unwrap() ^= 1;
253        assert!(matches!(
254            crypto_onetimeauth_verify(&RFC_TAG, &message, &RFC_KEY),
255            Err(Error::AuthenticationFailed)
256        ));
257        assert!(matches!(
258            crypto_onetimeauth_verify(&RFC_TAG, &RFC_MESSAGE[..RFC_MESSAGE.len() - 1], &RFC_KEY),
259            Err(Error::AuthenticationFailed)
260        ));
261
262        let mut key = RFC_KEY;
263        key[31] ^= 1;
264        assert!(matches!(
265            crypto_onetimeauth_verify(&RFC_TAG, RFC_MESSAGE, &key),
266            Err(Error::AuthenticationFailed)
267        ));
268    }
269
270    /// One-shot, verify and incremental (split at every boundary in
271    /// [`SPLITS`]) against libsodium for deterministic keys and messages of
272    /// every length around the Poly1305 block and past a kilobyte.
273    #[cfg(dryoc_native_tests)]
274    #[test]
275    fn test_matches_libsodium_at_block_boundaries() {
276        use crate::utils::test_util::XorShift64;
277
278        crate::native_test_util::init();
279
280        let mut rng = XorShift64::new(0x0a3e_71c9_5b2d_f804);
281        for len in [0usize, 1, 15, 16, 17, 31, 32, 33, 1023, 1024, 1025] {
282            let key: Key = rng.next_bytes32();
283            let message: Vec<u8> = (0..len.div_ceil(8))
284                .flat_map(|_| rng.next_u64().to_le_bytes())
285                .take(len)
286                .collect();
287            let mut expected = Mac::default();
288            // SAFETY: `expected` is `crypto_onetimeauth_BYTES` long, `message`
289            // is valid for its length and `key` is
290            // `crypto_onetimeauth_KEYBYTES`.
291            let rc = unsafe {
292                libsodium_sys::crypto_onetimeauth(
293                    expected.as_mut_ptr(),
294                    message.as_ptr(),
295                    len as libc::c_ulonglong,
296                    key.as_ptr(),
297                )
298            };
299            assert_eq!(rc, 0);
300
301            let mut mac = Mac::default();
302            crypto_onetimeauth(&mut mac, &message, &key);
303            assert_eq!(mac, expected, "len {len}");
304            crypto_onetimeauth_verify(&expected, &message, &key).expect("verify");
305            for split in SPLITS.into_iter().filter(|&split| split <= len) {
306                let (head, rest) = message.split_at(split);
307                assert_eq!(
308                    incremental(&key, &[head, rest]),
309                    expected,
310                    "len {len}, split {split}"
311                );
312            }
313        }
314    }
315}