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