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