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