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