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