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 begin_digest(
1962 &self,
1963 algorithm: crate::xmldsig::DigestAlgorithm,
1964 ) -> Result<Box<dyn crate::provider::DigestState + '_>, crate::provider::ProviderError>
1965 {
1966 crate::provider::default_provider().begin_digest(algorithm)
1967 }
1968
1969 #[cfg(feature = "xmldsig")]
1970 fn sign(
1971 &self,
1972 key: &dyn crate::xmldsig::SigningKey,
1973 algorithm: crate::xmldsig::SignatureAlgorithm,
1974 data: &[u8],
1975 ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
1976 crate::provider::CryptoProvider::sign(
1977 &crate::provider::RustCryptoProvider,
1978 key,
1979 algorithm,
1980 data,
1981 )
1982 }
1983
1984 #[cfg(feature = "xmldsig")]
1985 fn verify(
1986 &self,
1987 key: &dyn crate::xmldsig::VerifyingKey,
1988 algorithm: crate::xmldsig::SignatureAlgorithm,
1989 data: &[u8],
1990 signature: &[u8],
1991 ) -> Result<bool, crate::xmldsig::DsigError> {
1992 crate::provider::CryptoProvider::verify(
1993 &crate::provider::RustCryptoProvider,
1994 key,
1995 algorithm,
1996 data,
1997 signature,
1998 )
1999 }
2000
2001 fn encrypt_data(
2002 &self,
2003 algorithm: DataEncryptionAlgorithm,
2004 key: &[u8],
2005 plaintext: &[u8],
2006 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2007 if let Some(ciphertext) = &self.ciphertext {
2008 return Ok(ciphertext.clone());
2009 }
2010 crate::provider::CryptoProvider::encrypt_data(
2011 &crate::provider::RustCryptoProvider,
2012 algorithm,
2013 key,
2014 plaintext,
2015 )
2016 }
2017
2018 fn decrypt_data(
2019 &self,
2020 algorithm: DataEncryptionAlgorithm,
2021 key: &[u8],
2022 ciphertext: &[u8],
2023 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2024 crate::provider::CryptoProvider::decrypt_data(
2025 &crate::provider::RustCryptoProvider,
2026 algorithm,
2027 key,
2028 ciphertext,
2029 )
2030 }
2031
2032 fn wrap_key(
2033 &self,
2034 algorithm: KeyWrapAlgorithm,
2035 kek: &[u8],
2036 key: &[u8],
2037 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2038 if let Some(wrapped_key) = &self.wrapped_key {
2039 return Ok(wrapped_key.clone());
2040 }
2041 crate::provider::CryptoProvider::wrap_key(
2042 &crate::provider::RustCryptoProvider,
2043 algorithm,
2044 kek,
2045 key,
2046 )
2047 }
2048
2049 fn unwrap_key(
2050 &self,
2051 algorithm: KeyWrapAlgorithm,
2052 kek: &[u8],
2053 wrapped: &[u8],
2054 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2055 crate::provider::CryptoProvider::unwrap_key(
2056 &crate::provider::RustCryptoProvider,
2057 algorithm,
2058 kek,
2059 wrapped,
2060 )
2061 }
2062
2063 fn transport_key(
2064 &self,
2065 key: &dyn crate::provider::KeyTransportKey,
2066 parameters: &RsaOaepParameters,
2067 plaintext: &[u8],
2068 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2069 self.transport_calls.fetch_add(1, Ordering::Relaxed);
2070 if let Some(transported_key) = &self.transported_key {
2071 return Ok(transported_key.clone());
2072 }
2073 crate::provider::CryptoProvider::transport_key(
2074 &crate::provider::RustCryptoProvider,
2075 key,
2076 parameters,
2077 plaintext,
2078 )
2079 }
2080
2081 fn recover_key(
2082 &self,
2083 key: &dyn crate::provider::KeyRecoveryKey,
2084 parameters: &RsaOaepParameters,
2085 ciphertext: &[u8],
2086 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2087 crate::provider::CryptoProvider::recover_key(
2088 &crate::provider::RustCryptoProvider,
2089 key,
2090 parameters,
2091 ciphertext,
2092 )
2093 }
2094 }
2095
2096 #[test]
2097 fn direct_key_round_trips_every_content_algorithm() {
2098 for algorithm in [
2101 DataEncryptionAlgorithm::Aes128Cbc,
2102 DataEncryptionAlgorithm::Aes256Cbc,
2103 DataEncryptionAlgorithm::Aes128Gcm,
2104 DataEncryptionAlgorithm::Aes256Gcm,
2105 ] {
2106 for plaintext in [b"".as_slice(), b"sixteen-byte-msg", b"not aligned"] {
2107 let key = vec![0x31; algorithm.key_len()];
2108 let encrypted = EncryptedDataBuilder::new(algorithm)
2109 .direct_key(key.clone())
2110 .direct_key_name("content-key")
2111 .encrypt_binary(plaintext)
2112 .expect("supported direct encryption must succeed");
2113 assert_eq!(
2114 decrypt(
2115 &encrypted.encrypted_data_xml,
2116 &SymmetricKeyDecryptor::new(key)
2117 )
2118 .expect("generated ciphertext must decrypt"),
2119 super::super::DecryptedContent::Bytes(plaintext.to_vec())
2120 );
2121 }
2122 }
2123 }
2124
2125 #[test]
2126 fn aes_key_wrap_round_trips_and_preserves_recipient_metadata() {
2127 let kek = [0x44; 32];
2128 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2129 .add_recipient(
2130 EncryptionRecipient::aes_key_wrap(kek, KeyWrapAlgorithm::AesKw256)
2131 .recipient("service-a")
2132 .key_name("shared-kek"),
2133 )
2134 .encrypt_xml("<secret>value</secret>")
2135 .expect("AES-KW encryption must succeed");
2136 let parsed = parse_encrypted_data(&encrypted.encrypted_data_xml)
2137 .expect("generated EncryptedData must parse");
2138 assert_eq!(
2139 parsed.encrypted_keys[0].recipient.as_deref(),
2140 Some("service-a")
2141 );
2142 assert_eq!(
2143 parsed.encrypted_keys[0].key_name.as_deref(),
2144 Some("shared-kek")
2145 );
2146 assert_eq!(
2147 decrypt(&encrypted.encrypted_data_xml, &KekDecryptor::new(kek))
2148 .expect("wrapped key must decrypt"),
2149 super::super::DecryptedContent::Xml("<secret>value</secret>".into())
2150 );
2151 }
2152
2153 #[test]
2154 fn aes_key_wrap_rejects_mismatched_kek_before_provider_dispatch() {
2155 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2158 .recipient_aes_kw([0x44; 32], KeyWrapAlgorithm::AesKw128);
2159
2160 assert!(matches!(
2161 builder.validate_configuration_with_policy(&builder.policy),
2162 Err(XmlEncError::InvalidEncryptionConfig(_))
2163 ));
2164 }
2165
2166 #[test]
2167 fn rsa_oaep_round_trips_configurable_parameters() {
2168 let private = RsaPrivateKey::new(&mut UnwrapErr(SysRng), 2048)
2169 .expect("test RSA key generation must succeed");
2170 let public = RsaPublicKey::from(&private);
2171 let parameters =
2172 RsaOaepParameters::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha512)
2173 .label(b"recipient-label".to_vec());
2174 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes256Gcm)
2175 .add_recipient(
2176 EncryptionRecipient::rsa_oaep(public)
2177 .oaep_parameters(parameters)
2178 .recipient("rsa-recipient"),
2179 )
2180 .encrypt_xml("<secret/>")
2181 .expect("RSA-OAEP encryption must succeed");
2182 assert_eq!(
2183 decrypt(
2184 &encrypted.encrypted_data_xml,
2185 &PrivateKeyDecryptor::new(private)
2186 )
2187 .expect("RSA recipient must recover content key"),
2188 super::super::DecryptedContent::Xml("<secret/>".into())
2189 );
2190 }
2191
2192 #[test]
2193 fn legacy_oaep_serializes_explicit_non_default_mgf() {
2194 let public = RsaPublicKey::from_public_key_pem(include_str!(
2197 "../../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"
2198 ))
2199 .expect("tracked RSA public key must parse");
2200 let parameters = RsaOaepParameters {
2201 algorithm: super::super::KeyTransportAlgorithm::RsaOaepMgf1p,
2202 digest: OaepDigestAlgorithm::Sha256,
2203 mgf_digest: OaepDigestAlgorithm::Sha256,
2204 label: Vec::new(),
2205 };
2206 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2207 .add_recipient(EncryptionRecipient::rsa_oaep(public).oaep_parameters(parameters))
2208 .encrypt_binary(b"legacy OAEP with explicit MGF")
2209 .expect("libxmlsec1-compatible legacy OAEP parameters must encrypt");
2210 assert!(encrypted.encrypted_data_xml.contains("xenc11:MGF"));
2211 }
2212
2213 #[test]
2214 fn encryption_policy_reports_the_rejected_mgf_uri() {
2215 let public = RsaPublicKey::from_public_key_pem(include_str!(
2218 "../../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem"
2219 ))
2220 .expect("tracked RSA public key must parse");
2221 let parameters = RsaOaepParameters {
2222 algorithm: super::super::KeyTransportAlgorithm::RsaOaepMgf1p,
2223 digest: OaepDigestAlgorithm::Sha256,
2224 mgf_digest: OaepDigestAlgorithm::Sha384,
2225 label: Vec::new(),
2226 };
2227 let policy = crate::policy::EncryptionPolicy {
2228 key_transport_algorithms: Some(std::collections::HashSet::from([parameters.algorithm])),
2229 oaep_digests: Some(std::collections::HashSet::from([parameters.digest])),
2230 ..crate::policy::EncryptionPolicy::default()
2231 };
2232 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2233 .policy(policy)
2234 .add_recipient(EncryptionRecipient::rsa_oaep(public).oaep_parameters(parameters));
2235
2236 assert!(matches!(
2237 builder.validate_configuration_with_policy(&builder.policy),
2238 Err(XmlEncError::Policy(
2239 crate::policy::PolicyViolation::Algorithm { algorithm, .. }
2240 )) if algorithm == OaepDigestAlgorithm::Sha384.mgf_uri().expect("MGF1-SHA384 URI")
2241 ));
2242 }
2243
2244 #[test]
2245 fn encrypt_document_replaces_element_and_self_closing_content() {
2246 let key = [0x55; 16];
2247 let document =
2248 "<root><target ID=\"element\"><child/></target><empty ID=\"content\"/></root>";
2249 let encrypted_element = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2250 .direct_key(key)
2251 .encrypt_document(
2252 document,
2253 DocumentEncryptionOptions {
2254 element_id: Some("element"),
2255 },
2256 )
2257 .expect("element replacement must succeed");
2258 let decrypted_element =
2259 decrypt_document(&encrypted_element, None, &SymmetricKeyDecryptor::new(key))
2260 .expect("element replacement must round-trip");
2261 assert_eq!(decrypted_element, document);
2262
2263 let encrypted_content = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2264 .encryption_type(EncryptedDataType::Content)
2265 .direct_key(key)
2266 .encrypt_document(
2267 document,
2268 DocumentEncryptionOptions {
2269 element_id: Some("content"),
2270 },
2271 )
2272 .expect("self-closing content replacement must expand the element");
2273 assert!(encrypted_content.contains("<empty ID=\"content\"><xenc:EncryptedData"));
2274 let decrypted_content =
2275 decrypt_document(&encrypted_content, None, &SymmetricKeyDecryptor::new(key))
2276 .expect("empty content must decrypt");
2277 assert_eq!(
2278 decrypted_content,
2279 "<root><target ID=\"element\"><child/></target><empty ID=\"content\"></empty></root>"
2280 );
2281 }
2282
2283 #[test]
2284 fn invalid_configuration_and_bounds_fail_before_encryption() {
2285 let no_key = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2286 .encrypt_binary(b"data")
2287 .expect_err("missing key source must fail");
2288 assert!(matches!(no_key, XmlEncError::InvalidEncryptionConfig(_)));
2289
2290 assert!(
2291 validate_plaintext_len(MAX_ENCRYPTION_PLAINTEXT_LEN, MAX_ENCRYPTION_PLAINTEXT_LEN,)
2292 .is_ok()
2293 );
2294 assert!(matches!(
2295 validate_plaintext_len(
2296 MAX_ENCRYPTION_PLAINTEXT_LEN + 1,
2297 MAX_ENCRYPTION_PLAINTEXT_LEN,
2298 ),
2299 Err(XmlEncError::Policy(
2300 crate::policy::PolicyViolation::ResourceLimit {
2301 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2302 ..
2303 }
2304 ))
2305 ));
2306
2307 assert!(matches!(
2308 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2309 .direct_key([0_u8; 15])
2310 .encrypt_binary(b"data"),
2311 Err(XmlEncError::InvalidKeySize { .. })
2312 ));
2313
2314 let too_many_recipients = (0..=MAX_ENCRYPTION_RECIPIENTS).fold(
2315 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm),
2316 |builder, _| builder.recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128),
2317 );
2318 assert!(matches!(
2319 too_many_recipients.encrypt_binary(b"data"),
2320 Err(XmlEncError::Policy(
2321 crate::policy::PolicyViolation::ResourceLimit {
2322 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
2323 ..
2324 }
2325 ))
2326 ));
2327
2328 let oversized_metadata = "x".repeat(MAX_ENCRYPTION_METADATA_LEN + 1);
2329 assert!(matches!(
2330 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2331 .direct_key([0_u8; 16])
2332 .id(oversized_metadata)
2333 .encrypt_binary(b"data"),
2334 Err(XmlEncError::Policy(
2335 crate::policy::PolicyViolation::ResourceLimit {
2336 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
2337 ..
2338 }
2339 ))
2340 ));
2341 }
2342
2343 #[test]
2344 fn oversized_xml_is_rejected_before_parsing() {
2345 let oversized_malformed = format!(
2348 "<child>{}</unclosed>",
2349 "x".repeat(MAX_ENCRYPTION_PLAINTEXT_LEN)
2350 );
2351
2352 assert!(matches!(
2353 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2354 .encryption_type(EncryptedDataType::Content)
2355 .direct_key([0_u8; 16])
2356 .encrypt_xml(&oversized_malformed),
2357 Err(XmlEncError::Policy(
2358 crate::policy::PolicyViolation::ResourceLimit {
2359 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2360 ..
2361 }
2362 ))
2363 ));
2364 }
2365
2366 #[test]
2367 fn oversized_document_is_rejected_before_parsing() {
2368 let oversized_malformed = format!(
2371 "<root>{}</unclosed>",
2372 "x".repeat(MAX_ENCRYPTION_DOCUMENT_LEN)
2373 );
2374
2375 assert!(matches!(
2376 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2377 .direct_key([0_u8; 16])
2378 .encrypt_document(&oversized_malformed, DocumentEncryptionOptions::default()),
2379 Err(XmlEncError::Policy(
2380 crate::policy::PolicyViolation::ResourceLimit {
2381 resource: crate::policy::resource_name::XML_DOCUMENT,
2382 ..
2383 }
2384 ))
2385 ));
2386 }
2387
2388 #[test]
2389 fn encrypted_replacement_must_fit_document_policy() {
2390 for encrypted_type in [EncryptedDataType::Element, EncryptedDataType::Content] {
2393 let document = "<root><target ID=\"selected\">x</target></root>";
2394 let policy = crate::policy::EncryptionPolicy {
2395 resources: crate::policy::ResourcePolicy {
2396 max_xml_document_bytes: document.len(),
2397 ..crate::policy::ResourcePolicy::default()
2398 },
2399 ..crate::policy::EncryptionPolicy::default()
2400 };
2401
2402 assert!(matches!(
2403 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2404 .encryption_type(encrypted_type)
2405 .direct_key([0_u8; 16])
2406 .policy(policy)
2407 .encrypt_document(
2408 document,
2409 DocumentEncryptionOptions {
2410 element_id: Some("selected"),
2411 },
2412 ),
2413 Err(XmlEncError::Policy(
2414 crate::policy::PolicyViolation::ResourceLimit {
2415 resource: crate::policy::resource_name::XML_DOCUMENT,
2416 ..
2417 }
2418 ))
2419 ));
2420 }
2421 }
2422
2423 #[test]
2424 fn binary_encryption_preserves_an_opaque_type_hint() {
2425 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2428 .encryption_type(EncryptedDataType::Other("urn:example:binary".into()))
2429 .direct_key([0x42_u8; 16])
2430 .encrypt_binary(b"opaque payload")
2431 .expect("opaque binary Type must be accepted");
2432
2433 assert!(
2434 result
2435 .encrypted_data_xml
2436 .contains("Type=\"urn:example:binary\"")
2437 );
2438 }
2439
2440 #[test]
2441 fn binary_encryption_bounds_an_opaque_type_hint() {
2442 let maximum = 64;
2445 let policy = crate::policy::EncryptionPolicy {
2446 resources: crate::policy::ResourcePolicy {
2447 max_encryption_metadata_bytes: maximum,
2448 ..crate::policy::ResourcePolicy::default()
2449 },
2450 ..crate::policy::EncryptionPolicy::default()
2451 };
2452 let encrypt = |uri: String| {
2453 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2454 .encryption_type(EncryptedDataType::Other(uri))
2455 .direct_key([0x42_u8; 16])
2456 .policy(policy.clone())
2457 .encrypt_binary(b"opaque payload")
2458 };
2459
2460 encrypt(format!("urn:{}", "x".repeat(maximum - 4)))
2461 .expect("metadata at the configured boundary must remain accepted");
2462 assert!(matches!(
2463 encrypt(format!("urn:{}", "x".repeat(maximum - 3))),
2464 Err(XmlEncError::Policy(
2465 crate::policy::PolicyViolation::ResourceLimit {
2466 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
2467 maximum: 64,
2468 actual: 65,
2469 }
2470 ))
2471 ));
2472 }
2473
2474 #[test]
2475 fn encryption_policy_bounds_xml_nodes_at_both_parse_entry_points() {
2476 let policy = crate::policy::EncryptionPolicy {
2479 resources: crate::policy::ResourcePolicy {
2480 max_xml_nodes: 4,
2481 ..crate::policy::ResourcePolicy::default()
2482 },
2483 ..crate::policy::EncryptionPolicy::default()
2484 };
2485 let builder = || {
2486 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2487 .direct_key([0_u8; 16])
2488 .policy(policy.clone())
2489 };
2490 let xml = "<root><a/><b/><c/><d/></root>";
2491
2492 assert!(matches!(
2493 builder().encrypt_xml(xml),
2494 Err(XmlEncError::Policy(
2495 crate::policy::PolicyViolation::ResourceLimit {
2496 resource: crate::policy::resource_name::XML_NODES,
2497 maximum: 4,
2498 actual: 5,
2499 }
2500 ))
2501 ));
2502 assert!(matches!(
2503 builder().encrypt_document(xml, DocumentEncryptionOptions::default()),
2504 Err(XmlEncError::Policy(
2505 crate::policy::PolicyViolation::ResourceLimit {
2506 resource: crate::policy::resource_name::XML_NODES,
2507 maximum: 4,
2508 actual: 5,
2509 }
2510 ))
2511 ));
2512 }
2513
2514 #[test]
2515 fn encryption_entry_points_enforce_policy_depth() {
2516 let xml = "<root><child><leaf/></child></root>";
2519 let policy = crate::policy::EncryptionPolicy {
2520 resources: crate::policy::ResourcePolicy {
2521 max_xml_depth: 2,
2522 ..crate::policy::ResourcePolicy::default()
2523 },
2524 ..crate::policy::EncryptionPolicy::default()
2525 };
2526 let builder = || {
2527 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2528 .direct_key([0_u8; 16])
2529 .policy(policy.clone())
2530 };
2531 let mut document = XmlDocument::parse(xml).expect("wide retained fixture must parse");
2532
2533 assert!(matches!(
2534 builder().encrypt_document(xml, DocumentEncryptionOptions::default()),
2535 Err(XmlEncError::Policy(
2536 crate::policy::PolicyViolation::ResourceLimit {
2537 resource: crate::policy::resource_name::XML_DEPTH,
2538 maximum: 2,
2539 actual: 3,
2540 }
2541 ))
2542 ));
2543 assert!(matches!(
2544 builder().encrypt_owned_document(&mut document, DocumentEncryptionOptions::default()),
2545 Err(XmlEncError::Policy(
2546 crate::policy::PolicyViolation::ResourceLimit {
2547 resource: crate::policy::resource_name::XML_DEPTH,
2548 maximum: 2,
2549 actual: 3,
2550 }
2551 ))
2552 ));
2553 }
2554
2555 #[test]
2556 fn encryption_plaintext_and_owned_mutations_use_the_active_depth() {
2557 let policy = crate::policy::EncryptionPolicy {
2560 resources: crate::policy::ResourcePolicy {
2561 max_xml_depth: 1,
2562 ..crate::policy::ResourcePolicy::default()
2563 },
2564 ..crate::policy::EncryptionPolicy::default()
2565 };
2566 let builder = || {
2567 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2568 .direct_key([0_u8; 16])
2569 .policy(policy.clone())
2570 };
2571
2572 assert!(matches!(
2573 builder().encrypt_xml("<root><child/></root>"),
2574 Err(XmlEncError::Policy(
2575 crate::policy::PolicyViolation::ResourceLimit {
2576 resource: crate::policy::resource_name::XML_DEPTH,
2577 maximum: 1,
2578 actual: 2,
2579 }
2580 ))
2581 ));
2582
2583 let generated = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2584 .direct_key([0_u8; 16])
2585 .encryption_type(EncryptedDataType::Content)
2586 .encrypt_xml("")
2587 .expect("generated content replacement must parse");
2588 let generated_document = Document::parse(&generated.encrypted_data_xml)
2589 .expect("generated replacement must be XML");
2590 let generated_depth = generated_document
2591 .descendants()
2592 .filter(|node| node.is_element())
2593 .map(|node| {
2594 node.ancestors()
2595 .filter(|ancestor| ancestor.is_element())
2596 .count()
2597 })
2598 .max()
2599 .expect("generated replacement has elements");
2600 let mutation_policy = crate::policy::EncryptionPolicy {
2601 resources: crate::policy::ResourcePolicy {
2602 max_xml_depth: generated_depth,
2603 ..crate::policy::ResourcePolicy::default()
2604 },
2605 ..crate::policy::EncryptionPolicy::default()
2606 };
2607 let mut document = XmlDocument::parse("<root/>").expect("shallow fixture must parse");
2608 let original = document.as_xml().to_owned();
2609 let generation = document.generation();
2610 assert!(matches!(
2611 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2612 .direct_key([0_u8; 16])
2613 .encryption_type(EncryptedDataType::Content)
2614 .policy(mutation_policy)
2615 .encrypt_owned_document(&mut document, DocumentEncryptionOptions::default()),
2616 Err(XmlEncError::Policy(
2617 crate::policy::PolicyViolation::ResourceLimit {
2618 resource: crate::policy::resource_name::XML_DEPTH,
2619 maximum,
2620 actual,
2621 }
2622 )) if maximum == generated_depth && actual == generated_depth + 1
2623 ));
2624 assert_eq!(document.as_xml(), original);
2625 assert_eq!(document.generation(), generation);
2626 }
2627
2628 #[test]
2629 fn owned_encryption_checks_active_node_limit_before_target_selection() {
2630 let policy = crate::policy::EncryptionPolicy {
2633 resources: crate::policy::ResourcePolicy {
2634 max_xml_nodes: 1,
2635 ..crate::policy::ResourcePolicy::default()
2636 },
2637 ..crate::policy::EncryptionPolicy::default()
2638 };
2639 let mut document = XmlDocument::parse("<root><first/><second/></root>")
2640 .expect("retained fixture must parse");
2641 let before = document.as_xml().to_owned();
2642
2643 let error = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2644 .direct_key([0_u8; 16])
2645 .policy(policy)
2646 .encrypt_owned_document(
2647 &mut document,
2648 DocumentEncryptionOptions {
2649 element_id: Some("missing"),
2650 },
2651 )
2652 .expect_err("active node ceiling must precede target selection");
2653
2654 assert!(matches!(
2655 error,
2656 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2657 resource: crate::policy::resource_name::XML_NODES,
2658 maximum: 1,
2659 actual,
2660 }) if actual > 1
2661 ));
2662 assert_eq!(document.as_xml(), before);
2663 assert_eq!(document.generation(), 0);
2664 }
2665
2666 #[test]
2667 fn document_encryption_initial_parse_uses_the_policy_work_budget() {
2668 let xml = "<root/>";
2671 let policy = crate::policy::EncryptionPolicy {
2672 resources: crate::policy::ResourcePolicy {
2673 max_xml_parse_work_bytes: 0,
2674 ..crate::policy::ResourcePolicy::default()
2675 },
2676 ..crate::policy::EncryptionPolicy::default()
2677 };
2678
2679 let error = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2680 .direct_key([0_u8; 16])
2681 .policy(policy)
2682 .encrypt_document(xml, DocumentEncryptionOptions::default())
2683 .expect_err("a zero parse-work budget must reject the input parse");
2684
2685 assert!(matches!(
2686 error,
2687 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2688 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
2689 maximum: 0,
2690 actual,
2691 }) if actual == xml.len()
2692 ));
2693 }
2694
2695 #[test]
2696 fn content_plaintext_node_limit_excludes_the_internal_wrapper() {
2697 let policy = |max_xml_nodes| crate::policy::EncryptionPolicy {
2698 resources: crate::policy::ResourcePolicy {
2699 max_xml_nodes,
2700 ..crate::policy::ResourcePolicy::default()
2701 },
2702 ..crate::policy::EncryptionPolicy::default()
2703 };
2704
2705 let policy_three = policy(3);
2706 let budget_three = XmlParseWorkBudget::from_resources(&policy_three.resources);
2707 validate_xml_plaintext(
2708 "<first/><second/>",
2709 &EncryptedDataType::Content,
2710 &policy_three,
2711 &budget_three,
2712 crate::XmlBackend::default(),
2713 )
2714 .expect("the caller root and two elements must fit a three-node policy");
2715 let policy_two = policy(2);
2716 let budget_two = XmlParseWorkBudget::from_resources(&policy_two.resources);
2717 assert!(matches!(
2718 validate_xml_plaintext(
2719 "<first/><second/>",
2720 &EncryptedDataType::Content,
2721 &policy_two,
2722 &budget_two,
2723 crate::XmlBackend::default(),
2724 ),
2725 Err(XmlEncError::Policy(
2726 crate::policy::PolicyViolation::ResourceLimit {
2727 resource: crate::policy::resource_name::XML_NODES,
2728 maximum: 2,
2729 actual: 3,
2730 }
2731 ))
2732 ));
2733 }
2734
2735 #[test]
2736 fn content_plaintext_byte_limit_excludes_only_the_internal_wrapper() {
2737 let xml = "<first/><second/>";
2741 let maximum = xml.len() - 1;
2742 let policy = crate::policy::EncryptionPolicy {
2743 resources: crate::policy::ResourcePolicy {
2744 max_xml_document_bytes: maximum,
2745 max_encryption_plaintext_bytes: xml.len(),
2746 ..crate::policy::ResourcePolicy::default()
2747 },
2748 ..crate::policy::EncryptionPolicy::default()
2749 };
2750 let budget = XmlParseWorkBudget::from_resources(&policy.resources);
2751
2752 assert!(matches!(
2753 validate_xml_plaintext(
2754 xml,
2755 &EncryptedDataType::Content,
2756 &policy,
2757 &budget,
2758 crate::XmlBackend::default(),
2759 ),
2760 Err(XmlEncError::Policy(
2761 crate::policy::PolicyViolation::ResourceLimit {
2762 resource: crate::policy::resource_name::XML_DOCUMENT,
2763 maximum: observed_maximum,
2764 actual,
2765 }
2766 )) if observed_maximum == maximum && actual == xml.len()
2767 ));
2768 assert_eq!(
2769 budget.consumed(),
2770 0,
2771 "oversized XML must fail before parsing"
2772 );
2773 }
2774
2775 #[test]
2776 fn generated_encrypted_data_parse_uses_the_active_node_limit() {
2777 let xml = "<EncryptedData><CipherData><CipherValue>AA==</CipherValue></CipherData></EncryptedData>";
2780 let policy = crate::policy::EncryptionPolicy {
2781 resources: crate::policy::ResourcePolicy {
2782 max_xml_nodes: 1,
2783 ..crate::policy::ResourcePolicy::default()
2784 },
2785 ..crate::policy::EncryptionPolicy::default()
2786 };
2787 let budget = XmlParseWorkBudget::from_resources(&policy.resources);
2788
2789 assert!(matches!(
2790 count_generated_encrypted_data_nodes(
2791 xml,
2792 &policy,
2793 &budget,
2794 crate::XmlBackend::default(),
2795 ),
2796 Err(XmlEncError::Policy(
2797 crate::policy::PolicyViolation::ResourceLimit {
2798 resource: crate::policy::resource_name::XML_NODES,
2799 maximum: 1,
2800 actual: 2,
2801 }
2802 ))
2803 ));
2804 }
2805
2806 #[test]
2807 fn document_encryption_bounds_projected_replacement_nodes() {
2808 fn policy(max_xml_nodes: usize) -> crate::policy::EncryptionPolicy {
2809 crate::policy::EncryptionPolicy {
2810 resources: crate::policy::ResourcePolicy {
2811 max_xml_nodes,
2812 ..crate::policy::ResourcePolicy::default()
2813 },
2814 ..crate::policy::EncryptionPolicy::default()
2815 }
2816 }
2817
2818 let generated_nodes = {
2819 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2820 .direct_key([0_u8; 16])
2821 .encrypt_binary(b"payload")
2822 .expect("default policy must permit generated EncryptedData");
2823 Document::parse(&encrypted.encrypted_data_xml)
2824 .expect("generated EncryptedData must parse")
2825 .root_element()
2826 .descendants()
2827 .count()
2828 };
2829
2830 let element_actual = match EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2834 .direct_key([0_u8; 16])
2835 .policy(policy(generated_nodes))
2836 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2837 {
2838 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2839 resource: "XML nodes",
2840 maximum,
2841 actual,
2842 })) if maximum == generated_nodes && actual > maximum => actual,
2843 result => panic!("expected projected element node bound, got {result:?}"),
2844 };
2845 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2846 .direct_key([0_u8; 16])
2847 .policy(policy(element_actual))
2848 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2849 .expect("the exact projected element node limit must be accepted");
2850
2851 let content_actual = match EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2855 .encryption_type(EncryptedDataType::Content)
2856 .direct_key([0_u8; 16])
2857 .policy(policy(generated_nodes))
2858 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2859 {
2860 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2861 resource: "XML nodes",
2862 maximum,
2863 actual,
2864 })) if maximum == generated_nodes && actual > maximum => actual,
2865 result => panic!("expected projected content node bound, got {result:?}"),
2866 };
2867 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2868 .encryption_type(EncryptedDataType::Content)
2869 .direct_key([0_u8; 16])
2870 .policy(policy(content_actual))
2871 .encrypt_document("<root/>", DocumentEncryptionOptions::default())
2872 .expect("the exact projected content node limit must be accepted");
2873 }
2874
2875 #[test]
2876 fn standalone_encryption_bounds_generated_xml_nodes() {
2877 fn policy(max_xml_nodes: usize) -> crate::policy::EncryptionPolicy {
2878 crate::policy::EncryptionPolicy {
2879 resources: crate::policy::ResourcePolicy {
2880 max_xml_nodes,
2881 ..crate::policy::ResourcePolicy::default()
2882 },
2883 ..crate::policy::EncryptionPolicy::default()
2884 }
2885 }
2886
2887 assert!(matches!(
2890 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2891 .direct_key([0_u8; 16])
2892 .policy(policy(1))
2893 .encrypt_binary(b"payload"),
2894 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2895 resource: "XML nodes",
2896 maximum: 1,
2897 actual,
2898 })) if actual > 1
2899 ));
2900
2901 let generated = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2902 .direct_key([0_u8; 16])
2903 .encrypt_binary(b"payload")
2904 .expect("default policy must permit generated EncryptedData");
2905 let document = Document::parse(&generated.encrypted_data_xml)
2906 .expect("generated EncryptedData must parse");
2907 let actual = document.root().descendants().count();
2908 assert!(matches!(
2909 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2910 .direct_key([0_u8; 16])
2911 .policy(policy(actual - 1))
2912 .encrypt_binary(b"payload"),
2913 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2914 resource: "XML nodes",
2915 maximum,
2916 actual: reported,
2917 })) if maximum == actual - 1 && reported == actual
2918 ));
2919 let exact = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2920 .direct_key([0_u8; 16])
2921 .policy(policy(actual))
2922 .encrypt_binary(b"payload")
2923 .expect("the exact generated node limit must be accepted");
2924 let exact_document =
2925 Document::parse(&exact.encrypted_data_xml).expect("exact-boundary output must parse");
2926 let decryption_policy = crate::policy::DecryptionPolicy {
2927 resources: crate::policy::ResourcePolicy {
2928 max_xml_nodes: actual,
2929 ..crate::policy::ResourcePolicy::default()
2930 },
2931 ..crate::policy::DecryptionPolicy::default()
2932 };
2933 super::super::parse::parse_encrypted_data_node_with_policy(
2934 exact_document.root_element(),
2935 &decryption_policy,
2936 )
2937 .expect("output accepted at the exact limit must be consumable at the same limit");
2938 }
2939
2940 #[test]
2941 fn document_dtd_is_controlled_only_by_operation_policy() {
2942 let document = "<!DOCTYPE root [<!ELEMENT root ANY>]><root/>";
2945 assert!(matches!(
2946 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2947 .direct_key([0_u8; 16])
2948 .encrypt_document(document, DocumentEncryptionOptions { element_id: None },),
2949 Err(XmlEncError::XmlParse(_))
2950 ));
2951 let mut policy = crate::policy::EncryptionPolicy::default();
2952 policy.xml.allow_internal_dtd = true;
2953 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2954 .direct_key([0_u8; 16])
2955 .policy(policy.clone())
2956 .encrypt_document(document, DocumentEncryptionOptions { element_id: None })
2957 .expect("the explicit operation policy should permit parsing");
2958 }
2959
2960 #[test]
2961 fn invalid_resource_policy_is_rejected_at_every_entry_point() {
2962 let mut policy = crate::policy::EncryptionPolicy::default();
2965 policy.resources.max_encryption_plaintext_bytes =
2966 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING + 1;
2967 let builder = || {
2968 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
2969 .direct_key([0_u8; 16])
2970 .policy(policy.clone())
2971 };
2972
2973 assert!(matches!(
2974 builder().encrypt_xml("<broken>"),
2975 Err(XmlEncError::Policy(_))
2976 ));
2977 assert!(matches!(
2978 builder().encrypt_binary(b"x"),
2979 Err(XmlEncError::Policy(_))
2980 ));
2981 assert!(matches!(
2982 builder().encrypt_document("<broken>", DocumentEncryptionOptions::default()),
2983 Err(XmlEncError::Policy(_))
2984 ));
2985 }
2986
2987 #[test]
2988 fn standalone_encrypted_output_obeys_document_byte_ceiling() {
2989 let policy = |maximum| crate::policy::EncryptionPolicy {
2992 resources: crate::policy::ResourcePolicy {
2993 max_xml_document_bytes: maximum,
2994 ..crate::policy::ResourcePolicy::default()
2995 },
2996 ..crate::policy::EncryptionPolicy::default()
2997 };
2998
2999 let binary_len = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3000 .direct_key([0_u8; 16])
3001 .encrypt_binary(b"bounded binary")
3002 .expect("baseline binary encryption must succeed")
3003 .encrypted_data_xml
3004 .len();
3005 assert!(matches!(
3006 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3007 .direct_key([0_u8; 16])
3008 .policy(policy(binary_len - 1))
3009 .encrypt_binary(b"bounded binary"),
3010 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3011 resource: crate::policy::resource_name::XML_DOCUMENT,
3012 maximum,
3013 actual,
3014 }))
3015 if maximum == binary_len - 1 && actual == binary_len
3016 ));
3017 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3018 .direct_key([0_u8; 16])
3019 .policy(policy(binary_len))
3020 .encrypt_binary(b"bounded binary")
3021 .expect("the exact standalone binary output bound must be accepted");
3022
3023 let xml_len = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3024 .direct_key([0_u8; 16])
3025 .encrypt_xml("<secret>bounded XML</secret>")
3026 .expect("baseline XML encryption must succeed")
3027 .encrypted_data_xml
3028 .len();
3029 assert!(matches!(
3030 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3031 .direct_key([0_u8; 16])
3032 .policy(policy(xml_len - 1))
3033 .encrypt_xml("<secret>bounded XML</secret>"),
3034 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3035 resource: crate::policy::resource_name::XML_DOCUMENT,
3036 maximum,
3037 actual,
3038 }))
3039 if maximum == xml_len - 1 && actual == xml_len
3040 ));
3041 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3042 .direct_key([0_u8; 16])
3043 .policy(policy(xml_len))
3044 .encrypt_xml("<secret>bounded XML</secret>")
3045 .expect("the exact standalone XML output bound must be accepted");
3046 }
3047
3048 #[test]
3049 fn zero_resource_ceilings_allow_operations_that_consume_none() {
3050 let policy = crate::policy::EncryptionPolicy {
3053 resources: crate::policy::ResourcePolicy {
3054 max_encryption_plaintext_bytes: 0,
3055 max_encryption_recipients: 0,
3056 ..crate::policy::ResourcePolicy::default()
3057 },
3058 ..crate::policy::EncryptionPolicy::default()
3059 };
3060
3061 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3062 .direct_key([0_u8; 16])
3063 .policy(policy.clone())
3064 .encrypt_binary(&[])
3065 .expect("zero ceilings must allow resources the operation does not consume");
3066
3067 assert!(matches!(
3068 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3069 .direct_key([0_u8; 16])
3070 .policy(policy.clone())
3071 .encrypt_binary(b"x"),
3072 Err(XmlEncError::Policy(
3073 crate::policy::PolicyViolation::ResourceLimit {
3074 resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
3075 maximum: 0,
3076 actual: 1
3077 }
3078 ))
3079 ));
3080 assert!(matches!(
3081 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3082 .recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128)
3083 .policy(policy)
3084 .encrypt_binary(&[]),
3085 Err(XmlEncError::Policy(
3086 crate::policy::PolicyViolation::ResourceLimit {
3087 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
3088 maximum: 0,
3089 actual: 1,
3090 }
3091 ))
3092 ));
3093 }
3094
3095 #[test]
3096 fn encryption_enforces_key_candidate_budget_before_inspection() {
3097 let deny_keys = crate::policy::EncryptionPolicy {
3100 resources: crate::policy::ResourcePolicy {
3101 max_key_candidates: 0,
3102 ..crate::policy::ResourcePolicy::default()
3103 },
3104 ..crate::policy::EncryptionPolicy::default()
3105 };
3106
3107 for error in [
3108 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3109 .direct_key([0_u8; 15])
3110 .policy(deny_keys.clone())
3111 .encrypt_binary(b"data")
3112 .expect_err("a direct key must consume one candidate"),
3113 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3114 .recipient_aes_kw([0_u8; 15], KeyWrapAlgorithm::AesKw128)
3115 .policy(deny_keys)
3116 .encrypt_binary(b"data")
3117 .expect_err("a recipient key must consume one candidate"),
3118 ] {
3119 assert!(matches!(
3120 error,
3121 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3122 resource: crate::policy::resource_name::KEY_CANDIDATES,
3123 maximum: 0,
3124 actual: 1,
3125 })
3126 ));
3127 }
3128
3129 let recipient =
3130 || EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128);
3131 let policy_with_candidate_limit = |maximum| crate::policy::EncryptionPolicy {
3132 resources: crate::policy::ResourcePolicy {
3133 max_key_candidates: maximum,
3134 ..crate::policy::ResourcePolicy::default()
3135 },
3136 ..crate::policy::EncryptionPolicy::default()
3137 };
3138 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3139 .add_recipient(recipient())
3140 .add_recipient(recipient());
3141
3142 assert!(matches!(
3143 builder
3144 .clone()
3145 .policy(policy_with_candidate_limit(1))
3146 .encrypt_binary(b"data"),
3147 Err(XmlEncError::Policy(
3148 crate::policy::PolicyViolation::ResourceLimit {
3149 resource: crate::policy::resource_name::KEY_CANDIDATES,
3150 maximum: 1,
3151 actual: 2,
3152 }
3153 ))
3154 ));
3155 builder
3156 .policy(policy_with_candidate_limit(2))
3157 .encrypt_binary(b"data")
3158 .expect("the exact key-candidate boundary must be accepted");
3159 }
3160
3161 #[test]
3162 fn element_plaintext_enforces_replacement_node_contract() {
3163 let builder =
3166 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm).direct_key([0_u8; 16]);
3167 builder
3168 .encrypt_xml("\n<!--before--><secret/><!--after-->\n")
3169 .expect("one element with boundary trivia must be accepted");
3170
3171 assert!(matches!(
3172 builder.encrypt_xml("<?target value?><secret/>"),
3173 Err(XmlEncError::InvalidStructure(_))
3174 ));
3175 }
3176
3177 #[test]
3178 fn empty_key_names_are_rejected_before_serialization() {
3179 assert!(matches!(
3182 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3183 .direct_key([0_u8; 16])
3184 .direct_key_name("")
3185 .encrypt_binary(b"data"),
3186 Err(XmlEncError::InvalidEncryptionConfig(_))
3187 ));
3188 assert!(matches!(
3189 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3190 .add_recipient(
3191 EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128)
3192 .key_name("")
3193 )
3194 .encrypt_binary(b"data"),
3195 Err(XmlEncError::InvalidEncryptionConfig(_))
3196 ));
3197 }
3198
3199 #[test]
3200 fn xml_forbidden_metadata_characters_are_rejected() {
3201 assert!(matches!(
3204 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3205 .direct_key([0_u8; 16])
3206 .id("invalid\0id")
3207 .encrypt_binary(b"data"),
3208 Err(XmlEncError::InvalidEncryptionConfig(_))
3209 ));
3210 assert!(matches!(
3211 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3212 .direct_key([0_u8; 16])
3213 .direct_key_name("invalid\0name")
3214 .encrypt_binary(b"data"),
3215 Err(XmlEncError::InvalidEncryptionConfig(_))
3216 ));
3217 assert!(matches!(
3218 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3219 .add_recipient(
3220 EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128)
3221 .recipient("invalid\0recipient")
3222 )
3223 .encrypt_binary(b"data"),
3224 Err(XmlEncError::InvalidEncryptionConfig(_))
3225 ));
3226 assert!(matches!(
3227 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3228 .add_recipient(
3229 EncryptionRecipient::aes_key_wrap([0_u8; 16], KeyWrapAlgorithm::AesKw128)
3230 .key_name("invalid\0name")
3231 )
3232 .encrypt_binary(b"data"),
3233 Err(XmlEncError::InvalidEncryptionConfig(_))
3234 ));
3235 }
3236
3237 #[test]
3238 fn encrypted_data_id_must_be_an_xml_ncname() {
3239 for invalid in ["bad id", "1leading", "qualified:name", ""] {
3242 assert!(matches!(
3243 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3244 .direct_key([0_u8; 16])
3245 .id(invalid)
3246 .encrypt_binary(b"data"),
3247 Err(XmlEncError::InvalidEncryptionConfig(_))
3248 ));
3249 }
3250
3251 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3252 .direct_key([0_u8; 16])
3253 .id("Δοκιμή")
3254 .encrypt_binary(b"data")
3255 .expect("Unicode XML NCNames must remain valid identifiers");
3256 }
3257
3258 #[test]
3259 fn rejects_malformed_custom_provider_ciphertext_before_serialization() {
3260 for (algorithm, ciphertext) in [
3263 (DataEncryptionAlgorithm::Aes128Gcm, vec![0_u8; 27]),
3264 (DataEncryptionAlgorithm::Aes128Cbc, vec![0_u8; 31]),
3265 (DataEncryptionAlgorithm::Aes256Cbc, vec![0_u8; 33]),
3266 ] {
3267 let error = EncryptedDataBuilder::new(algorithm)
3268 .provider(Arc::new(OverridingOutputProvider {
3269 ciphertext: Some(ciphertext),
3270 wrapped_key: None,
3271 transported_key: None,
3272 transport_calls: AtomicUsize::new(0),
3273 }))
3274 .direct_key(vec![0_u8; algorithm.key_len()])
3275 .encrypt_binary(b"data")
3276 .expect_err("malformed provider output must fail before XML serialization");
3277 assert!(matches!(
3278 error,
3279 XmlEncError::DataTooShort { .. } | XmlEncError::InvalidCbcCiphertextLength { .. }
3280 ));
3281 }
3282 }
3283
3284 #[test]
3285 fn rejects_overlong_custom_provider_ciphertext_before_serialization() {
3286 for (algorithm, expected, actual) in [
3289 (DataEncryptionAlgorithm::Aes128Gcm, 32, 33),
3290 (DataEncryptionAlgorithm::Aes128Cbc, 32, 48),
3291 ] {
3292 let error = EncryptedDataBuilder::new(algorithm)
3293 .provider(Arc::new(OverridingOutputProvider {
3294 ciphertext: Some(vec![0_u8; actual]),
3295 wrapped_key: None,
3296 transported_key: None,
3297 transport_calls: AtomicUsize::new(0),
3298 }))
3299 .direct_key(vec![0_u8; algorithm.key_len()])
3300 .encrypt_binary(b"data")
3301 .expect_err("overlong provider output must fail before XML serialization");
3302 assert!(matches!(
3303 error,
3304 XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSize {
3305 operation: crate::provider::ProviderOperation::Encrypt,
3306 expected: observed_expected,
3307 actual: observed_actual,
3308 }) if observed_expected == expected && observed_actual == actual
3309 ));
3310 }
3311 }
3312
3313 #[test]
3314 fn rejects_malformed_custom_provider_wrapped_keys_before_serialization() {
3315 for wrapped_key in [vec![], vec![0_u8; 23], vec![0_u8; 25]] {
3318 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3319 .provider(Arc::new(OverridingOutputProvider {
3320 ciphertext: None,
3321 wrapped_key: Some(wrapped_key),
3322 transported_key: None,
3323 transport_calls: AtomicUsize::new(0),
3324 }))
3325 .recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128)
3326 .encrypt_binary(b"data");
3327 assert!(matches!(
3328 result,
3329 Err(XmlEncError::InvalidWrappedKeyLength { expected: 24, actual })
3330 if actual != 24
3331 ));
3332 }
3333
3334 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3335 .provider(Arc::new(OverridingOutputProvider {
3336 ciphertext: None,
3337 wrapped_key: Some(vec![0_u8; 24]),
3338 transported_key: None,
3339 transport_calls: AtomicUsize::new(0),
3340 }))
3341 .recipient_aes_kw([0_u8; 16], KeyWrapAlgorithm::AesKw128)
3342 .encrypt_binary(b"data")
3343 .expect("exact RFC 3394 wrapped-key length must remain accepted");
3344 }
3345
3346 #[test]
3347 fn rejects_malformed_custom_provider_rsa_transport_before_serialization() {
3348 let private_key = RsaPrivateKey::new(&mut UnwrapErr(SysRng), 2048)
3351 .expect("RSA key generation should succeed");
3352 let public_key = RsaPublicKey::from(&private_key);
3353 for transported_key in [vec![], vec![0_u8; 255], vec![0_u8; 257]] {
3354 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3355 .provider(Arc::new(OverridingOutputProvider {
3356 ciphertext: None,
3357 wrapped_key: None,
3358 transported_key: Some(transported_key),
3359 transport_calls: AtomicUsize::new(0),
3360 }))
3361 .recipient_rsa_oaep(public_key.clone())
3362 .encrypt_binary(b"data");
3363 assert!(matches!(
3364 result,
3365 Err(XmlEncError::InvalidWrappedKeyLength {
3366 expected: 256,
3367 actual,
3368 }) if actual != 256
3369 ));
3370 }
3371
3372 EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3373 .provider(Arc::new(OverridingOutputProvider {
3374 ciphertext: None,
3375 wrapped_key: None,
3376 transported_key: Some(vec![0_u8; 256]),
3377 transport_calls: AtomicUsize::new(0),
3378 }))
3379 .recipient_rsa_oaep(public_key)
3380 .encrypt_binary(b"data")
3381 .expect("modulus-sized RSA transport output must remain accepted");
3382 }
3383
3384 #[test]
3385 fn custom_provider_encrypts_with_an_opaque_transport_key() {
3386 let provider = Arc::new(OverridingOutputProvider {
3389 ciphertext: None,
3390 wrapped_key: None,
3391 transported_key: Some(vec![0x5a; 256]),
3392 transport_calls: AtomicUsize::new(0),
3393 });
3394 let key = Arc::new(OpaqueTransportKey {
3395 modulus: vec![0x80; 256],
3396 exponent: vec![0x01, 0x00, 0x01],
3397 });
3398
3399 let encrypted = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3400 .provider(provider.clone())
3401 .recipient_key_transport(key)
3402 .encrypt_binary(b"opaque provider key")
3403 .expect("custom provider must accept its opaque transport key");
3404
3405 assert_eq!(provider.transport_calls.load(Ordering::Relaxed), 1);
3406 assert!(encrypted.encrypted_data_xml.contains("rsa-oaep"));
3407 }
3408
3409 #[test]
3410 fn opaque_transport_preflight_rejects_weak_rsa_metadata() {
3411 let key = OpaqueTransportKey {
3414 modulus: vec![0x80; 128],
3415 exponent: vec![0x01, 0x00, 0x01],
3416 };
3417 assert!(matches!(
3418 validate_key_transport_recipient(&key, &crate::policy::EncryptionPolicy::default()),
3419 Err(XmlEncError::Policy(
3420 crate::policy::PolicyViolation::KeySize { .. }
3421 ))
3422 ));
3423 }
3424
3425 #[test]
3426 fn encryption_policy_rejects_weak_rsa_recipient_before_provider_dispatch() {
3427 let private_key = RsaPrivateKey::new(&mut UnwrapErr(SysRng), 1024)
3429 .expect("test RSA key generation should succeed");
3430 let provider = Arc::new(OverridingOutputProvider {
3431 ciphertext: None,
3432 wrapped_key: None,
3433 transported_key: Some(vec![0_u8; 128]),
3434 transport_calls: AtomicUsize::new(0),
3435 });
3436
3437 let result = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3438 .provider(provider.clone())
3439 .recipient_rsa_oaep(RsaPublicKey::from(&private_key))
3440 .encrypt_binary(b"data");
3441
3442 assert!(matches!(
3443 result,
3444 Err(XmlEncError::Policy(
3445 crate::policy::PolicyViolation::KeySize {
3446 operation: "encryption",
3447 minimum_bits: 2048,
3448 maximum_bits: 8192,
3449 actual_bits: 1024,
3450 ..
3451 }
3452 ))
3453 ));
3454 assert_eq!(provider.transport_calls.load(Ordering::Relaxed), 0);
3455 }
3456
3457 #[test]
3458 fn debug_output_redacts_symmetric_key_material() {
3459 let direct_key = b"direct-key-secret".to_vec();
3460 let kek = b"key-wrap-secret!".to_vec();
3461 let builder = EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
3462 .direct_key(direct_key.clone());
3463 let recipient = EncryptionRecipient::aes_key_wrap(kek.clone(), KeyWrapAlgorithm::AesKw128);
3464
3465 let builder_debug = format!("{builder:?}");
3466 let recipient_debug = format!("{recipient:?}");
3467 assert!(builder_debug.contains("[REDACTED]"));
3468 assert!(recipient_debug.contains("[REDACTED]"));
3469 assert!(!builder_debug.contains(&format!("{direct_key:?}")));
3470 assert!(!recipient_debug.contains(&format!("{kek:?}")));
3471 }
3472}