use bytes::Bytes;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::message::SecurityLevel;
use crate::v3::{
AuthProtocol, CryptoBackend, CryptoError, CryptoResult, LocalizedKey, PrivKey, PrivProtocol,
};
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
struct Password(Vec<u8>);
impl AsRef<[u8]> for Password {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Clone)]
enum UsmCredentials {
NoAuthNoPriv,
Passwords {
auth: (AuthProtocol, Password),
privacy: Option<(PrivProtocol, Password)>,
},
MasterKeys(crate::v3::MasterKeys),
}
#[derive(Clone)]
pub struct UsmConfig {
username: Bytes,
credentials: UsmCredentials,
context_name: Bytes,
crypto_backend: CryptoBackend,
crypto_backend_explicit: bool,
}
#[derive(Clone)]
pub struct UsmUser {
config: UsmConfig,
}
impl UsmUser {
pub fn new(username: impl Into<Bytes>) -> Self {
Self {
config: UsmConfig::new(username),
}
}
pub fn auth(
mut self,
protocol: AuthProtocol,
password: impl AsRef<[u8]>,
) -> CryptoResult<Self> {
self.config = self.config.auth(protocol, password)?;
Ok(self)
}
pub fn auth_priv(
mut self,
auth_protocol: AuthProtocol,
auth_password: impl AsRef<[u8]>,
priv_protocol: PrivProtocol,
priv_password: impl AsRef<[u8]>,
) -> CryptoResult<Self> {
self.config =
self.config
.auth_priv(auth_protocol, auth_password, priv_protocol, priv_password)?;
Ok(self)
}
pub fn with_crypto_backend(mut self, backend: CryptoBackend) -> CryptoResult<Self> {
self.config = self.config.with_crypto_backend(backend)?;
Ok(self)
}
#[must_use]
pub fn crypto_backend(&self) -> Option<CryptoBackend> {
self.config.crypto_backend()
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
pub fn with_master_keys(mut self, master_keys: crate::v3::MasterKeys) -> CryptoResult<Self> {
self.config = self.config.with_master_keys(master_keys)?;
Ok(self)
}
#[must_use]
pub fn username(&self) -> &Bytes {
self.config.username()
}
#[must_use]
pub fn auth_protocol(&self) -> Option<AuthProtocol> {
self.config.auth_protocol()
}
#[must_use]
pub fn priv_protocol(&self) -> Option<PrivProtocol> {
self.config.priv_protocol()
}
#[must_use]
pub fn maximum_security_level(&self) -> SecurityLevel {
self.config.security_level()
}
pub(crate) fn validate_and_precompute(&mut self) -> CryptoResult<()> {
self.config.validate_and_precompute()
}
pub(crate) fn derive_keys(&self, engine_id: &[u8]) -> CryptoResult<DerivedKeys> {
self.config.derive_keys_inner(engine_id)
}
}
impl std::fmt::Debug for UsmUser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UsmUser")
.field("username", self.username())
.field("auth_protocol", &self.auth_protocol())
.field("priv_protocol", &self.priv_protocol())
.field("maximum_security_level", &self.maximum_security_level())
.field("crypto_backend", &self.config.crypto_backend)
.finish()
}
}
impl UsmConfig {
pub fn new(username: impl Into<Bytes>) -> Self {
Self {
username: username.into(),
credentials: UsmCredentials::NoAuthNoPriv,
context_name: Bytes::new(),
crypto_backend: CryptoBackend::default_backend().unwrap_or(CryptoBackend::RustCrypto),
crypto_backend_explicit: false,
}
}
pub fn auth(
mut self,
protocol: AuthProtocol,
password: impl AsRef<[u8]>,
) -> CryptoResult<Self> {
self.credentials = UsmCredentials::Passwords {
auth: (protocol, Password(password.as_ref().to_vec())),
privacy: None,
};
self.validate_credential_capabilities()?;
Ok(self)
}
pub fn auth_priv(
mut self,
auth_protocol: AuthProtocol,
auth_password: impl AsRef<[u8]>,
priv_protocol: PrivProtocol,
priv_password: impl AsRef<[u8]>,
) -> CryptoResult<Self> {
self.credentials = UsmCredentials::Passwords {
auth: (auth_protocol, Password(auth_password.as_ref().to_vec())),
privacy: Some((priv_protocol, Password(priv_password.as_ref().to_vec()))),
};
self.validate_credential_capabilities()?;
Ok(self)
}
pub fn with_crypto_backend(mut self, backend: CryptoBackend) -> CryptoResult<Self> {
if !backend.is_compiled() {
return Err(CryptoError::BackendNotCompiled(backend));
}
self.crypto_backend = backend;
self.crypto_backend_explicit = true;
self.validate_credential_capabilities()?;
if let UsmCredentials::MasterKeys(master_keys) = &mut self.credentials {
master_keys.set_crypto_backend(backend);
}
Ok(self)
}
#[must_use]
pub fn crypto_backend(&self) -> Option<CryptoBackend> {
self.crypto_backend
.is_compiled()
.then_some(self.crypto_backend)
}
#[must_use]
pub fn context_name(mut self, context_name: impl Into<Bytes>) -> Self {
self.context_name = context_name.into();
self
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
pub fn with_master_keys(
mut self,
mut master_keys: crate::v3::MasterKeys,
) -> CryptoResult<Self> {
self.crypto_backend
.validate_auth_protocol(master_keys.auth_protocol())?;
if let Some(protocol) = master_keys.priv_protocol() {
self.crypto_backend.validate_priv_protocol(protocol)?;
}
master_keys.set_crypto_backend(self.crypto_backend);
self.credentials = UsmCredentials::MasterKeys(master_keys);
Ok(self)
}
#[must_use]
pub fn username(&self) -> &Bytes {
&self.username
}
#[must_use]
pub fn configured_context_name(&self) -> &Bytes {
&self.context_name
}
#[must_use]
pub fn auth_protocol(&self) -> Option<AuthProtocol> {
match &self.credentials {
UsmCredentials::NoAuthNoPriv => None,
UsmCredentials::Passwords { auth, .. } => Some(auth.0),
UsmCredentials::MasterKeys(master_keys) => Some(master_keys.auth_protocol()),
}
}
#[must_use]
pub fn priv_protocol(&self) -> Option<PrivProtocol> {
match &self.credentials {
UsmCredentials::NoAuthNoPriv | UsmCredentials::Passwords { privacy: None, .. } => None,
UsmCredentials::Passwords {
privacy: Some((protocol, _)),
..
} => Some(*protocol),
UsmCredentials::MasterKeys(master_keys) => master_keys.priv_protocol(),
}
}
#[must_use]
pub fn security_level(&self) -> SecurityLevel {
match &self.credentials {
UsmCredentials::NoAuthNoPriv => SecurityLevel::NoAuthNoPriv,
UsmCredentials::Passwords { privacy: None, .. } => SecurityLevel::AuthNoPriv,
UsmCredentials::Passwords {
privacy: Some(_), ..
} => SecurityLevel::AuthPriv,
UsmCredentials::MasterKeys(master_keys) if master_keys.priv_protocol().is_some() => {
SecurityLevel::AuthPriv
}
UsmCredentials::MasterKeys(_) => SecurityLevel::AuthNoPriv,
}
}
fn validate_credential_capabilities(&self) -> CryptoResult<()> {
match &self.credentials {
UsmCredentials::NoAuthNoPriv => Ok(()),
UsmCredentials::MasterKeys(master_keys) => {
if !self.crypto_backend_explicit && CryptoBackend::default_backend().is_none() {
return Err(CryptoError::BackendUnavailable);
}
self.crypto_backend
.validate_auth_protocol(master_keys.auth_protocol())?;
if let Some(protocol) = master_keys.priv_protocol() {
self.crypto_backend.validate_priv_protocol(protocol)?;
}
Ok(())
}
UsmCredentials::Passwords { auth, privacy } => {
if auth.1.as_ref().len() < crate::v3::auth::MIN_PASSWORD_LENGTH
|| privacy.as_ref().is_some_and(|(_, password)| {
password.as_ref().len() < crate::v3::auth::MIN_PASSWORD_LENGTH
})
{
return Err(CryptoError::PasswordTooShort);
}
if !self.crypto_backend_explicit && CryptoBackend::default_backend().is_none() {
return Err(CryptoError::BackendUnavailable);
}
self.crypto_backend.validate_auth_protocol(auth.0)?;
if let Some((protocol, _)) = privacy {
self.crypto_backend.validate_priv_protocol(*protocol)?;
}
Ok(())
}
}
}
pub(crate) fn validate_and_precompute(&mut self) -> CryptoResult<()> {
if !(1..=32).contains(&self.username.len()) {
return Err(CryptoError::InvalidUsmUsernameLength {
length: self.username.len(),
});
}
self.validate_credential_capabilities()?;
match &self.credentials {
UsmCredentials::NoAuthNoPriv | UsmCredentials::MasterKeys(_) => Ok(()),
UsmCredentials::Passwords { auth, privacy } => {
let (auth_protocol, auth_password) = auth;
let master_keys = crate::v3::MasterKeys::new_with_backend(
*auth_protocol,
auth_password.as_ref(),
self.crypto_backend,
)?;
let master_keys = match privacy {
Some((priv_protocol, priv_password)) => {
master_keys.with_privacy(*priv_protocol, priv_password.as_ref())?
}
None => master_keys,
};
self.credentials = UsmCredentials::MasterKeys(master_keys);
Ok(())
}
}
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
pub fn derive_keys(&self, engine_id: &[u8]) -> crate::v3::CryptoResult<DerivedKeys> {
self.derive_keys_inner(engine_id)
}
pub(crate) fn derive_keys_inner(
&self,
engine_id: &[u8],
) -> crate::v3::CryptoResult<DerivedKeys> {
match &self.credentials {
UsmCredentials::NoAuthNoPriv => Ok(DerivedKeys {
auth_key: None,
priv_key: None,
}),
UsmCredentials::MasterKeys(master_keys) => {
tracing::trace!(target: "async_snmp::client", { engine_id_len = engine_id.len(), auth_protocol = ?master_keys.auth_protocol(), priv_protocol = ?master_keys.priv_protocol() }, "localizing from cached master keys");
let (auth_key, priv_key) = master_keys.localize(engine_id)?;
Ok(DerivedKeys {
auth_key: Some(auth_key),
priv_key,
})
}
UsmCredentials::Passwords { auth, privacy } => {
let (auth_protocol, auth_password) = auth;
tracing::trace!(target: "async_snmp::client", { engine_id_len = engine_id.len(), auth_protocol = ?auth_protocol }, "deriving localized keys from passwords");
let auth_key = LocalizedKey::from_password_with_backend(
*auth_protocol,
auth_password.as_ref(),
engine_id,
self.crypto_backend,
)?;
let priv_key = privacy
.as_ref()
.map(|(priv_protocol, priv_password)| {
PrivKey::from_password_with_backend(
*auth_protocol,
*priv_protocol,
priv_password.as_ref(),
engine_id,
self.crypto_backend,
)
})
.transpose()?;
Ok(DerivedKeys {
auth_key: Some(auth_key),
priv_key,
})
}
}
}
}
impl std::fmt::Debug for UsmConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (auth, auth_password, privacy, priv_password, master_keys) = match &self.credentials {
UsmCredentials::NoAuthNoPriv => (None, None, None, None, None),
UsmCredentials::Passwords { auth, privacy } => (
Some(auth.0),
Some("[REDACTED]"),
privacy.as_ref().map(|value| value.0),
privacy.as_ref().map(|_| "[REDACTED]"),
None,
),
UsmCredentials::MasterKeys(master_keys) => (
Some(master_keys.auth_protocol()),
None,
master_keys.priv_protocol(),
None,
Some("[REDACTED]"),
),
};
f.debug_struct("UsmConfig")
.field("username", &self.username)
.field("auth_protocol", &auth)
.field("auth_password", &auth_password)
.field("priv_protocol", &privacy)
.field("priv_password", &priv_password)
.field("context_name", &self.context_name)
.field("crypto_backend", &self.crypto_backend)
.field("master_keys", &master_keys)
.finish()
}
}
#[derive(Debug)]
pub struct DerivedKeys {
pub auth_key: Option<LocalizedKey>,
pub priv_key: Option<PrivKey>,
}
#[cfg(test)]
mod tests {
use super::*;
use static_assertions::assert_not_impl_any;
assert_not_impl_any!(Password: PartialEq, Eq, PartialOrd, Ord, std::hash::Hash);
assert_not_impl_any!(UsmCredentials: PartialEq, Eq, PartialOrd, Ord, std::hash::Hash);
#[test]
fn test_usm_user_config_no_auth() {
let config = UsmConfig::new(Bytes::from_static(b"testuser"));
assert_eq!(config.security_level(), SecurityLevel::NoAuthNoPriv);
assert_eq!(config.auth_protocol(), None);
assert_eq!(config.priv_protocol(), None);
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_usm_user_config_auth_only() {
let config = UsmConfig::new(Bytes::from_static(b"testuser"))
.auth(AuthProtocol::Sha1, b"password123")
.unwrap();
assert_eq!(config.security_level(), SecurityLevel::AuthNoPriv);
assert_eq!(config.auth_protocol(), Some(AuthProtocol::Sha1));
assert_eq!(config.priv_protocol(), None);
assert!(config.configured_context_name().is_empty());
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_usm_user_config_auth_priv() {
let config = UsmConfig::new(Bytes::from_static(b"testuser"))
.auth_priv(
AuthProtocol::Sha256,
b"authpass",
PrivProtocol::Aes128,
b"privpass",
)
.unwrap();
assert_eq!(config.security_level(), SecurityLevel::AuthPriv);
assert_eq!(config.auth_protocol(), Some(AuthProtocol::Sha256));
assert_eq!(config.priv_protocol(), Some(PrivProtocol::Aes128));
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_usm_user_config_master_key_levels() {
let auth = crate::v3::MasterKeys::new(AuthProtocol::Sha256, b"authpass").unwrap();
let auth_config = UsmConfig::new("user").with_master_keys(auth).unwrap();
assert_eq!(auth_config.security_level(), SecurityLevel::AuthNoPriv);
assert_eq!(auth_config.auth_protocol(), Some(AuthProtocol::Sha256));
assert_eq!(auth_config.priv_protocol(), None);
let auth_priv = crate::v3::MasterKeys::new(AuthProtocol::Sha256, b"authpass")
.unwrap()
.with_privacy(PrivProtocol::Aes128, b"privpass")
.unwrap();
let auth_priv_config = UsmConfig::new("user").with_master_keys(auth_priv).unwrap();
assert_eq!(auth_priv_config.security_level(), SecurityLevel::AuthPriv);
assert_eq!(auth_priv_config.auth_protocol(), Some(AuthProtocol::Sha256));
assert_eq!(auth_priv_config.priv_protocol(), Some(PrivProtocol::Aes128));
let keys = auth_priv_config.derive_keys(b"test-engine-id").unwrap();
assert!(keys.auth_key.is_some());
assert!(keys.priv_key.is_some());
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_password_configurators_replace_master_keys() {
let master_keys = crate::v3::MasterKeys::new(AuthProtocol::Sha256, b"masterauthpass")
.unwrap()
.with_privacy(PrivProtocol::Aes128, b"masterprivpass")
.unwrap();
let auth_config = UsmConfig::new("user")
.with_master_keys(master_keys.clone())
.unwrap()
.auth(AuthProtocol::Sha1, b"passwordauth")
.unwrap();
assert_eq!(auth_config.security_level(), SecurityLevel::AuthNoPriv);
assert_eq!(auth_config.auth_protocol(), Some(AuthProtocol::Sha1));
assert_eq!(auth_config.priv_protocol(), None);
let auth_priv_config = UsmConfig::new("user")
.with_master_keys(master_keys)
.unwrap()
.auth_priv(
AuthProtocol::Sha512,
b"otherauthpass",
PrivProtocol::Aes256Blumenthal,
b"otherprivpass",
)
.unwrap();
assert_eq!(auth_priv_config.security_level(), SecurityLevel::AuthPriv);
assert_eq!(auth_priv_config.auth_protocol(), Some(AuthProtocol::Sha512));
assert_eq!(
auth_priv_config.priv_protocol(),
Some(PrivProtocol::Aes256Blumenthal)
);
assert!(
auth_priv_config
.derive_keys(b"test-engine-id")
.unwrap()
.priv_key
.is_some()
);
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_master_key_debug_redacts_key_material() {
let master_keys = crate::v3::MasterKeys::new(AuthProtocol::Sha256, b"masterauthpass")
.unwrap()
.with_privacy(PrivProtocol::Aes128, b"masterprivpass")
.unwrap();
let rendered = format!(
"{:?}",
UsmConfig::new("user")
.with_master_keys(master_keys)
.unwrap()
);
assert!(rendered.contains("[REDACTED]"), "{rendered}");
assert!(rendered.contains("auth_password: None"), "{rendered}");
assert!(rendered.contains("priv_password: None"), "{rendered}");
assert!(rendered.contains("master_keys: Some"), "{rendered}");
assert!(!rendered.contains("masterauthpass"), "{rendered}");
assert!(!rendered.contains("masterprivpass"), "{rendered}");
}
#[test]
fn test_usm_user_config_context_name() {
let config = UsmConfig::new(Bytes::from_static(b"testuser")).context_name("ctx");
assert_eq!(config.configured_context_name().as_ref(), b"ctx");
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_usm_user_config_derive_keys() {
let config = UsmConfig::new(Bytes::from_static(b"testuser"))
.auth(AuthProtocol::Sha1, b"password123")
.unwrap();
let engine_id = b"test-engine-id";
let keys = config.derive_keys(engine_id).unwrap();
assert!(keys.auth_key.is_some());
assert!(keys.priv_key.is_none());
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_usm_user_config_derive_keys_with_privacy() {
let config = UsmConfig::new(Bytes::from_static(b"testuser"))
.auth_priv(
AuthProtocol::Sha256,
b"authpass",
PrivProtocol::Aes128,
b"privpass",
)
.unwrap();
let engine_id = b"test-engine-id";
let keys = config.derive_keys(engine_id).unwrap();
assert!(keys.auth_key.is_some());
assert!(keys.priv_key.is_some());
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_precompute_master_keys_replaces_passwords() {
let mut config = UsmConfig::new(Bytes::from_static(b"testuser"))
.auth_priv(
AuthProtocol::Sha256,
b"authpass",
PrivProtocol::Aes128,
b"privpass",
)
.unwrap();
config.validate_and_precompute().unwrap();
assert!(matches!(config.credentials, UsmCredentials::MasterKeys(_)));
config.validate_and_precompute().unwrap();
assert!(matches!(config.credentials, UsmCredentials::MasterKeys(_)));
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn test_precompute_master_keys_preserves_derivation() {
let engine_id = b"\x80\x00\x00\x00\x01test-engine";
let uncached = UsmConfig::new(Bytes::from_static(b"u"))
.auth(AuthProtocol::Sha256, b"authpass")
.unwrap();
let mut cached = uncached.clone();
cached.validate_and_precompute().unwrap();
let a = uncached.derive_keys(engine_id).unwrap();
let b = cached.derive_keys(engine_id).unwrap();
assert_eq!(
a.auth_key.as_ref().map(AsRef::as_ref),
b.auth_key.as_ref().map(AsRef::as_ref),
"auth key must match between password and master-key paths"
);
let uncached = UsmConfig::new(Bytes::from_static(b"u"))
.auth_priv(
AuthProtocol::Sha1,
b"authpassword",
PrivProtocol::Aes128,
b"privpassword",
)
.unwrap();
let mut cached = uncached.clone();
cached.validate_and_precompute().unwrap();
let a = uncached.derive_keys(engine_id).unwrap();
let b = cached.derive_keys(engine_id).unwrap();
assert_eq!(
a.auth_key.as_ref().map(AsRef::as_ref),
b.auth_key.as_ref().map(AsRef::as_ref),
);
let a_priv = a.priv_key.as_ref().expect("password path privacy key");
let b_priv = b.priv_key.as_ref().expect("master-key path privacy key");
assert_eq!(a_priv.protocol(), b_priv.protocol());
assert_eq!(a_priv.encryption_key(), b_priv.encryption_key());
let uncached = UsmConfig::new(Bytes::from_static(b"u"))
.auth_priv(
AuthProtocol::Sha1,
b"sharedpassword",
PrivProtocol::Aes128,
b"sharedpassword",
)
.unwrap();
let mut cached = uncached.clone();
cached.validate_and_precompute().unwrap();
let a = uncached.derive_keys(engine_id).unwrap();
let b = cached.derive_keys(engine_id).unwrap();
assert_eq!(
a.auth_key.as_ref().map(AsRef::as_ref),
b.auth_key.as_ref().map(AsRef::as_ref),
);
let a_priv = a.priv_key.as_ref().expect("password path privacy key");
let b_priv = b.priv_key.as_ref().expect("master-key path privacy key");
assert_eq!(a_priv.protocol(), b_priv.protocol());
assert_eq!(a_priv.encryption_key(), b_priv.encryption_key());
}
#[test]
fn validate_username_octet_boundaries() {
for username in [vec![0xff], vec![b'u'; 32]] {
let mut config = UsmConfig::new(Bytes::from(username));
assert!(config.validate_and_precompute().is_ok());
}
for username in [Vec::new(), vec![b'u'; 33]] {
let expected = username.len();
let mut config = UsmConfig::new(Bytes::from(username));
assert_eq!(
config.validate_and_precompute(),
Err(CryptoError::InvalidUsmUsernameLength { length: expected })
);
}
}
#[test]
fn credential_configuration_rejects_each_short_password() {
assert!(matches!(
UsmConfig::new("user").auth(AuthProtocol::Sha256, b"1234567"),
Err(CryptoError::PasswordTooShort)
));
assert!(matches!(
UsmConfig::new("user").auth_priv(
AuthProtocol::Sha256,
b"12345678",
PrivProtocol::Aes128,
b"1234567",
),
Err(CryptoError::PasswordTooShort)
));
}
#[cfg(not(any(feature = "crypto-rustcrypto", feature = "crypto-fips")))]
#[test]
fn password_credentials_are_rejected_without_crypto_backend() {
assert!(matches!(
UsmConfig::new("user").auth(AuthProtocol::Sha256, b"authpassword"),
Err(CryptoError::BackendUnavailable)
));
assert!(matches!(
UsmUser::new("user").auth(AuthProtocol::Sha256, b"authpassword"),
Err(CryptoError::BackendUnavailable)
));
}
#[cfg(any(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn validate_accepts_eight_octet_passwords() {
let mut config = UsmConfig::new("user")
.auth_priv(
AuthProtocol::Sha256,
b"12345678",
PrivProtocol::Aes128,
b"abcdefgh",
)
.unwrap();
config.validate_and_precompute().unwrap();
assert!(matches!(config.credentials, UsmCredentials::MasterKeys(_)));
}
#[cfg(feature = "crypto-fips")]
#[test]
fn selected_fips_backend_rejects_unsupported_password_protocols() {
assert!(matches!(
UsmConfig::new("user")
.with_crypto_backend(CryptoBackend::AwsLcFips)
.unwrap()
.auth(AuthProtocol::Md5, b"password"),
Err(CryptoError::UnsupportedAlgorithm("MD5"))
));
assert!(matches!(
UsmConfig::new("user")
.with_crypto_backend(CryptoBackend::AwsLcFips)
.unwrap()
.auth_priv(
AuthProtocol::Sha256,
b"password",
PrivProtocol::Des,
b"password",
),
Err(CryptoError::UnsupportedAlgorithm("DES"))
));
}
#[cfg(all(feature = "crypto-fips", not(feature = "crypto-rustcrypto")))]
#[test]
fn fips_only_build_exposes_stable_backend_identities_and_capabilities() {
assert_eq!(
CryptoBackend::default_backend(),
Some(CryptoBackend::AwsLcFips)
);
assert!(CryptoBackend::AwsLcFips.is_compiled());
assert!(!CryptoBackend::RustCrypto.is_compiled());
assert_eq!(
UsmConfig::new("user")
.with_crypto_backend(CryptoBackend::RustCrypto)
.unwrap_err(),
CryptoError::BackendNotCompiled(CryptoBackend::RustCrypto)
);
}
#[cfg(feature = "crypto-fips")]
#[test]
fn selected_fips_backend_rejects_unsupported_master_key_privacy() {
assert!(matches!(
crate::v3::MasterKeys::new_with_backend(
AuthProtocol::Sha256,
b"password",
CryptoBackend::AwsLcFips,
)
.unwrap()
.with_privacy_same_password(PrivProtocol::Des),
Err(CryptoError::UnsupportedAlgorithm("DES"))
));
}
#[cfg(all(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
#[test]
fn selected_fips_backend_rejects_unsupported_master_key_auth() {
let master_keys = crate::v3::MasterKeys::new_with_backend(
AuthProtocol::Md5,
b"password",
CryptoBackend::RustCrypto,
)
.unwrap();
let config = UsmConfig::new("user")
.with_master_keys(master_keys)
.unwrap()
.with_crypto_backend(CryptoBackend::AwsLcFips);
assert!(matches!(
config,
Err(CryptoError::UnsupportedAlgorithm("MD5"))
));
}
}