Skip to main content

dryoc/
auth.rs

1//! # Secret-key message authentication
2//!
3//! [`Auth`] implements libsodium's secret-key authentication, based on
4//! HMAC-SHA512-256.
5//!
6//! Use [`Auth`] to authenticate messages when:
7//!
8//! * you want to authenticate arbitrary messages
9//! * you have a pre-shared key between both parties
10//! * (optionally) you want to share the authentication tag publicly
11//!
12//! The same HMAC key can authenticate multiple messages. Keep the key secret,
13//! and use separate keys when protocols require domain separation.
14//!
15//! # Rustaceous API example, single-part interface
16//!
17//! ```
18//! use dryoc::auth::*;
19//! use dryoc::types::*;
20//!
21//! // Generate a random key
22//! let key = Key::generate();
23//!
24//! // Compute the MAC in one shot
25//! let mac: Mac = Auth::compute(&key, b"Data to authenticate");
26//!
27//! // Verify the MAC
28//! Auth::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::auth::*;
35//! use dryoc::types::*;
36//!
37//! // Generate a random key
38//! let key = Key::generate();
39//!
40//! // Initialize the MAC
41//! let mut mac = Auth::new(&key);
42//! mac.update(b"Multi-part");
43//! mac.update(b"data");
44//! let mac: Mac = mac.finalize();
45//!
46//! // Verify the MAC
47//! let mut verify_mac = Auth::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 invalid data fails
53//! let mut verify_mac = Auth::new(&key);
54//! verify_mac.update(b"Multi-part");
55//! verify_mac.update(b"bad data");
56//! verify_mac
57//!     .verify(&mac)
58//!     .expect_err("verify should have failed");
59//! ```
60
61#[cfg(feature = "alloc")]
62use alloc::vec::Vec;
63
64use crate::classic::crypto_auth::{
65    AuthState, crypto_auth, crypto_auth_final, crypto_auth_init, crypto_auth_update,
66    crypto_auth_verify,
67};
68use crate::constants::{CRYPTO_AUTH_BYTES, CRYPTO_AUTH_KEYBYTES};
69use crate::error::Error;
70use crate::types::*;
71use crate::utils::verify_ct;
72
73/// Stack-allocated key for secret-key authentication.
74pub type Key = StackByteArray<CRYPTO_AUTH_KEYBYTES>;
75/// Stack-allocated message authentication code for secret-key authentication.
76pub type Mac = StackByteArray<CRYPTO_AUTH_BYTES>;
77
78#[cfg(any(
79    all(feature = "protected", any(unix, windows)),
80    all(doc, not(doctest), feature = "std")
81))]
82#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
83pub mod protected {
84    //! # Protected memory type aliases for [`Auth`]
85    //!
86    //! Protected-memory aliases for authentication keys and codes.
87    //!
88    //! ## Example
89    //!
90    //! ```
91    //! use dryoc::auth::Auth;
92    //! use dryoc::auth::protected::*;
93    //!
94    //! // Create a randomly generated key, lock it, protect it as read-only
95    //! let key = Key::generate_readonly_locked().expect("generate failed");
96    //! let input =
97    //!     HeapBytes::from_slice_into_readonly_locked(b"super secret input").expect("input failed");
98    //! // Compute the message authentication code
99    //! let mac: Locked<Mac> = Auth::compute(&key, &input);
100    //! ```
101    use super::*;
102    pub use crate::protected::*;
103
104    /// Heap-allocated, page-aligned secret key for authentication with
105    /// protected memory.
106    pub type Key = HeapByteArray<CRYPTO_AUTH_KEYBYTES>;
107    /// Heap-allocated, page-aligned authentication code for use with protected
108    /// memory.
109    pub type Mac = HeapByteArray<CRYPTO_AUTH_BYTES>;
110}
111
112/// Secret-key authentication implementation based on libsodium's
113/// HMAC-SHA512-256 `crypto_auth_*` functions.
114pub struct Auth {
115    state: AuthState,
116}
117
118impl Auth {
119    /// Computes the message authentication code for `input` using `key`.
120    #[must_use]
121    pub fn compute<
122        Output: NewByteArray<CRYPTO_AUTH_BYTES>,
123        Key: ByteArray<CRYPTO_AUTH_KEYBYTES>,
124        Input: Bytes + ?Sized,
125    >(
126        key: &Key,
127        input: &Input,
128    ) -> Output {
129        let mut output = Output::new_byte_array();
130        crypto_auth(output.as_mut_array(), input.as_slice(), key.as_array());
131        output
132    }
133
134    /// Computes the message authentication code and returns it as a [`Vec`].
135    ///
136    /// This is a convenience wrapper around [`Auth::compute`].
137    #[cfg(feature = "alloc")]
138    #[must_use]
139    pub fn compute_to_vec<Key: ByteArray<CRYPTO_AUTH_KEYBYTES>, Input: Bytes + ?Sized>(
140        key: &Key,
141        input: &Input,
142    ) -> Vec<u8> {
143        Self::compute::<Mac, _, _>(key, input).to_vec()
144    }
145
146    /// Verifies that `other_mac` authenticates `input` under `key`.
147    ///
148    /// # Errors
149    ///
150    /// Returns an error if `other_mac` does not match the authentication code
151    /// computed from `key` and `input`.
152    pub fn compute_and_verify<
153        OtherMac: ByteArray<CRYPTO_AUTH_BYTES>,
154        Key: ByteArray<CRYPTO_AUTH_KEYBYTES>,
155        Input: Bytes + ?Sized,
156    >(
157        other_mac: &OtherMac,
158        key: &Key,
159        input: &Input,
160    ) -> Result<(), Error> {
161        crypto_auth_verify(other_mac.as_array(), input.as_slice(), key.as_array())
162    }
163
164    /// Returns a new incremental authenticator for `key`.
165    #[must_use]
166    pub fn new<Key: ByteArray<CRYPTO_AUTH_KEYBYTES>>(key: &Key) -> Self {
167        Self {
168            state: crypto_auth_init(key.as_array()),
169        }
170    }
171
172    /// Updates the secret-key authenticator at `self` with `input`.
173    pub fn update<Input: Bytes + ?Sized>(&mut self, input: &Input) {
174        crypto_auth_update(&mut self.state, input.as_slice())
175    }
176
177    /// Finalizes this secret-key authenticator, returning the message
178    /// authentication code.
179    #[must_use]
180    pub fn finalize<Output: NewByteArray<CRYPTO_AUTH_BYTES>>(self) -> Output {
181        let mut output = Output::new_byte_array();
182        crypto_auth_final(self.state, output.as_mut_array());
183        output
184    }
185
186    /// Finalizes this secret-key authenticator, returning the message
187    /// authentication code as a [`Vec`]. Convenience wrapper around
188    /// [`Auth::finalize`].
189    #[cfg(feature = "alloc")]
190    #[must_use]
191    pub fn finalize_to_vec(self) -> Vec<u8> {
192        self.finalize::<Mac>().to_vec()
193    }
194
195    /// Finalizes this authenticator, and verifies that the computed code
196    /// matches `other_mac` using a constant-time comparison.
197    ///
198    /// # Errors
199    ///
200    /// Returns an error if `other_mac` does not match the authentication code
201    /// computed from the data passed to [`Auth::update`].
202    pub fn verify<OtherMac: ByteArray<CRYPTO_AUTH_BYTES>>(
203        self,
204        other_mac: &OtherMac,
205    ) -> Result<(), Error> {
206        let computed_mac: Mac = self.finalize();
207
208        verify_ct(other_mac.as_array(), computed_mac.as_array())
209    }
210}
211
212#[cfg(all(test, feature = "alloc"))]
213mod tests {
214    use super::*;
215    // RFC 4231 cases 1-4 for HMAC-SHA-512, truncated to the 32 bytes
216    // `crypto_auth` (HMAC-SHA-512-256) emits. Keys shorter than 32 bytes are
217    // zero-padded, which HMAC defines to yield the same tag.
218    use crate::classic::crypto_auth_hmac_impl::test_util::RFC4231_PADDABLE_KEYS as CASES;
219
220    fn padded_key(key: &[u8]) -> Key {
221        let mut padded = Key::default();
222        padded[..key.len()].copy_from_slice(key);
223        padded
224    }
225
226    #[test]
227    fn rfc4231_vectors_through_single_and_multi_part_interfaces() {
228        for case in CASES {
229            let (key, message, expected) = (padded_key(case.key), case.data, case.sha512256());
230
231            assert_eq!(Auth::compute_to_vec(&key, &message), expected);
232            let fixed: Mac = Auth::compute(&key, &message);
233            assert_eq!(fixed.as_slice(), expected.as_slice());
234            Auth::compute_and_verify(&fixed, &key, &message).expect("verify failed");
235
236            let split = message.len() / 2;
237            let mut auth = Auth::new(&key);
238            auth.update(&message[..split]);
239            auth.update(&[][..]);
240            auth.update(&message[split..]);
241            assert_eq!(auth.finalize_to_vec(), expected);
242
243            let mut verifier = Auth::new(&key);
244            verifier.update(&message);
245            verifier.verify(&fixed).expect("incremental verify failed");
246
247            for index in [0, CRYPTO_AUTH_BYTES - 1] {
248                let mut flipped = fixed.clone();
249                flipped[index] ^= 1;
250                assert!(matches!(
251                    Auth::compute_and_verify(&flipped, &key, &message),
252                    Err(Error::AuthenticationFailed)
253                ));
254                let mut verifier = Auth::new(&key);
255                verifier.update(&message);
256                assert!(matches!(
257                    verifier.verify(&flipped),
258                    Err(Error::AuthenticationFailed)
259                ));
260            }
261
262            let mut wrong_key = key.clone();
263            wrong_key[CRYPTO_AUTH_KEYBYTES - 1] ^= 1;
264            assert!(matches!(
265                Auth::compute_and_verify(&fixed, &wrong_key, &message),
266                Err(Error::AuthenticationFailed)
267            ));
268            let mut verifier = Auth::new(&key);
269            verifier.update(&message[..message.len() - 1]);
270            assert!(matches!(
271                verifier.verify(&fixed),
272                Err(Error::AuthenticationFailed)
273            ));
274        }
275    }
276
277    #[test]
278    fn rustaceous_and_classic_macs_verify_each_other() {
279        for case in CASES {
280            let (key, message) = (padded_key(case.key), case.data);
281            let mac = Auth::compute_to_vec(&key, &message);
282            crypto_auth_verify(
283                mac.as_slice().try_into().expect("MAC length"),
284                message,
285                key.as_array(),
286            )
287            .expect("classic verify");
288
289            let mut classic = [0u8; CRYPTO_AUTH_BYTES];
290            crypto_auth(&mut classic, message, key.as_array());
291            Auth::compute_and_verify(&classic, &key, &message).expect("rustaceous verify");
292            let mut verifier = Auth::new(&key);
293            verifier.update(&message);
294            verifier.verify(&classic).expect("incremental verify");
295        }
296    }
297
298    #[cfg(all(feature = "protected", any(unix, windows)))]
299    #[test]
300    fn locked_key_and_input_produce_the_same_mac() {
301        use crate::auth::protected::*;
302
303        for case in CASES {
304            let (key, message, expected) = (case.key, case.data, case.sha512256());
305            let key = protected::Key::from_slice_into_readonly_locked(padded_key(key).as_slice())
306                .expect("lock key");
307            let input = HeapBytes::from_slice_into_readonly_locked(message).expect("lock input");
308
309            let mac: Locked<protected::Mac> = Auth::compute(&key, &input);
310            assert_eq!(mac.as_slice(), expected.as_slice());
311            Auth::compute_and_verify(&mac, &key, &input).expect("verify failed");
312            let mut verifier = Auth::new(&key);
313            verifier.update(&input);
314            verifier.verify(&mac).expect("incremental verify failed");
315        }
316    }
317
318    #[cfg(dryoc_native_tests)]
319    #[test]
320    fn rfc4231_keys_match_libsodium() {
321        use crate::native_test_util::auth_hmacsha512256;
322
323        for case in CASES {
324            let (key, message) = (padded_key(case.key), case.data);
325            let so_tag = auth_hmacsha512256(message, key.as_slice());
326            assert_eq!(Auth::compute_to_vec(&key, &message), so_tag);
327            Auth::compute_and_verify(&so_tag, &key, &message).expect("verify sodium tag");
328        }
329    }
330}