use zeroize::{Zeroize, ZeroizeOnDrop};
use super::AuthProtocol;
use super::crypto::{CryptoBackend, CryptoResult};
pub const MIN_PASSWORD_LENGTH: usize = 8;
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct MasterKey {
key: Vec<u8>,
#[zeroize(skip)]
protocol: AuthProtocol,
#[zeroize(skip)]
backend: CryptoBackend,
}
impl MasterKey {
pub fn from_password(protocol: AuthProtocol, password: &[u8]) -> CryptoResult<Self> {
if password.len() < MIN_PASSWORD_LENGTH {
return Err(super::CryptoError::PasswordTooShort);
}
Self::from_password_with_backend(protocol, password, CryptoBackend::require_default()?)
}
pub fn from_password_with_backend(
protocol: AuthProtocol,
password: &[u8],
backend: CryptoBackend,
) -> CryptoResult<Self> {
if password.len() < MIN_PASSWORD_LENGTH {
return Err(super::CryptoError::PasswordTooShort);
}
let key = password_to_key_with_backend(backend, protocol, password)?;
Ok(Self {
key,
protocol,
backend,
})
}
pub fn from_str_password(protocol: AuthProtocol, password: &str) -> CryptoResult<Self> {
Self::from_password(protocol, password.as_bytes())
}
pub fn from_bytes(protocol: AuthProtocol, key: impl Into<Vec<u8>>) -> CryptoResult<Self> {
let key = key.into();
if key.len() != protocol.digest_len() {
return Err(super::CryptoError::InvalidKeyLength);
}
Self::from_bytes_with_backend(protocol, key, CryptoBackend::require_default()?)
}
pub fn from_bytes_with_backend(
protocol: AuthProtocol,
key: impl Into<Vec<u8>>,
backend: CryptoBackend,
) -> CryptoResult<Self> {
let key = key.into();
if key.len() != protocol.digest_len() {
return Err(super::CryptoError::InvalidKeyLength);
}
backend.validate_auth_protocol(protocol)?;
Ok(Self {
key,
protocol,
backend,
})
}
pub fn localize(&self, engine_id: &[u8]) -> CryptoResult<LocalizedKey> {
self.backend.validate_auth_protocol(self.protocol)?;
let localized =
localize_key_with_backend(self.backend, self.protocol, &self.key, engine_id)?;
Ok(LocalizedKey {
key: localized,
protocol: self.protocol,
backend: self.backend,
})
}
#[must_use]
pub fn protocol(&self) -> AuthProtocol {
self.protocol
}
#[must_use]
pub fn crypto_backend(&self) -> CryptoBackend {
self.backend
}
pub(crate) fn set_crypto_backend(&mut self, backend: CryptoBackend) {
self.backend = backend;
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.key
}
}
impl std::fmt::Debug for MasterKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MasterKey")
.field("protocol", &self.protocol)
.field("key", &"[REDACTED]")
.finish()
}
}
impl AsRef<[u8]> for MasterKey {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct LocalizedKey {
key: Vec<u8>,
#[zeroize(skip)]
protocol: AuthProtocol,
#[zeroize(skip)]
backend: CryptoBackend,
}
impl LocalizedKey {
pub fn from_password(
protocol: AuthProtocol,
password: &[u8],
engine_id: &[u8],
) -> CryptoResult<Self> {
if password.len() < MIN_PASSWORD_LENGTH {
return Err(super::CryptoError::PasswordTooShort);
}
Self::from_password_with_backend(
protocol,
password,
engine_id,
CryptoBackend::require_default()?,
)
}
pub fn from_password_with_backend(
protocol: AuthProtocol,
password: &[u8],
engine_id: &[u8],
backend: CryptoBackend,
) -> CryptoResult<Self> {
MasterKey::from_password_with_backend(protocol, password, backend)?.localize(engine_id)
}
pub fn from_str_password(
protocol: AuthProtocol,
password: &str,
engine_id: &[u8],
) -> CryptoResult<Self> {
Self::from_password(protocol, password.as_bytes(), engine_id)
}
pub fn from_master_key(master: &MasterKey, engine_id: &[u8]) -> CryptoResult<Self> {
master.localize(engine_id)
}
pub fn from_bytes(protocol: AuthProtocol, key: impl Into<Vec<u8>>) -> CryptoResult<Self> {
let key = key.into();
if key.len() != protocol.digest_len() {
return Err(super::CryptoError::InvalidKeyLength);
}
Self::from_bytes_with_backend(protocol, key, CryptoBackend::require_default()?)
}
pub fn from_bytes_with_backend(
protocol: AuthProtocol,
key: impl Into<Vec<u8>>,
backend: CryptoBackend,
) -> CryptoResult<Self> {
let key = key.into();
if key.len() != protocol.digest_len() {
return Err(super::CryptoError::InvalidKeyLength);
}
backend.validate_auth_protocol(protocol)?;
Ok(Self {
key,
protocol,
backend,
})
}
#[must_use]
pub fn protocol(&self) -> AuthProtocol {
self.protocol
}
#[must_use]
pub fn crypto_backend(&self) -> CryptoBackend {
self.backend
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.key
}
#[must_use]
pub fn mac_len(&self) -> usize {
self.protocol.mac_len()
}
pub fn compute_hmac(&self, data: &[u8]) -> CryptoResult<Vec<u8>> {
compute_hmac(self.backend, self.protocol, &self.key, data)
}
pub fn verify_hmac(&self, data: &[u8], expected: &[u8]) -> CryptoResult<bool> {
let computed = self.compute_hmac(data)?;
if computed.len() != expected.len() {
return Ok(false);
}
let mut result = 0u8;
for (a, b) in computed.iter().zip(expected.iter()) {
result |= a ^ b;
}
Ok(result == 0)
}
}
impl std::fmt::Debug for LocalizedKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LocalizedKey")
.field("protocol", &self.protocol)
.field("key", &"[REDACTED]")
.finish()
}
}
impl AsRef<[u8]> for LocalizedKey {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
#[cfg(all(test, not(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))))]
mod no_backend_tests {
use super::*;
use crate::v3::PrivProtocol;
fn unavailable_master_keys() -> MasterKeys {
MasterKeys {
auth_master: MasterKey {
key: vec![0_u8; AuthProtocol::Sha256.digest_len()],
protocol: AuthProtocol::Sha256,
backend: CryptoBackend::RustCrypto,
},
priv_protocol: None,
priv_master: None,
}
}
#[test]
fn active_auth_key_constructors_reject_unavailable_backend() {
for protocol in [
AuthProtocol::Md5,
AuthProtocol::Sha1,
AuthProtocol::Sha224,
AuthProtocol::Sha256,
AuthProtocol::Sha384,
AuthProtocol::Sha512,
] {
let len = protocol.digest_len();
assert_eq!(
MasterKey::from_bytes(protocol, vec![0_u8; len - 1]).unwrap_err(),
super::super::CryptoError::InvalidKeyLength
);
assert_eq!(
MasterKey::from_bytes(protocol, vec![0_u8; len]).unwrap_err(),
super::super::CryptoError::BackendUnavailable
);
assert_eq!(
LocalizedKey::from_bytes(protocol, vec![0_u8; len - 1]).unwrap_err(),
super::super::CryptoError::InvalidKeyLength
);
assert_eq!(
LocalizedKey::from_bytes(protocol, vec![0_u8; len]).unwrap_err(),
super::super::CryptoError::BackendUnavailable
);
}
assert_eq!(
MasterKey::from_password(AuthProtocol::Sha256, b"short").unwrap_err(),
super::super::CryptoError::PasswordTooShort
);
assert_eq!(
LocalizedKey::from_password(AuthProtocol::Sha256, b"short", b"engine-id").unwrap_err(),
super::super::CryptoError::PasswordTooShort
);
}
#[test]
fn master_keys_privacy_password_validation_precedes_backend_capability() {
assert_eq!(
unavailable_master_keys()
.with_privacy(PrivProtocol::Aes128, b"1234567")
.unwrap_err(),
super::super::CryptoError::PasswordTooShort
);
assert_eq!(
unavailable_master_keys()
.with_privacy(PrivProtocol::Aes128, b"12345678")
.unwrap_err(),
super::super::CryptoError::BackendNotCompiled(CryptoBackend::RustCrypto)
);
}
}
#[cfg(test)]
fn password_to_key(protocol: AuthProtocol, password: &[u8]) -> CryptoResult<Vec<u8>> {
password_to_key_with_backend(CryptoBackend::require_default()?, protocol, password)
}
fn password_to_key_with_backend(
backend: CryptoBackend,
protocol: AuthProtocol,
password: &[u8],
) -> CryptoResult<Vec<u8>> {
backend.password_to_key(protocol, password)
}
fn localize_key_with_backend(
backend: CryptoBackend,
protocol: AuthProtocol,
master_key: &[u8],
engine_id: &[u8],
) -> CryptoResult<Vec<u8>> {
backend.localize_key(protocol, master_key, engine_id)
}
fn compute_hmac(
backend: CryptoBackend,
protocol: AuthProtocol,
key: &[u8],
data: &[u8],
) -> CryptoResult<Vec<u8>> {
backend.compute_hmac(protocol, key, &[data], protocol.mac_len())
}
fn compute_hmac_slices(
backend: CryptoBackend,
protocol: AuthProtocol,
key: &[u8],
slices: &[&[u8]],
) -> CryptoResult<Vec<u8>> {
backend.compute_hmac(protocol, key, slices, protocol.mac_len())
}
pub fn authenticate_message(
key: &LocalizedKey,
message: &mut [u8],
auth_offset: usize,
auth_len: usize,
) -> CryptoResult<()> {
if auth_len != key.mac_len() {
return Err(super::CryptoError::CipherError);
}
let end = match auth_offset.checked_add(auth_len) {
Some(e) if e <= message.len() => e,
_ => return Err(super::CryptoError::CipherError),
};
let mac = key.compute_hmac(message)?;
message[auth_offset..end].copy_from_slice(&mac);
Ok(())
}
pub fn verify_message(
key: &LocalizedKey,
message: &[u8],
auth_offset: usize,
auth_len: usize,
) -> CryptoResult<bool> {
const MAX_MAC_LEN: usize = 48;
if auth_len > MAX_MAC_LEN {
return Ok(false);
}
let end = match auth_offset.checked_add(auth_len) {
Some(e) if e <= message.len() => e,
_ => return Ok(false),
};
let received_mac = &message[auth_offset..end];
let computed = {
let zeros: [u8; MAX_MAC_LEN] = [0u8; MAX_MAC_LEN];
compute_hmac_slices(
key.backend,
key.protocol,
key.as_bytes(),
&[&message[..auth_offset], &zeros[..auth_len], &message[end..]],
)?
};
if computed.len() != received_mac.len() {
return Ok(false);
}
let mut result = 0u8;
for (a, b) in computed.iter().zip(received_mac.iter()) {
result |= a ^ b;
}
Ok(result == 0)
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct MasterKeys {
auth_master: MasterKey,
#[zeroize(skip)]
priv_protocol: Option<super::PrivProtocol>,
priv_master: Option<MasterKey>,
}
impl MasterKeys {
pub fn new(auth_protocol: AuthProtocol, auth_password: &[u8]) -> CryptoResult<Self> {
Self::new_with_backend(
auth_protocol,
auth_password,
CryptoBackend::require_default()?,
)
}
pub fn new_with_backend(
auth_protocol: AuthProtocol,
auth_password: &[u8],
backend: CryptoBackend,
) -> CryptoResult<Self> {
Ok(Self {
auth_master: MasterKey::from_password_with_backend(
auth_protocol,
auth_password,
backend,
)?,
priv_protocol: None,
priv_master: None,
})
}
pub fn with_privacy_same_password(
mut self,
priv_protocol: super::PrivProtocol,
) -> CryptoResult<Self> {
self.auth_master
.crypto_backend()
.validate_priv_protocol(priv_protocol)?;
self.priv_protocol = Some(priv_protocol);
Ok(self)
}
pub fn with_privacy(
mut self,
priv_protocol: super::PrivProtocol,
priv_password: &[u8],
) -> CryptoResult<Self> {
if priv_password.len() < MIN_PASSWORD_LENGTH {
return Err(super::CryptoError::PasswordTooShort);
}
self.auth_master
.crypto_backend()
.validate_priv_protocol(priv_protocol)?;
self.priv_protocol = Some(priv_protocol);
self.priv_master = Some(MasterKey::from_password_with_backend(
self.auth_master.protocol(),
priv_password,
self.auth_master.crypto_backend(),
)?);
Ok(self)
}
#[must_use]
pub fn auth_master(&self) -> &MasterKey {
&self.auth_master
}
#[must_use]
pub fn priv_master(&self) -> Option<&MasterKey> {
if self.priv_protocol.is_some() {
Some(self.priv_master.as_ref().unwrap_or(&self.auth_master))
} else {
None
}
}
#[must_use]
pub fn priv_protocol(&self) -> Option<super::PrivProtocol> {
self.priv_protocol
}
#[must_use]
pub fn auth_protocol(&self) -> AuthProtocol {
self.auth_master.protocol()
}
#[must_use]
pub fn crypto_backend(&self) -> CryptoBackend {
self.auth_master.crypto_backend()
}
pub(crate) fn set_crypto_backend(&mut self, backend: CryptoBackend) {
self.auth_master.set_crypto_backend(backend);
if let Some(master) = &mut self.priv_master {
master.set_crypto_backend(backend);
}
}
pub fn localize(
&self,
engine_id: &[u8],
) -> CryptoResult<(LocalizedKey, Option<crate::v3::PrivKey>)> {
let auth_key = self.auth_master.localize(engine_id)?;
let priv_key = self
.priv_protocol
.map(|priv_protocol| {
let master = self.priv_master.as_ref().unwrap_or(&self.auth_master);
crate::v3::PrivKey::from_master_key(master, priv_protocol, engine_id)
})
.transpose()?;
Ok((auth_key, priv_key))
}
}
impl std::fmt::Debug for MasterKeys {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MasterKeys")
.field("auth_protocol", &self.auth_master.protocol())
.field("priv_protocol", &self.priv_protocol)
.field("has_separate_priv_password", &self.priv_master.is_some())
.finish()
}
}
#[cfg(test)]
pub(crate) fn extend_key(
protocol: AuthProtocol,
key: &[u8],
target_len: usize,
) -> CryptoResult<Vec<u8>> {
extend_key_with_backend(CryptoBackend::require_default()?, protocol, key, target_len)
}
pub(crate) fn extend_key_with_backend(
backend: CryptoBackend,
protocol: AuthProtocol,
key: &[u8],
target_len: usize,
) -> CryptoResult<Vec<u8>> {
if key.len() >= target_len {
return Ok(key[..target_len].to_vec());
}
let mut result = key.to_vec();
while result.len() < target_len {
let hash = backend.hash(protocol, &result)?;
result.extend_from_slice(&hash);
}
result.truncate(target_len);
Ok(result)
}
#[cfg(test)]
pub(crate) fn extend_key_reeder(
protocol: AuthProtocol,
key: &[u8],
engine_id: &[u8],
target_len: usize,
) -> CryptoResult<Vec<u8>> {
extend_key_reeder_with_backend(
CryptoBackend::require_default()?,
protocol,
key,
engine_id,
target_len,
)
}
pub(crate) fn extend_key_reeder_with_backend(
backend: CryptoBackend,
protocol: AuthProtocol,
key: &[u8],
engine_id: &[u8],
target_len: usize,
) -> CryptoResult<Vec<u8>> {
if key.len() >= target_len {
return Ok(key[..target_len].to_vec());
}
let mut result = key.to_vec();
let mut current_kul = key.to_vec();
while result.len() < target_len {
let ku = password_to_key_with_backend(backend, protocol, ¤t_kul)?;
let new_kul = localize_key_with_backend(backend, protocol, &ku, engine_id)?;
let bytes_needed = target_len - result.len();
let bytes_to_copy = bytes_needed.min(new_kul.len());
result.extend_from_slice(&new_kul[..bytes_to_copy]);
current_kul = new_kul;
}
Ok(result)
}
#[cfg(all(test, any(feature = "crypto-rustcrypto", feature = "crypto-fips")))]
mod tests {
use super::*;
use crate::format::hex::{decode as decode_hex, encode as encode_hex};
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_password_to_key_md5() {
let password = b"maplesyrup";
let key = password_to_key(AuthProtocol::Md5, password).unwrap();
assert_eq!(key.len(), 16);
assert_eq!(encode_hex(&key), "9faf3283884e92834ebc9847d8edd963");
}
#[test]
fn test_password_to_key_sha1() {
let password = b"maplesyrup";
let key = password_to_key(AuthProtocol::Sha1, password).unwrap();
assert_eq!(key.len(), 20);
assert_eq!(encode_hex(&key), "9fb5cc0381497b3793528939ff788d5d79145211");
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_localize_key_md5() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let key = LocalizedKey::from_password(AuthProtocol::Md5, password, &engine_id).unwrap();
assert_eq!(key.as_bytes().len(), 16);
assert_eq!(
encode_hex(key.as_bytes()),
"526f5eed9fcce26f8964c2930787d82b"
);
}
#[test]
fn test_localize_key_sha1() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let key = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id).unwrap();
assert_eq!(key.as_bytes().len(), 20);
assert_eq!(
encode_hex(key.as_bytes()),
"6695febc9288e36282235fc7151f128497b38f3f"
);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_hmac_computation() {
let key = LocalizedKey::from_bytes(
AuthProtocol::Md5,
vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
],
)
.unwrap();
let data = b"test message";
let mac = key.compute_hmac(data).unwrap();
assert_eq!(mac.len(), 12);
assert!(key.verify_hmac(data, &mac).unwrap());
let mut wrong_mac = mac.clone();
wrong_mac[0] ^= 0xFF;
assert!(!key.verify_hmac(data, &wrong_mac).unwrap());
assert!(!key.verify_hmac(b"different message", &mac).unwrap());
assert!(!key.verify_hmac(data, &mac[..8]).unwrap());
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_verify_message_oversized_auth_len() {
let key = LocalizedKey::from_bytes(
AuthProtocol::Md5,
vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
],
)
.unwrap();
let message = vec![0u8; 128];
let result = verify_message(&key, &message, 10, 49).unwrap();
assert!(!result);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_authenticate_message_oob_offset_errors() {
let key = LocalizedKey::from_bytes(
AuthProtocol::Md5,
vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
],
)
.unwrap();
let mut message = vec![0u8; 32];
assert_eq!(
authenticate_message(&key, &mut message, 30, 12),
Err(super::super::CryptoError::CipherError)
);
let mut message = vec![0u8; 32];
assert_eq!(
authenticate_message(&key, &mut message, usize::MAX, 12),
Err(super::super::CryptoError::CipherError)
);
}
#[test]
fn test_authenticate_message_mismatched_auth_len_errors() {
let key = LocalizedKey::from_bytes(AuthProtocol::Sha256, vec![0x01; 32]).unwrap();
let mut message = vec![0u8; 32];
assert_eq!(
authenticate_message(&key, &mut message, 0, 12),
Err(super::super::CryptoError::CipherError)
);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_empty_password_rejected() {
assert_eq!(
password_to_key(AuthProtocol::Md5, b""),
Err(super::super::CryptoError::PasswordTooShort)
);
}
#[test]
fn test_short_password_rejected() {
let engine_id = decode_hex("000000000000000000000002").unwrap();
let short = b"1234567";
assert!(matches!(
MasterKey::from_password(AuthProtocol::Sha1, short),
Err(super::super::CryptoError::PasswordTooShort)
));
assert_eq!(
LocalizedKey::from_password(AuthProtocol::Sha1, short, &engine_id).err(),
Some(super::super::CryptoError::PasswordTooShort)
);
assert!(matches!(
MasterKeys::new(AuthProtocol::Sha1, short),
Err(super::super::CryptoError::PasswordTooShort)
));
}
#[test]
fn test_min_length_password_accepted() {
let engine_id = decode_hex("000000000000000000000002").unwrap();
let ok = b"12345678";
assert!(MasterKey::from_password(AuthProtocol::Sha1, ok).is_ok());
assert!(LocalizedKey::from_password(AuthProtocol::Sha1, ok, &engine_id).is_ok());
assert!(MasterKeys::new(AuthProtocol::Sha1, ok).is_ok());
}
#[test]
fn test_raw_auth_key_lengths_for_each_protocol_and_backend() {
let protocols = [
AuthProtocol::Md5,
AuthProtocol::Sha1,
AuthProtocol::Sha224,
AuthProtocol::Sha256,
AuthProtocol::Sha384,
AuthProtocol::Sha512,
];
let backends = [
#[cfg(feature = "crypto-rustcrypto")]
CryptoBackend::RustCrypto,
#[cfg(feature = "crypto-fips")]
CryptoBackend::AwsLcFips,
];
for backend in backends {
for protocol in protocols {
let len = protocol.digest_len();
for actual in [len - 1, len + 1] {
assert!(matches!(
MasterKey::from_bytes_with_backend(protocol, vec![0xAA; actual], backend),
Err(super::super::CryptoError::InvalidKeyLength)
));
assert!(matches!(
LocalizedKey::from_bytes_with_backend(
protocol,
vec![0xAA; actual],
backend
),
Err(super::super::CryptoError::InvalidKeyLength)
));
}
let master = MasterKey::from_bytes_with_backend(protocol, vec![0xAA; len], backend);
let localized =
LocalizedKey::from_bytes_with_backend(protocol, vec![0xAA; len], backend);
let is_supported = match backend {
CryptoBackend::RustCrypto => true,
CryptoBackend::AwsLcFips => protocol != AuthProtocol::Md5,
};
if !is_supported {
assert!(matches!(
master,
Err(super::super::CryptoError::UnsupportedAlgorithm("MD5"))
));
assert!(matches!(
localized,
Err(super::super::CryptoError::UnsupportedAlgorithm("MD5"))
));
} else {
assert_eq!(master.unwrap().as_bytes().len(), len);
assert_eq!(localized.unwrap().as_bytes().len(), len);
}
}
}
}
#[test]
fn test_generic_hmac_vectors_use_internal_arbitrary_key_helper() {
fn generic_hmac(protocol: AuthProtocol, key: &[u8], data: &[u8]) -> Vec<u8> {
compute_hmac(
CryptoBackend::default_backend().unwrap(),
protocol,
key,
data,
)
.unwrap()
}
let cases = [
(
AuthProtocol::Sha1,
vec![0x0b; 20],
b"Hi There".as_slice(),
"b617318655057264e28bc0b6",
),
(
AuthProtocol::Sha224,
vec![0x0b; 20],
b"Hi There".as_slice(),
"896fb1128abbdf196832107cd49df33f",
),
(
AuthProtocol::Sha256,
vec![0x0b; 20],
b"Hi There".as_slice(),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da7",
),
(
AuthProtocol::Sha384,
vec![0x0b; 20],
b"Hi There".as_slice(),
"afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59c",
),
(
AuthProtocol::Sha512,
vec![0x0b; 20],
b"Hi There".as_slice(),
"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4",
),
(
AuthProtocol::Sha1,
b"Jefe".to_vec(),
b"what do ya want for nothing?".as_slice(),
"effcdf6ae5eb2fa2d27416d5",
),
(
AuthProtocol::Sha256,
b"Jefe".to_vec(),
b"what do ya want for nothing?".as_slice(),
"5bdcc146bf60754e6a042426089575c75a003f089d273983",
),
(
AuthProtocol::Sha1,
vec![0x0c; 20],
b"Test With Truncation".as_slice(),
"4c1a03424b55e07fe7f27be1",
),
];
for (protocol, key, data, expected) in cases {
assert_eq!(encode_hex(&generic_hmac(protocol, &key, data)), expected);
}
}
#[test]
fn test_from_str_password() {
let engine_id = decode_hex("000000000000000000000002").unwrap();
let key_from_bytes =
LocalizedKey::from_password(AuthProtocol::Sha1, b"maplesyrup", &engine_id).unwrap();
let key_from_str =
LocalizedKey::from_str_password(AuthProtocol::Sha1, "maplesyrup", &engine_id).unwrap();
assert_eq!(key_from_bytes.as_bytes(), key_from_str.as_bytes());
assert_eq!(key_from_bytes.protocol(), key_from_str.protocol());
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_master_key_localize_md5() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let master = MasterKey::from_password(AuthProtocol::Md5, password).unwrap();
let localized_via_master = master.localize(&engine_id).unwrap();
let localized_direct =
LocalizedKey::from_password(AuthProtocol::Md5, password, &engine_id).unwrap();
assert_eq!(localized_via_master.as_bytes(), localized_direct.as_bytes());
assert_eq!(localized_via_master.protocol(), localized_direct.protocol());
assert_eq!(
encode_hex(master.as_bytes()),
"9faf3283884e92834ebc9847d8edd963"
);
}
#[test]
fn test_master_key_localize_sha1() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let master = MasterKey::from_password(AuthProtocol::Sha1, password).unwrap();
let localized_via_master = master.localize(&engine_id).unwrap();
let localized_direct =
LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id).unwrap();
assert_eq!(localized_via_master.as_bytes(), localized_direct.as_bytes());
assert_eq!(
encode_hex(master.as_bytes()),
"9fb5cc0381497b3793528939ff788d5d79145211"
);
}
#[test]
fn test_master_key_reuse_for_multiple_engines() {
let password = b"maplesyrup";
let engine_id_1 = decode_hex("000000000000000000000001").unwrap();
let engine_id_2 = decode_hex("000000000000000000000002").unwrap();
let master = MasterKey::from_password(AuthProtocol::Sha256, password).unwrap();
let key1 = master.localize(&engine_id_1).unwrap();
let key2 = master.localize(&engine_id_2).unwrap();
assert_ne!(key1.as_bytes(), key2.as_bytes());
let direct1 =
LocalizedKey::from_password(AuthProtocol::Sha256, password, &engine_id_1).unwrap();
let direct2 =
LocalizedKey::from_password(AuthProtocol::Sha256, password, &engine_id_2).unwrap();
assert_eq!(key1.as_bytes(), direct1.as_bytes());
assert_eq!(key2.as_bytes(), direct2.as_bytes());
}
#[test]
fn test_from_master_key() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let master = MasterKey::from_password(AuthProtocol::Sha256, password).unwrap();
let key_via_localize = master.localize(&engine_id).unwrap();
let key_via_from_master = LocalizedKey::from_master_key(&master, &engine_id).unwrap();
assert_eq!(key_via_localize.as_bytes(), key_via_from_master.as_bytes());
}
#[test]
fn test_master_keys_auth_only() {
let engine_id = decode_hex("000000000000000000000002").unwrap();
let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword").unwrap();
assert_eq!(master_keys.auth_protocol(), AuthProtocol::Sha256);
assert!(master_keys.priv_protocol().is_none());
assert!(master_keys.priv_master().is_none());
let (auth_key, priv_key) = master_keys.localize(&engine_id).unwrap();
assert!(priv_key.is_none());
assert_eq!(auth_key.protocol(), AuthProtocol::Sha256);
}
#[test]
fn test_master_keys_with_privacy_same_password() {
use crate::v3::PrivProtocol;
let engine_id = decode_hex("000000000000000000000002").unwrap();
let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"sharedpassword")
.unwrap()
.with_privacy_same_password(PrivProtocol::Aes128)
.unwrap();
assert_eq!(master_keys.auth_protocol(), AuthProtocol::Sha256);
assert_eq!(master_keys.priv_protocol(), Some(PrivProtocol::Aes128));
let (auth_key, priv_key) = master_keys.localize(&engine_id).unwrap();
assert!(priv_key.is_some());
assert_eq!(auth_key.protocol(), AuthProtocol::Sha256);
}
#[test]
fn test_master_keys_with_privacy_different_password() {
use crate::v3::PrivProtocol;
let engine_id = decode_hex("000000000000000000000002").unwrap();
let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword")
.unwrap()
.with_privacy(PrivProtocol::Aes128, b"privpassword")
.unwrap();
let (_auth_key, priv_key) = master_keys.localize(&engine_id).unwrap();
assert!(priv_key.is_some());
let same_password_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword")
.unwrap()
.with_privacy_same_password(PrivProtocol::Aes128)
.unwrap();
let (_, priv_key_same) = same_password_keys.localize(&engine_id).unwrap();
assert_ne!(
priv_key.as_ref().unwrap().encryption_key(),
priv_key_same.as_ref().unwrap().encryption_key()
);
}
#[test]
fn test_master_keys_privacy_password_validation_precedes_derivation() {
use crate::v3::PrivProtocol;
assert_eq!(
MasterKeys::new(AuthProtocol::Sha256, b"authpassword")
.unwrap()
.with_privacy(PrivProtocol::Aes128, b"1234567")
.unwrap_err(),
super::super::CryptoError::PasswordTooShort
);
}
#[cfg(feature = "crypto-fips")]
#[test]
fn test_master_keys_privacy_password_validation_precedes_protocol_capability() {
use crate::v3::PrivProtocol;
let master_keys = || {
MasterKeys::new_with_backend(
AuthProtocol::Sha256,
b"authpassword",
CryptoBackend::AwsLcFips,
)
.unwrap()
};
assert_eq!(
master_keys()
.with_privacy(PrivProtocol::Des, b"1234567")
.unwrap_err(),
super::super::CryptoError::PasswordTooShort
);
assert_eq!(
master_keys()
.with_privacy(PrivProtocol::Des, b"12345678")
.unwrap_err(),
super::super::CryptoError::UnsupportedAlgorithm("DES")
);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_reeder_extend_key_md5_kat() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let k1 = LocalizedKey::from_password(AuthProtocol::Md5, password, &engine_id).unwrap();
assert_eq!(
encode_hex(k1.as_bytes()),
"526f5eed9fcce26f8964c2930787d82b"
);
let extended = extend_key_reeder(AuthProtocol::Md5, k1.as_bytes(), &engine_id, 32).unwrap();
assert_eq!(extended.len(), 32);
assert_eq!(
encode_hex(&extended),
"526f5eed9fcce26f8964c2930787d82b79eff44a90650ee0a3a40abfac5acc12"
);
}
#[test]
fn test_reeder_extend_key_sha1_kat() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let k1 = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id).unwrap();
assert_eq!(
encode_hex(k1.as_bytes()),
"6695febc9288e36282235fc7151f128497b38f3f"
);
let extended =
extend_key_reeder(AuthProtocol::Sha1, k1.as_bytes(), &engine_id, 40).unwrap();
assert_eq!(extended.len(), 40);
assert_eq!(
encode_hex(&extended),
"6695febc9288e36282235fc7151f128497b38f3f9b8b6d78936ba6e7d19dfd9cd2d5065547743fb5"
);
}
#[test]
fn test_reeder_extend_key_sha1_to_32_bytes() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let k1 = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id).unwrap();
let extended =
extend_key_reeder(AuthProtocol::Sha1, k1.as_bytes(), &engine_id, 32).unwrap();
assert_eq!(extended.len(), 32);
assert_eq!(
encode_hex(&extended),
"6695febc9288e36282235fc7151f128497b38f3f9b8b6d78936ba6e7d19dfd9c"
);
}
#[test]
fn test_reeder_extend_key_truncation() {
let long_key = vec![0xAAu8; 64];
let engine_id = decode_hex("000000000000000000000002").unwrap();
let extended = extend_key_reeder(AuthProtocol::Sha256, &long_key, &engine_id, 32).unwrap();
assert_eq!(extended.len(), 32);
assert_eq!(extended, vec![0xAAu8; 32]);
}
#[test]
fn test_reeder_vs_blumenthal_differ() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let k1 = LocalizedKey::from_password(AuthProtocol::Sha1, password, &engine_id).unwrap();
let reeder = extend_key_reeder(AuthProtocol::Sha1, k1.as_bytes(), &engine_id, 32).unwrap();
let blumenthal = extend_key(AuthProtocol::Sha1, k1.as_bytes(), 32).unwrap();
assert_eq!(reeder.len(), 32);
assert_eq!(blumenthal.len(), 32);
assert_eq!(&reeder[..20], &blumenthal[..20]);
assert_ne!(&reeder[20..], &blumenthal[20..]);
}
}