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