Skip to main content

xml_sec/xmlenc/
encrypt.rs

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