Skip to main content

xml_sec/xmlenc/
encrypt.rs

1//! XMLEnc content encryption, key wrapping, and XML generation.
2
3use 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
28/// Validate an RSA recipient key against the compiled encryption policy.
29///
30/// Key registries can use this preflight before selecting a candidate, ensuring
31/// ordered searches skip keys that the encryption operation would reject.
32pub 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
39/// Validate an opaque RSA transport key against the compiled encryption policy.
40///
41/// The handle's public metadata must identify the exact key used by the
42/// selected provider. This preflight lets provider-owned key registries apply
43/// the same policy as [`EncryptedDataBuilder`] before selecting a candidate.
44pub 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/// Builder for complete `EncryptedData` fragments and document replacement.
57#[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    /// Create a builder for a content-encryption algorithm.
95    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    /// Replace the complete immutable encryption policy snapshot.
109    pub fn policy(mut self, policy: crate::policy::EncryptionPolicy) -> Self {
110        self.policy = policy;
111        self
112    }
113
114    /// Select the cryptographic provider for this operation context.
115    pub fn provider(mut self, provider: Arc<dyn crate::provider::CryptoProvider>) -> Self {
116        self.provider = provider;
117        self
118    }
119
120    /// Set whether XML encryption covers one element or its child content.
121    pub fn encryption_type(mut self, encrypted_type: EncryptedDataType) -> Self {
122        self.encrypted_type = encrypted_type;
123        self
124    }
125
126    /// Set the generated `EncryptedData` identifier.
127    pub fn id(mut self, id: impl Into<String>) -> Self {
128        self.id = Some(id.into());
129        self
130    }
131
132    /// Use a caller-managed content key instead of generating and wrapping one.
133    pub fn direct_key(mut self, key: impl Into<Vec<u8>>) -> Self {
134        self.direct_key = Some(key.into());
135        self
136    }
137
138    /// Emit a direct `KeyName` hint for a caller-managed content key.
139    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    /// Add one independently wrapped recipient of the generated content key.
145    pub fn add_recipient(mut self, recipient: EncryptionRecipient) -> Self {
146        self.recipients.push(recipient);
147        self
148    }
149
150    /// Add an RSA-OAEP recipient using secure XMLEnc 1.1 defaults.
151    pub fn recipient_rsa_oaep(self, public_key: RsaPublicKey) -> Self {
152        self.add_recipient(EncryptionRecipient::rsa_oaep(public_key))
153    }
154
155    /// Add an RSA-OAEP recipient backed by an opaque provider key handle.
156    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    /// Add an AES Key Wrap recipient.
164    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    /// Encrypt one complete XML element or an XML content fragment.
169    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    /// Encrypt opaque bytes, preserving a configured non-XML `Type` hint.
187    ///
188    /// Element and Content are XML replacement semantics and are omitted from
189    /// binary output. Any other URI remains application metadata.
190    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    /// Encrypt and replace the document root or one element selected by XML ID.
206    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    /// Encrypt and replace a node in a reusable owned XML document.
223    ///
224    /// Successful mutation advances the document generation and invalidates
225    /// identities captured before this call.
226    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(&parameters.algorithm))
469                        || self.policy.oaep_digests.as_ref().is_some_and(|allowed| {
470                            !allowed.contains(&parameters.digest)
471                                || !allowed.contains(&parameters.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    // The standalone parser adds one document node that is not inserted into
693    // the caller's tree. Bound the generated subtree by the active policy while
694    // admitting that transient root so exact-fit content replacement remains valid.
695    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(&parameters.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            // The wrapper exists only to parse an XML fragment. Its node must
994            // not consume the caller-owned byte, node, or depth allowance.
995            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    // `Node::range()` ends at this element's closing tag, so its `</` marker is
1074    // necessarily the final one even when child text or CDATA contains `</`.
1075    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        // All emitted wire layouts must be accepted by the existing independent
1326        // decrypt path, including empty plaintext and full-block CBC padding.
1327        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        // Algorithm URIs define the KEK size. Provider implementations are
1383        // capabilities, not authorities allowed to reinterpret wire semantics.
1384        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        // The legacy URI has no MGF child on the wire, so accepting another
1422        // digest here would let a permissive provider emit ambiguous ciphertext.
1423        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        // The input bound protects the parser and the Content wrapper
1544        // allocation, so size must take precedence over malformed XML.
1545        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        // The document API has a separate parser-input bound because the
1567        // selected plaintext may be much smaller than its enclosing document.
1568        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        // Cipher framing, base64, and EncryptedData markup expand the selected
1589        // range; the returned document must remain valid input to decryption.
1590        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        // A non-XML Type URI describes opaque application bytes. It must survive
1624        // binary encryption so decryption can continue returning byte content.
1625        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        // Generated metadata must obey the same policy as reciprocal parsing so
1641        // the builder cannot emit an EncryptedData document it would reject.
1642        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        // XML plaintext and whole-document encryption are separate parser paths;
1675        // both must consume the same immutable operation-policy node ceiling.
1676        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        // Whole-document encryption must apply the same depth policy to string
1715        // and retained inputs before target selection or encryption work.
1716        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        // A document parsed under broad defaults must not retain those defaults
1756        // when a stricter encryption operation parses or commits replacement XML.
1757        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        // A missing selector must not bypass the active policy or make target
1829        // discovery traverse a retained document parsed under a wider ceiling.
1830        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        // Parsing the caller's XML and parsing the encrypted replacement must
1867        // consume one operation-wide allowance rather than independent caps.
1868        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        // The parser-only wrapper must not replace the caller's byte ceiling.
1934        // Encryption plaintext may be larger than the XML input policy, but an
1935        // XML Content fragment still has to satisfy both limits before parsing.
1936        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        // Generated XML is still operation work: reject it in the parser under
1968        // the active policy rather than allocating up to the absolute ceiling.
1969        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        // The source document fits the low limit, but the generated
2016        // EncryptedData replacement does not. The ceiling admits the standalone
2017        // fragment so this specifically exercises whole-document projection.
2018        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        // Content replacement retains the selected element. In particular, a
2037        // self-closing element expands around EncryptedData without adding an
2038        // extra source node to the projection.
2039        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        // Binary encryption has no input XML tree, but its generated EncryptedData
2073        // must still be consumable under the same operation-policy node ceiling.
2074        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        // Parser behavior comes from the immutable operation snapshot; target
2128        // selection options cannot independently weaken or tighten it.
2129        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        // Entry points must reject an invalid snapshot before parsing or using
2148        // any caller-selected limit derived from it.
2149        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        // The returned fragment must remain admissible to the reciprocal parser;
2175        // plaintext bounds alone do not account for framing, base64, or markup.
2176        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        // Zero is deny-all, not an invalid policy. Direct-key encryption has no
2236        // recipients, and an empty binary payload consumes no plaintext bytes.
2237        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        // Candidate accounting must reject configured keys before validating or
2283        // dispatching them, while accepting the exact operation-wide boundary.
2284        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        // Element ciphertext must be safe for the reciprocal document replacement:
2349        // boundary whitespace/comments are harmless, but processing instructions are not.
2350        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        // The reciprocal parser rejects empty KeyName elements, so encryption
2365        // must not emit output that its own decrypt pipeline cannot consume.
2366        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        // XML escaping cannot legalize forbidden XML 1.0 code points, so every
2387        // caller-controlled attribute/text path must fail before serialization.
2388        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        // xsd:ID derives from NCName; escaping arbitrary attribute text cannot
2425        // make whitespace, a leading digit, or a colon schema-valid.
2426        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        // Providers supply primitives, but the facade owns the standard wire
2446        // contract and must not serialize output its own decryptor rejects.
2447        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        // Provider success cannot change the algorithm-defined relationship
2472        // between plaintext and ciphertext length.
2473        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        // RFC 3394 adds exactly one 64-bit integrity block. Accepting any other
2501        // provider output would emit EncryptedKey data no recipient can unwrap.
2502        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        // RSA ciphertext is exactly one modulus wide. Enforcing that invariant
2534        // here prevents custom providers from emitting undecryptable XML.
2535        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        // The key exposes only public policy metadata. Successful encryption
2572        // proves orchestration never needs a concrete RustCrypto RSA object.
2573        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        // Provider-owned keys cannot bypass the same outbound RSA policy used
2597        // by the RustCrypto convenience constructor.
2598        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        // Provider capability cannot weaken the outbound recipient-key policy.
2613        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}