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