1use std::{fmt, sync::Arc};
4
5use crate::xml::dom::{Document, Node};
6use base64::{Engine as _, engine::general_purpose::STANDARD};
7use quick_xml::{
8 Writer,
9 events::{BytesEnd, BytesStart, BytesText, Event},
10};
11use rsa::RsaPublicKey;
12
13use crate::document::{
14 DocumentParseSettings, XmlDocument, XmlDocumentError, XmlParseWorkBudget,
15 parse_borrowed_with_settings_and_budget,
16};
17use crate::xml::{is_xml_1_0_character, is_xml_ncname};
18
19use super::types::{XMLDSIG_NS, XMLENC_NS, XMLENC11_NS};
20use super::{
21 DataEncryptionAlgorithm, DocumentEncryptionOptions, EncryptedDataType, EncryptionRecipient,
22 EncryptionResult, KeyWrapAlgorithm, ReplacementMode, RsaOaepParameters, XmlEncError,
23 has_single_element_with_boundary_trivia, map_document_error,
24};
25
26const XML_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r'];
27
28pub fn validate_rsa_recipient_key(
33 key: &RsaPublicKey,
34 policy: &crate::policy::EncryptionPolicy,
35) -> Result<(), XmlEncError> {
36 validate_key_transport_recipient(key, policy)
37}
38
39pub fn validate_key_transport_recipient(
45 key: &dyn crate::provider::KeyTransportKey,
46 policy: &crate::policy::EncryptionPolicy,
47) -> Result<(), XmlEncError> {
48 let modulus = key.rsa_modulus();
49 let exponent = key.rsa_exponent();
50 policy
51 .rsa_keys
52 .validate_components("encryption", &modulus, &exponent)?;
53 Ok(())
54}
55
56#[derive(Clone)]
58pub struct EncryptedDataBuilder {
59 algorithm: DataEncryptionAlgorithm,
60 encrypted_type: EncryptedDataType,
61 id: Option<String>,
62 direct_key: Option<Vec<u8>>,
63 direct_key_name: Option<String>,
64 recipients: Vec<EncryptionRecipient>,
65 policy: crate::policy::EncryptionPolicy,
66 provider: Arc<dyn crate::provider::CryptoProvider>,
67 xml_backend: crate::XmlBackend,
68}
69
70struct GeneratedEncryption {
71 result: EncryptionResult,
72 xml_nodes: usize,
73}
74
75impl fmt::Debug for EncryptedDataBuilder {
76 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77 formatter
78 .debug_struct("EncryptedDataBuilder")
79 .field("algorithm", &self.algorithm)
80 .field("encrypted_type", &self.encrypted_type)
81 .field("id", &self.id)
82 .field(
83 "direct_key",
84 &self.direct_key.as_ref().map(|_| "[REDACTED]"),
85 )
86 .field("direct_key_name", &self.direct_key_name)
87 .field("recipients", &self.recipients)
88 .field("policy", &self.policy)
89 .field("provider", &self.provider.name())
90 .finish()
91 }
92}
93
94impl EncryptedDataBuilder {
95 pub fn new(algorithm: DataEncryptionAlgorithm) -> Self {
97 Self {
98 algorithm,
99 encrypted_type: EncryptedDataType::Element,
100 id: None,
101 direct_key: None,
102 direct_key_name: None,
103 recipients: Vec::new(),
104 policy: crate::policy::EncryptionPolicy::default(),
105 provider: Arc::new(crate::provider::RustCryptoProvider),
106 xml_backend: crate::XmlBackend::default(),
107 }
108 }
109
110 pub fn policy(mut self, policy: crate::policy::EncryptionPolicy) -> Self {
112 self.policy = policy;
113 self
114 }
115
116 pub fn provider(mut self, provider: Arc<dyn crate::provider::CryptoProvider>) -> Self {
118 self.provider = provider;
119 self
120 }
121
122 pub fn xml_backend(mut self, backend: crate::XmlBackend) -> Self {
124 self.xml_backend = backend;
125 self
126 }
127
128 fn document_parse_settings(&self) -> DocumentParseSettings {
129 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources)
130 .with_backend(self.xml_backend)
131 }
132
133 pub fn encryption_type(mut self, encrypted_type: EncryptedDataType) -> Self {
135 self.encrypted_type = encrypted_type;
136 self
137 }
138
139 pub fn id(mut self, id: impl Into<String>) -> Self {
141 self.id = Some(id.into());
142 self
143 }
144
145 pub fn direct_key(mut self, key: impl Into<Vec<u8>>) -> Self {
147 self.direct_key = Some(key.into());
148 self
149 }
150
151 pub fn direct_key_name(mut self, key_name: impl Into<String>) -> Self {
153 self.direct_key_name = Some(key_name.into());
154 self
155 }
156
157 pub fn add_recipient(mut self, recipient: EncryptionRecipient) -> Self {
159 self.recipients.push(recipient);
160 self
161 }
162
163 pub fn recipient_rsa_oaep(self, public_key: RsaPublicKey) -> Self {
165 self.add_recipient(EncryptionRecipient::rsa_oaep(public_key))
166 }
167
168 pub fn recipient_key_transport(
170 self,
171 public_key: Arc<dyn crate::provider::KeyTransportKey>,
172 ) -> Self {
173 self.add_recipient(EncryptionRecipient::provider_key_transport(public_key))
174 }
175
176 pub fn recipient_aes_kw(self, kek: impl Into<Vec<u8>>, algorithm: KeyWrapAlgorithm) -> Self {
178 self.add_recipient(EncryptionRecipient::aes_key_wrap(kek, algorithm))
179 }
180
181 pub fn encrypt_xml(&self, xml: &str) -> Result<EncryptionResult, XmlEncError> {
183 self.policy.validate()?;
184 self.validate_plaintext_len(xml.len())?;
185 let parse_budget = XmlParseWorkBudget::from_resources(&self.policy.resources);
186 validate_xml_plaintext(
187 xml,
188 &self.encrypted_type,
189 &self.policy,
190 &parse_budget,
191 self.xml_backend,
192 )?;
193 let generated = self.encrypt_payload(
194 xml.as_bytes(),
195 Some(self.encrypted_type.clone()),
196 &parse_budget,
197 )?;
198 validate_standalone_encrypted_data_nodes(
199 generated.xml_nodes,
200 self.policy.resources.effective_xml_nodes() as usize,
201 )?;
202 Ok(generated.result)
203 }
204
205 pub fn encrypt_binary(&self, data: &[u8]) -> Result<EncryptionResult, XmlEncError> {
210 self.policy.validate()?;
211 let parse_budget = XmlParseWorkBudget::from_resources(&self.policy.resources);
212 let encrypted_type = match &self.encrypted_type {
213 EncryptedDataType::Other(uri) => Some(EncryptedDataType::Other(uri.clone())),
214 EncryptedDataType::Element | EncryptedDataType::Content => None,
215 };
216 let generated = self.encrypt_payload(data, encrypted_type, &parse_budget)?;
217 validate_standalone_encrypted_data_nodes(
218 generated.xml_nodes,
219 self.policy.resources.effective_xml_nodes() as usize,
220 )?;
221 Ok(generated.result)
222 }
223
224 pub fn encrypt_document(
226 &self,
227 xml: &str,
228 options: DocumentEncryptionOptions<'_>,
229 ) -> Result<String, XmlEncError> {
230 self.policy.validate()?;
231 self.validate_document_len(xml.len())?;
232 let parse_budget = XmlParseWorkBudget::from_resources(&self.policy.resources);
233 let settings = self.document_parse_settings();
234 let mut document =
235 XmlDocument::parse_with_settings_and_budget(xml.to_owned(), settings, &parse_budget)
236 .map_err(|error| map_document_error(error, settings))?;
237 self.encrypt_owned_document_with_budget(&mut document, options, &parse_budget)?;
238 Ok(document.into_xml())
239 }
240
241 pub fn encrypt_owned_document(
246 &self,
247 document: &mut XmlDocument,
248 options: DocumentEncryptionOptions<'_>,
249 ) -> Result<(), XmlEncError> {
250 let parse_budget = XmlParseWorkBudget::from_resources(&self.policy.resources);
251 self.encrypt_owned_document_with_budget(document, options, &parse_budget)
252 }
253
254 fn encrypt_owned_document_with_budget(
255 &self,
256 document: &mut XmlDocument,
257 options: DocumentEncryptionOptions<'_>,
258 parse_budget: &XmlParseWorkBudget,
259 ) -> Result<(), XmlEncError> {
260 self.policy.validate()?;
261 document.validate_operation_policy(&self.policy.xml, &self.policy.resources)?;
262 let document_nodes = document.with_view(|view| view.node_count());
263 let (target, source, content_boundaries, selected_nodes) = document.with_view(|view| {
264 let selected = select_encryption_target(view.document(), options.element_id)?;
265 let source = &view.xml()[selected.range()];
266 let content_boundaries = match self.encrypted_type {
267 EncryptedDataType::Element => {
268 self.validate_plaintext_len(source.len())?;
269 None
270 }
271 EncryptedDataType::Content => {
272 let boundaries = element_content_boundaries(source)?;
273 self.validate_plaintext_len(boundaries.content.len())?;
274 Some(boundaries)
275 }
276 EncryptedDataType::Other(_) => None,
277 };
278 Ok::<_, XmlEncError>((
279 view.node_identity(selected),
280 source.to_owned(),
281 content_boundaries,
282 selected.descendants().count(),
283 ))
284 })?;
285
286 match self.encrypted_type {
287 EncryptedDataType::Element => {
288 let generated = self.encrypt_payload(
289 source.as_bytes(),
290 Some(EncryptedDataType::Element),
291 parse_budget,
292 )?;
293 let result = generated.result;
294 validate_replacement_document_len(
295 document.as_xml().len(),
296 source.len(),
297 result.encrypted_data_xml.len(),
298 self.policy.resources.max_xml_document_bytes,
299 )?;
300 validate_replacement_node_counts(
301 document_nodes,
302 selected_nodes,
303 generated.xml_nodes,
304 ReplacementMode::ReplaceElement,
305 self.policy.resources.effective_xml_nodes() as usize,
306 )?;
307 let settings = self.document_parse_settings();
308 document
309 .replace_element_with_budget(
310 target,
311 &result.encrypted_data_xml,
312 settings,
313 parse_budget,
314 )
315 .map_err(|error| map_document_error(error, settings))?;
316 Ok(())
317 }
318 EncryptedDataType::Content => {
319 let boundaries = content_boundaries.ok_or_else(|| {
320 XmlEncError::InvalidStructure(
321 "content encryption target boundaries are unavailable".into(),
322 )
323 })?;
324 let plaintext = &source[boundaries.content.clone()];
325 let generated = self.encrypt_payload(
326 plaintext.as_bytes(),
327 Some(EncryptedDataType::Content),
328 parse_budget,
329 )?;
330 let result = generated.result;
331 let (removed, inserted) = if boundaries.self_closing {
332 let slash = source[..boundaries.start_tag_end]
333 .rfind('/')
334 .ok_or_else(|| {
335 XmlEncError::InvalidStructure("self-closing tag has no slash".into())
336 })?;
337 (
338 source.len(),
339 slash
340 .saturating_add(result.encrypted_data_xml.len())
341 .saturating_add(boundaries.qualified_name.len())
342 .saturating_add(4),
343 )
344 } else {
345 (boundaries.content.len(), result.encrypted_data_xml.len())
346 };
347 validate_replacement_document_len(
348 document.as_xml().len(),
349 removed,
350 inserted,
351 self.policy.resources.max_xml_document_bytes,
352 )?;
353 validate_replacement_node_counts(
354 document_nodes,
355 selected_nodes,
356 generated.xml_nodes,
357 ReplacementMode::ReplaceContent,
358 self.policy.resources.effective_xml_nodes() as usize,
359 )?;
360 let settings = self.document_parse_settings();
361 document
362 .replace_content_with_budget(
363 target,
364 &result.encrypted_data_xml,
365 settings,
366 parse_budget,
367 )
368 .map_err(|error| map_document_error(error, settings))?;
369 Ok(())
370 }
371 EncryptedDataType::Other(_) => Err(XmlEncError::InvalidEncryptionConfig(
372 "document encryption requires Element or Content Type".into(),
373 )),
374 }
375 }
376
377 fn encrypt_payload(
378 &self,
379 plaintext: &[u8],
380 encrypted_type: Option<EncryptedDataType>,
381 parse_budget: &XmlParseWorkBudget,
382 ) -> Result<GeneratedEncryption, XmlEncError> {
383 self.validate_plaintext_len(plaintext.len())?;
384 self.validate_configuration()?;
385
386 let content_key = if let Some(key) = &self.direct_key {
387 validate_content_key(self.algorithm, key)?;
388 key.clone()
389 } else {
390 random_bytes(self.provider.as_ref(), self.algorithm.key_len())?
391 };
392 let ciphertext = encrypt_content(
393 self.provider.as_ref(),
394 self.algorithm,
395 &content_key,
396 plaintext,
397 )?;
398 let encrypted_keys = self
399 .recipients
400 .iter()
401 .map(|recipient| wrap_content_key(self.provider.as_ref(), recipient, &content_key))
402 .collect::<Result<Vec<_>, _>>()?;
403 let encrypted_data_xml = render_encrypted_data(
404 self.algorithm,
405 encrypted_type.as_ref(),
406 self.id.as_deref(),
407 self.direct_key_name.as_deref(),
408 &encrypted_keys,
409 &ciphertext,
410 )?;
411 self.validate_document_len(encrypted_data_xml.len())?;
412 let xml_nodes = count_generated_encrypted_data_nodes(
413 &encrypted_data_xml,
414 &self.policy,
415 parse_budget,
416 self.xml_backend,
417 )?;
418 let replacement = match encrypted_type {
419 Some(EncryptedDataType::Content) => ReplacementMode::ReplaceContent,
420 Some(EncryptedDataType::Element | EncryptedDataType::Other(_)) | None => {
421 ReplacementMode::ReplaceElement
422 }
423 };
424 Ok(GeneratedEncryption {
425 result: EncryptionResult {
426 encrypted_data_xml,
427 replacement,
428 },
429 xml_nodes,
430 })
431 }
432
433 fn validate_configuration(&self) -> Result<(), XmlEncError> {
434 self.policy.validate()?;
435 if let EncryptedDataType::Other(uri) = &self.encrypted_type {
436 self.validate_metadata("EncryptedData Type", Some(uri))?;
437 }
438 if self
439 .policy
440 .data_algorithms
441 .as_ref()
442 .is_some_and(|allowed| !allowed.contains(&self.algorithm))
443 {
444 return Err(crate::policy::PolicyViolation::Algorithm {
445 operation: "encryption",
446 algorithm: self.algorithm.to_string(),
447 }
448 .into());
449 }
450 if self.recipients.len() > self.policy.resources.max_encryption_recipients {
451 return Err(crate::policy::PolicyViolation::ResourceLimit {
452 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
453 maximum: self.policy.resources.max_encryption_recipients,
454 actual: self.recipients.len(),
455 }
456 .into());
457 }
458 let key_candidates = self.recipients.len() + usize::from(self.direct_key.is_some());
459 self.policy
460 .resources
461 .validate_key_candidates(key_candidates)?;
462 self.validate_metadata("EncryptedData Id", self.id.as_deref())?;
463 if self.id.as_deref().is_some_and(|id| !is_xml_ncname(id)) {
464 return Err(XmlEncError::InvalidEncryptionConfig(
465 "EncryptedData Id must be an XML NCName".into(),
466 ));
467 }
468 self.validate_key_name("direct KeyName", self.direct_key_name.as_deref())?;
469 for recipient in &self.recipients {
470 match recipient {
471 EncryptionRecipient::RsaOaep {
472 public_key,
473 parameters,
474 recipient,
475 key_name,
476 } => {
477 validate_key_transport_recipient(public_key.as_ref(), &self.policy)?;
478 if parameters.algorithm == super::KeyTransportAlgorithm::RsaOaepMgf1p
479 && parameters.mgf_digest != super::OaepDigestAlgorithm::Sha1
480 {
481 return Err(XmlEncError::InvalidEncryptionConfig(
482 "legacy RSA-OAEP fixes MGF1 to SHA-1".into(),
483 ));
484 }
485 if self
486 .policy
487 .key_transport_algorithms
488 .as_ref()
489 .is_some_and(|allowed| !allowed.contains(¶meters.algorithm))
490 || self.policy.oaep_digests.as_ref().is_some_and(|allowed| {
491 !allowed.contains(¶meters.digest)
492 || !allowed.contains(¶meters.mgf_digest)
493 })
494 {
495 return Err(crate::policy::PolicyViolation::Algorithm {
496 operation: "encryption",
497 algorithm: parameters.algorithm.uri().to_string(),
498 }
499 .into());
500 }
501 self.validate_metadata("EncryptedKey Recipient", recipient.as_deref())?;
502 self.validate_key_name("EncryptedKey KeyName", key_name.as_deref())?;
503 self.validate_metadata_len(parameters.label.len())?;
504 }
505 EncryptionRecipient::AesKeyWrap {
506 kek,
507 algorithm,
508 recipient,
509 key_name,
510 } => {
511 if self
512 .policy
513 .key_wrap_algorithms
514 .as_ref()
515 .is_some_and(|allowed| !allowed.contains(algorithm))
516 {
517 return Err(crate::policy::PolicyViolation::Algorithm {
518 operation: "encryption",
519 algorithm: algorithm.uri().to_string(),
520 }
521 .into());
522 }
523 if kek.len() != algorithm.key_len() {
524 return Err(XmlEncError::InvalidEncryptionConfig(format!(
525 "{} requires a {}-byte key-encryption key, got {} bytes",
526 algorithm.uri(),
527 algorithm.key_len(),
528 kek.len()
529 )));
530 }
531 self.validate_metadata("EncryptedKey Recipient", recipient.as_deref())?;
532 self.validate_key_name("EncryptedKey KeyName", key_name.as_deref())?;
533 }
534 }
535 }
536 match (self.direct_key.is_some(), self.recipients.is_empty()) {
537 (false, true) => Err(XmlEncError::InvalidEncryptionConfig(
538 "configure a direct content key or at least one wrapped recipient".into(),
539 )),
540 (true, false) => Err(XmlEncError::InvalidEncryptionConfig(
541 "a direct content key cannot be combined with wrapped recipients".into(),
542 )),
543 _ if self.direct_key_name.is_some() && self.direct_key.is_none() => {
544 Err(XmlEncError::InvalidEncryptionConfig(
545 "direct KeyName requires a direct content key".into(),
546 ))
547 }
548 _ => Ok(()),
549 }
550 }
551
552 fn validate_metadata(
553 &self,
554 field: &'static str,
555 value: Option<&str>,
556 ) -> Result<(), XmlEncError> {
557 validate_metadata(
558 field,
559 value,
560 self.policy.resources.max_encryption_metadata_bytes,
561 )
562 }
563
564 fn validate_key_name(
565 &self,
566 field: &'static str,
567 value: Option<&str>,
568 ) -> Result<(), XmlEncError> {
569 validate_key_name(
570 field,
571 value,
572 self.policy.resources.max_encryption_metadata_bytes,
573 )
574 }
575
576 fn validate_metadata_len(&self, actual: usize) -> Result<(), XmlEncError> {
577 validate_metadata_len(actual, self.policy.resources.max_encryption_metadata_bytes)
578 }
579
580 fn validate_plaintext_len(&self, actual: usize) -> Result<(), XmlEncError> {
581 validate_plaintext_len(actual, self.policy.resources.max_encryption_plaintext_bytes)
582 }
583
584 fn validate_document_len(&self, actual: usize) -> Result<(), XmlEncError> {
585 validate_document_len(actual, self.policy.resources.max_xml_document_bytes)
586 }
587}
588
589fn validate_metadata(
590 field: &'static str,
591 value: Option<&str>,
592 maximum: usize,
593) -> Result<(), XmlEncError> {
594 if value.is_some_and(|value| !value.chars().all(is_xml_1_0_character)) {
595 return Err(XmlEncError::InvalidEncryptionConfig(format!(
596 "{field} contains a character forbidden by XML 1.0"
597 )));
598 }
599 validate_metadata_len(value.map_or(0, str::len), maximum)
600}
601
602fn validate_key_name(
603 field: &'static str,
604 value: Option<&str>,
605 maximum: usize,
606) -> Result<(), XmlEncError> {
607 if value.is_some_and(str::is_empty) {
608 return Err(XmlEncError::InvalidEncryptionConfig(format!(
609 "{field} must not be empty"
610 )));
611 }
612 validate_metadata(field, value, maximum)
613}
614
615fn validate_metadata_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
616 if actual <= maximum {
617 Ok(())
618 } else {
619 Err(crate::policy::PolicyViolation::ResourceLimit {
620 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
621 maximum,
622 actual,
623 }
624 .into())
625 }
626}
627
628#[derive(Debug)]
629struct WrappedKey {
630 algorithm_uri: &'static str,
631 oaep: Option<RsaOaepParameters>,
632 recipient: Option<String>,
633 key_name: Option<String>,
634 ciphertext: Vec<u8>,
635}
636
637#[derive(Debug)]
638struct ContentBoundaries {
639 content: std::ops::Range<usize>,
640 self_closing: bool,
641 qualified_name: String,
642 start_tag_end: usize,
643}
644
645fn validate_plaintext_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
646 if actual <= maximum {
647 Ok(())
648 } else {
649 Err(crate::policy::PolicyViolation::ResourceLimit {
650 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
651 maximum,
652 actual,
653 }
654 .into())
655 }
656}
657
658fn validate_document_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
659 if actual > maximum {
660 return Err(crate::policy::PolicyViolation::ResourceLimit {
661 resource: crate::policy::resource_name::XML_DOCUMENT,
662 maximum,
663 actual,
664 }
665 .into());
666 }
667 Ok(())
668}
669
670fn validate_replacement_document_len(
671 document_len: usize,
672 removed_len: usize,
673 inserted_len: usize,
674 maximum: usize,
675) -> Result<(), XmlEncError> {
676 let actual = document_len
677 .saturating_sub(removed_len)
678 .saturating_add(inserted_len);
679 validate_document_len(actual, maximum)
680}
681
682fn validate_replacement_node_counts(
683 document_nodes: usize,
684 selected_nodes: usize,
685 inserted_nodes: usize,
686 replacement: ReplacementMode,
687 maximum: usize,
688) -> Result<(), XmlEncError> {
689 let removed_nodes = match replacement {
690 ReplacementMode::ReplaceElement => selected_nodes,
691 ReplacementMode::ReplaceContent => selected_nodes.saturating_sub(1),
692 };
693 let actual = document_nodes
694 .saturating_sub(removed_nodes)
695 .saturating_add(inserted_nodes);
696 if actual > maximum {
697 return Err(crate::policy::PolicyViolation::ResourceLimit {
698 resource: crate::policy::resource_name::XML_NODES,
699 maximum,
700 actual,
701 }
702 .into());
703 }
704 Ok(())
705}
706
707fn count_generated_encrypted_data_nodes(
708 encrypted_data_xml: &str,
709 policy: &crate::policy::EncryptionPolicy,
710 parse_budget: &XmlParseWorkBudget,
711 backend: crate::XmlBackend,
712) -> Result<usize, XmlEncError> {
713 let maximum_nodes = policy.resources.effective_xml_nodes();
714 let settings = DocumentParseSettings {
718 nodes_limit: maximum_nodes.saturating_add(1),
719 ..DocumentParseSettings::from_policy(&policy.xml, &policy.resources).with_backend(backend)
720 };
721 let generated =
722 parse_borrowed_with_settings_and_budget(encrypted_data_xml, settings, Some(parse_budget))
723 .map_err(|error| match error {
724 XmlDocumentError::Parse(crate::xml::dom::ParseError::NodesLimitReached) => {
725 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
726 resource: crate::policy::resource_name::XML_NODES,
727 maximum: maximum_nodes as usize,
728 actual: maximum_nodes as usize + 1,
729 })
730 }
731 error => map_document_error(error, settings),
732 })?;
733 Ok(generated.root_element().descendants().count())
734}
735
736fn validate_standalone_encrypted_data_nodes(
737 encrypted_data_subtree_nodes: usize,
738 maximum: usize,
739) -> Result<(), XmlEncError> {
740 let actual = encrypted_data_subtree_nodes.saturating_add(1);
741 if actual > maximum {
742 return Err(crate::policy::PolicyViolation::ResourceLimit {
743 resource: crate::policy::resource_name::XML_NODES,
744 maximum,
745 actual,
746 }
747 .into());
748 }
749 Ok(())
750}
751
752fn validate_content_key(algorithm: DataEncryptionAlgorithm, key: &[u8]) -> Result<(), XmlEncError> {
753 if key.len() == algorithm.key_len() {
754 Ok(())
755 } else {
756 Err(XmlEncError::InvalidKeySize {
757 algorithm,
758 expected: algorithm.key_len(),
759 actual: key.len(),
760 })
761 }
762}
763
764fn random_bytes(
765 provider: &dyn crate::provider::CryptoProvider,
766 len: usize,
767) -> Result<Vec<u8>, XmlEncError> {
768 provider.require_capability(crate::provider::ProviderCapability::Random)?;
769 let mut bytes = vec![0_u8; len];
770 provider.fill_random(&mut bytes)?;
771 Ok(bytes)
772}
773
774fn encrypt_content(
775 provider: &dyn crate::provider::CryptoProvider,
776 algorithm: DataEncryptionAlgorithm,
777 key: &[u8],
778 plaintext: &[u8],
779) -> Result<Vec<u8>, XmlEncError> {
780 provider.require_capability(crate::provider::ProviderCapability::Encrypt(algorithm))?;
781 let ciphertext = provider.encrypt_data(algorithm, key, plaintext)?;
782 super::types::validate_ciphertext_framing(algorithm, ciphertext.len())?;
783 let expected = algorithm
784 .ciphertext_len_for_plaintext(plaintext.len())
785 .ok_or(crate::policy::PolicyViolation::ResourceLimit {
786 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
787 maximum: crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
788 actual: plaintext.len(),
789 })?;
790 if ciphertext.len() != expected {
791 return Err(crate::provider::ProviderError::InvalidOutputSize {
792 operation: crate::provider::ProviderOperation::Encrypt,
793 expected,
794 actual: ciphertext.len(),
795 }
796 .into());
797 }
798 Ok(ciphertext)
799}
800
801fn wrap_content_key(
802 provider: &dyn crate::provider::CryptoProvider,
803 recipient: &EncryptionRecipient,
804 content_key: &[u8],
805) -> Result<WrappedKey, XmlEncError> {
806 match recipient {
807 EncryptionRecipient::RsaOaep {
808 public_key,
809 parameters,
810 recipient,
811 key_name,
812 } => Ok(WrappedKey {
813 algorithm_uri: parameters.algorithm.uri(),
814 oaep: Some(parameters.clone()),
815 recipient: recipient.clone(),
816 key_name: key_name.clone(),
817 ciphertext: wrap_rsa_oaep(provider, public_key.as_ref(), parameters, content_key)?,
818 }),
819 EncryptionRecipient::AesKeyWrap {
820 kek,
821 algorithm,
822 recipient,
823 key_name,
824 } => {
825 provider
826 .require_capability(crate::provider::ProviderCapability::KeyWrap(*algorithm))?;
827 let wrapped = provider.wrap_key(*algorithm, kek, content_key)?;
828 let expected = content_key.len() + 8;
829 if wrapped.len() != expected {
830 return Err(XmlEncError::InvalidWrappedKeyLength {
831 expected,
832 actual: wrapped.len(),
833 });
834 }
835 Ok(WrappedKey {
836 algorithm_uri: algorithm.uri(),
837 oaep: None,
838 recipient: recipient.clone(),
839 key_name: key_name.clone(),
840 ciphertext: wrapped,
841 })
842 }
843 }
844}
845
846fn wrap_rsa_oaep(
847 provider: &dyn crate::provider::CryptoProvider,
848 public_key: &dyn crate::provider::KeyTransportKey,
849 parameters: &RsaOaepParameters,
850 content_key: &[u8],
851) -> Result<Vec<u8>, XmlEncError> {
852 provider.require_capability(crate::provider::ProviderCapability::KeyTransport(
853 parameters,
854 ))?;
855 let ciphertext = provider
856 .transport_key(public_key, parameters, content_key)
857 .map_err(|error| match error {
858 crate::provider::ProviderError::Random(message) => XmlEncError::Rng(message),
859 crate::provider::ProviderError::InvalidInput(reason) => {
860 XmlEncError::InvalidEncryptionConfig(reason.to_string())
861 }
862 error => XmlEncError::RsaEncrypt(error.to_string()),
863 })?;
864 let expected = public_key.rsa_modulus().len();
865 if ciphertext.len() != expected {
866 return Err(XmlEncError::InvalidWrappedKeyLength {
867 expected,
868 actual: ciphertext.len(),
869 });
870 }
871 Ok(ciphertext)
872}
873
874fn render_encrypted_data(
875 algorithm: DataEncryptionAlgorithm,
876 encrypted_type: Option<&EncryptedDataType>,
877 id: Option<&str>,
878 direct_key_name: Option<&str>,
879 encrypted_keys: &[WrappedKey],
880 ciphertext: &[u8],
881) -> Result<String, XmlEncError> {
882 let mut writer = Writer::new(Vec::new());
883 let mut root = BytesStart::new("xenc:EncryptedData");
884 root.push_attribute(("xmlns:xenc", XMLENC_NS));
885 root.push_attribute(("xmlns:xenc11", XMLENC11_NS));
886 root.push_attribute(("xmlns:ds", XMLDSIG_NS));
887 if let Some(id) = id {
888 root.push_attribute(("Id", id));
889 }
890 if let Some(encrypted_type) = encrypted_type {
891 let uri = match encrypted_type {
892 EncryptedDataType::Element => format!("{XMLENC_NS}Element"),
893 EncryptedDataType::Content => format!("{XMLENC_NS}Content"),
894 EncryptedDataType::Other(uri) => uri.clone(),
895 };
896 root.push_attribute(("Type", uri.as_str()));
897 }
898 write_event(&mut writer, Event::Start(root))?;
899 write_empty_with_algorithm(&mut writer, "xenc:EncryptionMethod", algorithm.uri())?;
900
901 if direct_key_name.is_some() || !encrypted_keys.is_empty() {
902 write_event(&mut writer, Event::Start(BytesStart::new("ds:KeyInfo")))?;
903 if let Some(key_name) = direct_key_name {
904 write_text_element(&mut writer, "ds:KeyName", key_name)?;
905 }
906 for encrypted_key in encrypted_keys {
907 write_encrypted_key(&mut writer, encrypted_key)?;
908 }
909 write_event(&mut writer, Event::End(BytesEnd::new("ds:KeyInfo")))?;
910 }
911
912 write_cipher_data(&mut writer, ciphertext)?;
913 write_event(&mut writer, Event::End(BytesEnd::new("xenc:EncryptedData")))?;
914 String::from_utf8(writer.into_inner())
915 .map_err(|error| XmlEncError::XmlSerialize(error.to_string()))
916}
917
918fn write_encrypted_key(
919 writer: &mut Writer<Vec<u8>>,
920 encrypted_key: &WrappedKey,
921) -> Result<(), XmlEncError> {
922 let mut start = BytesStart::new("xenc:EncryptedKey");
923 if let Some(recipient) = encrypted_key.recipient.as_deref() {
924 start.push_attribute(("Recipient", recipient));
925 }
926 write_event(writer, Event::Start(start))?;
927
928 if let Some(parameters) = encrypted_key.oaep.as_ref() {
929 let mut method = BytesStart::new("xenc:EncryptionMethod");
930 method.push_attribute(("Algorithm", encrypted_key.algorithm_uri));
931 write_event(writer, Event::Start(method))?;
932 if !parameters.label.is_empty() {
933 write_text_element(
934 writer,
935 "xenc:OAEPparams",
936 &STANDARD.encode(¶meters.label),
937 )?;
938 }
939 write_empty_with_algorithm(writer, "ds:DigestMethod", parameters.digest.uri())?;
940 if parameters.algorithm == super::KeyTransportAlgorithm::RsaOaep11 {
941 write_empty_with_algorithm(writer, "xenc11:MGF", parameters.mgf_digest.mgf_uri())?;
942 }
943 write_event(writer, Event::End(BytesEnd::new("xenc:EncryptionMethod")))?;
944 } else {
945 write_empty_with_algorithm(writer, "xenc:EncryptionMethod", encrypted_key.algorithm_uri)?;
946 }
947
948 if let Some(key_name) = encrypted_key.key_name.as_deref() {
949 write_event(writer, Event::Start(BytesStart::new("ds:KeyInfo")))?;
950 write_text_element(writer, "ds:KeyName", key_name)?;
951 write_event(writer, Event::End(BytesEnd::new("ds:KeyInfo")))?;
952 }
953 write_cipher_data(writer, &encrypted_key.ciphertext)?;
954 write_event(writer, Event::End(BytesEnd::new("xenc:EncryptedKey")))
955}
956
957fn write_cipher_data(writer: &mut Writer<Vec<u8>>, value: &[u8]) -> Result<(), XmlEncError> {
958 write_event(writer, Event::Start(BytesStart::new("xenc:CipherData")))?;
959 write_text_element(writer, "xenc:CipherValue", &STANDARD.encode(value))?;
960 write_event(writer, Event::End(BytesEnd::new("xenc:CipherData")))
961}
962
963fn write_empty_with_algorithm(
964 writer: &mut Writer<Vec<u8>>,
965 name: &str,
966 algorithm: &str,
967) -> Result<(), XmlEncError> {
968 let mut element = BytesStart::new(name);
969 element.push_attribute(("Algorithm", algorithm));
970 write_event(writer, Event::Empty(element))
971}
972
973fn write_text_element(
974 writer: &mut Writer<Vec<u8>>,
975 name: &str,
976 text: &str,
977) -> Result<(), XmlEncError> {
978 write_event(writer, Event::Start(BytesStart::new(name)))?;
979 write_event(writer, Event::Text(BytesText::new(text)))?;
980 write_event(writer, Event::End(BytesEnd::new(name)))
981}
982
983fn write_event(writer: &mut Writer<Vec<u8>>, event: Event<'_>) -> Result<(), XmlEncError> {
984 writer
985 .write_event(event)
986 .map_err(|error| XmlEncError::XmlSerialize(error.to_string()))
987}
988
989fn validate_xml_plaintext(
990 xml: &str,
991 encrypted_type: &EncryptedDataType,
992 policy: &crate::policy::EncryptionPolicy,
993 parse_budget: &XmlParseWorkBudget,
994 backend: crate::XmlBackend,
995) -> Result<(), XmlEncError> {
996 let settings =
997 DocumentParseSettings::from_policy(&policy.xml, &policy.resources).with_backend(backend);
998 match encrypted_type {
999 EncryptedDataType::Element => {
1000 let document =
1001 parse_borrowed_with_settings_and_budget(xml, settings, Some(parse_budget))
1002 .map_err(|error| map_document_error(error, settings))?;
1003 if !has_single_element_with_boundary_trivia(document.root()) {
1004 return Err(XmlEncError::InvalidStructure(
1005 "Element plaintext must contain exactly one element".into(),
1006 ));
1007 }
1008 Ok(())
1009 }
1010 EncryptedDataType::Content => {
1011 const WRAPPER_START: &str = "<xmlsec-content>";
1012 const WRAPPER_END: &str = "</xmlsec-content>";
1013
1014 policy.resources.validate_xml_document_len(xml.len())?;
1015 let wrapped = format!("{WRAPPER_START}{xml}{WRAPPER_END}");
1016 let wrapper_bytes = WRAPPER_START.len() + WRAPPER_END.len();
1017 let wrapped_settings = DocumentParseSettings {
1020 nodes_limit: settings.nodes_limit.saturating_add(1),
1021 depth_limit: settings.depth_limit.saturating_add(1),
1022 max_bytes: settings.max_bytes.saturating_add(wrapper_bytes),
1023 ..settings
1024 };
1025 parse_borrowed_with_settings_and_budget(&wrapped, wrapped_settings, Some(parse_budget))
1026 .map_err(|error| match error {
1027 XmlDocumentError::DocumentTooLarge { actual, .. } => {
1028 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1029 resource: crate::policy::resource_name::XML_DOCUMENT,
1030 maximum: settings.max_bytes,
1031 actual: actual.saturating_sub(wrapper_bytes),
1032 })
1033 }
1034 XmlDocumentError::Parse(crate::xml::dom::ParseError::NodesLimitReached) => {
1035 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1036 resource: crate::policy::resource_name::XML_NODES,
1037 maximum: settings.nodes_limit as usize,
1038 actual: settings.nodes_limit as usize + 1,
1039 })
1040 }
1041 XmlDocumentError::DocumentTooDeep { actual, .. } => {
1042 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1043 resource: crate::policy::resource_name::XML_DEPTH,
1044 maximum: settings.depth_limit,
1045 actual: actual.saturating_sub(1),
1046 })
1047 }
1048 error => map_document_error(error, wrapped_settings),
1049 })?;
1050 Ok(())
1051 }
1052 EncryptedDataType::Other(_) => Err(XmlEncError::InvalidEncryptionConfig(
1053 "encrypt_xml requires Element or Content Type".into(),
1054 )),
1055 }
1056}
1057
1058fn select_encryption_target<'a, 'input>(
1059 document: &'a Document<'input>,
1060 id: Option<&str>,
1061) -> Result<Node<'a, 'input>, XmlEncError> {
1062 let Some(id) = id else {
1063 return Ok(document.root_element());
1064 };
1065 let mut matches = document.descendants().filter(|node| {
1066 node.is_element()
1067 && ["Id", "ID", "id"]
1068 .iter()
1069 .any(|name| node.attribute(*name) == Some(id))
1070 });
1071 let selected = matches
1072 .next()
1073 .ok_or(XmlEncError::EncryptionTargetNotFound)?;
1074 if matches.next().is_some() {
1075 return Err(XmlEncError::AmbiguousEncryptionTarget);
1076 }
1077 Ok(selected)
1078}
1079
1080fn element_content_boundaries(source: &str) -> Result<ContentBoundaries, XmlEncError> {
1081 let tag_end = find_start_tag_end(source)?;
1082 let before_end = source[..tag_end].trim_end_matches(XML_WHITESPACE);
1083 let self_closing = before_end.ends_with('/');
1084 let name_end = source[1..]
1085 .find(|character: char| character.is_ascii_whitespace() || matches!(character, '/' | '>'))
1086 .map(|index| index + 1)
1087 .ok_or_else(|| XmlEncError::InvalidStructure("source element has no name".into()))?;
1088 let qualified_name = source[1..name_end].to_owned();
1089 if self_closing {
1090 return Ok(ContentBoundaries {
1091 content: tag_end..tag_end,
1092 self_closing: true,
1093 qualified_name,
1094 start_tag_end: tag_end,
1095 });
1096 }
1097 let closing_start = source
1100 .rfind("</")
1101 .ok_or_else(|| XmlEncError::InvalidStructure("source element has no closing tag".into()))?;
1102 Ok(ContentBoundaries {
1103 content: tag_end + 1..closing_start,
1104 self_closing: false,
1105 qualified_name,
1106 start_tag_end: tag_end,
1107 })
1108}
1109
1110fn find_start_tag_end(source: &str) -> Result<usize, XmlEncError> {
1111 let mut quote = None;
1112 for (index, character) in source.char_indices() {
1113 match (quote, character) {
1114 (Some(expected), actual) if expected == actual => quote = None,
1115 (None, '\'' | '"') => quote = Some(character),
1116 (None, '>') => return Ok(index),
1117 _ => {}
1118 }
1119 }
1120 Err(XmlEncError::InvalidStructure(
1121 "source element start tag is unterminated".into(),
1122 ))
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 use std::sync::atomic::{AtomicUsize, Ordering};
1128
1129 use std::sync::Arc;
1130
1131 use getrandom::SysRng;
1132 use getrandom::rand_core::UnwrapErr;
1133 use rsa::pkcs8::DecodePublicKey as _;
1134 use rsa::{RsaPrivateKey, RsaPublicKey};
1135
1136 use super::*;
1137 use crate::hard_limits::{
1138 ENCRYPTION_METADATA_BYTE_CEILING as MAX_ENCRYPTION_METADATA_LEN,
1139 ENCRYPTION_PLAINTEXT_BYTE_CEILING as MAX_ENCRYPTION_PLAINTEXT_LEN,
1140 ENCRYPTION_RECIPIENT_CEILING as MAX_ENCRYPTION_RECIPIENTS,
1141 XML_DOCUMENT_BYTE_CEILING as MAX_ENCRYPTION_DOCUMENT_LEN,
1142 };
1143 use crate::xmlenc::{
1144 KekDecryptor, OaepDigestAlgorithm, PrivateKeyDecryptor, SymmetricKeyDecryptor, decrypt,
1145 decrypt_document, parse_encrypted_data,
1146 };
1147
1148 #[derive(Debug)]
1149 struct OverridingOutputProvider {
1150 ciphertext: Option<Vec<u8>>,
1151 wrapped_key: Option<Vec<u8>>,
1152 transported_key: Option<Vec<u8>>,
1153 transport_calls: AtomicUsize,
1154 }
1155
1156 struct OpaqueTransportKey {
1157 modulus: Vec<u8>,
1158 exponent: Vec<u8>,
1159 }
1160
1161 impl crate::provider::KeyTransportKey for OpaqueTransportKey {
1162 fn rsa_modulus(&self) -> std::borrow::Cow<'_, [u8]> {
1163 std::borrow::Cow::Borrowed(&self.modulus)
1164 }
1165
1166 fn rsa_exponent(&self) -> std::borrow::Cow<'_, [u8]> {
1167 std::borrow::Cow::Borrowed(&self.exponent)
1168 }
1169
1170 fn transport_with_provider(
1171 &self,
1172 _provider: &dyn crate::provider::CryptoProvider,
1173 _parameters: &RsaOaepParameters,
1174 _plaintext: &[u8],
1175 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1176 panic!("custom provider must own transport for its opaque key")
1177 }
1178 }
1179
1180 impl crate::provider::CryptoProvider for OverridingOutputProvider {
1181 fn name(&self) -> &'static str {
1182 "overriding-output-test"
1183 }
1184
1185 fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
1186 crate::provider::CryptoProvider::supports(
1187 &crate::provider::RustCryptoProvider,
1188 capability,
1189 )
1190 }
1191
1192 fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
1193 crate::provider::CryptoProvider::fill_random(
1194 &crate::provider::RustCryptoProvider,
1195 output,
1196 )
1197 }
1198
1199 fn derive_key(
1200 &self,
1201 parameters: &crate::provider::KdfParameters<'_>,
1202 secret: &[u8],
1203 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1204 crate::provider::RustCryptoProvider.derive_key(parameters, secret)
1205 }
1206
1207 #[cfg(feature = "xmldsig")]
1208 fn digest(
1209 &self,
1210 algorithm: crate::xmldsig::DigestAlgorithm,
1211 data: &[u8],
1212 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1213 crate::provider::CryptoProvider::digest(
1214 &crate::provider::RustCryptoProvider,
1215 algorithm,
1216 data,
1217 )
1218 }
1219
1220 #[cfg(feature = "xmldsig")]
1221 fn sign(
1222 &self,
1223 key: &dyn crate::xmldsig::SigningKey,
1224 algorithm: crate::xmldsig::SignatureAlgorithm,
1225 data: &[u8],
1226 ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
1227 crate::provider::CryptoProvider::sign(
1228 &crate::provider::RustCryptoProvider,
1229 key,
1230 algorithm,
1231 data,
1232 )
1233 }
1234
1235 #[cfg(feature = "xmldsig")]
1236 fn verify(
1237 &self,
1238 key: &dyn crate::xmldsig::VerifyingKey,
1239 algorithm: crate::xmldsig::SignatureAlgorithm,
1240 data: &[u8],
1241 signature: &[u8],
1242 ) -> Result<bool, crate::xmldsig::DsigError> {
1243 crate::provider::CryptoProvider::verify(
1244 &crate::provider::RustCryptoProvider,
1245 key,
1246 algorithm,
1247 data,
1248 signature,
1249 )
1250 }
1251
1252 fn encrypt_data(
1253 &self,
1254 algorithm: DataEncryptionAlgorithm,
1255 key: &[u8],
1256 plaintext: &[u8],
1257 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1258 if let Some(ciphertext) = &self.ciphertext {
1259 return Ok(ciphertext.clone());
1260 }
1261 crate::provider::CryptoProvider::encrypt_data(
1262 &crate::provider::RustCryptoProvider,
1263 algorithm,
1264 key,
1265 plaintext,
1266 )
1267 }
1268
1269 fn decrypt_data(
1270 &self,
1271 algorithm: DataEncryptionAlgorithm,
1272 key: &[u8],
1273 ciphertext: &[u8],
1274 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1275 crate::provider::CryptoProvider::decrypt_data(
1276 &crate::provider::RustCryptoProvider,
1277 algorithm,
1278 key,
1279 ciphertext,
1280 )
1281 }
1282
1283 fn wrap_key(
1284 &self,
1285 algorithm: KeyWrapAlgorithm,
1286 kek: &[u8],
1287 key: &[u8],
1288 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1289 if let Some(wrapped_key) = &self.wrapped_key {
1290 return Ok(wrapped_key.clone());
1291 }
1292 crate::provider::CryptoProvider::wrap_key(
1293 &crate::provider::RustCryptoProvider,
1294 algorithm,
1295 kek,
1296 key,
1297 )
1298 }
1299
1300 fn unwrap_key(
1301 &self,
1302 algorithm: KeyWrapAlgorithm,
1303 kek: &[u8],
1304 wrapped: &[u8],
1305 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1306 crate::provider::CryptoProvider::unwrap_key(
1307 &crate::provider::RustCryptoProvider,
1308 algorithm,
1309 kek,
1310 wrapped,
1311 )
1312 }
1313
1314 fn transport_key(
1315 &self,
1316 key: &dyn crate::provider::KeyTransportKey,
1317 parameters: &RsaOaepParameters,
1318 plaintext: &[u8],
1319 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1320 self.transport_calls.fetch_add(1, Ordering::Relaxed);
1321 if let Some(transported_key) = &self.transported_key {
1322 return Ok(transported_key.clone());
1323 }
1324 crate::provider::CryptoProvider::transport_key(
1325 &crate::provider::RustCryptoProvider,
1326 key,
1327 parameters,
1328 plaintext,
1329 )
1330 }
1331
1332 fn recover_key(
1333 &self,
1334 key: &dyn crate::provider::KeyRecoveryKey,
1335 parameters: &RsaOaepParameters,
1336 ciphertext: &[u8],
1337 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1338 crate::provider::CryptoProvider::recover_key(
1339 &crate::provider::RustCryptoProvider,
1340 key,
1341 parameters,
1342 ciphertext,
1343 )
1344 }
1345 }
1346
1347 #[test]
1348 fn direct_key_round_trips_every_content_algorithm() {
1349 for algorithm in [
1352 DataEncryptionAlgorithm::Aes128Cbc,
1353 DataEncryptionAlgorithm::Aes256Cbc,
1354 DataEncryptionAlgorithm::Aes128Gcm,
1355 DataEncryptionAlgorithm::Aes256Gcm,
1356 ] {
1357 for plaintext in [b"".as_slice(), b"sixteen-byte-msg", b"not aligned"] {
1358 let key = vec![0x31; algorithm.key_len()];
1359 let encrypted = EncryptedDataBuilder::new(algorithm)
1360 .direct_key(key.clone())
1361 .direct_key_name("content-key")
1362 .encrypt_binary(plaintext)
1363 .expect("supported direct encryption must succeed");
1364 assert_eq!(
1365 decrypt(
1366 &encrypted.encrypted_data_xml,
1367 &SymmetricKeyDecryptor::new(key)
1368 )
1369 .expect("generated ciphertext must decrypt"),
1370 super::super::DecryptedContent::Bytes(plaintext.to_vec())
1371 );
1372 }
1373 }
1374 }
1375
1376 #[test]
1377 fn aes_key_wrap_round_trips_and_preserves_recipient_metadata() {
1378 let kek = [0x44; 32];
1379 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1380 .add_recipient(
1381 EncryptionRecipient::aes_key_wrap(kek, KeyWrapAlgorithm::AesKw256)
1382 .recipient("service-a")
1383 .key_name("shared-kek"),
1384 )
1385 .encrypt_xml("<secret>value</secret>")
1386 .expect("AES-KW encryption must succeed");
1387 let parsed = parse_encrypted_data(&encrypted.encrypted_data_xml)
1388 .expect("generated EncryptedData must parse");
1389 assert_eq!(
1390 parsed.encrypted_keys[0].recipient.as_deref(),
1391 Some("service-a")
1392 );
1393 assert_eq!(
1394 parsed.encrypted_keys[0].key_name.as_deref(),
1395 Some("shared-kek")
1396 );
1397 assert_eq!(
1398 decrypt(&encrypted.encrypted_data_xml, &KekDecryptor::new(kek))
1399 .expect("wrapped key must decrypt"),
1400 super::super::DecryptedContent::Xml("<secret>value</secret>".into())
1401 );
1402 }
1403
1404 #[test]
1405 fn aes_key_wrap_rejects_mismatched_kek_before_provider_dispatch() {
1406 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1409 .recipient_aes_kw([0x44; 32], KeyWrapAlgorithm::AesKw128);
1410
1411 assert!(matches!(
1412 builder.validate_configuration(),
1413 Err(XmlEncError::InvalidEncryptionConfig(_))
1414 ));
1415 }
1416
1417 #[test]
1418 fn rsa_oaep_round_trips_configurable_parameters() {
1419 let private = RsaPrivateKey::new(&mut UnwrapErr(SysRng), 2048)
1420 .expect("test RSA key generation must succeed");
1421 let public = RsaPublicKey::from(&private);
1422 let parameters =
1423 RsaOaepParameters::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha512)
1424 .label(b"recipient-label".to_vec());
1425 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes256Gcm)
1426 .add_recipient(
1427 EncryptionRecipient::rsa_oaep(public)
1428 .oaep_parameters(parameters)
1429 .recipient("rsa-recipient"),
1430 )
1431 .encrypt_xml("<secret/>")
1432 .expect("RSA-OAEP encryption must succeed");
1433 assert_eq!(
1434 decrypt(
1435 &encrypted.encrypted_data_xml,
1436 &PrivateKeyDecryptor::new(private)
1437 )
1438 .expect("RSA recipient must recover content key"),
1439 super::super::DecryptedContent::Xml("<secret/>".into())
1440 );
1441 }
1442
1443 #[test]
1444 fn legacy_oaep_rejects_non_sha1_mgf_during_configuration_validation() {
1445 let public = RsaPublicKey::from_public_key_pem(include_str!(
1448 "../../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"
1449 ))
1450 .expect("tracked RSA public key must parse");
1451 let parameters = RsaOaepParameters {
1452 algorithm: super::super::KeyTransportAlgorithm::RsaOaepMgf1p,
1453 digest: OaepDigestAlgorithm::Sha256,
1454 mgf_digest: OaepDigestAlgorithm::Sha256,
1455 label: Vec::new(),
1456 };
1457 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1458 .add_recipient(EncryptionRecipient::rsa_oaep(public).oaep_parameters(parameters));
1459
1460 assert!(matches!(
1461 builder.validate_configuration(),
1462 Err(XmlEncError::InvalidEncryptionConfig(_))
1463 ));
1464 }
1465
1466 #[test]
1467 fn encrypt_document_replaces_element_and_self_closing_content() {
1468 let key = [0x55; 16];
1469 let document =
1470 "<root><target ID=\"element\"><child/></target><empty ID=\"content\"/></root>";
1471 let encrypted_element = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1472 .direct_key(key)
1473 .encrypt_document(
1474 document,
1475 DocumentEncryptionOptions {
1476 element_id: Some("element"),
1477 },
1478 )
1479 .expect("element replacement must succeed");
1480 let decrypted_element =
1481 decrypt_document(&encrypted_element, None, &SymmetricKeyDecryptor::new(key))
1482 .expect("element replacement must round-trip");
1483 assert_eq!(decrypted_element, document);
1484
1485 let encrypted_content = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1486 .encryption_type(EncryptedDataType::Content)
1487 .direct_key(key)
1488 .encrypt_document(
1489 document,
1490 DocumentEncryptionOptions {
1491 element_id: Some("content"),
1492 },
1493 )
1494 .expect("self-closing content replacement must expand the element");
1495 assert!(encrypted_content.contains("<empty ID=\"content\"><xenc:EncryptedData"));
1496 let decrypted_content =
1497 decrypt_document(&encrypted_content, None, &SymmetricKeyDecryptor::new(key))
1498 .expect("empty content must decrypt");
1499 assert_eq!(
1500 decrypted_content,
1501 "<root><target ID=\"element\"><child/></target><empty ID=\"content\"></empty></root>"
1502 );
1503 }
1504
1505 #[test]
1506 fn invalid_configuration_and_bounds_fail_before_encryption() {
1507 let no_key = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1508 .encrypt_binary(b"data")
1509 .expect_err("missing key source must fail");
1510 assert!(matches!(no_key, XmlEncError::InvalidEncryptionConfig(_)));
1511
1512 assert!(
1513 validate_plaintext_len(MAX_ENCRYPTION_PLAINTEXT_LEN, MAX_ENCRYPTION_PLAINTEXT_LEN,)
1514 .is_ok()
1515 );
1516 assert!(matches!(
1517 validate_plaintext_len(
1518 MAX_ENCRYPTION_PLAINTEXT_LEN + 1,
1519 MAX_ENCRYPTION_PLAINTEXT_LEN,
1520 ),
1521 Err(XmlEncError::Policy(
1522 crate::policy::PolicyViolation::ResourceLimit {
1523 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1524 ..
1525 }
1526 ))
1527 ));
1528
1529 assert!(matches!(
1530 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1531 .direct_key([0_u8; 15])
1532 .encrypt_binary(b"data"),
1533 Err(XmlEncError::InvalidKeySize { .. })
1534 ));
1535
1536 let too_many_recipients = (0..=MAX_ENCRYPTION_RECIPIENTS).fold(
1537 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm),
1538 |builder, _| builder.recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128),
1539 );
1540 assert!(matches!(
1541 too_many_recipients.encrypt_binary(b"data"),
1542 Err(XmlEncError::Policy(
1543 crate::policy::PolicyViolation::ResourceLimit {
1544 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
1545 ..
1546 }
1547 ))
1548 ));
1549
1550 let oversized_metadata = "x".repeat(MAX_ENCRYPTION_METADATA_LEN + 1);
1551 assert!(matches!(
1552 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1553 .direct_key([0_u8; 16])
1554 .id(oversized_metadata)
1555 .encrypt_binary(b"data"),
1556 Err(XmlEncError::Policy(
1557 crate::policy::PolicyViolation::ResourceLimit {
1558 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
1559 ..
1560 }
1561 ))
1562 ));
1563 }
1564
1565 #[test]
1566 fn oversized_xml_is_rejected_before_parsing() {
1567 let oversized_malformed = format!(
1570 "<child>{}</unclosed>",
1571 "x".repeat(MAX_ENCRYPTION_PLAINTEXT_LEN)
1572 );
1573
1574 assert!(matches!(
1575 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1576 .encryption_type(EncryptedDataType::Content)
1577 .direct_key([0_u8; 16])
1578 .encrypt_xml(&oversized_malformed),
1579 Err(XmlEncError::Policy(
1580 crate::policy::PolicyViolation::ResourceLimit {
1581 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1582 ..
1583 }
1584 ))
1585 ));
1586 }
1587
1588 #[test]
1589 fn oversized_document_is_rejected_before_parsing() {
1590 let oversized_malformed = format!(
1593 "<root>{}</unclosed>",
1594 "x".repeat(MAX_ENCRYPTION_DOCUMENT_LEN)
1595 );
1596
1597 assert!(matches!(
1598 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1599 .direct_key([0_u8; 16])
1600 .encrypt_document(&oversized_malformed, DocumentEncryptionOptions::default()),
1601 Err(XmlEncError::Policy(
1602 crate::policy::PolicyViolation::ResourceLimit {
1603 resource: crate::policy::resource_name::XML_DOCUMENT,
1604 ..
1605 }
1606 ))
1607 ));
1608 }
1609
1610 #[test]
1611 fn encrypted_replacement_must_fit_document_policy() {
1612 for encrypted_type in [EncryptedDataType::Element, EncryptedDataType::Content] {
1615 let document = "<root><target ID=\"selected\">x</target></root>";
1616 let policy = crate::policy::EncryptionPolicy {
1617 resources: crate::policy::ResourcePolicy {
1618 max_xml_document_bytes: document.len(),
1619 ..crate::policy::ResourcePolicy::default()
1620 },
1621 ..crate::policy::EncryptionPolicy::default()
1622 };
1623
1624 assert!(matches!(
1625 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1626 .encryption_type(encrypted_type)
1627 .direct_key([0_u8; 16])
1628 .policy(policy)
1629 .encrypt_document(
1630 document,
1631 DocumentEncryptionOptions {
1632 element_id: Some("selected"),
1633 },
1634 ),
1635 Err(XmlEncError::Policy(
1636 crate::policy::PolicyViolation::ResourceLimit {
1637 resource: crate::policy::resource_name::XML_DOCUMENT,
1638 ..
1639 }
1640 ))
1641 ));
1642 }
1643 }
1644
1645 #[test]
1646 fn binary_encryption_preserves_an_opaque_type_hint() {
1647 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1650 .encryption_type(EncryptedDataType::Other("urn:example:binary".into()))
1651 .direct_key([0x42_u8; 16])
1652 .encrypt_binary(b"opaque payload")
1653 .expect("opaque binary Type must be accepted");
1654
1655 assert!(
1656 result
1657 .encrypted_data_xml
1658 .contains("Type=\"urn:example:binary\"")
1659 );
1660 }
1661
1662 #[test]
1663 fn binary_encryption_bounds_an_opaque_type_hint() {
1664 let maximum = 64;
1667 let policy = crate::policy::EncryptionPolicy {
1668 resources: crate::policy::ResourcePolicy {
1669 max_encryption_metadata_bytes: maximum,
1670 ..crate::policy::ResourcePolicy::default()
1671 },
1672 ..crate::policy::EncryptionPolicy::default()
1673 };
1674 let encrypt = |uri: String| {
1675 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1676 .encryption_type(EncryptedDataType::Other(uri))
1677 .direct_key([0x42_u8; 16])
1678 .policy(policy.clone())
1679 .encrypt_binary(b"opaque payload")
1680 };
1681
1682 encrypt(format!("urn:{}", "x".repeat(maximum - 4)))
1683 .expect("metadata at the configured boundary must remain accepted");
1684 assert!(matches!(
1685 encrypt(format!("urn:{}", "x".repeat(maximum - 3))),
1686 Err(XmlEncError::Policy(
1687 crate::policy::PolicyViolation::ResourceLimit {
1688 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
1689 maximum: 64,
1690 actual: 65,
1691 }
1692 ))
1693 ));
1694 }
1695
1696 #[test]
1697 fn encryption_policy_bounds_xml_nodes_at_both_parse_entry_points() {
1698 let policy = crate::policy::EncryptionPolicy {
1701 resources: crate::policy::ResourcePolicy {
1702 max_xml_nodes: 4,
1703 ..crate::policy::ResourcePolicy::default()
1704 },
1705 ..crate::policy::EncryptionPolicy::default()
1706 };
1707 let builder = || {
1708 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1709 .direct_key([0_u8; 16])
1710 .policy(policy.clone())
1711 };
1712 let xml = "<root><a/><b/><c/><d/></root>";
1713
1714 assert!(matches!(
1715 builder().encrypt_xml(xml),
1716 Err(XmlEncError::Policy(
1717 crate::policy::PolicyViolation::ResourceLimit {
1718 resource: crate::policy::resource_name::XML_NODES,
1719 maximum: 4,
1720 actual: 5,
1721 }
1722 ))
1723 ));
1724 assert!(matches!(
1725 builder().encrypt_document(xml, DocumentEncryptionOptions::default()),
1726 Err(XmlEncError::Policy(
1727 crate::policy::PolicyViolation::ResourceLimit {
1728 resource: crate::policy::resource_name::XML_NODES,
1729 maximum: 4,
1730 actual: 5,
1731 }
1732 ))
1733 ));
1734 }
1735
1736 #[test]
1737 fn encryption_entry_points_enforce_policy_depth() {
1738 let xml = "<root><child><leaf/></child></root>";
1741 let policy = crate::policy::EncryptionPolicy {
1742 resources: crate::policy::ResourcePolicy {
1743 max_xml_depth: 2,
1744 ..crate::policy::ResourcePolicy::default()
1745 },
1746 ..crate::policy::EncryptionPolicy::default()
1747 };
1748 let builder = || {
1749 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1750 .direct_key([0_u8; 16])
1751 .policy(policy.clone())
1752 };
1753 let mut document = XmlDocument::parse(xml).expect("wide retained fixture must parse");
1754
1755 assert!(matches!(
1756 builder().encrypt_document(xml, DocumentEncryptionOptions::default()),
1757 Err(XmlEncError::Policy(
1758 crate::policy::PolicyViolation::ResourceLimit {
1759 resource: crate::policy::resource_name::XML_DEPTH,
1760 maximum: 2,
1761 actual: 3,
1762 }
1763 ))
1764 ));
1765 assert!(matches!(
1766 builder().encrypt_owned_document(&mut document, DocumentEncryptionOptions::default()),
1767 Err(XmlEncError::Policy(
1768 crate::policy::PolicyViolation::ResourceLimit {
1769 resource: crate::policy::resource_name::XML_DEPTH,
1770 maximum: 2,
1771 actual: 3,
1772 }
1773 ))
1774 ));
1775 }
1776
1777 #[test]
1778 fn encryption_plaintext_and_owned_mutations_use_the_active_depth() {
1779 let policy = crate::policy::EncryptionPolicy {
1782 resources: crate::policy::ResourcePolicy {
1783 max_xml_depth: 1,
1784 ..crate::policy::ResourcePolicy::default()
1785 },
1786 ..crate::policy::EncryptionPolicy::default()
1787 };
1788 let builder = || {
1789 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1790 .direct_key([0_u8; 16])
1791 .policy(policy.clone())
1792 };
1793
1794 assert!(matches!(
1795 builder().encrypt_xml("<root><child/></root>"),
1796 Err(XmlEncError::Policy(
1797 crate::policy::PolicyViolation::ResourceLimit {
1798 resource: crate::policy::resource_name::XML_DEPTH,
1799 maximum: 1,
1800 actual: 2,
1801 }
1802 ))
1803 ));
1804
1805 let generated = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1806 .direct_key([0_u8; 16])
1807 .encryption_type(EncryptedDataType::Content)
1808 .encrypt_xml("")
1809 .expect("generated content replacement must parse");
1810 let generated_document = Document::parse(&generated.encrypted_data_xml)
1811 .expect("generated replacement must be XML");
1812 let generated_depth = generated_document
1813 .descendants()
1814 .filter(|node| node.is_element())
1815 .map(|node| {
1816 node.ancestors()
1817 .filter(|ancestor| ancestor.is_element())
1818 .count()
1819 })
1820 .max()
1821 .expect("generated replacement has elements");
1822 let mutation_policy = crate::policy::EncryptionPolicy {
1823 resources: crate::policy::ResourcePolicy {
1824 max_xml_depth: generated_depth,
1825 ..crate::policy::ResourcePolicy::default()
1826 },
1827 ..crate::policy::EncryptionPolicy::default()
1828 };
1829 let mut document = XmlDocument::parse("<root/>").expect("shallow fixture must parse");
1830 let original = document.as_xml().to_owned();
1831 let generation = document.generation();
1832 assert!(matches!(
1833 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1834 .direct_key([0_u8; 16])
1835 .encryption_type(EncryptedDataType::Content)
1836 .policy(mutation_policy)
1837 .encrypt_owned_document(&mut document, DocumentEncryptionOptions::default()),
1838 Err(XmlEncError::Policy(
1839 crate::policy::PolicyViolation::ResourceLimit {
1840 resource: crate::policy::resource_name::XML_DEPTH,
1841 maximum,
1842 actual,
1843 }
1844 )) if maximum == generated_depth && actual == generated_depth + 1
1845 ));
1846 assert_eq!(document.as_xml(), original);
1847 assert_eq!(document.generation(), generation);
1848 }
1849
1850 #[test]
1851 fn owned_encryption_checks_active_node_limit_before_target_selection() {
1852 let policy = crate::policy::EncryptionPolicy {
1855 resources: crate::policy::ResourcePolicy {
1856 max_xml_nodes: 1,
1857 ..crate::policy::ResourcePolicy::default()
1858 },
1859 ..crate::policy::EncryptionPolicy::default()
1860 };
1861 let mut document = XmlDocument::parse("<root><first/><second/></root>")
1862 .expect("retained fixture must parse");
1863 let before = document.as_xml().to_owned();
1864
1865 let error = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1866 .direct_key([0_u8; 16])
1867 .policy(policy)
1868 .encrypt_owned_document(
1869 &mut document,
1870 DocumentEncryptionOptions {
1871 element_id: Some("missing"),
1872 },
1873 )
1874 .expect_err("active node ceiling must precede target selection");
1875
1876 assert!(matches!(
1877 error,
1878 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1879 resource: crate::policy::resource_name::XML_NODES,
1880 maximum: 1,
1881 actual,
1882 }) if actual > 1
1883 ));
1884 assert_eq!(document.as_xml(), before);
1885 assert_eq!(document.generation(), 0);
1886 }
1887
1888 #[test]
1889 fn document_encryption_initial_parse_uses_the_policy_work_budget() {
1890 let xml = "<root/>";
1893 let policy = crate::policy::EncryptionPolicy {
1894 resources: crate::policy::ResourcePolicy {
1895 max_xml_parse_work_bytes: 0,
1896 ..crate::policy::ResourcePolicy::default()
1897 },
1898 ..crate::policy::EncryptionPolicy::default()
1899 };
1900
1901 let error = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
1902 .direct_key([0_u8; 16])
1903 .policy(policy)
1904 .encrypt_document(xml, DocumentEncryptionOptions::default())
1905 .expect_err("a zero parse-work budget must reject the input parse");
1906
1907 assert!(matches!(
1908 error,
1909 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1910 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
1911 maximum: 0,
1912 actual,
1913 }) if actual == xml.len()
1914 ));
1915 }
1916
1917 #[test]
1918 fn content_plaintext_node_limit_excludes_the_internal_wrapper() {
1919 let policy = |max_xml_nodes| crate::policy::EncryptionPolicy {
1920 resources: crate::policy::ResourcePolicy {
1921 max_xml_nodes,
1922 ..crate::policy::ResourcePolicy::default()
1923 },
1924 ..crate::policy::EncryptionPolicy::default()
1925 };
1926
1927 let policy_three = policy(3);
1928 let budget_three = XmlParseWorkBudget::from_resources(&policy_three.resources);
1929 validate_xml_plaintext(
1930 "<first/><second/>",
1931 &EncryptedDataType::Content,
1932 &policy_three,
1933 &budget_three,
1934 crate::XmlBackend::default(),
1935 )
1936 .expect("the caller root and two elements must fit a three-node policy");
1937 let policy_two = policy(2);
1938 let budget_two = XmlParseWorkBudget::from_resources(&policy_two.resources);
1939 assert!(matches!(
1940 validate_xml_plaintext(
1941 "<first/><second/>",
1942 &EncryptedDataType::Content,
1943 &policy_two,
1944 &budget_two,
1945 crate::XmlBackend::default(),
1946 ),
1947 Err(XmlEncError::Policy(
1948 crate::policy::PolicyViolation::ResourceLimit {
1949 resource: crate::policy::resource_name::XML_NODES,
1950 maximum: 2,
1951 actual: 3,
1952 }
1953 ))
1954 ));
1955 }
1956
1957 #[test]
1958 fn content_plaintext_byte_limit_excludes_only_the_internal_wrapper() {
1959 let xml = "<first/><second/>";
1963 let maximum = xml.len() - 1;
1964 let policy = crate::policy::EncryptionPolicy {
1965 resources: crate::policy::ResourcePolicy {
1966 max_xml_document_bytes: maximum,
1967 max_encryption_plaintext_bytes: xml.len(),
1968 ..crate::policy::ResourcePolicy::default()
1969 },
1970 ..crate::policy::EncryptionPolicy::default()
1971 };
1972 let budget = XmlParseWorkBudget::from_resources(&policy.resources);
1973
1974 assert!(matches!(
1975 validate_xml_plaintext(
1976 xml,
1977 &EncryptedDataType::Content,
1978 &policy,
1979 &budget,
1980 crate::XmlBackend::default(),
1981 ),
1982 Err(XmlEncError::Policy(
1983 crate::policy::PolicyViolation::ResourceLimit {
1984 resource: crate::policy::resource_name::XML_DOCUMENT,
1985 maximum: observed_maximum,
1986 actual,
1987 }
1988 )) if observed_maximum == maximum && actual == xml.len()
1989 ));
1990 assert_eq!(
1991 budget.consumed(),
1992 0,
1993 "oversized XML must fail before parsing"
1994 );
1995 }
1996
1997 #[test]
1998 fn generated_encrypted_data_parse_uses_the_active_node_limit() {
1999 let xml = "<EncryptedData><CipherData><CipherValue>AA==</CipherValue></CipherData></EncryptedData>";
2002 let policy = crate::policy::EncryptionPolicy {
2003 resources: crate::policy::ResourcePolicy {
2004 max_xml_nodes: 1,
2005 ..crate::policy::ResourcePolicy::default()
2006 },
2007 ..crate::policy::EncryptionPolicy::default()
2008 };
2009 let budget = XmlParseWorkBudget::from_resources(&policy.resources);
2010
2011 assert!(matches!(
2012 count_generated_encrypted_data_nodes(
2013 xml,
2014 &policy,
2015 &budget,
2016 crate::XmlBackend::default(),
2017 ),
2018 Err(XmlEncError::Policy(
2019 crate::policy::PolicyViolation::ResourceLimit {
2020 resource: crate::policy::resource_name::XML_NODES,
2021 maximum: 1,
2022 actual: 2,
2023 }
2024 ))
2025 ));
2026 }
2027
2028 #[test]
2029 fn document_encryption_bounds_projected_replacement_nodes() {
2030 fn policy(max_xml_nodes: usize) -> crate::policy::EncryptionPolicy {
2031 crate::policy::EncryptionPolicy {
2032 resources: crate::policy::ResourcePolicy {
2033 max_xml_nodes,
2034 ..crate::policy::ResourcePolicy::default()
2035 },
2036 ..crate::policy::EncryptionPolicy::default()
2037 }
2038 }
2039
2040 let generated_nodes = {
2041 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2042 .direct_key([0_u8; 16])
2043 .encrypt_binary(b"payload")
2044 .expect("default policy must permit generated EncryptedData");
2045 Document::parse(&encrypted.encrypted_data_xml)
2046 .expect("generated EncryptedData must parse")
2047 .root_element()
2048 .descendants()
2049 .count()
2050 };
2051
2052 let element_actual = match EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2056 .direct_key([0_u8; 16])
2057 .policy(policy(generated_nodes))
2058 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2059 {
2060 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2061 resource: "XML nodes",
2062 maximum,
2063 actual,
2064 })) if maximum == generated_nodes && actual > maximum => actual,
2065 result => panic!("expected projected element node bound, got {result:?}"),
2066 };
2067 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2068 .direct_key([0_u8; 16])
2069 .policy(policy(element_actual))
2070 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2071 .expect("the exact projected element node limit must be accepted");
2072
2073 let content_actual = match EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2077 .encryption_type(EncryptedDataType::Content)
2078 .direct_key([0_u8; 16])
2079 .policy(policy(generated_nodes))
2080 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2081 {
2082 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2083 resource: "XML nodes",
2084 maximum,
2085 actual,
2086 })) if maximum == generated_nodes && actual > maximum => actual,
2087 result => panic!("expected projected content node bound, got {result:?}"),
2088 };
2089 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2090 .encryption_type(EncryptedDataType::Content)
2091 .direct_key([0_u8; 16])
2092 .policy(policy(content_actual))
2093 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2094 .expect("the exact projected content node limit must be accepted");
2095 }
2096
2097 #[test]
2098 fn standalone_encryption_bounds_generated_xml_nodes() {
2099 fn policy(max_xml_nodes: usize) -> crate::policy::EncryptionPolicy {
2100 crate::policy::EncryptionPolicy {
2101 resources: crate::policy::ResourcePolicy {
2102 max_xml_nodes,
2103 ..crate::policy::ResourcePolicy::default()
2104 },
2105 ..crate::policy::EncryptionPolicy::default()
2106 }
2107 }
2108
2109 assert!(matches!(
2112 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2113 .direct_key([0_u8; 16])
2114 .policy(policy(1))
2115 .encrypt_binary(b"payload"),
2116 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2117 resource: "XML nodes",
2118 maximum: 1,
2119 actual,
2120 })) if actual > 1
2121 ));
2122
2123 let generated = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2124 .direct_key([0_u8; 16])
2125 .encrypt_binary(b"payload")
2126 .expect("default policy must permit generated EncryptedData");
2127 let document = Document::parse(&generated.encrypted_data_xml)
2128 .expect("generated EncryptedData must parse");
2129 let actual = document.root().descendants().count();
2130 assert!(matches!(
2131 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2132 .direct_key([0_u8; 16])
2133 .policy(policy(actual - 1))
2134 .encrypt_binary(b"payload"),
2135 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2136 resource: "XML nodes",
2137 maximum,
2138 actual: reported,
2139 })) if maximum == actual - 1 && reported == actual
2140 ));
2141 let exact = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2142 .direct_key([0_u8; 16])
2143 .policy(policy(actual))
2144 .encrypt_binary(b"payload")
2145 .expect("the exact generated node limit must be accepted");
2146 let exact_document =
2147 Document::parse(&exact.encrypted_data_xml).expect("exact-boundary output must parse");
2148 let decryption_policy = crate::policy::DecryptionPolicy {
2149 resources: crate::policy::ResourcePolicy {
2150 max_xml_nodes: actual,
2151 ..crate::policy::ResourcePolicy::default()
2152 },
2153 ..crate::policy::DecryptionPolicy::default()
2154 };
2155 super::super::parse::parse_encrypted_data_node_with_policy(
2156 exact_document.root_element(),
2157 &decryption_policy,
2158 )
2159 .expect("output accepted at the exact limit must be consumable at the same limit");
2160 }
2161
2162 #[test]
2163 fn document_dtd_is_controlled_only_by_operation_policy() {
2164 let document = "<!DOCTYPE root [<!ELEMENT root ANY>]><root/>";
2167 assert!(matches!(
2168 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2169 .direct_key([0_u8; 16])
2170 .encrypt_document(document, DocumentEncryptionOptions { element_id: None },),
2171 Err(XmlEncError::XmlParse(_))
2172 ));
2173 let mut policy = crate::policy::EncryptionPolicy::default();
2174 policy.xml.allow_internal_dtd = true;
2175 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2176 .direct_key([0_u8; 16])
2177 .policy(policy.clone())
2178 .encrypt_document(document, DocumentEncryptionOptions { element_id: None })
2179 .expect("the explicit operation policy should permit parsing");
2180 }
2181
2182 #[test]
2183 fn invalid_resource_policy_is_rejected_at_every_entry_point() {
2184 let mut policy = crate::policy::EncryptionPolicy::default();
2187 policy.resources.max_encryption_plaintext_bytes =
2188 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING + 1;
2189 let builder = || {
2190 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2191 .direct_key([0_u8; 16])
2192 .policy(policy.clone())
2193 };
2194
2195 assert!(matches!(
2196 builder().encrypt_xml("<broken>"),
2197 Err(XmlEncError::Policy(_))
2198 ));
2199 assert!(matches!(
2200 builder().encrypt_binary(b"x"),
2201 Err(XmlEncError::Policy(_))
2202 ));
2203 assert!(matches!(
2204 builder().encrypt_document("<broken>", DocumentEncryptionOptions::default()),
2205 Err(XmlEncError::Policy(_))
2206 ));
2207 }
2208
2209 #[test]
2210 fn standalone_encrypted_output_obeys_document_byte_ceiling() {
2211 let policy = |maximum| crate::policy::EncryptionPolicy {
2214 resources: crate::policy::ResourcePolicy {
2215 max_xml_document_bytes: maximum,
2216 ..crate::policy::ResourcePolicy::default()
2217 },
2218 ..crate::policy::EncryptionPolicy::default()
2219 };
2220
2221 let binary_len = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2222 .direct_key([0_u8; 16])
2223 .encrypt_binary(b"bounded binary")
2224 .expect("baseline binary encryption must succeed")
2225 .encrypted_data_xml
2226 .len();
2227 assert!(matches!(
2228 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2229 .direct_key([0_u8; 16])
2230 .policy(policy(binary_len - 1))
2231 .encrypt_binary(b"bounded binary"),
2232 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2233 resource: crate::policy::resource_name::XML_DOCUMENT,
2234 maximum,
2235 actual,
2236 }))
2237 if maximum == binary_len - 1 && actual == binary_len
2238 ));
2239 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2240 .direct_key([0_u8; 16])
2241 .policy(policy(binary_len))
2242 .encrypt_binary(b"bounded binary")
2243 .expect("the exact standalone binary output bound must be accepted");
2244
2245 let xml_len = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2246 .direct_key([0_u8; 16])
2247 .encrypt_xml("<secret>bounded XML</secret>")
2248 .expect("baseline XML encryption must succeed")
2249 .encrypted_data_xml
2250 .len();
2251 assert!(matches!(
2252 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2253 .direct_key([0_u8; 16])
2254 .policy(policy(xml_len - 1))
2255 .encrypt_xml("<secret>bounded XML</secret>"),
2256 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2257 resource: crate::policy::resource_name::XML_DOCUMENT,
2258 maximum,
2259 actual,
2260 }))
2261 if maximum == xml_len - 1 && actual == xml_len
2262 ));
2263 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2264 .direct_key([0_u8; 16])
2265 .policy(policy(xml_len))
2266 .encrypt_xml("<secret>bounded XML</secret>")
2267 .expect("the exact standalone XML output bound must be accepted");
2268 }
2269
2270 #[test]
2271 fn zero_resource_ceilings_allow_operations_that_consume_none() {
2272 let policy = crate::policy::EncryptionPolicy {
2275 resources: crate::policy::ResourcePolicy {
2276 max_encryption_plaintext_bytes: 0,
2277 max_encryption_recipients: 0,
2278 ..crate::policy::ResourcePolicy::default()
2279 },
2280 ..crate::policy::EncryptionPolicy::default()
2281 };
2282
2283 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2284 .direct_key([0_u8; 16])
2285 .policy(policy.clone())
2286 .encrypt_binary(&[])
2287 .expect("zero ceilings must allow resources the operation does not consume");
2288
2289 assert!(matches!(
2290 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2291 .direct_key([0_u8; 16])
2292 .policy(policy.clone())
2293 .encrypt_binary(b"x"),
2294 Err(XmlEncError::Policy(
2295 crate::policy::PolicyViolation::ResourceLimit {
2296 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2297 maximum: 0,
2298 actual: 1
2299 }
2300 ))
2301 ));
2302 assert!(matches!(
2303 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2304 .recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128)
2305 .policy(policy)
2306 .encrypt_binary(&[]),
2307 Err(XmlEncError::Policy(
2308 crate::policy::PolicyViolation::ResourceLimit {
2309 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
2310 maximum: 0,
2311 actual: 1,
2312 }
2313 ))
2314 ));
2315 }
2316
2317 #[test]
2318 fn encryption_enforces_key_candidate_budget_before_inspection() {
2319 let deny_keys = crate::policy::EncryptionPolicy {
2322 resources: crate::policy::ResourcePolicy {
2323 max_key_candidates: 0,
2324 ..crate::policy::ResourcePolicy::default()
2325 },
2326 ..crate::policy::EncryptionPolicy::default()
2327 };
2328
2329 for error in [
2330 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2331 .direct_key([0_u8; 15])
2332 .policy(deny_keys.clone())
2333 .encrypt_binary(b"data")
2334 .expect_err("a direct key must consume one candidate"),
2335 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2336 .recipient_aes_kw([0_u8; 15], KeyWrapAlgorithm::AesKw128)
2337 .policy(deny_keys)
2338 .encrypt_binary(b"data")
2339 .expect_err("a recipient key must consume one candidate"),
2340 ] {
2341 assert!(matches!(
2342 error,
2343 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2344 resource: crate::policy::resource_name::KEY_CANDIDATES,
2345 maximum: 0,
2346 actual: 1,
2347 })
2348 ));
2349 }
2350
2351 let recipient =
2352 || EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128);
2353 let policy_with_candidate_limit = |maximum| crate::policy::EncryptionPolicy {
2354 resources: crate::policy::ResourcePolicy {
2355 max_key_candidates: maximum,
2356 ..crate::policy::ResourcePolicy::default()
2357 },
2358 ..crate::policy::EncryptionPolicy::default()
2359 };
2360 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2361 .add_recipient(recipient())
2362 .add_recipient(recipient());
2363
2364 assert!(matches!(
2365 builder
2366 .clone()
2367 .policy(policy_with_candidate_limit(1))
2368 .encrypt_binary(b"data"),
2369 Err(XmlEncError::Policy(
2370 crate::policy::PolicyViolation::ResourceLimit {
2371 resource: crate::policy::resource_name::KEY_CANDIDATES,
2372 maximum: 1,
2373 actual: 2,
2374 }
2375 ))
2376 ));
2377 builder
2378 .policy(policy_with_candidate_limit(2))
2379 .encrypt_binary(b"data")
2380 .expect("the exact key-candidate boundary must be accepted");
2381 }
2382
2383 #[test]
2384 fn element_plaintext_enforces_replacement_node_contract() {
2385 let builder =
2388 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm).direct_key([0_u8; 16]);
2389 builder
2390 .encrypt_xml("\n<!--before--><secret/><!--after-->\n")
2391 .expect("one element with boundary trivia must be accepted");
2392
2393 assert!(matches!(
2394 builder.encrypt_xml("<?target value?><secret/>"),
2395 Err(XmlEncError::InvalidStructure(_))
2396 ));
2397 }
2398
2399 #[test]
2400 fn empty_key_names_are_rejected_before_serialization() {
2401 assert!(matches!(
2404 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2405 .direct_key([0_u8; 16])
2406 .direct_key_name("")
2407 .encrypt_binary(b"data"),
2408 Err(XmlEncError::InvalidEncryptionConfig(_))
2409 ));
2410 assert!(matches!(
2411 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2412 .add_recipient(
2413 EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128)
2414 .key_name("")
2415 )
2416 .encrypt_binary(b"data"),
2417 Err(XmlEncError::InvalidEncryptionConfig(_))
2418 ));
2419 }
2420
2421 #[test]
2422 fn xml_forbidden_metadata_characters_are_rejected() {
2423 assert!(matches!(
2426 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2427 .direct_key([0_u8; 16])
2428 .id("invalid\0id")
2429 .encrypt_binary(b"data"),
2430 Err(XmlEncError::InvalidEncryptionConfig(_))
2431 ));
2432 assert!(matches!(
2433 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2434 .direct_key([0_u8; 16])
2435 .direct_key_name("invalid\0name")
2436 .encrypt_binary(b"data"),
2437 Err(XmlEncError::InvalidEncryptionConfig(_))
2438 ));
2439 assert!(matches!(
2440 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2441 .add_recipient(
2442 EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128)
2443 .recipient("invalid\0recipient")
2444 )
2445 .encrypt_binary(b"data"),
2446 Err(XmlEncError::InvalidEncryptionConfig(_))
2447 ));
2448 assert!(matches!(
2449 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2450 .add_recipient(
2451 EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128)
2452 .key_name("invalid\0name")
2453 )
2454 .encrypt_binary(b"data"),
2455 Err(XmlEncError::InvalidEncryptionConfig(_))
2456 ));
2457 }
2458
2459 #[test]
2460 fn encrypted_data_id_must_be_an_xml_ncname() {
2461 for invalid in ["bad id", "1leading", "qualified:name", ""] {
2464 assert!(matches!(
2465 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2466 .direct_key([0_u8; 16])
2467 .id(invalid)
2468 .encrypt_binary(b"data"),
2469 Err(XmlEncError::InvalidEncryptionConfig(_))
2470 ));
2471 }
2472
2473 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2474 .direct_key([0_u8; 16])
2475 .id("Δοκιμή")
2476 .encrypt_binary(b"data")
2477 .expect("Unicode XML NCNames must remain valid identifiers");
2478 }
2479
2480 #[test]
2481 fn rejects_malformed_custom_provider_ciphertext_before_serialization() {
2482 for (algorithm, ciphertext) in [
2485 (DataEncryptionAlgorithm::Aes128Gcm, vec![0_u8; 27]),
2486 (DataEncryptionAlgorithm::Aes128Cbc, vec![0_u8; 31]),
2487 (DataEncryptionAlgorithm::Aes256Cbc, vec![0_u8; 33]),
2488 ] {
2489 let error = EncryptedDataBuilder::new(algorithm)
2490 .provider(Arc::new(OverridingOutputProvider {
2491 ciphertext: Some(ciphertext),
2492 wrapped_key: None,
2493 transported_key: None,
2494 transport_calls: AtomicUsize::new(0),
2495 }))
2496 .direct_key(vec![0_u8; algorithm.key_len()])
2497 .encrypt_binary(b"data")
2498 .expect_err("malformed provider output must fail before XML serialization");
2499 assert!(matches!(
2500 error,
2501 XmlEncError::DataTooShort { .. } | XmlEncError::InvalidCbcCiphertextLength(_)
2502 ));
2503 }
2504 }
2505
2506 #[test]
2507 fn rejects_overlong_custom_provider_ciphertext_before_serialization() {
2508 for (algorithm, expected, actual) in [
2511 (DataEncryptionAlgorithm::Aes128Gcm, 32, 33),
2512 (DataEncryptionAlgorithm::Aes128Cbc, 32, 48),
2513 ] {
2514 let error = EncryptedDataBuilder::new(algorithm)
2515 .provider(Arc::new(OverridingOutputProvider {
2516 ciphertext: Some(vec![0_u8; actual]),
2517 wrapped_key: None,
2518 transported_key: None,
2519 transport_calls: AtomicUsize::new(0),
2520 }))
2521 .direct_key(vec![0_u8; algorithm.key_len()])
2522 .encrypt_binary(b"data")
2523 .expect_err("overlong provider output must fail before XML serialization");
2524 assert!(matches!(
2525 error,
2526 XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSize {
2527 operation: crate::provider::ProviderOperation::Encrypt,
2528 expected: observed_expected,
2529 actual: observed_actual,
2530 }) if observed_expected == expected && observed_actual == actual
2531 ));
2532 }
2533 }
2534
2535 #[test]
2536 fn rejects_malformed_custom_provider_wrapped_keys_before_serialization() {
2537 for wrapped_key in [vec![], vec![0_u8; 23], vec![0_u8; 25]] {
2540 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2541 .provider(Arc::new(OverridingOutputProvider {
2542 ciphertext: None,
2543 wrapped_key: Some(wrapped_key),
2544 transported_key: None,
2545 transport_calls: AtomicUsize::new(0),
2546 }))
2547 .recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128)
2548 .encrypt_binary(b"data");
2549 assert!(matches!(
2550 result,
2551 Err(XmlEncError::InvalidWrappedKeyLength { expected: 24, actual })
2552 if actual != 24
2553 ));
2554 }
2555
2556 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2557 .provider(Arc::new(OverridingOutputProvider {
2558 ciphertext: None,
2559 wrapped_key: Some(vec![0_u8; 24]),
2560 transported_key: None,
2561 transport_calls: AtomicUsize::new(0),
2562 }))
2563 .recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128)
2564 .encrypt_binary(b"data")
2565 .expect("exact RFC 3394 wrapped-key length must remain accepted");
2566 }
2567
2568 #[test]
2569 fn rejects_malformed_custom_provider_rsa_transport_before_serialization() {
2570 let private_key = RsaPrivateKey::new(&mut UnwrapErr(SysRng), 2048)
2573 .expect("RSA key generation should succeed");
2574 let public_key = RsaPublicKey::from(&private_key);
2575 for transported_key in [vec![], vec![0_u8; 255], vec![0_u8; 257]] {
2576 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2577 .provider(Arc::new(OverridingOutputProvider {
2578 ciphertext: None,
2579 wrapped_key: None,
2580 transported_key: Some(transported_key),
2581 transport_calls: AtomicUsize::new(0),
2582 }))
2583 .recipient_rsa_oaep(public_key.clone())
2584 .encrypt_binary(b"data");
2585 assert!(matches!(
2586 result,
2587 Err(XmlEncError::InvalidWrappedKeyLength {
2588 expected: 256,
2589 actual,
2590 }) if actual != 256
2591 ));
2592 }
2593
2594 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2595 .provider(Arc::new(OverridingOutputProvider {
2596 ciphertext: None,
2597 wrapped_key: None,
2598 transported_key: Some(vec![0_u8; 256]),
2599 transport_calls: AtomicUsize::new(0),
2600 }))
2601 .recipient_rsa_oaep(public_key)
2602 .encrypt_binary(b"data")
2603 .expect("modulus-sized RSA transport output must remain accepted");
2604 }
2605
2606 #[test]
2607 fn custom_provider_encrypts_with_an_opaque_transport_key() {
2608 let provider = Arc::new(OverridingOutputProvider {
2611 ciphertext: None,
2612 wrapped_key: None,
2613 transported_key: Some(vec![0x5a; 256]),
2614 transport_calls: AtomicUsize::new(0),
2615 });
2616 let key = Arc::new(OpaqueTransportKey {
2617 modulus: vec![0x80; 256],
2618 exponent: vec![0x01, 0x00, 0x01],
2619 });
2620
2621 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2622 .provider(provider.clone())
2623 .recipient_key_transport(key)
2624 .encrypt_binary(b"opaque provider key")
2625 .expect("custom provider must accept its opaque transport key");
2626
2627 assert_eq!(provider.transport_calls.load(Ordering::Relaxed), 1);
2628 assert!(encrypted.encrypted_data_xml.contains("rsa-oaep"));
2629 }
2630
2631 #[test]
2632 fn opaque_transport_preflight_rejects_weak_rsa_metadata() {
2633 let key = OpaqueTransportKey {
2636 modulus: vec![0x80; 128],
2637 exponent: vec![0x01, 0x00, 0x01],
2638 };
2639 assert!(matches!(
2640 validate_key_transport_recipient(&key, &crate::policy::EncryptionPolicy::default()),
2641 Err(XmlEncError::Policy(
2642 crate::policy::PolicyViolation::KeySize { .. }
2643 ))
2644 ));
2645 }
2646
2647 #[test]
2648 fn encryption_policy_rejects_weak_rsa_recipient_before_provider_dispatch() {
2649 let private_key = RsaPrivateKey::new(&mut UnwrapErr(SysRng), 1024)
2651 .expect("test RSA key generation should succeed");
2652 let provider = Arc::new(OverridingOutputProvider {
2653 ciphertext: None,
2654 wrapped_key: None,
2655 transported_key: Some(vec![0_u8; 128]),
2656 transport_calls: AtomicUsize::new(0),
2657 });
2658
2659 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2660 .provider(provider.clone())
2661 .recipient_rsa_oaep(RsaPublicKey::from(&private_key))
2662 .encrypt_binary(b"data");
2663
2664 assert!(matches!(
2665 result,
2666 Err(XmlEncError::Policy(
2667 crate::policy::PolicyViolation::KeySize {
2668 operation: "encryption",
2669 minimum_bits: 2048,
2670 maximum_bits: 8192,
2671 actual_bits: 1024,
2672 ..
2673 }
2674 ))
2675 ));
2676 assert_eq!(provider.transport_calls.load(Ordering::Relaxed), 0);
2677 }
2678
2679 #[test]
2680 fn debug_output_redacts_symmetric_key_material() {
2681 let direct_key = b"direct-key-secret".to_vec();
2682 let kek = b"key-wrap-secret!".to_vec();
2683 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2684 .direct_key(direct_key.clone());
2685 let recipient = EncryptionRecipient::aes_key_wrap(kek.clone(), KeyWrapAlgorithm::AesKw128);
2686
2687 let builder_debug = format!("{builder:?}");
2688 let recipient_debug = format!("{recipient:?}");
2689 assert!(builder_debug.contains("[REDACTED]"));
2690 assert!(recipient_debug.contains("[REDACTED]"));
2691 assert!(!builder_debug.contains(&format!("{direct_key:?}")));
2692 assert!(!recipient_debug.contains(&format!("{kek:?}")));
2693 }
2694}