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