1use crate::policy::{KeyEstablishmentPolicy, PolicyViolation};
7use crate::provider::{
8 CryptoProvider, EncapsulatedKey, KeyDecapsulationKey, KeyEncapsulationAlgorithm,
9 KeyEncapsulationKey, ProviderCapability, ProviderError, ProviderInputError,
10};
11use zeroize::Zeroizing;
12
13#[derive(Debug, thiserror::Error)]
15pub enum KeyEstablishmentError {
16 #[error("invalid EncapsulationMechanism: {0}")]
18 Structure(&'static str),
19 #[error(transparent)]
21 Policy(#[from] PolicyViolation),
22 #[error(transparent)]
24 Provider(#[from] ProviderError),
25}
26
27pub const ENCAPSULATION_NS: &str = "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#";
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct EncapsulationMechanism {
33 pub algorithm: KeyEncapsulationAlgorithm,
35 pub key_info: crate::xmldsig::KeyInfo,
37 pub ciphertext: Vec<u8>,
39}
40
41pub struct ParsedEncapsulation<'a, 'input> {
44 pub algorithm: KeyEncapsulationAlgorithm,
46 pub key_info: crate::Node<'a, 'input>,
48 pub cipher_value: crate::Node<'a, 'input>,
50 ciphertext: [u8; 1568],
51 ciphertext_len: usize,
52}
53
54impl ParsedEncapsulation<'_, '_> {
55 pub fn ciphertext(&self) -> &[u8] {
57 &self.ciphertext[..self.ciphertext_len]
58 }
59
60 #[cfg(feature = "xmldsig")]
61 pub(crate) fn into_ciphertext(self) -> [u8; 1568] {
62 self.ciphertext
63 }
64}
65
66pub fn parse_encapsulation<'a, 'input>(
68 node: crate::Node<'a, 'input>,
69 template: bool,
70) -> Result<ParsedEncapsulation<'a, 'input>, KeyEstablishmentError> {
71 use base64::Engine as _;
72 let invalid = KeyEstablishmentError::Structure;
73 if !node.has_tag_name((ENCAPSULATION_NS, "EncapsulationMechanism")) {
74 return Err(invalid("wrong element name or namespace"));
75 }
76 let algorithm = node
77 .attribute("Algorithm")
78 .and_then(KeyEncapsulationAlgorithm::from_uri)
79 .ok_or(invalid("unsupported or missing Algorithm"))?;
80 let mut children = node.children().filter(crate::Node::is_element);
84 let key_info = children
85 .next()
86 .filter(|n| n.has_tag_name((crate::xmldsig::parse::XMLDSIG_NS, "KeyInfo")))
87 .ok_or(invalid("expected KeyInfo first"))?;
88 let data = children
89 .next()
90 .filter(|n| n.has_tag_name(("http://www.w3.org/2001/04/xmlenc#", "CipherData")))
91 .ok_or(invalid("expected CipherData after KeyInfo"))?;
92 if children.next().is_some() {
93 return Err(invalid("unexpected mechanism child"));
94 }
95 let mut children = data.children().filter(crate::Node::is_element);
96 let cipher_value = children
97 .next()
98 .filter(|n| n.has_tag_name(("http://www.w3.org/2001/04/xmlenc#", "CipherValue")))
99 .ok_or(invalid("expected CipherValue"))?;
100 if children.next().is_some() || cipher_value.children().any(|n| n.is_element()) {
101 return Err(invalid("unexpected CipherData or CipherValue child"));
102 }
103 for parent in [node, data] {
104 if parent.children().filter(crate::Node::is_text).any(|n| {
105 n.text()
106 .is_some_and(|s| !s.bytes().all(|b| matches!(b, b' ' | b'\t' | b'\r' | b'\n')))
107 }) {
108 return Err(invalid("non-whitespace mixed content"));
109 }
110 }
111 let mut encoded = [0u8; 2092];
112 let mut len = 0;
113 for text in cipher_value.children().filter(crate::Node::is_text) {
114 for byte in text.text().unwrap_or_default().bytes() {
115 if matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
116 continue;
117 }
118 if len == encoded.len() {
119 return Err(invalid("oversized ciphertext"));
120 }
121 encoded[len] = byte;
122 len += 1;
123 }
124 }
125 let mut ciphertext = [0u8; 1568];
126 let ciphertext_len = base64::engine::general_purpose::STANDARD
131 .decode_slice(&encoded[..len], &mut ciphertext)
132 .map_err(|_| invalid("invalid ciphertext base64"))?;
133 if ciphertext_len != algorithm.ciphertext_len() && !(template && ciphertext_len == 0) {
134 return Err(invalid("ciphertext size does not match Algorithm"));
135 }
136 Ok(ParsedEncapsulation {
137 algorithm,
138 key_info,
139 cipher_value,
140 ciphertext,
141 ciphertext_len,
142 })
143}
144
145pub fn direct_encapsulation<'a, 'input>(
147 key_info: crate::Node<'a, 'input>,
148 template: bool,
149) -> Result<Option<ParsedEncapsulation<'a, 'input>>, KeyEstablishmentError> {
150 let mut mechanisms = key_info
151 .children()
152 .filter(|n| n.has_tag_name((ENCAPSULATION_NS, "EncapsulationMechanism")));
153 let Some(node) = mechanisms.next() else {
154 return Ok(None);
155 };
156 if mechanisms.next().is_some() {
157 return Err(KeyEstablishmentError::Structure(
158 "multiple mechanisms in one KeyInfo",
159 ));
160 }
161 parse_encapsulation(node, template).map(Some)
162}
163
164#[derive(Debug, Default)]
166pub struct KeyEstablishmentUsage {
167 pub(crate) hash_blocks: usize,
168 pub(crate) owned_bytes: usize,
169 pub(crate) modular_work: usize,
170 encapsulation_operations: usize,
171}
172
173impl KeyEstablishmentUsage {
174 pub fn encapsulate(
176 &mut self,
177 policy: &KeyEstablishmentPolicy,
178 provider: &dyn CryptoProvider,
179 key: &dyn KeyEncapsulationKey,
180 ) -> Result<EncapsulatedKey, KeyEstablishmentError> {
181 let algorithm = key.algorithm();
182 policy.validate()?;
183 policy.check_encapsulation(algorithm)?;
184 provider.require_capability(ProviderCapability::Encapsulate(algorithm))?;
185 provider.require_capability(ProviderCapability::Random)?;
186 self.reserve_encapsulation(policy, algorithm.ciphertext_len())?;
187 let result = provider.encapsulate_key(key)?;
188 if result.ciphertext.len() != algorithm.ciphertext_len() {
189 return Err(ProviderError::InvalidInput(ProviderInputError::MlKemCiphertext).into());
190 }
191 Ok(result)
192 }
193
194 pub fn decapsulate(
196 &mut self,
197 policy: &KeyEstablishmentPolicy,
198 provider: &dyn CryptoProvider,
199 key: &dyn KeyDecapsulationKey,
200 algorithm: KeyEncapsulationAlgorithm,
201 ciphertext: &[u8],
202 ) -> Result<Zeroizing<[u8; 32]>, KeyEstablishmentError> {
203 policy.validate()?;
204 policy.check_encapsulation(algorithm)?;
205 if key.algorithm() != algorithm || ciphertext.len() != algorithm.ciphertext_len() {
206 return Err(ProviderError::InvalidInput(ProviderInputError::MlKemCiphertext).into());
207 }
208 provider.require_capability(ProviderCapability::Decapsulate(algorithm))?;
209 self.reserve_encapsulation(policy, 0)?;
210 Ok(provider.decapsulate_key(key, ciphertext)?)
211 }
212
213 fn reserve_encapsulation(
214 &mut self,
215 policy: &KeyEstablishmentPolicy,
216 output_bytes: usize,
217 ) -> Result<(), PolicyViolation> {
218 let operations = reserve(
219 "key encapsulation operations",
220 self.encapsulation_operations,
221 1,
222 policy.max_encapsulation_operations,
223 )?;
224 self.commit_all(policy, 0, 0, output_bytes as u128)?;
231 self.encapsulation_operations = operations;
232 Ok(())
233 }
234
235 pub(crate) fn commit_all(
236 &mut self,
237 policy: &KeyEstablishmentPolicy,
238 blocks: u128,
239 modular: u128,
240 allocations: u128,
241 ) -> Result<(), PolicyViolation> {
242 let work = reserve(
243 crate::policy::resource_name::KEY_ESTABLISHMENT_HASH_BLOCKS,
244 self.hash_blocks,
245 blocks,
246 policy.max_hash_blocks,
247 )?;
248 let bytes = reserve(
249 crate::policy::resource_name::KEY_ESTABLISHMENT_OWNED_BYTES,
250 self.owned_bytes,
251 allocations,
252 policy.max_owned_bytes,
253 )?;
254 let modular = reserve(
255 crate::policy::resource_name::KEY_ESTABLISHMENT_MODULAR_WORK,
256 self.modular_work,
257 modular,
258 policy.max_modular_work,
259 )?;
260 self.hash_blocks = work;
261 self.owned_bytes = bytes;
262 self.modular_work = modular;
263 Ok(())
264 }
265}
266
267pub(crate) fn reserve(
268 resource: &'static str,
269 used: usize,
270 count: u128,
271 maximum: usize,
272) -> Result<usize, PolicyViolation> {
273 if used > maximum || count > (maximum - used) as u128 {
274 return Err(PolicyViolation::ResourceLimit {
275 resource,
276 maximum,
277 actual: usize::try_from(used as u128 + count).unwrap_or(usize::MAX),
278 });
279 }
280 Ok(used + count as usize)
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::provider::RustCryptoProvider;
287
288 #[cfg(feature = "experimental-pq")]
289 struct FailedRandom;
290
291 #[cfg(feature = "experimental-pq")]
292 impl CryptoProvider for FailedRandom {
293 fn name(&self) -> &'static str {
294 "failed-random"
295 }
296 fn supports(&self, capability: ProviderCapability<'_>) -> bool {
297 RustCryptoProvider.supports(capability)
298 }
299 fn fill_random(&self, _: &mut [u8]) -> Result<(), ProviderError> {
300 Err(ProviderError::Random("test entropy failure".into()))
301 }
302 fn encapsulate_key(
303 &self,
304 key: &dyn KeyEncapsulationKey,
305 ) -> Result<EncapsulatedKey, ProviderError> {
306 key.encapsulate_with_provider(self)
307 }
308 fn begin_digest(
309 &self,
310 _: crate::xmldsig::DigestAlgorithm,
311 ) -> Result<Box<dyn crate::provider::DigestState + '_>, ProviderError> {
312 unreachable!("no digest after failed entropy")
313 }
314 fn sign(
315 &self,
316 _: &dyn crate::xmldsig::SigningKey,
317 _: crate::xmldsig::SignatureAlgorithm,
318 _: &[u8],
319 ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
320 unreachable!("no signing after failed entropy")
321 }
322 fn verify(
323 &self,
324 _: &dyn crate::xmldsig::VerifyingKey,
325 _: crate::xmldsig::SignatureAlgorithm,
326 _: &[u8],
327 _: &[u8],
328 ) -> Result<bool, crate::xmldsig::DsigError> {
329 unreachable!("no verification in encapsulation")
330 }
331 fn derive_key(
332 &self,
333 _: &crate::provider::KdfParameters<'_>,
334 _: &[u8],
335 ) -> Result<Vec<u8>, ProviderError> {
336 unreachable!("KEM does not use a KDF")
337 }
338 #[cfg(feature = "xmlenc")]
339 fn encrypt_data(
340 &self,
341 _: crate::xmlenc::DataEncryptionAlgorithm,
342 _: &[u8],
343 _: &[u8],
344 ) -> Result<Vec<u8>, ProviderError> {
345 unreachable!("no encryption after failed entropy")
346 }
347 #[cfg(feature = "xmlenc")]
348 fn decrypt_data(
349 &self,
350 _: crate::xmlenc::DataEncryptionAlgorithm,
351 _: &[u8],
352 _: &[u8],
353 ) -> Result<Vec<u8>, ProviderError> {
354 unreachable!("no decryption in encapsulation")
355 }
356 #[cfg(feature = "xmlenc")]
357 fn wrap_key(
358 &self,
359 _: crate::xmlenc::KeyWrapAlgorithm,
360 _: &[u8],
361 _: &[u8],
362 ) -> Result<Vec<u8>, ProviderError> {
363 unreachable!("no wrapping after failed entropy")
364 }
365 #[cfg(feature = "xmlenc")]
366 fn unwrap_key(
367 &self,
368 _: crate::xmlenc::KeyWrapAlgorithm,
369 _: &[u8],
370 _: &[u8],
371 ) -> Result<Vec<u8>, ProviderError> {
372 unreachable!("no unwrapping in encapsulation")
373 }
374 #[cfg(feature = "xmlenc")]
375 fn transport_key(
376 &self,
377 _: &dyn crate::provider::KeyTransportKey,
378 _: &crate::xmlenc::RsaOaepParameters,
379 _: &[u8],
380 ) -> Result<Vec<u8>, ProviderError> {
381 unreachable!("no RSA in encapsulation")
382 }
383 #[cfg(feature = "xmlenc")]
384 fn recover_key(
385 &self,
386 _: &dyn crate::provider::KeyRecoveryKey,
387 _: &crate::xmlenc::RsaOaepParameters,
388 _: &[u8],
389 ) -> Result<Vec<u8>, ProviderError> {
390 unreachable!("no RSA in encapsulation")
391 }
392 }
393
394 #[cfg(feature = "experimental-pq")]
395 #[test]
396 fn entropy_failure_is_terminal_and_charged() {
397 let algorithm = KeyEncapsulationAlgorithm::MlKem512;
400 let key = crate::provider::RustCryptoMlKemPrivateKey::from_seed(algorithm, &[1; 64])
401 .expect("deterministic test recipient")
402 .public_key();
403 let policy = KeyEstablishmentPolicy {
404 encapsulation_algorithms: [algorithm].into(),
405 max_encapsulation_operations: 1,
406 ..KeyEstablishmentPolicy::default()
407 };
408 let mut usage = KeyEstablishmentUsage::default();
409 assert!(matches!(
410 usage.encapsulate(&policy, &FailedRandom, &key),
411 Err(KeyEstablishmentError::Provider(ProviderError::Random(_)))
412 ));
413 assert_eq!(usage.encapsulation_operations, 1);
414 assert!(matches!(
415 usage.encapsulate(&policy, &RustCryptoProvider, &key),
416 Err(KeyEstablishmentError::Policy(
417 PolicyViolation::ResourceLimit { .. }
418 ))
419 ));
420 assert!(
421 crate::provider::RustCryptoMlKemPrivateKey::generate(&FailedRandom, algorithm).is_err()
422 );
423 }
424
425 #[test]
426 fn maximum_ciphertext_decodes_without_estimate_workspace() {
427 use base64::Engine as _;
428 let mechanism = |bytes: &[u8]| {
432 format!(
433 "<e:EncapsulationMechanism xmlns:e=\"{ENCAPSULATION_NS}\" xmlns:d=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:x=\"http://www.w3.org/2001/04/xmlenc#\" Algorithm=\"{}\"><d:KeyInfo/><x:CipherData><x:CipherValue>{}</x:CipherValue></x:CipherData></e:EncapsulationMechanism>",
434 KeyEncapsulationAlgorithm::MlKem1024.uri(),
435 base64::engine::general_purpose::STANDARD.encode(bytes)
436 )
437 };
438 let bytes = [7; 1568];
439 let xml = mechanism(&bytes);
440 let document = crate::Document::parse(&xml).expect("maximum ciphertext XML");
441 let parsed = parse_encapsulation(document.root_element(), false).expect("exact capacity");
442 assert_eq!(parsed.ciphertext(), bytes);
443 let spaced = xml.replace("BwcH", "Bw\n cH");
444 let document = crate::Document::parse(&spaced).expect("whitespace ciphertext XML");
445 assert_eq!(
446 parse_encapsulation(document.root_element(), false)
447 .expect("whitespace does not consume ciphertext capacity")
448 .ciphertext(),
449 bytes
450 );
451 let oversized = mechanism(&[7; 1569]);
452 let document = crate::Document::parse(&oversized).expect("oversized ciphertext XML");
453 assert!(parse_encapsulation(document.root_element(), false).is_err());
454 }
455
456 #[test]
457 fn mechanism_grammar_rejects_ambiguous_and_malformed_input() {
458 let valid = format!(
461 "<e:EncapsulationMechanism xmlns:e=\"{ENCAPSULATION_NS}\" xmlns:d=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:x=\"http://www.w3.org/2001/04/xmlenc#\" Algorithm=\"{}\"><d:KeyInfo/><x:CipherData><x:CipherValue/></x:CipherData></e:EncapsulationMechanism>",
462 KeyEncapsulationAlgorithm::MlKem512.uri()
463 );
464 let document = crate::Document::parse(&valid).expect("template XML");
465 assert!(parse_encapsulation(document.root_element(), true).is_ok());
466 assert!(parse_encapsulation(document.root_element(), false).is_err());
467 for invalid in [
468 valid.replace("<d:KeyInfo/>", "<d:KeyInfo/><d:KeyInfo/>"),
469 valid.replace("<d:KeyInfo/>", "unexpected<d:KeyInfo/>"),
470 valid.replace(
471 "<x:CipherValue/>",
472 "<x:CipherValue><d:KeyInfo/></x:CipherValue>",
473 ),
474 valid.replace("<x:CipherValue/>", "<x:CipherValue>AA==</x:CipherValue>"),
475 valid.replace("<x:CipherValue/>", "<x:CipherValue>!</x:CipherValue>"),
476 valid.replace("#ml-kem-512", "#ml-kem-512 "),
477 ] {
478 let document = crate::Document::parse(&invalid).expect("well-formed invalid mechanism");
479 assert!(parse_encapsulation(document.root_element(), true).is_err());
480 }
481 }
482
483 struct NeverCalled;
484 impl KeyDecapsulationKey for NeverCalled {
485 fn algorithm(&self) -> KeyEncapsulationAlgorithm {
486 KeyEncapsulationAlgorithm::MlKem768
487 }
488 fn decapsulate(&self, _: &[u8]) -> Result<Zeroizing<[u8; 32]>, ProviderError> {
489 panic!("preflight must reject before private key work")
490 }
491 }
492
493 #[test]
494 fn permission_and_framing_precede_private_key_work() {
495 let algorithm = KeyEncapsulationAlgorithm::MlKem768;
497 let mut usage = KeyEstablishmentUsage::default();
498 let mut policy = KeyEstablishmentPolicy::default();
499 assert!(matches!(
500 usage.decapsulate(&policy, &RustCryptoProvider, &NeverCalled, algorithm, &[]),
501 Err(KeyEstablishmentError::Policy(
502 PolicyViolation::Algorithm { .. }
503 ))
504 ));
505 policy.encapsulation_algorithms.insert(algorithm);
506 assert!(matches!(
507 usage.decapsulate(&policy, &RustCryptoProvider, &NeverCalled, algorithm, &[]),
508 Err(KeyEstablishmentError::Provider(
509 ProviderError::InvalidInput(_)
510 ))
511 ));
512 }
513
514 #[test]
515 fn failed_attempts_cannot_reset_shared_allowance() {
516 let mut usage = KeyEstablishmentUsage::default();
519 let mut policy = KeyEstablishmentPolicy {
520 max_encapsulation_operations: 1,
521 ..KeyEstablishmentPolicy::default()
522 };
523 usage
524 .reserve_encapsulation(&policy, 768)
525 .expect("first attempt fits");
526 let bytes = usage.owned_bytes;
527 assert!(usage.reserve_encapsulation(&policy, 768).is_err());
528 assert_eq!(usage.owned_bytes, bytes);
529 policy.max_owned_bytes = bytes;
530 assert!(usage.commit_all(&policy, 0, 0, 1).is_err());
531 assert_eq!(usage.owned_bytes, bytes);
532 }
533}