blob: e7957a7b375a3f70984b303d2b9f4eba6c6397d5 [file] [log] [blame]
// MIT License
// Copyright (c) 2020-2022 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<Tag, UnknownCryptoError> {
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<u8>) -> 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<u8>) -> 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<u8>) -> 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<u8>) -> 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()
}
}
}