1use super::PolicyViolation;
4use crate::xmldsig::DigestAlgorithm;
5use std::collections::HashSet;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum KeyAgreementAlgorithm {
10 EcdhEs,
12 X25519,
14 DhEs,
16 LegacyDh,
18}
19
20impl KeyAgreementAlgorithm {
21 pub const fn uri(self) -> &'static str {
23 match self {
24 Self::EcdhEs => "http://www.w3.org/2009/xmlenc11#ECDH-ES",
25 Self::X25519 => "http://www.w3.org/2021/04/xmldsig-more#x25519",
26 Self::DhEs => "http://www.w3.org/2009/xmlenc11#dh-es",
27 Self::LegacyDh => "http://www.w3.org/2001/04/xmlenc#dh",
28 }
29 }
30
31 pub fn from_uri(uri: &str) -> Option<Self> {
33 [Self::EcdhEs, Self::X25519, Self::DhEs, Self::LegacyDh]
34 .into_iter()
35 .find(|algorithm| algorithm.uri() == uri)
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum KeyDerivationAlgorithm {
42 ConcatKdf,
44 Hkdf,
46 Pbkdf2,
48 LegacyDh,
50}
51
52impl KeyDerivationAlgorithm {
53 pub const fn uri(self) -> &'static str {
55 match self {
56 Self::ConcatKdf => "http://www.w3.org/2009/xmlenc11#ConcatKDF",
57 Self::Hkdf => "http://www.w3.org/2021/04/xmldsig-more#hkdf",
58 Self::Pbkdf2 => "http://www.w3.org/2009/xmlenc11#pbkdf2",
59 Self::LegacyDh => "http://www.w3.org/2001/04/xmlenc#dh",
60 }
61 }
62
63 pub fn from_uri(uri: &str) -> Option<Self> {
65 [Self::ConcatKdf, Self::Hkdf, Self::Pbkdf2, Self::LegacyDh]
66 .into_iter()
67 .find(|algorithm| algorithm.uri() == uri)
68 }
69}
70
71#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
73pub enum KemContentAuthentication {
74 #[default]
76 RequireAuthenticatedCipher,
77 ExternalAuthenticated,
82}
83
84#[derive(Debug, Clone)]
90pub struct KeyEstablishmentPolicy {
91 pub encapsulation_algorithms: HashSet<crate::provider::KeyEncapsulationAlgorithm>,
93 pub kem_content_authentication: KemContentAuthentication,
95 pub max_encapsulation_operations: usize,
97 pub agreement_algorithms: Option<HashSet<KeyAgreementAlgorithm>>,
99 pub derivation_algorithms: Option<HashSet<KeyDerivationAlgorithm>>,
101 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
104 pub max_hash_blocks: usize,
106 pub max_owned_bytes: usize,
110 pub max_dh_modulus_bits: usize,
112 pub minimum_dh_modulus_bits: usize,
115 pub minimum_dh_subgroup_bits: usize,
117 pub max_modular_work: usize,
120}
121
122impl Default for KeyEstablishmentPolicy {
123 fn default() -> Self {
124 Self {
125 encapsulation_algorithms: HashSet::new(),
126 kem_content_authentication: KemContentAuthentication::default(),
127 max_encapsulation_operations: 64,
128 agreement_algorithms: None,
129 derivation_algorithms: None,
130 digest_algorithms: None,
131 max_hash_blocks: 1_000_000,
132 max_owned_bytes: 8 * 1024 * 1024,
133 max_dh_modulus_bits: 8_192,
134 minimum_dh_modulus_bits: 2_048,
135 minimum_dh_subgroup_bits: 224,
136 max_modular_work: 64_000_000,
137 }
138 }
139}
140
141impl KeyEstablishmentPolicy {
142 #[cfg(feature = "xmlenc")]
144 pub(crate) fn check_kem_content(
145 &self,
146 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
147 ) -> Result<(), PolicyViolation> {
148 permission(
155 algorithm.cbc_block_len().is_none()
156 || self.kem_content_authentication
157 == KemContentAuthentication::ExternalAuthenticated,
158 "direct KEM content without external authentication",
159 algorithm.uri(),
160 )
161 }
162
163 pub fn validate(&self) -> Result<(), PolicyViolation> {
165 super::ResourcePolicy::within(
166 "key encapsulation operations",
167 self.max_encapsulation_operations,
168 crate::hard_limits::KEY_ENCAPSULATION_OPERATION_CEILING,
169 )?;
170 for (value, minimum, resource) in [
171 (
172 self.minimum_dh_modulus_bits,
173 512,
174 super::resource_name::DH_MODULUS_BITS,
175 ),
176 (
177 self.minimum_dh_subgroup_bits,
178 160,
179 super::resource_name::DH_SUBGROUP_BITS,
180 ),
181 ] {
182 if value < minimum || value > self.max_dh_modulus_bits {
183 return Err(PolicyViolation::InvalidResourceLimit {
184 resource,
185 actual: value,
186 requirement: "minimum must respect the wire floor and modulus maximum",
187 });
188 }
189 }
190 if self.minimum_dh_subgroup_bits >= self.max_dh_modulus_bits {
194 return Err(PolicyViolation::InvalidResourceLimit {
195 resource: super::resource_name::DH_SUBGROUP_BITS,
196 actual: self.minimum_dh_subgroup_bits,
197 requirement: "subgroup minimum must be strictly below modulus maximum",
198 });
199 }
200 super::ResourcePolicy::within(
201 super::resource_name::DH_MODULUS_BITS,
202 self.max_dh_modulus_bits,
203 crate::hard_limits::DH_MODULUS_BIT_CEILING,
204 )?;
205 super::ResourcePolicy::within(
206 super::resource_name::KEY_ESTABLISHMENT_MODULAR_WORK,
207 self.max_modular_work,
208 crate::hard_limits::KEY_ESTABLISHMENT_MODULAR_WORK_CEILING,
209 )?;
210 super::ResourcePolicy::within(
211 super::resource_name::KEY_ESTABLISHMENT_HASH_BLOCKS,
212 self.max_hash_blocks,
213 crate::hard_limits::KEY_ESTABLISHMENT_HASH_BLOCK_CEILING,
214 )?;
215 super::ResourcePolicy::within(
216 super::resource_name::KEY_ESTABLISHMENT_OWNED_BYTES,
217 self.max_owned_bytes,
218 crate::hard_limits::KEY_ESTABLISHMENT_BYTE_CEILING,
219 )
220 }
221
222 #[cfg(feature = "xmlenc")]
224 pub(crate) fn check_dh_domain(
225 &self,
226 p_bits: usize,
227 q_bits: usize,
228 ) -> Result<(), PolicyViolation> {
229 for (actual_bits, minimum_bits, key_type) in [
230 (p_bits, self.minimum_dh_modulus_bits, "DH modulus"),
231 (q_bits, self.minimum_dh_subgroup_bits, "DH subgroup"),
232 ] {
233 if actual_bits < minimum_bits || actual_bits > self.max_dh_modulus_bits {
234 return Err(PolicyViolation::KeySize {
235 operation: "key agreement",
236 key_type,
237 minimum_bits,
238 maximum_bits: self.max_dh_modulus_bits,
239 actual_bits,
240 });
241 }
242 }
243 Ok(())
244 }
245
246 pub fn check_agreement(&self, algorithm: KeyAgreementAlgorithm) -> Result<(), PolicyViolation> {
248 let allowed = match &self.agreement_algorithms {
249 Some(allowed) => allowed.contains(&algorithm),
250 None => matches!(
251 algorithm,
252 KeyAgreementAlgorithm::EcdhEs | KeyAgreementAlgorithm::X25519
253 ),
254 };
255 permission(allowed, "key agreement", algorithm.uri())
256 }
257
258 pub fn check_encapsulation(
260 &self,
261 algorithm: crate::provider::KeyEncapsulationAlgorithm,
262 ) -> Result<(), PolicyViolation> {
263 permission(
264 self.encapsulation_algorithms.contains(&algorithm),
265 "key encapsulation",
266 algorithm.uri(),
267 )
268 }
269
270 pub fn check_derivation(
273 &self,
274 algorithm: KeyDerivationAlgorithm,
275 digest: DigestAlgorithm,
276 ) -> Result<(), PolicyViolation> {
277 let allowed = match &self.derivation_algorithms {
278 Some(allowed) => allowed.contains(&algorithm),
279 None => algorithm != KeyDerivationAlgorithm::LegacyDh,
280 };
281 permission(allowed, "key derivation", algorithm.uri())?;
282 let allowed = match &self.digest_algorithms {
283 Some(allowed) => allowed.contains(&digest),
284 None => matches!(
285 digest,
286 DigestAlgorithm::Sha224
287 | DigestAlgorithm::Sha256
288 | DigestAlgorithm::Sha384
289 | DigestAlgorithm::Sha512
290 ),
291 };
292 permission(allowed, "key derivation digest", digest.uri())
293 }
294}
295
296fn permission(allowed: bool, operation: &'static str, uri: &str) -> Result<(), PolicyViolation> {
297 if allowed {
298 Ok(())
299 } else {
300 Err(PolicyViolation::Algorithm {
301 operation,
302 algorithm: uri.to_owned(),
303 })
304 }
305}