// MIT License // Copyright (c) 2020-2023 The orion Developers // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! Message authentication. //! //! # Use case: //! `orion::auth` can be used to ensure message integrity and authenticity by //! using a secret key. //! //! An example of this could be securing APIs by having a user of a given API //! sign their API request and having the API server verify these signed API //! requests. //! //! # About: //! - Uses BLAKE2b-256 in keyed mode. //! //! # Parameters: //! - `secret_key`: Secret key used to authenticate `data`. //! - `data`: Data to be authenticated. //! - `expected`: The expected authentication [`Tag`]. //! //! # Errors: //! An error will be returned if: //! - The calculated [`Tag`] does not match the expected. //! - The [`SecretKey`] supplied is less than 32 bytes or greater than 64 bytes. //! - The expected [`Tag`] is not 32 bytes when verifying. //! //! # Panics: //! A panic will occur if: //! - More than 2*(2^64-1) bytes of data are authenticated. //! //! # Security: //! - The secret key should always be generated using a CSPRNG. //! [`SecretKey::default()`] can be used for //! this; it will generate a [`SecretKey`] of 32 bytes. //! - The required minimum length for a [`SecretKey`] is 32 bytes. //! //! # Example: //! ```rust //! use orion::auth; //! //! // There exists a shared key between the user and API server //! let key = auth::SecretKey::default(); //! //! // User generates message and authentication tag //! let msg = "Some message.".as_bytes(); //! let expected_tag = auth::authenticate(&key, msg)?; //! //! // API server verifies the authenticity of the message with the tag //! assert!(auth::authenticate_verify(&expected_tag, &key, &msg).is_ok()); //! # Ok::<(), orion::errors::UnknownCryptoError>(()) //! ``` #![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] pub use super::hltypes::SecretKey; pub use crate::hazardous::mac::blake2b::Tag; use crate::{ errors::UnknownCryptoError, hazardous::mac::blake2b::{self, Blake2b}, }; /// The Tag size (bytes) to be output by BLAKE2b in keyed mode. const BLAKE2B_TAG_SIZE: usize = 32; /// The minimum `SecretKey` size (bytes) to be used by BLAKE2b in keyed mode. const BLAKE2B_MIN_KEY_SIZE: usize = 32; #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Authenticate a message using BLAKE2b-256 in keyed mode. pub fn authenticate(secret_key: &SecretKey, data: &[u8]) -> Result { if secret_key.len() < BLAKE2B_MIN_KEY_SIZE { return Err(UnknownCryptoError); } let blake2b_secret_key = blake2b::SecretKey::from_slice(secret_key.unprotected_as_bytes())?; let mut state = Blake2b::new(&blake2b_secret_key, BLAKE2B_TAG_SIZE)?; state.update(data)?; state.finalize() } #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Authenticate and verify a message using BLAKE2b-256 in keyed mode. pub fn authenticate_verify( expected: &Tag, secret_key: &SecretKey, data: &[u8], ) -> Result<(), UnknownCryptoError> { if secret_key.len() < BLAKE2B_MIN_KEY_SIZE || expected.len() != BLAKE2B_TAG_SIZE { return Err(UnknownCryptoError); } let key = blake2b::SecretKey::from_slice(secret_key.unprotected_as_bytes())?; Blake2b::verify(expected, &key, BLAKE2B_TAG_SIZE, data) } // Testing public functions in the module. #[cfg(test)] mod public { use super::*; mod test_auth_and_verify { use super::*; #[test] fn test_authenticate_verify_bad_key() { let sec_key_correct = SecretKey::generate(64).unwrap(); let sec_key_false = SecretKey::default(); let msg = "what do ya want for nothing?".as_bytes().to_vec(); let mac_bob = authenticate(&sec_key_correct, &msg).unwrap(); assert!(authenticate_verify(&mac_bob, &sec_key_correct, &msg).is_ok()); assert!(authenticate_verify(&mac_bob, &sec_key_false, &msg).is_err()); } #[test] fn test_authenticate_verify_bad_msg() { let sec_key = SecretKey::generate(64).unwrap(); let msg = "what do ya want for nothing?".as_bytes().to_vec(); let mac_bob = authenticate(&sec_key, &msg).unwrap(); assert!(authenticate_verify(&mac_bob, &sec_key, &msg).is_ok()); assert!(authenticate_verify(&mac_bob, &sec_key, b"bad msg").is_err()); } #[test] fn test_authenticate_key_too_small() { let sec_key = SecretKey::generate(31).unwrap(); let msg = "what do ya want for nothing?".as_bytes().to_vec(); assert!(authenticate(&sec_key, &msg).is_err()); } #[test] fn test_authenticate_verify_key_too_small() { let sec_key = SecretKey::generate(31).unwrap(); let msg = "what do ya want for nothing?".as_bytes().to_vec(); let mac = Tag::from_slice(&[0u8; 32][..]).unwrap(); assert!(authenticate_verify(&mac, &sec_key, &msg).is_err()); } } #[quickcheck] #[cfg(feature = "safe_api")] /// Authentication and verifying that tag with the same parameters /// should always be true. fn prop_authenticate_verify(input: Vec) -> bool { let sk = SecretKey::default(); let tag = authenticate(&sk, &input[..]).unwrap(); authenticate_verify(&tag, &sk, &input[..]).is_ok() } #[quickcheck] #[cfg(feature = "safe_api")] /// Authentication and verifying that tag with a different key should /// never be true. fn prop_verify_fail_diff_key(input: Vec) -> bool { let sk = SecretKey::default(); let sk2 = SecretKey::default(); let tag = authenticate(&sk, &input[..]).unwrap(); authenticate_verify(&tag, &sk2, &input[..]).is_err() } #[quickcheck] #[cfg(feature = "safe_api")] /// Authentication and verifying that tag with different input should /// never be true. fn prop_verify_fail_diff_input(input: Vec) -> bool { let sk = SecretKey::default(); let tag = authenticate(&sk, &input[..]).unwrap(); authenticate_verify(&tag, &sk, b"Completely wrong input").is_err() } use crate::hazardous::hash::blake2::blake2b_core::BLAKE2B_KEYSIZE; #[quickcheck] #[cfg(feature = "safe_api")] /// Verify the bounds of 32..=64 (inclusive) for the `SecretKey` used /// in `authenticate/authenticate_verify`. fn prop_authenticate_key_size(input: Vec) -> bool { let sec_key_res = SecretKey::from_slice(&input); if input.is_empty() || input.len() >= u32::MAX as usize { return sec_key_res.is_err(); } let sec_key = sec_key_res.unwrap(); let msg = "what do ya want for nothing?".as_bytes().to_vec(); let auth_res = authenticate(&sec_key, &msg); if input.len() >= BLAKE2B_MIN_KEY_SIZE && input.len() <= BLAKE2B_KEYSIZE { auth_res.is_ok() } else { auth_res.is_err() } } }