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 X448,
16 DhEs,
18 LegacyDh,
20}
21
22impl KeyAgreementAlgorithm {
23 pub const fn uri(self) -> &'static str {
25 match self {
26 Self::EcdhEs => "http://www.w3.org/2009/xmlenc11#ECDH-ES",
27 Self::X25519 => "http://www.w3.org/2021/04/xmldsig-more#x25519",
28 Self::X448 => "http://www.w3.org/2021/04/xmldsig-more#x448",
29 Self::DhEs => "http://www.w3.org/2009/xmlenc11#dh-es",
30 Self::LegacyDh => "http://www.w3.org/2001/04/xmlenc#dh",
31 }
32 }
33
34 pub fn from_uri(uri: &str) -> Option<Self> {
36 [
37 Self::EcdhEs,
38 Self::X25519,
39 Self::X448,
40 Self::DhEs,
41 Self::LegacyDh,
42 ]
43 .into_iter()
44 .find(|algorithm| algorithm.uri() == uri)
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum KeyDerivationAlgorithm {
51 ConcatKdf,
53 Hkdf,
55 Pbkdf2,
57 LegacyDh,
59}
60
61impl KeyDerivationAlgorithm {
62 pub const fn uri(self) -> &'static str {
64 match self {
65 Self::ConcatKdf => "http://www.w3.org/2009/xmlenc11#ConcatKDF",
66 Self::Hkdf => "http://www.w3.org/2021/04/xmldsig-more#hkdf",
67 Self::Pbkdf2 => "http://www.w3.org/2009/xmlenc11#pbkdf2",
68 Self::LegacyDh => "http://www.w3.org/2001/04/xmlenc#dh",
69 }
70 }
71
72 pub fn from_uri(uri: &str) -> Option<Self> {
74 [Self::ConcatKdf, Self::Hkdf, Self::Pbkdf2, Self::LegacyDh]
75 .into_iter()
76 .find(|algorithm| algorithm.uri() == uri)
77 }
78}
79
80#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
82pub enum KemContentAuthentication {
83 #[default]
85 RequireAuthenticatedCipher,
86 ExternalAuthenticated,
91}
92
93#[derive(Debug, Clone)]
99pub struct KeyEstablishmentPolicy {
100 pub encapsulation_algorithms: HashSet<crate::provider::KeyEncapsulationAlgorithm>,
102 pub kem_content_authentication: KemContentAuthentication,
104 pub max_encapsulation_operations: usize,
106 pub agreement_algorithms: Option<HashSet<KeyAgreementAlgorithm>>,
108 pub derivation_algorithms: Option<HashSet<KeyDerivationAlgorithm>>,
110 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
113 pub max_hash_blocks: usize,
115 pub max_owned_bytes: usize,
119 pub max_dh_modulus_bits: usize,
121 pub minimum_dh_modulus_bits: usize,
124 pub minimum_dh_subgroup_bits: usize,
126 pub max_modular_work: usize,
129}
130
131impl Default for KeyEstablishmentPolicy {
132 fn default() -> Self {
133 Self {
134 encapsulation_algorithms: HashSet::new(),
135 kem_content_authentication: KemContentAuthentication::default(),
136 max_encapsulation_operations: 64,
137 agreement_algorithms: None,
138 derivation_algorithms: None,
139 digest_algorithms: None,
140 max_hash_blocks: 1_000_000,
141 max_owned_bytes: 8 * 1024 * 1024,
142 max_dh_modulus_bits: 8_192,
143 minimum_dh_modulus_bits: 2_048,
144 minimum_dh_subgroup_bits: 224,
145 max_modular_work: 64_000_000,
146 }
147 }
148}
149
150impl KeyEstablishmentPolicy {
151 #[cfg(feature = "xmlenc")]
153 pub(crate) fn check_kem_content(
154 &self,
155 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
156 ) -> Result<(), PolicyViolation> {
157 permission(
164 algorithm.is_authenticated()
165 || self.kem_content_authentication
166 == KemContentAuthentication::ExternalAuthenticated,
167 "direct KEM content without external authentication",
168 algorithm.uri(),
169 )
170 }
171
172 pub fn validate(&self) -> Result<(), PolicyViolation> {
174 super::ResourcePolicy::within(
175 "key encapsulation operations",
176 self.max_encapsulation_operations,
177 crate::hard_limits::KEY_ENCAPSULATION_OPERATION_CEILING,
178 )?;
179 for (value, minimum, resource) in [
180 (
181 self.minimum_dh_modulus_bits,
182 512,
183 super::resource_name::DH_MODULUS_BITS,
184 ),
185 (
186 self.minimum_dh_subgroup_bits,
187 160,
188 super::resource_name::DH_SUBGROUP_BITS,
189 ),
190 ] {
191 if value < minimum || value > self.max_dh_modulus_bits {
192 return Err(PolicyViolation::InvalidResourceLimit {
193 resource,
194 actual: value,
195 requirement: "minimum must respect the wire floor and modulus maximum",
196 });
197 }
198 }
199 if self.minimum_dh_subgroup_bits >= self.max_dh_modulus_bits {
203 return Err(PolicyViolation::InvalidResourceLimit {
204 resource: super::resource_name::DH_SUBGROUP_BITS,
205 actual: self.minimum_dh_subgroup_bits,
206 requirement: "subgroup minimum must be strictly below modulus maximum",
207 });
208 }
209 super::ResourcePolicy::within(
210 super::resource_name::DH_MODULUS_BITS,
211 self.max_dh_modulus_bits,
212 crate::hard_limits::DH_MODULUS_BIT_CEILING,
213 )?;
214 super::ResourcePolicy::within(
215 super::resource_name::KEY_ESTABLISHMENT_MODULAR_WORK,
216 self.max_modular_work,
217 crate::hard_limits::KEY_ESTABLISHMENT_MODULAR_WORK_CEILING,
218 )?;
219 super::ResourcePolicy::within(
220 super::resource_name::KEY_ESTABLISHMENT_HASH_BLOCKS,
221 self.max_hash_blocks,
222 crate::hard_limits::KEY_ESTABLISHMENT_HASH_BLOCK_CEILING,
223 )?;
224 super::ResourcePolicy::within(
225 super::resource_name::KEY_ESTABLISHMENT_OWNED_BYTES,
226 self.max_owned_bytes,
227 crate::hard_limits::KEY_ESTABLISHMENT_BYTE_CEILING,
228 )
229 }
230
231 #[cfg(feature = "xmlenc")]
233 pub(crate) fn check_dh_domain(
234 &self,
235 p_bits: usize,
236 q_bits: usize,
237 ) -> Result<(), PolicyViolation> {
238 for (actual_bits, minimum_bits, key_type) in [
239 (p_bits, self.minimum_dh_modulus_bits, "DH modulus"),
240 (q_bits, self.minimum_dh_subgroup_bits, "DH subgroup"),
241 ] {
242 if actual_bits < minimum_bits || actual_bits > self.max_dh_modulus_bits {
243 return Err(PolicyViolation::KeySize {
244 operation: "key agreement",
245 key_type,
246 minimum_bits,
247 maximum_bits: self.max_dh_modulus_bits,
248 actual_bits,
249 });
250 }
251 }
252 Ok(())
253 }
254
255 pub fn check_agreement(&self, algorithm: KeyAgreementAlgorithm) -> Result<(), PolicyViolation> {
257 let allowed = match &self.agreement_algorithms {
258 Some(allowed) => allowed.contains(&algorithm),
259 None => matches!(
260 algorithm,
261 KeyAgreementAlgorithm::EcdhEs | KeyAgreementAlgorithm::X25519
262 ),
263 };
264 permission(allowed, "key agreement", algorithm.uri())
265 }
266
267 pub fn check_encapsulation(
269 &self,
270 algorithm: crate::provider::KeyEncapsulationAlgorithm,
271 ) -> Result<(), PolicyViolation> {
272 permission(
273 self.encapsulation_algorithms.contains(&algorithm),
274 "key encapsulation",
275 algorithm.uri(),
276 )
277 }
278
279 pub fn check_derivation(
282 &self,
283 algorithm: KeyDerivationAlgorithm,
284 digest: DigestAlgorithm,
285 ) -> Result<(), PolicyViolation> {
286 let allowed = match &self.derivation_algorithms {
287 Some(allowed) => allowed.contains(&algorithm),
288 None => algorithm != KeyDerivationAlgorithm::LegacyDh,
289 };
290 permission(allowed, "key derivation", algorithm.uri())?;
291 self.check_derivation_digest(digest)
292 }
293
294 pub fn check_derivation_digest(&self, digest: DigestAlgorithm) -> Result<(), PolicyViolation> {
296 let allowed = match &self.digest_algorithms {
297 Some(allowed) => allowed.contains(&digest),
298 None => matches!(
299 digest,
300 DigestAlgorithm::Sha224
301 | DigestAlgorithm::Sha256
302 | DigestAlgorithm::Sha384
303 | DigestAlgorithm::Sha512
304 ),
305 };
306 permission(allowed, "key derivation digest", digest.uri())
307 }
308}
309
310fn permission(allowed: bool, operation: &'static str, uri: &str) -> Result<(), PolicyViolation> {
311 if allowed {
312 Ok(())
313 } else {
314 Err(PolicyViolation::Algorithm {
315 operation,
316 algorithm: uri.to_owned(),
317 })
318 }
319}