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