use super::PolicyViolation;
use crate::xmldsig::DigestAlgorithm;
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyAgreementAlgorithm {
EcdhEs,
X25519,
X448,
DhEs,
LegacyDh,
}
impl KeyAgreementAlgorithm {
pub const fn uri(self) -> &'static str {
match self {
Self::EcdhEs => "http://www.w3.org/2009/xmlenc11#ECDH-ES",
Self::X25519 => "http://www.w3.org/2021/04/xmldsig-more#x25519",
Self::X448 => "http://www.w3.org/2021/04/xmldsig-more#x448",
Self::DhEs => "http://www.w3.org/2009/xmlenc11#dh-es",
Self::LegacyDh => "http://www.w3.org/2001/04/xmlenc#dh",
}
}
pub fn from_uri(uri: &str) -> Option<Self> {
[
Self::EcdhEs,
Self::X25519,
Self::X448,
Self::DhEs,
Self::LegacyDh,
]
.into_iter()
.find(|algorithm| algorithm.uri() == uri)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyDerivationAlgorithm {
ConcatKdf,
Hkdf,
Pbkdf2,
LegacyDh,
}
impl KeyDerivationAlgorithm {
pub const fn uri(self) -> &'static str {
match self {
Self::ConcatKdf => "http://www.w3.org/2009/xmlenc11#ConcatKDF",
Self::Hkdf => "http://www.w3.org/2021/04/xmldsig-more#hkdf",
Self::Pbkdf2 => "http://www.w3.org/2009/xmlenc11#pbkdf2",
Self::LegacyDh => "http://www.w3.org/2001/04/xmlenc#dh",
}
}
pub fn from_uri(uri: &str) -> Option<Self> {
[Self::ConcatKdf, Self::Hkdf, Self::Pbkdf2, Self::LegacyDh]
.into_iter()
.find(|algorithm| algorithm.uri() == uri)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum KemContentAuthentication {
#[default]
RequireAuthenticatedCipher,
ExternalAuthenticated,
}
#[derive(Debug, Clone)]
pub struct KeyEstablishmentPolicy {
pub encapsulation_algorithms: HashSet<crate::provider::KeyEncapsulationAlgorithm>,
pub kem_content_authentication: KemContentAuthentication,
pub max_encapsulation_operations: usize,
pub agreement_algorithms: Option<HashSet<KeyAgreementAlgorithm>>,
pub derivation_algorithms: Option<HashSet<KeyDerivationAlgorithm>>,
pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
pub max_hash_blocks: usize,
pub max_owned_bytes: usize,
pub max_dh_modulus_bits: usize,
pub minimum_dh_modulus_bits: usize,
pub minimum_dh_subgroup_bits: usize,
pub max_modular_work: usize,
}
impl Default for KeyEstablishmentPolicy {
fn default() -> Self {
Self {
encapsulation_algorithms: HashSet::new(),
kem_content_authentication: KemContentAuthentication::default(),
max_encapsulation_operations: 64,
agreement_algorithms: None,
derivation_algorithms: None,
digest_algorithms: None,
max_hash_blocks: 1_000_000,
max_owned_bytes: 8 * 1024 * 1024,
max_dh_modulus_bits: 8_192,
minimum_dh_modulus_bits: 2_048,
minimum_dh_subgroup_bits: 224,
max_modular_work: 64_000_000,
}
}
}
impl KeyEstablishmentPolicy {
#[cfg(feature = "xmlenc")]
pub(crate) fn check_kem_content(
&self,
algorithm: crate::xmlenc::DataEncryptionAlgorithm,
) -> Result<(), PolicyViolation> {
permission(
algorithm.is_authenticated()
|| self.kem_content_authentication
== KemContentAuthentication::ExternalAuthenticated,
"direct KEM content without external authentication",
algorithm.uri(),
)
}
pub fn validate(&self) -> Result<(), PolicyViolation> {
super::ResourcePolicy::within(
"key encapsulation operations",
self.max_encapsulation_operations,
crate::hard_limits::KEY_ENCAPSULATION_OPERATION_CEILING,
)?;
for (value, minimum, resource) in [
(
self.minimum_dh_modulus_bits,
512,
super::resource_name::DH_MODULUS_BITS,
),
(
self.minimum_dh_subgroup_bits,
160,
super::resource_name::DH_SUBGROUP_BITS,
),
] {
if value < minimum || value > self.max_dh_modulus_bits {
return Err(PolicyViolation::InvalidResourceLimit {
resource,
actual: value,
requirement: "minimum must respect the wire floor and modulus maximum",
});
}
}
if self.minimum_dh_subgroup_bits >= self.max_dh_modulus_bits {
return Err(PolicyViolation::InvalidResourceLimit {
resource: super::resource_name::DH_SUBGROUP_BITS,
actual: self.minimum_dh_subgroup_bits,
requirement: "subgroup minimum must be strictly below modulus maximum",
});
}
super::ResourcePolicy::within(
super::resource_name::DH_MODULUS_BITS,
self.max_dh_modulus_bits,
crate::hard_limits::DH_MODULUS_BIT_CEILING,
)?;
super::ResourcePolicy::within(
super::resource_name::KEY_ESTABLISHMENT_MODULAR_WORK,
self.max_modular_work,
crate::hard_limits::KEY_ESTABLISHMENT_MODULAR_WORK_CEILING,
)?;
super::ResourcePolicy::within(
super::resource_name::KEY_ESTABLISHMENT_HASH_BLOCKS,
self.max_hash_blocks,
crate::hard_limits::KEY_ESTABLISHMENT_HASH_BLOCK_CEILING,
)?;
super::ResourcePolicy::within(
super::resource_name::KEY_ESTABLISHMENT_OWNED_BYTES,
self.max_owned_bytes,
crate::hard_limits::KEY_ESTABLISHMENT_BYTE_CEILING,
)
}
#[cfg(feature = "xmlenc")]
pub(crate) fn check_dh_domain(
&self,
p_bits: usize,
q_bits: usize,
) -> Result<(), PolicyViolation> {
for (actual_bits, minimum_bits, key_type) in [
(p_bits, self.minimum_dh_modulus_bits, "DH modulus"),
(q_bits, self.minimum_dh_subgroup_bits, "DH subgroup"),
] {
if actual_bits < minimum_bits || actual_bits > self.max_dh_modulus_bits {
return Err(PolicyViolation::KeySize {
operation: "key agreement",
key_type,
minimum_bits,
maximum_bits: self.max_dh_modulus_bits,
actual_bits,
});
}
}
Ok(())
}
pub fn check_agreement(&self, algorithm: KeyAgreementAlgorithm) -> Result<(), PolicyViolation> {
let allowed = match &self.agreement_algorithms {
Some(allowed) => allowed.contains(&algorithm),
None => matches!(
algorithm,
KeyAgreementAlgorithm::EcdhEs | KeyAgreementAlgorithm::X25519
),
};
permission(allowed, "key agreement", algorithm.uri())
}
pub fn check_encapsulation(
&self,
algorithm: crate::provider::KeyEncapsulationAlgorithm,
) -> Result<(), PolicyViolation> {
permission(
self.encapsulation_algorithms.contains(&algorithm),
"key encapsulation",
algorithm.uri(),
)
}
pub fn check_derivation(
&self,
algorithm: KeyDerivationAlgorithm,
digest: DigestAlgorithm,
) -> Result<(), PolicyViolation> {
let allowed = match &self.derivation_algorithms {
Some(allowed) => allowed.contains(&algorithm),
None => algorithm != KeyDerivationAlgorithm::LegacyDh,
};
permission(allowed, "key derivation", algorithm.uri())?;
self.check_derivation_digest(digest)
}
pub fn check_derivation_digest(&self, digest: DigestAlgorithm) -> Result<(), PolicyViolation> {
let allowed = match &self.digest_algorithms {
Some(allowed) => allowed.contains(&digest),
None => matches!(
digest,
DigestAlgorithm::Sha224
| DigestAlgorithm::Sha256
| DigestAlgorithm::Sha384
| DigestAlgorithm::Sha512
),
};
permission(allowed, "key derivation digest", digest.uri())
}
}
fn permission(allowed: bool, operation: &'static str, uri: &str) -> Result<(), PolicyViolation> {
if allowed {
Ok(())
} else {
Err(PolicyViolation::Algorithm {
operation,
algorithm: uri.to_owned(),
})
}
}