Skip to main content

xml_sec/xmlenc/
decrypt.rs

1//! XMLEnc decryption entry point and key resolvers.
2
3use std::{fmt, sync::Arc};
4
5#[cfg(test)]
6use crate::xml::dom::Document;
7use base64::{Engine as _, engine::general_purpose::STANDARD};
8
9use crate::document::{DocumentParseSettings, XmlDocument, XmlParseWorkBudget};
10use crate::operation::{
11    OperationExecutionContext, OperationNodeId, OperationNodeKind, OperationPlanError,
12    OperationStage,
13};
14use rsa::RsaPrivateKey;
15
16use super::parse::{
17    parse_encrypted_data_node_with_policy_and_budget,
18    parse_encrypted_data_with_policy_backend_and_budget, validate_encrypted_data_metadata,
19};
20use super::types::{MAX_CIPHER_VALUE_BASE64_LEN, XMLENC_NS, validate_ciphertext_framing};
21use super::{
22    DataEncryptionAlgorithm, DecryptedContent, EncryptedData, EncryptedDataType, EncryptedKey,
23    KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm, RsaOaepParameters, XmlEncError,
24    map_document_error,
25};
26
27#[cfg(test)]
28use super::parse_encrypted_data;
29
30/// Aggregate key-candidate work allowance for one cryptographic operation.
31///
32/// Resolver implementations must consume one unit before each key lookup or
33/// unwrap attempt. A single budget is shared across direct and recipient keys.
34#[derive(Debug)]
35pub struct KeyCandidateBudget {
36    maximum: usize,
37    remaining: usize,
38}
39
40impl KeyCandidateBudget {
41    /// Create the fixed implementation-wide budget for one operation.
42    pub fn for_operation() -> Self {
43        Self::with_limit(crate::hard_limits::KEY_CANDIDATE_CEILING)
44    }
45
46    /// Create a budget from a validated operation policy ceiling.
47    pub fn with_limit(maximum: usize) -> Self {
48        Self {
49            maximum,
50            remaining: maximum,
51        }
52    }
53
54    /// Number of candidate attempts still available to this operation.
55    pub const fn remaining(&self) -> usize {
56        self.remaining
57    }
58
59    /// Charge attempted candidate work before performing it.
60    pub fn consume(&mut self, count: usize) -> Result<(), XmlEncError> {
61        if count > self.remaining {
62            return Err(crate::policy::PolicyViolation::ResourceLimit {
63                resource: crate::policy::resource_name::KEY_CANDIDATES,
64                maximum: self.maximum,
65                actual: self
66                    .maximum
67                    .saturating_sub(self.remaining)
68                    .saturating_add(count),
69            }
70            .into());
71        }
72        self.remaining -= count;
73        Ok(())
74    }
75
76    fn account_returned_candidates(
77        &mut self,
78        remaining_before: usize,
79        returned: usize,
80    ) -> Result<(), XmlEncError> {
81        let resolver_charged = remaining_before.saturating_sub(self.remaining);
82        self.consume(returned.saturating_sub(resolver_charged))
83    }
84}
85
86/// Supplies a content-encryption key for parsed XMLEnc data.
87pub trait DecryptionKeyResolver {
88    /// Resolve the symmetric key for `algorithm`, optionally unwrapping `encrypted_key`.
89    fn resolve_key(
90        &self,
91        provider: &dyn crate::provider::CryptoProvider,
92        algorithm: DataEncryptionAlgorithm,
93        encrypted_key: Option<&EncryptedKey>,
94    ) -> Result<Vec<u8>, XmlEncError>;
95
96    /// Resolve ordered candidate keys for one prepared decryption operation.
97    ///
98    /// The default preserves single-key resolver behavior. Key rings override
99    /// this method so parsing, policy validation, and ciphertext decoding occur
100    /// once while only authenticated primitive decryption is retried. Overrides
101    /// must consume the shared budget before every lookup or unwrap attempt.
102    /// The context also accounts for any returned candidates an implementation
103    /// did not explicitly charge. Returning [`XmlEncError::Policy`] rejects the
104    /// complete operation and never advances to another key source; use a
105    /// candidate-local error such as [`XmlEncError::KeyNotFound`] when later
106    /// ordered sources are still eligible.
107    fn resolve_key_candidates(
108        &self,
109        provider: &dyn crate::provider::CryptoProvider,
110        algorithm: DataEncryptionAlgorithm,
111        encrypted_key: Option<&EncryptedKey>,
112        budget: &mut KeyCandidateBudget,
113    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
114        budget.consume(1)?;
115        self.resolve_key(provider, algorithm, encrypted_key)
116            .map(|key| vec![key])
117    }
118}
119
120/// Caller-owned target selection for document decryption.
121#[derive(Debug, Clone, Copy, Default)]
122pub struct DocumentDecryptionOptions<'a> {
123    /// Select a specific `EncryptedData` by its `Id` attribute.
124    pub encrypted_data_id: Option<&'a str>,
125}
126
127/// Immutable XMLEnc decryption operation context.
128pub struct DecryptContext<'a> {
129    resolver: &'a dyn DecryptionKeyResolver,
130    policy: crate::policy::DecryptionPolicy,
131    provider: &'a dyn crate::provider::CryptoProvider,
132    xml_backend: crate::XmlBackend,
133    id_attributes: &'a [crate::IdAttributeRegistration],
134}
135
136struct DecryptionPlanNodes {
137    document: OperationNodeId,
138    key: OperationNodeId,
139    crypto: OperationNodeId,
140    evidence: OperationNodeId,
141    mutation: Option<OperationNodeId>,
142}
143
144struct DecryptionOperationBudgets {
145    key_candidates: std::cell::RefCell<KeyCandidateBudget>,
146    xml_parse: XmlParseWorkBudget,
147}
148
149impl DecryptionOperationBudgets {
150    fn from_policy(policy: &crate::policy::DecryptionPolicy) -> Self {
151        Self {
152            key_candidates: std::cell::RefCell::new(KeyCandidateBudget::with_limit(
153                policy.resources.max_key_candidates,
154            )),
155            xml_parse: XmlParseWorkBudget::from_resources(&policy.resources),
156        }
157    }
158}
159
160fn compile_decryption_plan(
161    operation: &mut OperationExecutionContext<
162        crate::policy::DecryptionPolicy,
163        DecryptionOperationBudgets,
164    >,
165    mutation: bool,
166) -> Result<DecryptionPlanNodes, XmlEncError> {
167    let document = operation.add_node(OperationNodeKind::Document, OperationStage::Parse, None);
168    let key = operation.add_node(
169        OperationNodeKind::Key { index: 0 },
170        OperationStage::Resolve,
171        None,
172    );
173    operation
174        .add_dependency(key, document)
175        .map_err(map_decryption_plan_error)?;
176    let crypto = operation.add_node(OperationNodeKind::Crypto, OperationStage::Crypto, None);
177    operation
178        .add_dependency(crypto, key)
179        .map_err(map_decryption_plan_error)?;
180    let evidence = operation.add_node(OperationNodeKind::Evidence, OperationStage::Evidence, None);
181    operation
182        .add_dependency(evidence, crypto)
183        .map_err(map_decryption_plan_error)?;
184    let mutation = mutation.then(|| {
185        let node = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
186        operation
187            .add_dependency(node, evidence)
188            .expect("evidence-to-mutation stage order is fixed");
189        node
190    });
191    operation.compile().map_err(map_decryption_plan_error)?;
192    Ok(DecryptionPlanNodes {
193        document,
194        key,
195        crypto,
196        evidence,
197        mutation,
198    })
199}
200
201struct ProcessedDecryption<T> {
202    output: T,
203    operation:
204        OperationExecutionContext<crate::policy::DecryptionPolicy, DecryptionOperationBudgets>,
205    mutation: Option<OperationNodeId>,
206}
207
208fn map_decryption_plan_error(error: OperationPlanError) -> XmlEncError {
209    XmlEncError::from(error)
210}
211
212impl<'a> DecryptContext<'a> {
213    /// Create a context with the default decryption policy and RustCrypto provider.
214    pub fn new(resolver: &'a dyn DecryptionKeyResolver) -> Self {
215        Self {
216            resolver,
217            policy: crate::policy::DecryptionPolicy::default(),
218            provider: crate::provider::default_provider(),
219            xml_backend: crate::XmlBackend::default(),
220            id_attributes: &[],
221        }
222    }
223
224    /// Replace the complete immutable decryption policy snapshot.
225    pub fn policy(mut self, policy: crate::policy::DecryptionPolicy) -> Self {
226        self.policy = policy;
227        self
228    }
229
230    /// Select the cryptographic provider for this decryption operation.
231    pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
232        self.provider = provider;
233        self
234    }
235
236    /// Select the compiled XML parser backend for this decryption operation.
237    pub fn xml_backend(mut self, backend: crate::XmlBackend) -> Self {
238        self.xml_backend = backend;
239        self
240    }
241
242    fn document_parse_settings(&self) -> DocumentParseSettings {
243        DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources)
244            .with_backend(self.xml_backend)
245    }
246
247    /// Add caller-declared ID attributes for operation start-node lookup.
248    pub fn id_attributes(mut self, registrations: &'a [crate::IdAttributeRegistration]) -> Self {
249        self.id_attributes = registrations;
250        self
251    }
252
253    /// Parse and decrypt a standalone `EncryptedData` XML fragment.
254    pub fn decrypt(&self, xml: &str) -> Result<DecryptedContent, XmlEncError> {
255        let budgets = DecryptionOperationBudgets::from_policy(&self.policy);
256        let encrypted = parse_encrypted_data_with_policy_backend_and_budget(
257            xml,
258            &self.policy,
259            self.xml_backend,
260            &budgets.xml_parse,
261        )?;
262        self.process_decryption_candidates(&encrypted, None, false, budgets, |content, _| {
263            Ok(content)
264        })
265        .map(|processed| processed.output)
266    }
267
268    /// Decrypt an already parsed `EncryptedData` value.
269    pub fn decrypt_data(&self, encrypted: &EncryptedData) -> Result<DecryptedContent, XmlEncError> {
270        self.process_decryption_candidates(
271            encrypted,
272            None,
273            false,
274            DecryptionOperationBudgets::from_policy(&self.policy),
275            |content, _| Ok(content),
276        )
277        .map(|processed| processed.output)
278    }
279
280    fn process_decryption_candidates<T>(
281        &self,
282        encrypted: &EncryptedData,
283        document_binding: Option<(crate::DocumentIdentity, u64)>,
284        mutates_document: bool,
285        budgets: DecryptionOperationBudgets,
286        mut accept: impl FnMut(DecryptedContent, &XmlParseWorkBudget) -> Result<T, XmlEncError>,
287    ) -> Result<ProcessedDecryption<T>, XmlEncError> {
288        self.policy.resources.validate()?;
289        validate_encrypted_data_metadata(encrypted, &self.policy)?;
290        let mut operation =
291            OperationExecutionContext::new(self.policy.clone(), budgets, document_binding);
292        let plan_nodes = compile_decryption_plan(&mut operation, mutates_document)?;
293        let (algorithm, ciphertext) = operation.run(plan_nodes.document, || {
294            encrypted.encryption_method.validate_structure()?;
295            validate_recipient_count(
296                encrypted.encrypted_keys.len(),
297                self.policy.resources.max_encryption_recipients,
298            )?;
299            let algorithm =
300                DataEncryptionAlgorithm::from_uri(&encrypted.encryption_method.algorithm)?;
301            if self
302                .policy
303                .data_algorithms
304                .as_ref()
305                .is_some_and(|allowed| !allowed.contains(&algorithm))
306            {
307                return Err(crate::policy::PolicyViolation::Algorithm {
308                    operation: "decryption",
309                    algorithm: encrypted.encryption_method.algorithm.clone(),
310                }
311                .into());
312            }
313            self.provider
314                .require_capability(crate::provider::ProviderCapability::Decrypt(algorithm))?;
315            validate_typed_cipher_values(
316                encrypted,
317                algorithm,
318                self.policy.resources.max_encryption_plaintext_bytes,
319                self.policy.resources.max_xml_document_bytes,
320            )?;
321            let ciphertext = STANDARD
322                .decode(&encrypted.cipher_data.value)
323                .map_err(|error| XmlEncError::Base64(error.to_string()))?;
324            validate_content_framing_before_resolution(
325                algorithm,
326                ciphertext.len(),
327                &encrypted.encrypted_keys,
328                &self.policy,
329            )?;
330            validate_possible_plaintext_len(
331                algorithm,
332                ciphertext.len(),
333                self.policy.resources.max_encryption_plaintext_bytes,
334            )?;
335            Ok::<_, XmlEncError>((algorithm, ciphertext))
336        })?;
337        let keys = operation.run(plan_nodes.key, || {
338            resolve_content_key_candidates(
339                self.provider,
340                algorithm,
341                encrypted,
342                self.resolver,
343                &self.policy,
344                &mut operation.budgets().key_candidates.borrow_mut(),
345            )
346        })?;
347        let keys = compatible_decryption_key_candidates(algorithm, keys)?;
348        validate_decryption_key_candidates(algorithm, keys.len())?;
349        let output = operation.run(plan_nodes.crypto, || {
350            let mut last_error = None;
351            for key in keys {
352                let attempt = (|| {
353                    validate_key_len(algorithm, &key)?;
354                    let plaintext = self
355                        .provider
356                        .decrypt_data(algorithm, &key, &ciphertext)
357                        .map_err(|error| {
358                            map_data_decryption_error(algorithm, ciphertext.len(), error)
359                        })?;
360                    validate_provider_plaintext_len(algorithm, ciphertext.len(), plaintext.len())?;
361                    validate_plaintext_len(
362                        plaintext.len(),
363                        self.policy.resources.max_encryption_plaintext_bytes,
364                    )?;
365                    let content = match encrypted.encrypted_type.as_ref() {
366                        Some(EncryptedDataType::Element | EncryptedDataType::Content) => {
367                            DecryptedContent::Xml(String::from_utf8(plaintext)?)
368                        }
369                        Some(EncryptedDataType::Other(_)) | None => {
370                            DecryptedContent::Bytes(plaintext)
371                        }
372                    };
373                    accept(content, &operation.budgets().xml_parse)
374                })();
375                match attempt {
376                    Ok(output) => return Ok(output),
377                    Err(error) => last_error = Some(error),
378                }
379            }
380            Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
381        })?;
382        let output = operation.run(plan_nodes.evidence, || Ok::<_, XmlEncError>(output))?;
383        Ok(ProcessedDecryption {
384            output,
385            operation,
386            mutation: plan_nodes.mutation,
387        })
388    }
389
390    /// Decrypt and replace one selected `EncryptedData` in a caller-owned document.
391    pub fn decrypt_document(
392        &self,
393        xml: &str,
394        encrypted_data_id: Option<&str>,
395    ) -> Result<String, XmlEncError> {
396        decrypt_document_with_context(
397            xml,
398            DocumentEncryptedDataSelector::EncryptedDataId(encrypted_data_id),
399            self,
400        )
401    }
402
403    /// Decrypt and replace one selected `EncryptedData` in an owned document.
404    pub fn decrypt_owned_document(
405        &self,
406        document: &mut XmlDocument,
407        encrypted_data_id: Option<&str>,
408    ) -> Result<(), XmlEncError> {
409        let budgets = DecryptionOperationBudgets::from_policy(&self.policy);
410        decrypt_owned_document_with_context(
411            document,
412            DocumentEncryptedDataSelector::EncryptedDataId(encrypted_data_id),
413            self,
414            budgets,
415        )
416    }
417
418    /// Decrypt and replace the sole `EncryptedData` below an operation start
419    /// node selected by ID.
420    pub fn decrypt_document_from_start_node(
421        &self,
422        xml: &str,
423        start_node_id: Option<&str>,
424    ) -> Result<String, XmlEncError> {
425        decrypt_document_with_context(
426            xml,
427            DocumentEncryptedDataSelector::UniqueBelowStartNode(start_node_id),
428            self,
429        )
430    }
431
432    /// Decrypt and replace the first `EncryptedData` below an operation start
433    /// node selected by ID, leaving later encrypted descendants untouched.
434    pub fn decrypt_first_document_from_start_node(
435        &self,
436        xml: &str,
437        start_node_id: Option<&str>,
438    ) -> Result<String, XmlEncError> {
439        decrypt_document_with_context(
440            xml,
441            DocumentEncryptedDataSelector::FirstBelowStartNode(start_node_id),
442            self,
443        )
444    }
445}
446
447/// Resolver for direct, pre-shared AES content keys.
448#[derive(Clone)]
449pub struct SymmetricKeyDecryptor {
450    key: Vec<u8>,
451}
452
453impl fmt::Debug for SymmetricKeyDecryptor {
454    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
455        formatter
456            .debug_struct("SymmetricKeyDecryptor")
457            .field("key", &"[REDACTED]")
458            .finish()
459    }
460}
461
462impl SymmetricKeyDecryptor {
463    /// Create a direct symmetric-key resolver.
464    pub fn new(key: impl Into<Vec<u8>>) -> Self {
465        Self { key: key.into() }
466    }
467}
468
469impl DecryptionKeyResolver for SymmetricKeyDecryptor {
470    fn resolve_key(
471        &self,
472        _provider: &dyn crate::provider::CryptoProvider,
473        algorithm: DataEncryptionAlgorithm,
474        _encrypted_key: Option<&EncryptedKey>,
475    ) -> Result<Vec<u8>, XmlEncError> {
476        validate_key_len(algorithm, &self.key)?;
477        Ok(self.key.clone())
478    }
479}
480
481/// Resolver backed by an RSA private key for OAEP-wrapped session keys.
482#[derive(Clone)]
483pub struct PrivateKeyDecryptor {
484    key: Arc<dyn crate::provider::KeyRecoveryKey>,
485}
486
487/// Resolver backed by a pre-shared AES key-encryption key (KEK).
488#[derive(Clone)]
489pub struct KekDecryptor {
490    kek: Vec<u8>,
491}
492
493impl fmt::Debug for KekDecryptor {
494    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
495        formatter
496            .debug_struct("KekDecryptor")
497            .field("kek", &"[REDACTED]")
498            .finish()
499    }
500}
501
502impl KekDecryptor {
503    /// Create a resolver for RFC 3394 AES key-wrap `EncryptedKey` elements.
504    pub fn new(kek: impl Into<Vec<u8>>) -> Self {
505        Self { kek: kek.into() }
506    }
507}
508
509impl DecryptionKeyResolver for KekDecryptor {
510    fn resolve_key(
511        &self,
512        provider: &dyn crate::provider::CryptoProvider,
513        algorithm: DataEncryptionAlgorithm,
514        encrypted_key: Option<&EncryptedKey>,
515    ) -> Result<Vec<u8>, XmlEncError> {
516        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
517        encrypted_key.encryption_method.validate_structure()?;
518        let wrapped = STANDARD
519            .decode(&encrypted_key.cipher_data.value)
520            .map_err(|error| XmlEncError::Base64(error.to_string()))?;
521        let wrap_algorithm =
522            KeyWrapAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
523        let expected_kek_len = wrap_algorithm.key_len();
524        if self.kek.len() != expected_kek_len {
525            return Err(XmlEncError::InvalidKekSize {
526                algorithm: wrap_algorithm,
527                expected: expected_kek_len,
528                actual: self.kek.len(),
529            });
530        }
531        let expected_wrapped_len = algorithm.key_len() + 8;
532        if wrapped.len() != expected_wrapped_len {
533            return Err(XmlEncError::InvalidWrappedKeyLength {
534                expected: expected_wrapped_len,
535                actual: wrapped.len(),
536            });
537        }
538        provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(
539            wrap_algorithm,
540        ))?;
541        let key = provider
542            .unwrap_key(wrap_algorithm, &self.kek, &wrapped)
543            .map_err(|error| match error {
544                crate::provider::ProviderError::InvalidKeySize { expected, actual } => {
545                    XmlEncError::InvalidKekSize {
546                        algorithm: wrap_algorithm,
547                        expected,
548                        actual,
549                    }
550                }
551                crate::provider::ProviderError::AuthenticationFailed
552                | crate::provider::ProviderError::InvalidInput(
553                    crate::provider::ProviderInputError::AesKeyWrapFraming,
554                ) => XmlEncError::KeyWrapIntegrity,
555                error => XmlEncError::Provider(error),
556            })?;
557        validate_key_len(algorithm, &key)?;
558        Ok(key)
559    }
560}
561
562impl PrivateKeyDecryptor {
563    /// Create a resolver from an already-parsed RSA private key.
564    pub fn new(key: RsaPrivateKey) -> Self {
565        Self::provider_key(Arc::new(crate::provider::RustCryptoRsaPrivateKey::new(key)))
566    }
567
568    /// Create a resolver from an opaque provider-owned recovery key.
569    pub fn provider_key(key: Arc<dyn crate::provider::KeyRecoveryKey>) -> Self {
570        Self { key }
571    }
572}
573
574impl DecryptionKeyResolver for PrivateKeyDecryptor {
575    fn resolve_key(
576        &self,
577        provider: &dyn crate::provider::CryptoProvider,
578        algorithm: DataEncryptionAlgorithm,
579        encrypted_key: Option<&EncryptedKey>,
580    ) -> Result<Vec<u8>, XmlEncError> {
581        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
582        encrypted_key.encryption_method.validate_structure()?;
583        let wrapped = STANDARD
584            .decode(&encrypted_key.cipher_data.value)
585            .map_err(|error| XmlEncError::Base64(error.to_string()))?;
586        let label = encrypted_key
587            .encryption_method
588            .oaep_params
589            .clone()
590            .unwrap_or_default();
591        let transport =
592            KeyTransportAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
593        let key = match transport {
594            KeyTransportAlgorithm::RsaOaepMgf1p => self.decrypt_oaep_mgf1p(
595                provider,
596                encrypted_key.encryption_method.oaep_digest.as_deref(),
597                encrypted_key.encryption_method.mgf_algorithm.as_deref(),
598                label,
599                &wrapped,
600            ),
601            KeyTransportAlgorithm::RsaOaep11 => self.decrypt_oaep11(
602                provider,
603                encrypted_key.encryption_method.oaep_digest.as_deref(),
604                encrypted_key.encryption_method.mgf_algorithm.as_deref(),
605                label,
606                &wrapped,
607            ),
608        }?;
609        validate_key_len(algorithm, &key)?;
610        Ok(key)
611    }
612}
613
614impl PrivateKeyDecryptor {
615    fn decrypt_oaep_mgf1p(
616        &self,
617        provider: &dyn crate::provider::CryptoProvider,
618        digest: Option<&str>,
619        mgf: Option<&str>,
620        label: Vec<u8>,
621        wrapped: &[u8],
622    ) -> Result<Vec<u8>, XmlEncError> {
623        let parameters = RsaOaepParameters {
624            algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
625            digest: parse_oaep_digest(digest)?,
626            mgf_digest: parse_oaep_mgf_digest(mgf)?,
627            label,
628        };
629        recover_rsa_oaep(provider, self.key.as_ref(), &parameters, wrapped)
630    }
631
632    fn decrypt_oaep11(
633        &self,
634        provider: &dyn crate::provider::CryptoProvider,
635        digest: Option<&str>,
636        mgf: Option<&str>,
637        label: Vec<u8>,
638        wrapped: &[u8],
639    ) -> Result<Vec<u8>, XmlEncError> {
640        let parameters = RsaOaepParameters {
641            algorithm: KeyTransportAlgorithm::RsaOaep11,
642            digest: parse_oaep_digest(digest)?,
643            mgf_digest: parse_oaep_mgf_digest(mgf)?,
644            label,
645        };
646        recover_rsa_oaep(provider, self.key.as_ref(), &parameters, wrapped)
647    }
648}
649
650fn parse_oaep_digest(uri: Option<&str>) -> Result<OaepDigestAlgorithm, XmlEncError> {
651    let uri = uri.unwrap_or("http://www.w3.org/2000/09/xmldsig#sha1");
652    OaepDigestAlgorithm::from_uri(uri)
653        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned()))
654}
655
656fn parse_oaep_mgf_digest(uri: Option<&str>) -> Result<OaepDigestAlgorithm, XmlEncError> {
657    let uri = uri.unwrap_or("http://www.w3.org/2009/xmlenc11#mgf1sha1");
658    OaepDigestAlgorithm::from_mgf_uri(uri)
659        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned()))
660}
661
662fn recover_rsa_oaep(
663    provider: &dyn crate::provider::CryptoProvider,
664    key: &dyn crate::provider::KeyRecoveryKey,
665    parameters: &RsaOaepParameters,
666    wrapped: &[u8],
667) -> Result<Vec<u8>, XmlEncError> {
668    let expected = key.ciphertext_len();
669    if wrapped.len() != expected {
670        return Err(XmlEncError::InvalidWrappedKeyLength {
671            expected,
672            actual: wrapped.len(),
673        });
674    }
675    provider.require_capability(crate::provider::ProviderCapability::KeyRecovery(parameters))?;
676    provider
677        .recover_key(key, parameters, wrapped)
678        .map_err(|error| match error {
679            crate::provider::ProviderError::Random(message) => XmlEncError::Rng(message),
680            error @ (crate::provider::ProviderError::AuthenticationFailed
681            | crate::provider::ProviderError::InvalidInput(_)) => {
682                XmlEncError::Rsa(error.to_string())
683            }
684            error => XmlEncError::Provider(error),
685        })
686}
687
688/// Parse and decrypt a standalone `EncryptedData` XML fragment.
689pub fn decrypt(
690    xml: &str,
691    resolver: &dyn DecryptionKeyResolver,
692) -> Result<DecryptedContent, XmlEncError> {
693    DecryptContext::new(resolver).decrypt(xml)
694}
695
696/// Decrypt and replace one `EncryptedData` element in a caller-owned XML document.
697///
698/// When `encrypted_data_id` is `None`, the document must contain exactly one
699/// `EncryptedData`. The decrypted value must declare either the XMLEnc `Element`
700/// or `Content` type. Plaintext is parsed inside a bounded replacement wrapper
701/// before insertion, and the returned document is parsed again before exposure.
702pub fn decrypt_document(
703    xml: &str,
704    encrypted_data_id: Option<&str>,
705    resolver: &dyn DecryptionKeyResolver,
706) -> Result<String, XmlEncError> {
707    decrypt_document_with_options(
708        xml,
709        DocumentDecryptionOptions { encrypted_data_id },
710        resolver,
711    )
712}
713
714/// Decrypt and replace one `EncryptedData` using the default decryption policy.
715///
716/// Use [`DecryptContext`] when XML-input or resource policy must differ from
717/// the secure defaults; options here contain request selection only.
718pub fn decrypt_document_with_options(
719    xml: &str,
720    options: DocumentDecryptionOptions<'_>,
721    resolver: &dyn DecryptionKeyResolver,
722) -> Result<String, XmlEncError> {
723    DecryptContext::new(resolver).decrypt_document(xml, options.encrypted_data_id)
724}
725
726#[derive(Clone, Copy)]
727enum DocumentEncryptedDataSelector<'a> {
728    EncryptedDataId(Option<&'a str>),
729    UniqueBelowStartNode(Option<&'a str>),
730    FirstBelowStartNode(Option<&'a str>),
731}
732
733fn decrypt_document_with_context(
734    xml: &str,
735    selector: DocumentEncryptedDataSelector<'_>,
736    context: &DecryptContext<'_>,
737) -> Result<String, XmlEncError> {
738    context.policy.resources.validate()?;
739    validate_encryption_document_len(xml.len(), &context.policy)?;
740    let budgets = DecryptionOperationBudgets::from_policy(&context.policy);
741    let settings = context.document_parse_settings();
742    let mut document =
743        XmlDocument::parse_with_settings_and_budget(xml.to_owned(), settings, &budgets.xml_parse)
744            .map_err(|error| map_document_error(error, settings))?;
745    decrypt_owned_document_with_context(&mut document, selector, context, budgets)?;
746    Ok(document.into_xml())
747}
748
749fn decrypt_owned_document_with_context(
750    document: &mut XmlDocument,
751    selector: DocumentEncryptedDataSelector<'_>,
752    context: &DecryptContext<'_>,
753    budgets: DecryptionOperationBudgets,
754) -> Result<(), XmlEncError> {
755    context.policy.resources.validate()?;
756    document.validate_operation_policy(&context.policy.xml, &context.policy.resources)?;
757    let (target, target_len, encrypted) = document.with_view(|view| {
758        let start = match selector {
759            DocumentEncryptedDataSelector::UniqueBelowStartNode(Some(id))
760            | DocumentEncryptedDataSelector::FirstBelowStartNode(Some(id)) => view
761                .node_for_id(id, context.id_attributes)
762                .ok_or_else(|| XmlEncError::SelectedNodeUnavailable { id: id.to_owned() })
763                .and_then(|identity| view.resolve_node(identity).map_err(XmlEncError::from))?,
764            DocumentEncryptedDataSelector::UniqueBelowStartNode(None)
765            | DocumentEncryptedDataSelector::FirstBelowStartNode(None)
766            | DocumentEncryptedDataSelector::EncryptedDataId(_) => view.document().root(),
767        };
768        let encrypted_data_id = match selector {
769            DocumentEncryptedDataSelector::EncryptedDataId(id) => id,
770            DocumentEncryptedDataSelector::UniqueBelowStartNode(_)
771            | DocumentEncryptedDataSelector::FirstBelowStartNode(_) => None,
772        };
773        let mut matches = start.descendants().filter(|node| {
774            node.has_tag_name((XMLENC_NS, "EncryptedData"))
775                && encrypted_data_id.is_none_or(|id| node.attribute("Id") == Some(id))
776        });
777        let selected = matches.next().ok_or(XmlEncError::EncryptedDataNotFound)?;
778        if matches!(
779            selector,
780            DocumentEncryptedDataSelector::EncryptedDataId(_)
781                | DocumentEncryptedDataSelector::UniqueBelowStartNode(_)
782        ) && matches.next().is_some()
783        {
784            return Err(XmlEncError::AmbiguousEncryptedData);
785        }
786        Ok::<_, XmlEncError>((
787            view.node_identity(selected),
788            selected.range().len(),
789            parse_encrypted_data_node_with_policy_and_budget(
790                selected,
791                &context.policy,
792                &budgets.xml_parse,
793                context.xml_backend,
794            )?,
795        ))
796    })?;
797    let document_binding = Some((document.identity(), document.generation()));
798    let mut processed = context.process_decryption_candidates(
799        &encrypted,
800        document_binding,
801        true,
802        budgets,
803        |candidate, operation_parse_budget| {
804            let DecryptedContent::Xml(plaintext) = candidate else {
805                return Err(XmlEncError::ReplacementRequiresXml);
806            };
807            validate_encryption_document_len(
808                document
809                    .as_xml()
810                    .len()
811                    .saturating_sub(target_len)
812                    .saturating_add(plaintext.len()),
813                &context.policy,
814            )?;
815            let settings = context.document_parse_settings();
816            match encrypted.encrypted_type.as_ref() {
817                Some(EncryptedDataType::Element) => document
818                    .prepare_element_replacement_with_budget(
819                        target,
820                        &plaintext,
821                        settings,
822                        operation_parse_budget,
823                    )
824                    .map_err(|error| map_document_error(error, settings)),
825                Some(EncryptedDataType::Content) => document
826                    .prepare_node_fragment_replacement_with_budget(
827                        target,
828                        &plaintext,
829                        settings,
830                        operation_parse_budget,
831                    )
832                    .map_err(|error| map_document_error(error, settings)),
833                Some(EncryptedDataType::Other(_)) | None => {
834                    Err(XmlEncError::ReplacementRequiresXml)
835                }
836            }
837        },
838    )?;
839    let mutation = processed.mutation.ok_or_else(|| {
840        XmlEncError::OperationPlan("decryption mutation node is unavailable".into())
841    })?;
842    processed
843        .operation
844        .run_document_transition(mutation, document, |document, _| {
845            document
846                .commit_prepared(processed.output)
847                .map_err(|error| map_document_error(error, context.document_parse_settings()))
848        })
849}
850
851fn validate_encryption_document_len(
852    actual: usize,
853    policy: &crate::policy::DecryptionPolicy,
854) -> Result<(), XmlEncError> {
855    policy.resources.validate_xml_document_len(actual)?;
856    Ok(())
857}
858
859fn validate_recipient_count(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
860    if actual > maximum {
861        return Err(crate::policy::PolicyViolation::ResourceLimit {
862            resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
863            maximum,
864            actual,
865        }
866        .into());
867    }
868    Ok(())
869}
870
871/// Decrypt an already parsed `EncryptedData` value.
872pub fn decrypt_data(
873    encrypted: &EncryptedData,
874    resolver: &dyn DecryptionKeyResolver,
875) -> Result<DecryptedContent, XmlEncError> {
876    DecryptContext::new(resolver).decrypt_data(encrypted)
877}
878
879fn resolve_content_key_candidates(
880    provider: &dyn crate::provider::CryptoProvider,
881    algorithm: DataEncryptionAlgorithm,
882    encrypted: &EncryptedData,
883    resolver: &dyn DecryptionKeyResolver,
884    policy: &crate::policy::DecryptionPolicy,
885    budget: &mut KeyCandidateBudget,
886) -> Result<Vec<Vec<u8>>, XmlEncError> {
887    let mut last_error = None;
888    let mut candidates =
889        match resolve_candidates_with_budget(resolver, provider, algorithm, None, budget) {
890            Ok(keys) => keys,
891            Err(error) => {
892                record_candidate_source_error_or_fail_operation(error, &mut last_error)?;
893                Vec::new()
894            }
895        };
896    for encrypted_key in &encrypted.encrypted_keys {
897        if !encrypted_key_applies_to_data(encrypted_key, encrypted) {
898            continue;
899        }
900        if let Err(error) = validate_encrypted_key_policy(encrypted_key, policy) {
901            last_error = Some(error);
902            continue;
903        }
904        match resolve_candidates_with_budget(
905            resolver,
906            provider,
907            algorithm,
908            Some(encrypted_key),
909            budget,
910        ) {
911            Ok(keys) => candidates.extend(keys),
912            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
913        }
914    }
915    if candidates.is_empty() {
916        Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
917    } else {
918        Ok(candidates)
919    }
920}
921
922fn record_candidate_source_error_or_fail_operation(
923    error: XmlEncError,
924    last_error: &mut Option<XmlEncError>,
925) -> Result<(), XmlEncError> {
926    // Candidate-specific failures permit the next ordered key source. The
927    // shared work ceiling is operation-wide and must never be recoverable by
928    // advancing to another recipient.
929    if matches!(&error, XmlEncError::Policy(_)) {
930        return Err(error);
931    }
932    *last_error = Some(error);
933    Ok(())
934}
935
936fn resolve_candidates_with_budget(
937    resolver: &dyn DecryptionKeyResolver,
938    provider: &dyn crate::provider::CryptoProvider,
939    algorithm: DataEncryptionAlgorithm,
940    encrypted_key: Option<&EncryptedKey>,
941    budget: &mut KeyCandidateBudget,
942) -> Result<Vec<Vec<u8>>, XmlEncError> {
943    let remaining_before = budget.remaining();
944    let keys = resolver.resolve_key_candidates(provider, algorithm, encrypted_key, budget)?;
945    budget.account_returned_candidates(remaining_before, keys.len())?;
946    Ok(keys)
947}
948
949fn encrypted_key_applies_to_data(
950    encrypted_key: &EncryptedKey,
951    encrypted_data: &EncryptedData,
952) -> bool {
953    // XMLEnc association metadata is optional, but authoritative when present:
954    // DataReference identifies encrypted objects and CarriedKeyName identifies
955    // the transported key referenced by the enclosing ds:KeyName.
956    if let Some(references) = encrypted_key.reference_list.as_ref()
957        && !references.data_references.is_empty()
958    {
959        let Some(id) = encrypted_data.id.as_deref() else {
960            return false;
961        };
962        let target = format!("#{id}");
963        if !references.data_references.iter().any(|uri| uri == &target) {
964            return false;
965        }
966    }
967    if let (Some(carried), Some(expected)) = (
968        encrypted_key.carried_key_name.as_deref(),
969        encrypted_data.key_name.as_deref(),
970    ) && carried != expected
971    {
972        return false;
973    }
974    true
975}
976
977fn compatible_decryption_key_candidates(
978    algorithm: DataEncryptionAlgorithm,
979    keys: Vec<Vec<u8>>,
980) -> Result<Vec<Vec<u8>>, XmlEncError> {
981    let mut compatible = Vec::with_capacity(keys.len());
982    let mut last_error = None;
983    for key in keys {
984        match validate_key_len(algorithm, &key) {
985            Ok(()) if !compatible.iter().any(|existing| existing == &key) => {
986                compatible.push(key);
987            }
988            Ok(()) => {}
989            Err(error) => last_error = Some(error),
990        }
991    }
992    if compatible.is_empty() {
993        return Err(last_error.unwrap_or(XmlEncError::KeyNotFound));
994    }
995    Ok(compatible)
996}
997
998fn validate_decryption_key_candidates(
999    algorithm: DataEncryptionAlgorithm,
1000    actual: usize,
1001) -> Result<(), XmlEncError> {
1002    let maximum = crate::hard_limits::KEY_CANDIDATE_CEILING;
1003    if actual > maximum {
1004        return Err(crate::policy::PolicyViolation::ResourceLimit {
1005            resource: crate::policy::resource_name::KEY_CANDIDATES,
1006            maximum,
1007            actual,
1008        }
1009        .into());
1010    }
1011    if actual > 1
1012        && matches!(
1013            algorithm,
1014            DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc
1015        )
1016    {
1017        return Err(XmlEncError::AmbiguousKeyCandidates { algorithm, actual });
1018    }
1019    Ok(())
1020}
1021
1022fn validate_encrypted_key_policy(
1023    encrypted_key: &EncryptedKey,
1024    policy: &crate::policy::DecryptionPolicy,
1025) -> Result<(), XmlEncError> {
1026    encrypted_key.encryption_method.validate_structure()?;
1027    let uri = &encrypted_key.encryption_method.algorithm;
1028    if let Ok(transport) = KeyTransportAlgorithm::from_uri(uri) {
1029        if policy
1030            .key_transport_algorithms
1031            .as_ref()
1032            .is_some_and(|allowed| !allowed.contains(&transport))
1033        {
1034            return Err(crate::policy::PolicyViolation::Algorithm {
1035                operation: "decryption",
1036                algorithm: uri.clone(),
1037            }
1038            .into());
1039        }
1040        let method = &encrypted_key.encryption_method;
1041        let digest = parse_oaep_digest(method.oaep_digest.as_deref())?;
1042        let mgf_digest = parse_oaep_mgf_digest(method.mgf_algorithm.as_deref())?;
1043        let selected_algorithms = [
1044            (
1045                digest,
1046                method.oaep_digest.as_deref().unwrap_or(digest.uri()),
1047            ),
1048            (
1049                mgf_digest,
1050                method
1051                    .mgf_algorithm
1052                    .as_deref()
1053                    .unwrap_or(mgf_digest.mgf_uri()),
1054            ),
1055        ];
1056        for (selected, wire_uri) in selected_algorithms {
1057            if policy
1058                .oaep_digests
1059                .as_ref()
1060                .is_some_and(|allowed| !allowed.contains(&selected))
1061            {
1062                return Err(crate::policy::PolicyViolation::Algorithm {
1063                    operation: "decryption",
1064                    algorithm: wire_uri.to_owned(),
1065                }
1066                .into());
1067            }
1068        }
1069    } else {
1070        let wrap = KeyWrapAlgorithm::from_uri(uri)?;
1071        if policy
1072            .key_wrap_algorithms
1073            .as_ref()
1074            .is_some_and(|allowed| !allowed.contains(&wrap))
1075        {
1076            return Err(crate::policy::PolicyViolation::Algorithm {
1077                operation: "decryption",
1078                algorithm: uri.clone(),
1079            }
1080            .into());
1081        }
1082    }
1083    Ok(())
1084}
1085
1086fn validate_content_framing_before_resolution(
1087    algorithm: DataEncryptionAlgorithm,
1088    ciphertext_len: usize,
1089    encrypted_keys: &[EncryptedKey],
1090    policy: &crate::policy::DecryptionPolicy,
1091) -> Result<(), XmlEncError> {
1092    let Err(framing_error) = validate_ciphertext_framing(algorithm, ciphertext_len) else {
1093        return Ok(());
1094    };
1095
1096    // If no embedded key uses a supported transport, that envelope error is
1097    // more specific than content framing: the ciphertext cannot be interpreted
1098    // under any supported key path. This inspection performs no key resolution
1099    // and never dispatches malformed content to a cryptographic provider.
1100    if !encrypted_keys.is_empty() {
1101        let mut last_key_error = None;
1102        for encrypted_key in encrypted_keys {
1103            match validate_encrypted_key_policy(encrypted_key, policy) {
1104                Ok(()) => return Err(framing_error),
1105                Err(error) => last_key_error = Some(error),
1106            }
1107        }
1108        if let Some(error) = last_key_error {
1109            return Err(error);
1110        }
1111    }
1112    Err(framing_error)
1113}
1114
1115fn validate_typed_cipher_values(
1116    encrypted: &EncryptedData,
1117    algorithm: DataEncryptionAlgorithm,
1118    maximum_plaintext: usize,
1119    maximum_cipher_values: usize,
1120) -> Result<(), XmlEncError> {
1121    let maximum_ciphertext = match algorithm {
1122        DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc => {
1123            (maximum_plaintext / 16)
1124                .saturating_add(1)
1125                .saturating_mul(16)
1126                .saturating_add(16)
1127        }
1128        DataEncryptionAlgorithm::Aes128Gcm | DataEncryptionAlgorithm::Aes256Gcm => {
1129            maximum_plaintext.saturating_add(28)
1130        }
1131    };
1132    let projected = validate_cipher_value_len(&encrypted.cipher_data.value, maximum_ciphertext)?;
1133    if projected > maximum_ciphertext {
1134        return Err(crate::policy::PolicyViolation::ResourceLimit {
1135            resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1136            maximum: maximum_plaintext,
1137            actual: projected.saturating_sub(algorithm.minimum_ciphertext_len()),
1138        }
1139        .into());
1140    }
1141
1142    let mut aggregate_encoded = encrypted.cipher_data.value.len();
1143    if aggregate_encoded > maximum_cipher_values {
1144        return Err(crate::policy::PolicyViolation::ResourceLimit {
1145            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
1146            maximum: maximum_cipher_values,
1147            actual: aggregate_encoded,
1148        }
1149        .into());
1150    }
1151
1152    let maximum_wrapped_key = projected_decoded_len_for_encoded_len(MAX_CIPHER_VALUE_BASE64_LEN);
1153    for encrypted_key in &encrypted.encrypted_keys {
1154        validate_cipher_value_len(&encrypted_key.cipher_data.value, maximum_wrapped_key)?;
1155        aggregate_encoded = aggregate_encoded.saturating_add(encrypted_key.cipher_data.value.len());
1156        if aggregate_encoded > maximum_cipher_values {
1157            return Err(crate::policy::PolicyViolation::ResourceLimit {
1158                resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
1159                maximum: maximum_cipher_values,
1160                actual: aggregate_encoded,
1161            }
1162            .into());
1163        }
1164    }
1165    Ok(())
1166}
1167
1168fn validate_cipher_value_len(value: &str, maximum_decoded: usize) -> Result<usize, XmlEncError> {
1169    if value.len() > MAX_CIPHER_VALUE_BASE64_LEN {
1170        return Err(XmlEncError::InvalidStructure(format!(
1171            "CipherValue exceeds {MAX_CIPHER_VALUE_BASE64_LEN}-byte limit"
1172        )));
1173    }
1174    Ok(projected_decoded_len(value).min(maximum_decoded.saturating_add(1)))
1175}
1176
1177fn projected_decoded_len(value: &str) -> usize {
1178    let padding = value
1179        .as_bytes()
1180        .iter()
1181        .rev()
1182        .take(2)
1183        .take_while(|byte| **byte == b'=')
1184        .count();
1185    projected_decoded_len_for_encoded_len(value.len()).saturating_sub(padding)
1186}
1187
1188fn projected_decoded_len_for_encoded_len(encoded_len: usize) -> usize {
1189    encoded_len
1190        .checked_add(3)
1191        .map(|length| length / 4)
1192        .and_then(|quanta| quanta.checked_mul(3))
1193        .unwrap_or(usize::MAX)
1194}
1195
1196fn validate_key_len(algorithm: DataEncryptionAlgorithm, key: &[u8]) -> Result<(), XmlEncError> {
1197    if key.len() == algorithm.key_len() {
1198        Ok(())
1199    } else {
1200        Err(XmlEncError::InvalidKeySize {
1201            algorithm,
1202            expected: algorithm.key_len(),
1203            actual: key.len(),
1204        })
1205    }
1206}
1207
1208fn validate_possible_plaintext_len(
1209    algorithm: DataEncryptionAlgorithm,
1210    ciphertext_len: usize,
1211    maximum: usize,
1212) -> Result<(), XmlEncError> {
1213    // CBC's minimum includes a 16-byte IV and one padded block; using that
1214    // maximum-padding case yields the safe pre-decryption plaintext lower bound.
1215    let framing = algorithm.minimum_ciphertext_len();
1216    validate_plaintext_len(ciphertext_len.saturating_sub(framing), maximum)
1217}
1218
1219fn validate_provider_plaintext_len(
1220    algorithm: DataEncryptionAlgorithm,
1221    ciphertext_len: usize,
1222    plaintext_len: usize,
1223) -> Result<(), XmlEncError> {
1224    use crate::provider::{ProviderError, ProviderOperation};
1225
1226    match algorithm {
1227        DataEncryptionAlgorithm::Aes128Gcm | DataEncryptionAlgorithm::Aes256Gcm => {
1228            let expected = ciphertext_len - algorithm.minimum_ciphertext_len();
1229            if plaintext_len != expected {
1230                return Err(ProviderError::InvalidOutputSize {
1231                    operation: ProviderOperation::Decrypt,
1232                    expected,
1233                    actual: plaintext_len,
1234                }
1235                .into());
1236            }
1237        }
1238        DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc => {
1239            let padded_len = ciphertext_len - 16;
1240            let minimum = padded_len - 16;
1241            let maximum = padded_len - 1;
1242            if !(minimum..=maximum).contains(&plaintext_len) {
1243                return Err(ProviderError::InvalidOutputSizeRange {
1244                    operation: ProviderOperation::Decrypt,
1245                    minimum,
1246                    maximum,
1247                    actual: plaintext_len,
1248                }
1249                .into());
1250            }
1251        }
1252    }
1253    Ok(())
1254}
1255
1256fn validate_plaintext_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
1257    if actual <= maximum {
1258        Ok(())
1259    } else {
1260        Err(crate::policy::PolicyViolation::ResourceLimit {
1261            resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1262            maximum,
1263            actual,
1264        }
1265        .into())
1266    }
1267}
1268
1269fn map_data_decryption_error(
1270    algorithm: DataEncryptionAlgorithm,
1271    ciphertext_len: usize,
1272    error: crate::provider::ProviderError,
1273) -> XmlEncError {
1274    use crate::provider::ProviderError;
1275
1276    match (algorithm, error) {
1277        (
1278            DataEncryptionAlgorithm::Aes128Gcm | DataEncryptionAlgorithm::Aes256Gcm,
1279            ProviderError::AuthenticationFailed,
1280        ) => XmlEncError::AeadAuthenticationFailed,
1281        (
1282            DataEncryptionAlgorithm::Aes128Gcm | DataEncryptionAlgorithm::Aes256Gcm,
1283            ProviderError::InvalidInput(crate::provider::ProviderInputError::AesGcmFraming),
1284        ) => XmlEncError::DataTooShort {
1285            algorithm: "AES-GCM",
1286            minimum: 28,
1287            actual: ciphertext_len,
1288        },
1289        (
1290            DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc,
1291            ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcFraming),
1292        ) if ciphertext_len < 32 => XmlEncError::DataTooShort {
1293            algorithm: "AES-CBC",
1294            minimum: 32,
1295            actual: ciphertext_len,
1296        },
1297        (
1298            DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc,
1299            ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcFraming),
1300        ) => XmlEncError::InvalidCbcCiphertextLength(ciphertext_len.saturating_sub(16)),
1301        (
1302            DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc,
1303            ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcCiphertext),
1304        ) => XmlEncError::InvalidPadding,
1305        (_, error) => XmlEncError::Provider(error),
1306    }
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311    use std::cell::{Cell, RefCell};
1312    use std::sync::atomic::{AtomicUsize, Ordering};
1313
1314    use aes_gcm::{
1315        Aes128Gcm,
1316        aead::{AeadInOut, KeyInit},
1317    };
1318    use aes_kw::KwAes128;
1319    use base64::engine::general_purpose::STANDARD;
1320    use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
1321    use rsa::{Oaep, RsaPublicKey, pkcs8::DecodePrivateKey};
1322    use sha1::Sha1;
1323    use sha2::{Sha256, Sha384};
1324
1325    use super::*;
1326    use crate::xmlenc::{CipherData, EncryptionMethod};
1327
1328    struct RecipientKeyResolver {
1329        recipient: &'static str,
1330        key: Vec<u8>,
1331    }
1332
1333    struct CountingResolver {
1334        candidate_calls: Cell<usize>,
1335        key: Vec<u8>,
1336    }
1337
1338    struct AllCallsResolver {
1339        calls: Cell<usize>,
1340        key: Vec<u8>,
1341    }
1342
1343    struct CandidateResolver {
1344        keys: Vec<Vec<u8>>,
1345    }
1346
1347    #[test]
1348    fn document_decryption_initial_parse_uses_the_policy_work_budget() {
1349        // Candidate retries and replacement validation must inherit the same
1350        // allowance consumed by the caller document's initial parse.
1351        let xml = "<root/>";
1352        let policy = crate::policy::DecryptionPolicy {
1353            resources: crate::policy::ResourcePolicy {
1354                max_xml_parse_work_bytes: 0,
1355                ..crate::policy::ResourcePolicy::default()
1356            },
1357            ..crate::policy::DecryptionPolicy::default()
1358        };
1359        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
1360
1361        let error = DecryptContext::new(&resolver)
1362            .policy(policy)
1363            .decrypt_document(xml, None)
1364            .expect_err("a zero parse-work budget must reject the input parse");
1365
1366        assert!(matches!(
1367            error,
1368            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1369                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
1370                maximum: 0,
1371                actual,
1372            }) if actual == xml.len()
1373        ));
1374    }
1375
1376    struct AggregateRecipientResolver {
1377        attempts: Cell<usize>,
1378        key: Vec<u8>,
1379    }
1380
1381    struct AssociationRecordingResolver {
1382        visited: RefCell<Vec<String>>,
1383        key: Vec<u8>,
1384    }
1385
1386    struct OrderedRecipientResolver {
1387        wrong: Vec<u8>,
1388        correct: Vec<u8>,
1389    }
1390
1391    struct DirectAndRecipientResolver {
1392        direct: Vec<u8>,
1393        recipient: Vec<u8>,
1394    }
1395
1396    struct FailingDirectResolver {
1397        recipient: Vec<u8>,
1398    }
1399
1400    struct PolicyRejectingDirectResolver {
1401        recipient: Vec<u8>,
1402    }
1403
1404    struct MislabelledExhaustionResolver {
1405        direct: Vec<u8>,
1406    }
1407
1408    impl DecryptionKeyResolver for DirectAndRecipientResolver {
1409        fn resolve_key(
1410            &self,
1411            _provider: &dyn crate::provider::CryptoProvider,
1412            _algorithm: DataEncryptionAlgorithm,
1413            encrypted_key: Option<&EncryptedKey>,
1414        ) -> Result<Vec<u8>, XmlEncError> {
1415            Ok(if encrypted_key.is_some() {
1416                self.recipient.clone()
1417            } else {
1418                self.direct.clone()
1419            })
1420        }
1421    }
1422
1423    impl DecryptionKeyResolver for FailingDirectResolver {
1424        fn resolve_key(
1425            &self,
1426            _provider: &dyn crate::provider::CryptoProvider,
1427            algorithm: DataEncryptionAlgorithm,
1428            encrypted_key: Option<&EncryptedKey>,
1429        ) -> Result<Vec<u8>, XmlEncError> {
1430            if encrypted_key.is_some() {
1431                Ok(self.recipient.clone())
1432            } else {
1433                Err(XmlEncError::InvalidKeySize {
1434                    algorithm,
1435                    expected: 16,
1436                    actual: 8,
1437                })
1438            }
1439        }
1440    }
1441
1442    impl DecryptionKeyResolver for PolicyRejectingDirectResolver {
1443        fn resolve_key(
1444            &self,
1445            _provider: &dyn crate::provider::CryptoProvider,
1446            _algorithm: DataEncryptionAlgorithm,
1447            encrypted_key: Option<&EncryptedKey>,
1448        ) -> Result<Vec<u8>, XmlEncError> {
1449            if encrypted_key.is_some() {
1450                Ok(self.recipient.clone())
1451            } else {
1452                Err(crate::policy::PolicyViolation::KeyTrust {
1453                    reason: "test resolver rejected the operation",
1454                }
1455                .into())
1456            }
1457        }
1458    }
1459
1460    impl DecryptionKeyResolver for MislabelledExhaustionResolver {
1461        fn resolve_key(
1462            &self,
1463            _provider: &dyn crate::provider::CryptoProvider,
1464            _algorithm: DataEncryptionAlgorithm,
1465            _encrypted_key: Option<&EncryptedKey>,
1466        ) -> Result<Vec<u8>, XmlEncError> {
1467            Err(XmlEncError::KeyNotFound)
1468        }
1469
1470        fn resolve_key_candidates(
1471            &self,
1472            _provider: &dyn crate::provider::CryptoProvider,
1473            _algorithm: DataEncryptionAlgorithm,
1474            encrypted_key: Option<&EncryptedKey>,
1475            budget: &mut KeyCandidateBudget,
1476        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1477            if encrypted_key.is_none() {
1478                budget.consume(1)?;
1479                return Ok(vec![self.direct.clone()]);
1480            }
1481            budget.consume(budget.remaining().saturating_add(1))?;
1482            unreachable!("candidate budget exhaustion must return first")
1483        }
1484    }
1485
1486    impl DecryptionKeyResolver for OrderedRecipientResolver {
1487        fn resolve_key(
1488            &self,
1489            _provider: &dyn crate::provider::CryptoProvider,
1490            _algorithm: DataEncryptionAlgorithm,
1491            _encrypted_key: Option<&EncryptedKey>,
1492        ) -> Result<Vec<u8>, XmlEncError> {
1493            Err(XmlEncError::KeyNotFound)
1494        }
1495
1496        fn resolve_key_candidates(
1497            &self,
1498            _provider: &dyn crate::provider::CryptoProvider,
1499            _algorithm: DataEncryptionAlgorithm,
1500            encrypted_key: Option<&EncryptedKey>,
1501            budget: &mut KeyCandidateBudget,
1502        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1503            budget.consume(1)?;
1504            match encrypted_key.and_then(|key| key.id.as_deref()) {
1505                Some("first") => Ok(vec![self.wrong.clone()]),
1506                Some("second") => Ok(vec![self.correct.clone()]),
1507                _ => Err(XmlEncError::KeyNotFound),
1508            }
1509        }
1510    }
1511
1512    impl DecryptionKeyResolver for CandidateResolver {
1513        fn resolve_key(
1514            &self,
1515            _provider: &dyn crate::provider::CryptoProvider,
1516            _algorithm: DataEncryptionAlgorithm,
1517            _encrypted_key: Option<&EncryptedKey>,
1518        ) -> Result<Vec<u8>, XmlEncError> {
1519            Err(XmlEncError::KeyNotFound)
1520        }
1521
1522        fn resolve_key_candidates(
1523            &self,
1524            _provider: &dyn crate::provider::CryptoProvider,
1525            _algorithm: DataEncryptionAlgorithm,
1526            encrypted_key: Option<&EncryptedKey>,
1527            budget: &mut KeyCandidateBudget,
1528        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1529            if encrypted_key.is_none() {
1530                budget.consume(self.keys.len())?;
1531                Ok(self.keys.clone())
1532            } else {
1533                Err(XmlEncError::KeyNotFound)
1534            }
1535        }
1536    }
1537
1538    impl DecryptionKeyResolver for AggregateRecipientResolver {
1539        fn resolve_key(
1540            &self,
1541            _provider: &dyn crate::provider::CryptoProvider,
1542            _algorithm: DataEncryptionAlgorithm,
1543            _encrypted_key: Option<&EncryptedKey>,
1544        ) -> Result<Vec<u8>, XmlEncError> {
1545            Err(XmlEncError::KeyNotFound)
1546        }
1547
1548        fn resolve_key_candidates(
1549            &self,
1550            _provider: &dyn crate::provider::CryptoProvider,
1551            _algorithm: DataEncryptionAlgorithm,
1552            encrypted_key: Option<&EncryptedKey>,
1553            budget: &mut KeyCandidateBudget,
1554        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1555            let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1556            let attempts = budget.remaining();
1557            if attempts == 0 {
1558                budget.consume(1)?;
1559            }
1560            budget.consume(attempts)?;
1561            self.attempts.set(self.attempts.get() + attempts);
1562            if encrypted_key.id.as_deref() == Some("first") {
1563                Err(XmlEncError::KeyNotFound)
1564            } else {
1565                Ok(vec![self.key.clone()])
1566            }
1567        }
1568    }
1569
1570    impl DecryptionKeyResolver for AssociationRecordingResolver {
1571        fn resolve_key(
1572            &self,
1573            _provider: &dyn crate::provider::CryptoProvider,
1574            _algorithm: DataEncryptionAlgorithm,
1575            _encrypted_key: Option<&EncryptedKey>,
1576        ) -> Result<Vec<u8>, XmlEncError> {
1577            Err(XmlEncError::KeyNotFound)
1578        }
1579
1580        fn resolve_key_candidates(
1581            &self,
1582            _provider: &dyn crate::provider::CryptoProvider,
1583            _algorithm: DataEncryptionAlgorithm,
1584            encrypted_key: Option<&EncryptedKey>,
1585            budget: &mut KeyCandidateBudget,
1586        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1587            let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1588            budget.consume(1)?;
1589            self.visited
1590                .borrow_mut()
1591                .push(encrypted_key.id.clone().unwrap_or_default());
1592            Ok(vec![self.key.clone()])
1593        }
1594    }
1595
1596    fn associated_encrypted_key(
1597        id: &str,
1598        data_reference: Option<&str>,
1599        carried_key_name: Option<&str>,
1600    ) -> EncryptedKey {
1601        EncryptedKey {
1602            id: Some(id.into()),
1603            recipient: None,
1604            key_name: None,
1605            encryption_method: EncryptionMethod {
1606                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
1607                key_size_bits: None,
1608                oaep_digest: None,
1609                mgf_algorithm: None,
1610                oaep_params: None,
1611            },
1612            cipher_data: CipherData {
1613                value: STANDARD.encode([0_u8; 256]),
1614            },
1615            reference_list: data_reference.map(|uri| crate::xmlenc::ReferenceList {
1616                data_references: vec![uri.into()],
1617                key_references: Vec::new(),
1618            }),
1619            carried_key_name: carried_key_name.map(str::to_owned),
1620        }
1621    }
1622
1623    fn encrypted_data_with_recipients(
1624        key: &[u8],
1625        encrypted_keys: Vec<EncryptedKey>,
1626        key_name: Option<&str>,
1627    ) -> EncryptedData {
1628        EncryptedData {
1629            id: Some("target".into()),
1630            encrypted_type: None,
1631            encryption_method: EncryptionMethod {
1632                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
1633                key_size_bits: None,
1634                oaep_digest: None,
1635                mgf_algorithm: None,
1636                oaep_params: None,
1637            },
1638            key_name: key_name.map(str::to_owned),
1639            encrypted_keys,
1640            cipher_data: CipherData {
1641                value: STANDARD.encode(
1642                    crate::provider::default_provider()
1643                        .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, key, b"payload")
1644                        .expect("test encryption must succeed"),
1645                ),
1646            },
1647        }
1648    }
1649
1650    #[derive(Debug, Default)]
1651    struct PermissiveUnwrapProvider {
1652        decrypt_calls: AtomicUsize,
1653        unwrap_calls: AtomicUsize,
1654        recover_calls: AtomicUsize,
1655        plaintext: Vec<u8>,
1656        candidate_plaintexts: Vec<Vec<u8>>,
1657    }
1658
1659    struct OpaqueRecoveryKey;
1660
1661    impl crate::provider::KeyRecoveryKey for OpaqueRecoveryKey {
1662        fn ciphertext_len(&self) -> usize {
1663            256
1664        }
1665
1666        fn recover_with_provider(
1667            &self,
1668            _provider: &dyn crate::provider::CryptoProvider,
1669            _parameters: &RsaOaepParameters,
1670            _ciphertext: &[u8],
1671        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1672            panic!("custom provider must own recovery for its opaque key")
1673        }
1674    }
1675
1676    impl crate::provider::CryptoProvider for PermissiveUnwrapProvider {
1677        fn name(&self) -> &'static str {
1678            "permissive-unwrap-test"
1679        }
1680
1681        fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
1682            crate::provider::CryptoProvider::supports(
1683                &crate::provider::RustCryptoProvider,
1684                capability,
1685            )
1686        }
1687
1688        fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
1689            crate::provider::CryptoProvider::fill_random(
1690                &crate::provider::RustCryptoProvider,
1691                output,
1692            )
1693        }
1694
1695        fn derive_key(
1696            &self,
1697            parameters: &crate::provider::KdfParameters<'_>,
1698            secret: &[u8],
1699        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1700            crate::provider::RustCryptoProvider.derive_key(parameters, secret)
1701        }
1702
1703        #[cfg(feature = "xmldsig")]
1704        fn digest(
1705            &self,
1706            algorithm: crate::xmldsig::DigestAlgorithm,
1707            data: &[u8],
1708        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1709            crate::provider::CryptoProvider::digest(
1710                &crate::provider::RustCryptoProvider,
1711                algorithm,
1712                data,
1713            )
1714        }
1715
1716        #[cfg(feature = "xmldsig")]
1717        fn sign(
1718            &self,
1719            key: &dyn crate::xmldsig::SigningKey,
1720            algorithm: crate::xmldsig::SignatureAlgorithm,
1721            data: &[u8],
1722        ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
1723            crate::provider::CryptoProvider::sign(
1724                &crate::provider::RustCryptoProvider,
1725                key,
1726                algorithm,
1727                data,
1728            )
1729        }
1730
1731        #[cfg(feature = "xmldsig")]
1732        fn verify(
1733            &self,
1734            key: &dyn crate::xmldsig::VerifyingKey,
1735            algorithm: crate::xmldsig::SignatureAlgorithm,
1736            data: &[u8],
1737            signature: &[u8],
1738        ) -> Result<bool, crate::xmldsig::DsigError> {
1739            crate::provider::CryptoProvider::verify(
1740                &crate::provider::RustCryptoProvider,
1741                key,
1742                algorithm,
1743                data,
1744                signature,
1745            )
1746        }
1747
1748        fn encrypt_data(
1749            &self,
1750            algorithm: DataEncryptionAlgorithm,
1751            key: &[u8],
1752            plaintext: &[u8],
1753        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1754            crate::provider::CryptoProvider::encrypt_data(
1755                &crate::provider::RustCryptoProvider,
1756                algorithm,
1757                key,
1758                plaintext,
1759            )
1760        }
1761
1762        fn decrypt_data(
1763            &self,
1764            _algorithm: DataEncryptionAlgorithm,
1765            _key: &[u8],
1766            _ciphertext: &[u8],
1767        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1768            let index = self.decrypt_calls.fetch_add(1, Ordering::Relaxed);
1769            Ok(self
1770                .candidate_plaintexts
1771                .get(index)
1772                .unwrap_or(&self.plaintext)
1773                .clone())
1774        }
1775
1776        fn wrap_key(
1777            &self,
1778            algorithm: KeyWrapAlgorithm,
1779            kek: &[u8],
1780            key: &[u8],
1781        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1782            crate::provider::CryptoProvider::wrap_key(
1783                &crate::provider::RustCryptoProvider,
1784                algorithm,
1785                kek,
1786                key,
1787            )
1788        }
1789
1790        fn unwrap_key(
1791            &self,
1792            _algorithm: KeyWrapAlgorithm,
1793            _kek: &[u8],
1794            _wrapped: &[u8],
1795        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1796            self.unwrap_calls.fetch_add(1, Ordering::Relaxed);
1797            Ok(vec![0_u8; 16])
1798        }
1799
1800        fn transport_key(
1801            &self,
1802            key: &dyn crate::provider::KeyTransportKey,
1803            parameters: &RsaOaepParameters,
1804            plaintext: &[u8],
1805        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1806            crate::provider::CryptoProvider::transport_key(
1807                &crate::provider::RustCryptoProvider,
1808                key,
1809                parameters,
1810                plaintext,
1811            )
1812        }
1813
1814        fn recover_key(
1815            &self,
1816            _key: &dyn crate::provider::KeyRecoveryKey,
1817            _parameters: &RsaOaepParameters,
1818            _ciphertext: &[u8],
1819        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
1820            self.recover_calls.fetch_add(1, Ordering::Relaxed);
1821            Ok(vec![0_u8; 16])
1822        }
1823    }
1824
1825    impl DecryptionKeyResolver for CountingResolver {
1826        fn resolve_key(
1827            &self,
1828            _provider: &dyn crate::provider::CryptoProvider,
1829            _algorithm: DataEncryptionAlgorithm,
1830            encrypted_key: Option<&EncryptedKey>,
1831        ) -> Result<Vec<u8>, XmlEncError> {
1832            if encrypted_key.is_some() {
1833                self.candidate_calls.set(self.candidate_calls.get() + 1);
1834                Ok(self.key.clone())
1835            } else {
1836                Err(XmlEncError::KeyNotFound)
1837            }
1838        }
1839    }
1840
1841    impl DecryptionKeyResolver for AllCallsResolver {
1842        fn resolve_key(
1843            &self,
1844            _provider: &dyn crate::provider::CryptoProvider,
1845            _algorithm: DataEncryptionAlgorithm,
1846            _encrypted_key: Option<&EncryptedKey>,
1847        ) -> Result<Vec<u8>, XmlEncError> {
1848            self.calls.set(self.calls.get() + 1);
1849            Ok(self.key.clone())
1850        }
1851    }
1852
1853    impl DecryptionKeyResolver for RecipientKeyResolver {
1854        fn resolve_key(
1855            &self,
1856            _provider: &dyn crate::provider::CryptoProvider,
1857            _algorithm: DataEncryptionAlgorithm,
1858            encrypted_key: Option<&EncryptedKey>,
1859        ) -> Result<Vec<u8>, XmlEncError> {
1860            if encrypted_key.and_then(|key| key.recipient.as_deref()) == Some(self.recipient) {
1861                Ok(self.key.clone())
1862            } else {
1863                Err(XmlEncError::KeyNotFound)
1864            }
1865        }
1866    }
1867
1868    #[test]
1869    fn decrypts_gcm_and_rejects_tampering() {
1870        // Authentication must cover the complete ciphertext and tag before plaintext returns.
1871        let key = [7_u8; 16];
1872        let nonce = [9_u8; 12];
1873        let mut ciphertext = b"<Assertion>trusted</Assertion>".to_vec();
1874        Aes128Gcm::new_from_slice(&key)
1875            .expect("fixed key length")
1876            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
1877            .expect("test encryption must succeed");
1878        let mut wire = nonce.to_vec();
1879        wire.extend_from_slice(&ciphertext);
1880        let xml = format!(
1881            "<xenc:EncryptedData xmlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
1882            STANDARD.encode(&wire)
1883        );
1884        let decrypted = decrypt(&xml, &SymmetricKeyDecryptor::new(key))
1885            .expect("valid AES-GCM XML must decrypt");
1886        assert_eq!(
1887            decrypted,
1888            DecryptedContent::Xml("<Assertion>trusted</Assertion>".into())
1889        );
1890        let last = wire.len() - 1;
1891        wire[last] ^= 1;
1892        let tampered = xml.replace(&STANDARD.encode(ciphertext), &STANDARD.encode(&wire[12..]));
1893        assert!(matches!(
1894            decrypt(&tampered, &SymmetricKeyDecryptor::new(key)),
1895            Err(XmlEncError::AeadAuthenticationFailed)
1896        ));
1897    }
1898
1899    #[test]
1900    fn candidate_keys_retry_only_authenticated_decryption() {
1901        // Candidate selection belongs inside one prepared decryption operation:
1902        // structural validation and ciphertext decoding must not be repeated.
1903        let key = [7_u8; 16];
1904        let nonce = [9_u8; 12];
1905        let mut ciphertext = b"candidate plaintext".to_vec();
1906        Aes128Gcm::new_from_slice(&key)
1907            .expect("fixed key length")
1908            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
1909            .expect("test encryption must succeed");
1910        let mut wire = nonce.to_vec();
1911        wire.extend_from_slice(&ciphertext);
1912        let encrypted = EncryptedData {
1913            id: None,
1914            encrypted_type: None,
1915            encryption_method: EncryptionMethod {
1916                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
1917                key_size_bits: None,
1918                oaep_digest: None,
1919                mgf_algorithm: None,
1920                oaep_params: None,
1921            },
1922            key_name: None,
1923            encrypted_keys: Vec::new(),
1924            cipher_data: CipherData {
1925                value: STANDARD.encode(wire),
1926            },
1927        };
1928        let resolver = CandidateResolver {
1929            keys: vec![vec![1_u8; 16], key.to_vec()],
1930        };
1931
1932        let decrypted = DecryptContext::new(&resolver)
1933            .decrypt_data(&encrypted)
1934            .expect("a later authenticated candidate must decrypt");
1935
1936        assert_eq!(
1937            decrypted,
1938            DecryptedContent::Bytes(b"candidate plaintext".to_vec())
1939        );
1940    }
1941
1942    #[test]
1943    fn standalone_decryption_stops_after_first_successful_candidate() {
1944        // A successful authenticated candidate is the final standalone result;
1945        // later keys must not cause redundant decryptions or retained plaintexts.
1946        let provider = PermissiveUnwrapProvider {
1947            plaintext: b"accepted".to_vec(),
1948            ..PermissiveUnwrapProvider::default()
1949        };
1950        let resolver = CandidateResolver {
1951            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
1952        };
1953        let encrypted = EncryptedData {
1954            id: None,
1955            encrypted_type: None,
1956            encryption_method: EncryptionMethod {
1957                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
1958                key_size_bits: None,
1959                oaep_digest: None,
1960                mgf_algorithm: None,
1961                oaep_params: None,
1962            },
1963            key_name: None,
1964            encrypted_keys: Vec::new(),
1965            cipher_data: CipherData {
1966                value: STANDARD.encode([0_u8; 36]),
1967            },
1968        };
1969
1970        let result = DecryptContext::new(&resolver)
1971            .provider(&provider)
1972            .decrypt_data(&encrypted)
1973            .expect("the first successful candidate must be returned");
1974
1975        assert_eq!(result, DecryptedContent::Bytes(b"accepted".to_vec()));
1976        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
1977    }
1978
1979    #[test]
1980    fn document_decryption_discards_rejected_plaintext_before_next_candidate() {
1981        // Replacement validation may reject authenticated plaintext. The next
1982        // key is then tried, but candidates after the first valid XML stay unused.
1983        let provider = PermissiveUnwrapProvider {
1984            candidate_plaintexts: vec![b"<bad".to_vec(), b"<x/>".to_vec(), b"<u/>".to_vec()],
1985            ..PermissiveUnwrapProvider::default()
1986        };
1987        let resolver = CandidateResolver {
1988            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
1989        };
1990        let encrypted = format!(
1991            "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\" Type=\"{XMLENC_NS}Element\"><xenc:EncryptionMethod Algorithm=\"{}\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
1992            DataEncryptionAlgorithm::Aes128Gcm.uri(),
1993            STANDARD.encode([0_u8; 32]),
1994        );
1995
1996        let result = DecryptContext::new(&resolver)
1997            .provider(&provider)
1998            .decrypt_document(&encrypted, None)
1999            .expect("a later candidate with valid replacement XML must succeed");
2000
2001        assert_eq!(result, "<x/>");
2002        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 2);
2003    }
2004
2005    #[test]
2006    fn cbc_rejects_multiple_unordered_key_candidates() {
2007        // CBC padding cannot authenticate which candidate key is correct. A
2008        // resolver must select one key from trusted metadata before decryption.
2009        let encrypted = EncryptedData {
2010            id: None,
2011            encrypted_type: None,
2012            encryption_method: EncryptionMethod {
2013                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
2014                key_size_bits: None,
2015                oaep_digest: None,
2016                mgf_algorithm: None,
2017                oaep_params: None,
2018            },
2019            key_name: None,
2020            encrypted_keys: Vec::new(),
2021            cipher_data: CipherData {
2022                value: STANDARD.encode(
2023                    crate::provider::default_provider()
2024                        .encrypt_data(
2025                            DataEncryptionAlgorithm::Aes128Cbc,
2026                            &[7_u8; 16],
2027                            b"opaque plaintext",
2028                        )
2029                        .expect("test encryption must succeed"),
2030                ),
2031            },
2032        };
2033        let resolver = CandidateResolver {
2034            keys: vec![vec![1_u8; 16], vec![7_u8; 16]],
2035        };
2036
2037        let error = DecryptContext::new(&resolver)
2038            .decrypt_data(&encrypted)
2039            .expect_err("unauthenticated CBC must not guess among candidate keys");
2040
2041        assert!(matches!(
2042            error,
2043            XmlEncError::AmbiguousKeyCandidates {
2044                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
2045                actual: 2,
2046            }
2047        ));
2048    }
2049
2050    #[test]
2051    fn cbc_accepts_duplicate_copies_of_one_key_identity() {
2052        // Repeated sources containing the same key do not create the ambiguity
2053        // that unauthenticated CBC must reject between distinct key identities.
2054        let key = vec![0x27_u8; 16];
2055        let encrypted = EncryptedData {
2056            id: None,
2057            encrypted_type: None,
2058            encryption_method: EncryptionMethod {
2059                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
2060                key_size_bits: None,
2061                oaep_digest: None,
2062                mgf_algorithm: None,
2063                oaep_params: None,
2064            },
2065            key_name: None,
2066            encrypted_keys: Vec::new(),
2067            cipher_data: CipherData {
2068                value: STANDARD.encode(
2069                    crate::provider::default_provider()
2070                        .encrypt_data(
2071                            DataEncryptionAlgorithm::Aes128Cbc,
2072                            &key,
2073                            b"duplicate identity",
2074                        )
2075                        .expect("test encryption must succeed"),
2076                ),
2077            },
2078        };
2079        let resolver = CandidateResolver {
2080            keys: vec![key.clone(), key],
2081        };
2082
2083        assert_eq!(
2084            DecryptContext::new(&resolver)
2085                .decrypt_data(&encrypted)
2086                .expect("one distinct CBC key identity must decrypt"),
2087            DecryptedContent::Bytes(b"duplicate identity".to_vec())
2088        );
2089    }
2090
2091    #[test]
2092    fn candidate_keys_are_bounded_before_cryptographic_processing() {
2093        // A resolver is caller-controlled; its result cannot multiply one
2094        // prepared operation beyond the implementation safety ceiling.
2095        let encrypted = EncryptedData {
2096            id: None,
2097            encrypted_type: None,
2098            encryption_method: EncryptionMethod {
2099                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
2100                key_size_bits: None,
2101                oaep_digest: None,
2102                mgf_algorithm: None,
2103                oaep_params: None,
2104            },
2105            key_name: None,
2106            encrypted_keys: Vec::new(),
2107            cipher_data: CipherData {
2108                value: STANDARD.encode(vec![0_u8; 28]),
2109            },
2110        };
2111        let actual = crate::hard_limits::KEY_CANDIDATE_CEILING + 1;
2112        let resolver = CandidateResolver {
2113            keys: vec![vec![0_u8; 16]; actual],
2114        };
2115
2116        let error = DecryptContext::new(&resolver)
2117            .decrypt_data(&encrypted)
2118            .expect_err("oversized candidate sets must fail before decryption");
2119
2120        assert!(matches!(
2121            error,
2122            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2123                resource: crate::policy::resource_name::KEY_CANDIDATES,
2124                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
2125                actual: observed,
2126            }) if observed == actual
2127        ));
2128    }
2129
2130    #[test]
2131    fn operation_policy_controls_candidate_budget() {
2132        // A deployment-selected ceiling must reach resolver accounting; the
2133        // hard implementation ceiling is not the effective runtime policy.
2134        let encrypted = EncryptedData {
2135            id: None,
2136            encrypted_type: None,
2137            encryption_method: EncryptionMethod {
2138                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
2139                key_size_bits: None,
2140                oaep_digest: None,
2141                mgf_algorithm: None,
2142                oaep_params: None,
2143            },
2144            key_name: None,
2145            encrypted_keys: Vec::new(),
2146            cipher_data: CipherData {
2147                value: STANDARD.encode(vec![0_u8; 28]),
2148            },
2149        };
2150        let resolver = CandidateResolver {
2151            keys: vec![vec![0_u8; 16]; 3],
2152        };
2153        let mut policy = crate::policy::DecryptionPolicy::default();
2154        policy.resources.max_key_candidates = 2;
2155
2156        let error = DecryptContext::new(&resolver)
2157            .policy(policy)
2158            .decrypt_data(&encrypted)
2159            .expect_err("candidate accounting must use the operation policy ceiling");
2160
2161        assert!(matches!(
2162            error,
2163            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2164                resource: crate::policy::resource_name::KEY_CANDIDATES,
2165                maximum: 2,
2166                actual: 3,
2167            })
2168        ));
2169    }
2170
2171    #[test]
2172    fn candidate_work_ceiling_is_shared_across_recipients() {
2173        // The candidate ceiling bounds the complete decryption operation, not
2174        // each EncryptedKey independently.
2175        let key = vec![0x39_u8; 16];
2176        let resolver = AggregateRecipientResolver {
2177            attempts: Cell::new(0),
2178            key: key.clone(),
2179        };
2180        let encrypted = encrypted_data_with_recipients(
2181            &key,
2182            vec![
2183                associated_encrypted_key("first", None, None),
2184                associated_encrypted_key("second", None, None),
2185            ],
2186            None,
2187        );
2188
2189        DecryptContext::new(&resolver)
2190            .decrypt_data(&encrypted)
2191            .expect_err("a second recipient must not receive a fresh candidate allowance");
2192        assert_eq!(
2193            resolver.attempts.get(),
2194            crate::hard_limits::KEY_CANDIDATE_CEILING
2195        );
2196    }
2197
2198    #[test]
2199    fn candidate_budget_exhaustion_is_fatal_after_a_key_was_found() {
2200        // Candidate exhaustion must remain terminal even when it occurs in a
2201        // resolver path dedicated to one particular key family.
2202        let key = vec![0x49_u8; 16];
2203        let resolver = MislabelledExhaustionResolver {
2204            direct: key.clone(),
2205        };
2206        let encrypted = encrypted_data_with_recipients(
2207            &key,
2208            vec![associated_encrypted_key("recipient", None, None)],
2209            None,
2210        );
2211
2212        let error = DecryptContext::new(&resolver)
2213            .decrypt_data(&encrypted)
2214            .expect_err("candidate exhaustion must override an earlier usable key");
2215
2216        assert!(matches!(
2217            error,
2218            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2219                resource: "key candidates",
2220                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
2221                actual,
2222            }) if actual == crate::hard_limits::KEY_CANDIDATE_CEILING + 1
2223        ));
2224    }
2225
2226    #[test]
2227    fn authenticated_decryption_continues_after_wrong_unwrapped_recipient_key() {
2228        // A same-width key can unwrap successfully yet fail GCM authentication;
2229        // later applicable recipients must remain available to the data cipher.
2230        let correct = vec![0x53_u8; 16];
2231        let encrypted = encrypted_data_with_recipients(
2232            &correct,
2233            vec![
2234                associated_encrypted_key("first", None, None),
2235                associated_encrypted_key("second", None, None),
2236            ],
2237            None,
2238        );
2239        let resolver = OrderedRecipientResolver {
2240            wrong: vec![0x11_u8; 16],
2241            correct,
2242        };
2243
2244        let plaintext = DecryptContext::new(&resolver)
2245            .decrypt_data(&encrypted)
2246            .expect("the second recipient key must authenticate");
2247        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
2248    }
2249
2250    #[test]
2251    fn authenticated_decryption_continues_from_direct_key_to_recipient() {
2252        // Direct candidates and embedded recipients are one ordered lookup
2253        // space; a wrong direct GCM key must not hide a valid wrapped key.
2254        let correct = vec![0x63_u8; 16];
2255        let encrypted = encrypted_data_with_recipients(
2256            &correct,
2257            vec![associated_encrypted_key("recipient", None, None)],
2258            None,
2259        );
2260        let resolver = DirectAndRecipientResolver {
2261            direct: vec![0x19_u8; 16],
2262            recipient: correct,
2263        };
2264
2265        let plaintext = DecryptContext::new(&resolver)
2266            .decrypt_data(&encrypted)
2267            .expect("the embedded recipient must remain available after a direct candidate");
2268
2269        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
2270    }
2271
2272    #[test]
2273    fn authenticated_decryption_continues_after_direct_lookup_error() {
2274        // A candidate-local direct lookup failure must not suppress a valid
2275        // embedded recipient from the same ordered key-resolution operation.
2276        let correct = vec![0x64_u8; 16];
2277        let encrypted = encrypted_data_with_recipients(
2278            &correct,
2279            vec![associated_encrypted_key("recipient", None, None)],
2280            None,
2281        );
2282        let resolver = FailingDirectResolver { recipient: correct };
2283
2284        let plaintext = DecryptContext::new(&resolver)
2285            .decrypt_data(&encrypted)
2286            .expect("recipient lookup must follow a candidate-local direct error");
2287
2288        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
2289    }
2290
2291    #[test]
2292    fn resolver_policy_rejection_stops_before_later_recipient() {
2293        // A custom resolver's typed policy rejection is operation-wide; trying
2294        // a later recipient after it would allow key-source policy bypass.
2295        let correct = vec![0x65_u8; 16];
2296        let encrypted = encrypted_data_with_recipients(
2297            &correct,
2298            vec![associated_encrypted_key("recipient", None, None)],
2299            None,
2300        );
2301        let resolver = PolicyRejectingDirectResolver { recipient: correct };
2302
2303        let error = DecryptContext::new(&resolver)
2304            .decrypt_data(&encrypted)
2305            .expect_err("operation policy rejection must be fatal");
2306
2307        assert!(matches!(
2308            error,
2309            XmlEncError::Policy(crate::policy::PolicyViolation::KeyTrust {
2310                reason: "test resolver rejected the operation",
2311            })
2312        ));
2313    }
2314
2315    #[test]
2316    fn cbc_rejects_distinct_direct_and_recipient_candidates() {
2317        // Combining lookup sources must not make unauthenticated CBC choose the
2318        // direct key merely because it was resolved before the recipient key.
2319        let recipient = vec![0x73_u8; 16];
2320        let mut encrypted = encrypted_data_with_recipients(
2321            &recipient,
2322            vec![associated_encrypted_key("recipient", None, None)],
2323            None,
2324        );
2325        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes128Cbc.uri().into();
2326        encrypted.cipher_data.value = STANDARD.encode(
2327            crate::provider::default_provider()
2328                .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &recipient, b"payload")
2329                .expect("test encryption must succeed"),
2330        );
2331        let resolver = DirectAndRecipientResolver {
2332            direct: vec![0x29_u8; 16],
2333            recipient,
2334        };
2335
2336        let error = DecryptContext::new(&resolver)
2337            .decrypt_data(&encrypted)
2338            .expect_err("CBC must not guess between direct and recipient keys");
2339
2340        assert!(matches!(
2341            error,
2342            XmlEncError::AmbiguousKeyCandidates {
2343                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
2344                actual: 2,
2345            }
2346        ));
2347    }
2348
2349    #[test]
2350    fn cbc_ambiguity_ignores_algorithm_incompatible_key_widths() {
2351        // A wrong-width key cannot reach AES-CBC and therefore cannot make one
2352        // width-compatible candidate ambiguous.
2353        let key = vec![0x47_u8; 16];
2354        let ciphertext = crate::provider::default_provider()
2355            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &key, b"payload")
2356            .expect("test encryption must succeed");
2357        let encrypted = EncryptedData {
2358            id: None,
2359            encrypted_type: None,
2360            encryption_method: EncryptionMethod {
2361                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
2362                key_size_bits: None,
2363                oaep_digest: None,
2364                mgf_algorithm: None,
2365                oaep_params: None,
2366            },
2367            key_name: None,
2368            encrypted_keys: Vec::new(),
2369            cipher_data: CipherData {
2370                value: STANDARD.encode(ciphertext),
2371            },
2372        };
2373        let resolver = CandidateResolver {
2374            keys: vec![vec![0_u8; 32], key],
2375        };
2376
2377        assert_eq!(
2378            DecryptContext::new(&resolver)
2379                .decrypt_data(&encrypted)
2380                .expect("the sole width-compatible CBC key must be selected"),
2381            DecryptedContent::Bytes(b"payload".to_vec())
2382        );
2383    }
2384
2385    #[test]
2386    fn data_reference_selects_the_associated_encrypted_key() {
2387        // An explicit DataReference to another object contradicts this
2388        // EncryptedData even when that recipient can be unwrapped.
2389        let key = vec![0x51_u8; 16];
2390        let resolver = AssociationRecordingResolver {
2391            visited: RefCell::new(Vec::new()),
2392            key: key.clone(),
2393        };
2394        let encrypted = encrypted_data_with_recipients(
2395            &key,
2396            vec![
2397                associated_encrypted_key("unrelated", Some("#other"), None),
2398                associated_encrypted_key("matching", Some("#target"), None),
2399            ],
2400            None,
2401        );
2402
2403        DecryptContext::new(&resolver)
2404            .decrypt_data(&encrypted)
2405            .expect("the associated recipient must decrypt");
2406        assert_eq!(resolver.visited.into_inner(), ["matching"]);
2407    }
2408
2409    #[test]
2410    fn carried_key_name_selects_the_named_content_key() {
2411        // CarriedKeyName identifies the transported key named by the enclosing
2412        // EncryptedData KeyInfo; a contradictory label must be skipped.
2413        let key = vec![0x52_u8; 16];
2414        let resolver = AssociationRecordingResolver {
2415            visited: RefCell::new(Vec::new()),
2416            key: key.clone(),
2417        };
2418        let encrypted = encrypted_data_with_recipients(
2419            &key,
2420            vec![
2421                associated_encrypted_key("unrelated", None, Some("other")),
2422                associated_encrypted_key("matching", None, Some("wanted")),
2423            ],
2424            Some("wanted"),
2425        );
2426
2427        DecryptContext::new(&resolver)
2428            .decrypt_data(&encrypted)
2429            .expect("the matching carried key name must decrypt");
2430        assert_eq!(resolver.visited.into_inner(), ["matching"]);
2431    }
2432
2433    #[test]
2434    fn contradictory_encrypted_key_associations_fail_closed() {
2435        // Association metadata is authoritative when present. The resolver must
2436        // not see a recipient that explicitly names another encrypted object.
2437        let key = vec![0x53_u8; 16];
2438        let resolver = AssociationRecordingResolver {
2439            visited: RefCell::new(Vec::new()),
2440            key: key.clone(),
2441        };
2442        let encrypted = encrypted_data_with_recipients(
2443            &key,
2444            vec![associated_encrypted_key("unrelated", Some("#other"), None)],
2445            None,
2446        );
2447
2448        assert!(matches!(
2449            DecryptContext::new(&resolver).decrypt_data(&encrypted),
2450            Err(XmlEncError::KeyNotFound)
2451        ));
2452        assert!(resolver.visited.into_inner().is_empty());
2453    }
2454
2455    #[test]
2456    fn direct_symmetric_key_ignores_embedded_key_hints() {
2457        // A caller-supplied content key is authoritative for this resolver;
2458        // unrelated recipient hints must not disable direct-key decryption.
2459        let key = [0x28_u8; 16];
2460        let unrelated = EncryptedKey {
2461            id: None,
2462            recipient: Some("other-recipient".into()),
2463            key_name: None,
2464            encryption_method: super::super::EncryptionMethod {
2465                algorithm: "urn:unrelated:key-transport".into(),
2466                key_size_bits: None,
2467                oaep_digest: None,
2468                mgf_algorithm: None,
2469                oaep_params: None,
2470            },
2471            cipher_data: super::super::CipherData {
2472                value: STANDARD.encode([0_u8; 24]),
2473            },
2474            reference_list: None,
2475            carried_key_name: None,
2476        };
2477
2478        assert_eq!(
2479            SymmetricKeyDecryptor::new(key)
2480                .resolve_key(
2481                    crate::provider::default_provider(),
2482                    DataEncryptionAlgorithm::Aes128Gcm,
2483                    Some(&unrelated)
2484                )
2485                .expect("direct key must ignore unrelated embedded hints"),
2486            key
2487        );
2488    }
2489
2490    #[test]
2491    fn decrypts_with_the_matching_recipient_key() {
2492        // Multi-recipient KeyInfo must retain document order and continue after a
2493        // malformed unrelated key before accepting the intended one.
2494        let key = [0x29_u8; 16];
2495        let plaintext = "recipient-specific plaintext";
2496        let encrypted = encrypted_gcm_element("", plaintext, None, true, &key);
2497        let recipient_key = |recipient: &str, method: &str| {
2498            format!(
2499                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"{method}\">{}</xenc:EncryptionMethod><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>",
2500                if recipient == "alice" {
2501                    "<ds:DigestMethod Algorithm=\"urn:unsupported:digest\"/>"
2502                } else {
2503                    ""
2504                }
2505            )
2506        };
2507        let key_info = format!(
2508            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
2509            crate::xmlenc::types::XMLDSIG_NS,
2510            recipient_key("alice", KeyTransportAlgorithm::RsaOaep11.uri()),
2511            recipient_key("bob", KeyWrapAlgorithm::AesKw128.uri())
2512        );
2513        let xml = encrypted.replacen(
2514            "<xenc:CipherData>",
2515            &format!("{key_info}<xenc:CipherData>"),
2516            1,
2517        );
2518        let resolver = RecipientKeyResolver {
2519            recipient: "bob",
2520            key: key.to_vec(),
2521        };
2522
2523        assert_eq!(
2524            decrypt(&xml, &resolver).expect("second recipient key must be tried"),
2525            DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
2526        );
2527    }
2528
2529    #[test]
2530    fn decryption_policy_bounds_recipients_before_key_resolution() {
2531        // Both XML parsing and caller-constructed typed input must reject an
2532        // oversized recipient set before any resolver can inspect candidates.
2533        let key = [0x29_u8; 16];
2534        let encrypted = encrypted_gcm_element("", "bounded recipients", None, true, &key);
2535        let recipient_key = |recipient: &str| {
2536            format!(
2537                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"urn:test:key\"/><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>"
2538            )
2539        };
2540        let key_info = format!(
2541            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
2542            crate::xmlenc::types::XMLDSIG_NS,
2543            recipient_key("alice"),
2544            recipient_key("bob")
2545        );
2546        let xml = encrypted.replacen(
2547            "<xenc:CipherData>",
2548            &format!("{key_info}<xenc:CipherData>"),
2549            1,
2550        );
2551        let parsed = parse_encrypted_data(&xml).expect("default parser accepts two recipients");
2552        let policy = crate::policy::DecryptionPolicy {
2553            resources: crate::policy::ResourcePolicy {
2554                max_encryption_recipients: 1,
2555                ..crate::policy::ResourcePolicy::default()
2556            },
2557            ..crate::policy::DecryptionPolicy::default()
2558        };
2559        let resolver = SymmetricKeyDecryptor::new(key);
2560        let context = DecryptContext::new(&resolver).policy(policy);
2561
2562        for error in [
2563            context
2564                .decrypt(&xml)
2565                .expect_err("XML recipient collection must be bounded"),
2566            context
2567                .decrypt_data(&parsed)
2568                .expect_err("typed recipient collection must be bounded"),
2569        ] {
2570            assert!(matches!(
2571                error,
2572                XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2573                    resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
2574                    maximum: 1,
2575                    actual: 2,
2576                })
2577            ));
2578        }
2579    }
2580
2581    #[test]
2582    fn decrypts_session_key_wrapped_with_aes_kw() {
2583        // RFC 3394 unwrap must recover exactly the content algorithm's key length.
2584        let kek = [3_u8; 16];
2585        let session_key = [4_u8; 16];
2586        let mut wrapped = [0_u8; 24];
2587        KwAes128::new_from_slice(&kek)
2588            .expect("fixed KEK length")
2589            .wrap_key(&session_key, &mut wrapped)
2590            .expect("RFC 3394 test wrapping must succeed");
2591        let encrypted_key = EncryptedKey {
2592            id: None,
2593            recipient: None,
2594            key_name: None,
2595            encryption_method: super::super::EncryptionMethod {
2596                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
2597                key_size_bits: None,
2598                oaep_digest: None,
2599                mgf_algorithm: None,
2600                oaep_params: None,
2601            },
2602            cipher_data: super::super::CipherData {
2603                value: STANDARD.encode(wrapped),
2604            },
2605            reference_list: None,
2606            carried_key_name: None,
2607        };
2608        let resolved = KekDecryptor::new(kek)
2609            .resolve_key(
2610                crate::provider::default_provider(),
2611                DataEncryptionAlgorithm::Aes128Gcm,
2612                Some(&encrypted_key),
2613            )
2614            .expect("wrapped session key must resolve");
2615        assert_eq!(resolved, session_key);
2616    }
2617
2618    #[test]
2619    fn rejects_invalid_kek_before_custom_provider_dispatch() {
2620        // KEK length is part of the XMLEnc algorithm contract, not a provider
2621        // preference. A permissive provider must not bypass facade validation.
2622        let encrypted_key = EncryptedKey {
2623            id: None,
2624            recipient: None,
2625            key_name: None,
2626            encryption_method: super::super::EncryptionMethod {
2627                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
2628                key_size_bits: None,
2629                oaep_digest: None,
2630                mgf_algorithm: None,
2631                oaep_params: None,
2632            },
2633            cipher_data: super::super::CipherData {
2634                value: STANDARD.encode([0_u8; 24]),
2635            },
2636            reference_list: None,
2637            carried_key_name: None,
2638        };
2639        let provider = PermissiveUnwrapProvider::default();
2640
2641        assert!(matches!(
2642            KekDecryptor::new([0_u8; 32]).resolve_key(
2643                &provider,
2644                DataEncryptionAlgorithm::Aes128Gcm,
2645                Some(&encrypted_key),
2646            ),
2647            Err(XmlEncError::InvalidKekSize {
2648                algorithm: KeyWrapAlgorithm::AesKw128,
2649                expected: 16,
2650                actual: 32,
2651            })
2652        ));
2653        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
2654    }
2655
2656    #[test]
2657    fn rejects_content_ciphertext_framing_before_resolution_or_provider_dispatch() {
2658        // Algorithm framing belongs to the XMLEnc facade. A permissive provider
2659        // and resolver must never observe malformed standard CipherValue bytes.
2660        for (algorithm, ciphertext_len) in [
2661            (DataEncryptionAlgorithm::Aes128Gcm, 27),
2662            (DataEncryptionAlgorithm::Aes128Cbc, 33),
2663        ] {
2664            let resolver = AllCallsResolver {
2665                calls: Cell::new(0),
2666                key: vec![0_u8; algorithm.key_len()],
2667            };
2668            let provider = PermissiveUnwrapProvider::default();
2669            let encrypted = EncryptedData {
2670                id: None,
2671                encrypted_type: None,
2672                key_name: None,
2673                encryption_method: super::super::EncryptionMethod {
2674                    algorithm: algorithm.uri().into(),
2675                    key_size_bits: None,
2676                    oaep_digest: None,
2677                    mgf_algorithm: None,
2678                    oaep_params: None,
2679                },
2680                encrypted_keys: Vec::new(),
2681                cipher_data: super::super::CipherData {
2682                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
2683                },
2684            };
2685
2686            assert!(
2687                DecryptContext::new(&resolver)
2688                    .provider(&provider)
2689                    .decrypt_data(&encrypted)
2690                    .is_err()
2691            );
2692            assert_eq!(resolver.calls.get(), 0);
2693            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 0);
2694        }
2695    }
2696
2697    #[test]
2698    fn rejects_custom_provider_plaintext_outside_algorithm_bounds() {
2699        // A provider success result is still untrusted: GCM fixes the plaintext
2700        // length exactly, while CBC padding permits only one block-sized range.
2701        for (algorithm, ciphertext_len, plaintext_len) in [
2702            (DataEncryptionAlgorithm::Aes128Gcm, 32, 5),
2703            (DataEncryptionAlgorithm::Aes128Cbc, 32, 16),
2704        ] {
2705            let resolver = AllCallsResolver {
2706                calls: Cell::new(0),
2707                key: vec![0_u8; algorithm.key_len()],
2708            };
2709            let provider = PermissiveUnwrapProvider {
2710                plaintext: vec![0_u8; plaintext_len],
2711                ..PermissiveUnwrapProvider::default()
2712            };
2713            let encrypted = EncryptedData {
2714                id: None,
2715                encrypted_type: None,
2716                key_name: None,
2717                encryption_method: super::super::EncryptionMethod {
2718                    algorithm: algorithm.uri().into(),
2719                    key_size_bits: None,
2720                    oaep_digest: None,
2721                    mgf_algorithm: None,
2722                    oaep_params: None,
2723                },
2724                encrypted_keys: Vec::new(),
2725                cipher_data: super::super::CipherData {
2726                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
2727                },
2728            };
2729
2730            let error = DecryptContext::new(&resolver)
2731                .provider(&provider)
2732                .decrypt_data(&encrypted)
2733                .expect_err("impossible provider output length must fail");
2734            match algorithm {
2735                DataEncryptionAlgorithm::Aes128Gcm => assert!(matches!(
2736                    error,
2737                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSize {
2738                        operation: crate::provider::ProviderOperation::Decrypt,
2739                        expected: 4,
2740                        actual: 5,
2741                    })
2742                )),
2743                DataEncryptionAlgorithm::Aes128Cbc => assert!(matches!(
2744                    error,
2745                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSizeRange {
2746                        operation: crate::provider::ProviderOperation::Decrypt,
2747                        minimum: 0,
2748                        maximum: 15,
2749                        actual: 16,
2750                    })
2751                )),
2752                _ => unreachable!("the regression table covers one GCM and one CBC algorithm"),
2753            }
2754            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
2755        }
2756    }
2757
2758    #[test]
2759    fn rejects_malformed_aes_kw_before_custom_provider_dispatch() {
2760        // RFC 3394 adds exactly eight bytes to the transported content key;
2761        // permissive custom providers must not redefine that wire contract.
2762        let provider = PermissiveUnwrapProvider::default();
2763        for actual in [0, 23, 25] {
2764            let encrypted_key = EncryptedKey {
2765                id: None,
2766                recipient: None,
2767                key_name: None,
2768                encryption_method: super::super::EncryptionMethod {
2769                    algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
2770                    key_size_bits: None,
2771                    oaep_digest: None,
2772                    mgf_algorithm: None,
2773                    oaep_params: None,
2774                },
2775                cipher_data: super::super::CipherData {
2776                    value: STANDARD.encode(vec![0_u8; actual]),
2777                },
2778                reference_list: None,
2779                carried_key_name: None,
2780            };
2781            assert!(matches!(
2782                KekDecryptor::new([0_u8; 16]).resolve_key(
2783                    &provider,
2784                    DataEncryptionAlgorithm::Aes128Gcm,
2785                    Some(&encrypted_key),
2786                ),
2787                Err(XmlEncError::InvalidWrappedKeyLength {
2788                    expected: 24,
2789                    actual: output_len,
2790                }) if output_len == actual
2791            ));
2792        }
2793        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
2794    }
2795
2796    #[test]
2797    fn rejects_malformed_rsa_oaep_before_custom_provider_dispatch() {
2798        // RSA ciphertext width is the private modulus width, so malformed
2799        // transport bytes must be rejected before provider-owned recovery.
2800        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
2801            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
2802        ))
2803        .expect("RSA donor private key must parse");
2804        let provider = PermissiveUnwrapProvider::default();
2805        for actual in [0, 255, 257] {
2806            assert!(matches!(
2807                recover_rsa_oaep(
2808                    &provider,
2809                    &private_key,
2810                    &RsaOaepParameters::default(),
2811                    &vec![0_u8; actual],
2812                ),
2813                Err(XmlEncError::InvalidWrappedKeyLength {
2814                    expected: 256,
2815                    actual: output_len,
2816                }) if output_len == actual
2817            ));
2818        }
2819        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 0);
2820    }
2821
2822    #[test]
2823    fn custom_provider_recovers_with_an_opaque_private_key() {
2824        // The resolver knows only the public ciphertext width; private key
2825        // material remains entirely behind the provider/key-handle boundary.
2826        let encrypted_key = EncryptedKey {
2827            id: None,
2828            recipient: None,
2829            key_name: None,
2830            encryption_method: super::super::EncryptionMethod {
2831                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
2832                key_size_bits: None,
2833                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
2834                mgf_algorithm: Some(OaepDigestAlgorithm::Sha256.mgf_uri().into()),
2835                oaep_params: None,
2836            },
2837            cipher_data: super::super::CipherData {
2838                value: STANDARD.encode(vec![0x5a; 256]),
2839            },
2840            reference_list: None,
2841            carried_key_name: None,
2842        };
2843        let provider = PermissiveUnwrapProvider::default();
2844        let decryptor = PrivateKeyDecryptor::provider_key(Arc::new(OpaqueRecoveryKey));
2845
2846        let key = decryptor
2847            .resolve_key(
2848                &provider,
2849                DataEncryptionAlgorithm::Aes128Gcm,
2850                Some(&encrypted_key),
2851            )
2852            .expect("custom provider must recover through its opaque private key");
2853
2854        assert_eq!(key, vec![0_u8; 16]);
2855        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 1);
2856    }
2857
2858    #[test]
2859    fn rejects_truncated_gcm_and_invalid_wrapped_key() {
2860        // Framing and key-wrap integrity failures must occur before content is exposed.
2861        assert!(matches!(
2862            crate::provider::default_provider().decrypt_data(
2863                DataEncryptionAlgorithm::Aes128Gcm,
2864                &[0_u8; 16],
2865                &[0_u8; 27],
2866            ),
2867            Err(crate::provider::ProviderError::InvalidInput(
2868                crate::provider::ProviderInputError::AesGcmFraming
2869            ))
2870        ));
2871        let truncated = EncryptedData {
2872            id: None,
2873            encrypted_type: None,
2874            key_name: None,
2875            encryption_method: super::super::EncryptionMethod {
2876                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
2877                key_size_bits: None,
2878                oaep_digest: None,
2879                mgf_algorithm: None,
2880                oaep_params: None,
2881            },
2882            encrypted_keys: Vec::new(),
2883            cipher_data: super::super::CipherData {
2884                value: STANDARD.encode([0_u8; 27]),
2885            },
2886        };
2887        assert!(matches!(
2888            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16])).decrypt_data(&truncated),
2889            Err(XmlEncError::DataTooShort {
2890                algorithm: "AES-GCM",
2891                actual: 27,
2892                ..
2893            })
2894        ));
2895        let encrypted_key = EncryptedKey {
2896            id: None,
2897            recipient: None,
2898            key_name: None,
2899            encryption_method: super::super::EncryptionMethod {
2900                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
2901                key_size_bits: None,
2902                oaep_digest: None,
2903                mgf_algorithm: None,
2904                oaep_params: None,
2905            },
2906            cipher_data: super::super::CipherData {
2907                value: STANDARD.encode([0_u8; 24]),
2908            },
2909            reference_list: None,
2910            carried_key_name: None,
2911        };
2912        assert!(matches!(
2913            KekDecryptor::new([0_u8; 16]).resolve_key(
2914                crate::provider::default_provider(),
2915                DataEncryptionAlgorithm::Aes128Gcm,
2916                Some(&encrypted_key)
2917            ),
2918            Err(XmlEncError::KeyWrapIntegrity)
2919        ));
2920        assert!(matches!(
2921            KekDecryptor::new([0_u8; 32]).resolve_key(
2922                crate::provider::default_provider(),
2923                DataEncryptionAlgorithm::Aes128Gcm,
2924                Some(&encrypted_key)
2925            ),
2926            Err(XmlEncError::InvalidKekSize {
2927                algorithm: KeyWrapAlgorithm::AesKw128,
2928                expected: 16,
2929                actual: 32
2930            })
2931        ));
2932    }
2933
2934    #[test]
2935    fn decrypts_oaep11_with_independent_digest_and_mgf() {
2936        // XMLEnc 1.1 permits the OAEP digest and MGF1 digest to differ.
2937        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
2938            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
2939        ))
2940        .expect("RSA donor private key must parse");
2941        let public_key = RsaPublicKey::from(&private_key);
2942        let session_key = [6_u8; 16];
2943        let label = b"xmlenc-label".to_vec();
2944        let wrapped = public_key
2945            .encrypt(
2946                &mut ChaCha20Rng::from_seed([17_u8; 32]),
2947                Oaep::<Sha256, Sha384>::new_with_mgf_hash_and_label(label.clone()),
2948                &session_key,
2949            )
2950            .expect("OAEP test wrapping must succeed");
2951        let encrypted_key = EncryptedKey {
2952            id: Some("wrapped-key".into()),
2953            recipient: Some("recipient-a".into()),
2954            key_name: None,
2955            encryption_method: super::super::EncryptionMethod {
2956                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
2957                key_size_bits: None,
2958                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
2959                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha384".into()),
2960                oaep_params: Some(label),
2961            },
2962            cipher_data: super::super::CipherData {
2963                value: STANDARD.encode(wrapped),
2964            },
2965            reference_list: None,
2966            carried_key_name: None,
2967        };
2968        let resolved = PrivateKeyDecryptor::new(private_key)
2969            .resolve_key(
2970                crate::provider::default_provider(),
2971                DataEncryptionAlgorithm::Aes128Gcm,
2972                Some(&encrypted_key),
2973            )
2974            .expect("OAEP 1.1 wrapped key must resolve");
2975        assert_eq!(resolved, session_key);
2976    }
2977
2978    #[test]
2979    fn decrypts_legacy_oaep_uri_with_sha256_digest() {
2980        // The legacy URI defaults MGF1 to SHA-1 when no xenc11:MGF child is present.
2981        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
2982            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
2983        ))
2984        .expect("RSA donor private key must parse");
2985        let public_key = RsaPublicKey::from(&private_key);
2986        let session_key = [8_u8; 16];
2987        let wrapped = public_key
2988            .encrypt(
2989                &mut ChaCha20Rng::from_seed([19_u8; 32]),
2990                Oaep::<Sha256, Sha1>::new_with_mgf_hash(),
2991                &session_key,
2992            )
2993            .expect("legacy OAEP URI test wrapping must succeed");
2994        let encrypted_key = EncryptedKey {
2995            id: None,
2996            recipient: None,
2997            key_name: None,
2998            encryption_method: super::super::EncryptionMethod {
2999                algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p".into(),
3000                key_size_bits: None,
3001                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
3002                mgf_algorithm: None,
3003                oaep_params: None,
3004            },
3005            cipher_data: super::super::CipherData {
3006                value: STANDARD.encode(wrapped),
3007            },
3008            reference_list: None,
3009            carried_key_name: None,
3010        };
3011        let resolved = PrivateKeyDecryptor::new(private_key)
3012            .resolve_key(
3013                crate::provider::default_provider(),
3014                DataEncryptionAlgorithm::Aes128Gcm,
3015                Some(&encrypted_key),
3016            )
3017            .expect("legacy OAEP URI with SHA-256 must resolve");
3018        assert_eq!(resolved, session_key);
3019    }
3020
3021    #[test]
3022    fn decrypts_sha384_oaep_with_the_xmlenc_digest_uri() {
3023        // XML Encryption 1.1 reserves xmlenc#sha384 for SHA-384. Exercise both
3024        // OAEP algorithm URIs with their absent/explicit MGF1-SHA1 forms.
3025        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
3026            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
3027        ))
3028        .expect("RSA donor private key must parse");
3029        let public_key = RsaPublicKey::from(&private_key);
3030        let session_key = [9_u8; 16];
3031        let digest = "http://www.w3.org/2001/04/xmlenc#sha384";
3032
3033        for (algorithm, mgf_algorithm) in [
3034            ("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", None),
3035            (
3036                "http://www.w3.org/2009/xmlenc11#rsa-oaep",
3037                Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
3038            ),
3039        ] {
3040            let wrapped = public_key
3041                .encrypt(
3042                    &mut ChaCha20Rng::from_seed([23_u8; 32]),
3043                    Oaep::<Sha384, Sha1>::new_with_mgf_hash(),
3044                    &session_key,
3045                )
3046                .expect("SHA-384 OAEP test wrapping must succeed");
3047            let encrypted_key = EncryptedKey {
3048                id: None,
3049                recipient: None,
3050                key_name: None,
3051                encryption_method: super::super::EncryptionMethod {
3052                    algorithm: algorithm.into(),
3053                    key_size_bits: None,
3054                    oaep_digest: Some(digest.into()),
3055                    mgf_algorithm: mgf_algorithm.map(str::to_owned),
3056                    oaep_params: None,
3057                },
3058                cipher_data: super::super::CipherData {
3059                    value: STANDARD.encode(wrapped),
3060                },
3061                reference_list: None,
3062                carried_key_name: None,
3063            };
3064            let resolved = PrivateKeyDecryptor::new(private_key.clone())
3065                .resolve_key(
3066                    crate::provider::default_provider(),
3067                    DataEncryptionAlgorithm::Aes128Gcm,
3068                    Some(&encrypted_key),
3069                )
3070                .expect("official XMLENC SHA-384 URI must resolve");
3071            assert_eq!(resolved, session_key);
3072        }
3073    }
3074
3075    #[test]
3076    fn rejects_unknown_oaep_digest_and_mgf_as_unsupported() {
3077        // Unknown algorithm URIs are declaration errors, not generic RSA failures.
3078        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
3079            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
3080        ))
3081        .expect("RSA donor private key must parse");
3082        let decryptor = PrivateKeyDecryptor::new(private_key);
3083        let mut encrypted_key = EncryptedKey {
3084            id: None,
3085            recipient: None,
3086            key_name: None,
3087            encryption_method: super::super::EncryptionMethod {
3088                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
3089                key_size_bits: None,
3090                oaep_digest: Some("urn:unsupported:digest".into()),
3091                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha1".into()),
3092                oaep_params: None,
3093            },
3094            cipher_data: super::super::CipherData {
3095                value: STANDARD.encode([0_u8; 256]),
3096            },
3097            reference_list: None,
3098            carried_key_name: None,
3099        };
3100        assert!(matches!(
3101            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
3102            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:digest"
3103        ));
3104
3105        encrypted_key.encryption_method.oaep_digest = None;
3106        encrypted_key.encryption_method.mgf_algorithm = Some("urn:unsupported:mgf".into());
3107        assert!(matches!(
3108            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
3109            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:mgf"
3110        ));
3111    }
3112
3113    #[test]
3114    fn decryption_policy_enforces_oaep_digest_and_plaintext_limits() {
3115        // Algorithm and allocation policies are checked before key resolution
3116        // or plaintext materialization, including the document-declared MGF.
3117        let encrypted_key = EncryptedKey {
3118            id: None,
3119            recipient: Some("selected".into()),
3120            key_name: None,
3121            encryption_method: super::super::EncryptionMethod {
3122                algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
3123                key_size_bits: None,
3124                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
3125                mgf_algorithm: Some(OaepDigestAlgorithm::Sha384.mgf_uri().into()),
3126                oaep_params: None,
3127            },
3128            cipher_data: super::super::CipherData {
3129                value: STANDARD.encode([0_u8; 256]),
3130            },
3131            reference_list: None,
3132            carried_key_name: None,
3133        };
3134        let encrypted = EncryptedData {
3135            id: None,
3136            encrypted_type: None,
3137            key_name: None,
3138            encryption_method: super::super::EncryptionMethod {
3139                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3140                key_size_bits: None,
3141                oaep_digest: None,
3142                mgf_algorithm: None,
3143                oaep_params: None,
3144            },
3145            encrypted_keys: vec![encrypted_key],
3146            cipher_data: super::super::CipherData {
3147                value: STANDARD.encode([0_u8; 28]),
3148            },
3149        };
3150        let policy = crate::policy::DecryptionPolicy {
3151            oaep_digests: Some(std::collections::HashSet::from([
3152                OaepDigestAlgorithm::Sha256,
3153            ])),
3154            ..crate::policy::DecryptionPolicy::default()
3155        };
3156        assert!(matches!(
3157            DecryptContext::new(&RecipientKeyResolver {
3158                recipient: "selected",
3159                key: vec![0_u8; 16],
3160            })
3161            .policy(policy)
3162            .decrypt_data(&encrypted),
3163            Err(XmlEncError::Policy(
3164                crate::policy::PolicyViolation::Algorithm { algorithm, .. }
3165            )) if algorithm == OaepDigestAlgorithm::Sha384.mgf_uri()
3166        ));
3167
3168        let ciphertext = crate::provider::default_provider()
3169            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"four")
3170            .expect("test encryption must succeed");
3171        let bounded = EncryptedData {
3172            encrypted_keys: Vec::new(),
3173            cipher_data: super::super::CipherData {
3174                value: STANDARD.encode(ciphertext),
3175            },
3176            ..encrypted
3177        };
3178        let policy = crate::policy::DecryptionPolicy {
3179            resources: crate::policy::ResourcePolicy {
3180                max_encryption_plaintext_bytes: 3,
3181                ..crate::policy::ResourcePolicy::default()
3182            },
3183            ..crate::policy::DecryptionPolicy::default()
3184        };
3185        assert!(matches!(
3186            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
3187                .policy(policy)
3188                .decrypt_data(&bounded),
3189            Err(XmlEncError::Policy(
3190                crate::policy::PolicyViolation::ResourceLimit {
3191                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
3192                    maximum: 3,
3193                    actual: 4
3194                }
3195            ))
3196        ));
3197
3198        let cbc_ciphertext = crate::provider::default_provider()
3199            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &[0_u8; 16], b"four")
3200            .expect("test CBC encryption must succeed");
3201        let bounded_cbc = EncryptedData {
3202            encryption_method: super::super::EncryptionMethod {
3203                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
3204                key_size_bits: None,
3205                oaep_digest: None,
3206                mgf_algorithm: None,
3207                oaep_params: None,
3208            },
3209            encrypted_keys: Vec::new(),
3210            cipher_data: super::super::CipherData {
3211                value: STANDARD.encode(cbc_ciphertext),
3212            },
3213            ..bounded
3214        };
3215        let policy = crate::policy::DecryptionPolicy {
3216            resources: crate::policy::ResourcePolicy {
3217                max_encryption_plaintext_bytes: 4,
3218                ..crate::policy::ResourcePolicy::default()
3219            },
3220            ..crate::policy::DecryptionPolicy::default()
3221        };
3222        assert_eq!(
3223            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
3224                .policy(policy)
3225                .decrypt_data(&bounded_cbc)
3226                .expect("CBC plaintext at the configured limit must decrypt"),
3227            DecryptedContent::Bytes(b"four".to_vec())
3228        );
3229    }
3230
3231    #[test]
3232    fn typed_decryption_input_cannot_bypass_metadata_policy() {
3233        // Callers may construct EncryptedData directly instead of using the XML
3234        // parser, so the operation boundary must enforce the same metadata cap.
3235        let ciphertext = crate::provider::default_provider()
3236            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"data")
3237            .expect("test encryption must succeed");
3238        let encrypted = EncryptedData {
3239            id: Some("oversized".into()),
3240            encrypted_type: None,
3241            key_name: None,
3242            encryption_method: super::super::EncryptionMethod {
3243                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3244                key_size_bits: None,
3245                oaep_digest: None,
3246                mgf_algorithm: None,
3247                oaep_params: None,
3248            },
3249            encrypted_keys: Vec::new(),
3250            cipher_data: super::super::CipherData {
3251                value: STANDARD.encode(ciphertext),
3252            },
3253        };
3254        let policy = crate::policy::DecryptionPolicy {
3255            resources: crate::policy::ResourcePolicy {
3256                max_encryption_metadata_bytes: 8,
3257                ..crate::policy::ResourcePolicy::default()
3258            },
3259            ..crate::policy::DecryptionPolicy::default()
3260        };
3261
3262        let resolver = AllCallsResolver {
3263            calls: Cell::new(0),
3264            key: vec![0_u8; 16],
3265        };
3266        assert!(matches!(
3267            DecryptContext::new(&resolver)
3268                .policy(policy)
3269                .decrypt_data(&encrypted),
3270            Err(XmlEncError::Policy(
3271                crate::policy::PolicyViolation::ResourceLimit {
3272                    resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
3273                    maximum: 8,
3274                    actual: 9,
3275                }
3276            ))
3277        ));
3278        assert_eq!(
3279            resolver.calls.get(),
3280            0,
3281            "metadata preflight must gate resolver-controlled work"
3282        );
3283    }
3284
3285    #[test]
3286    fn typed_cipher_values_are_bounded_before_decode_or_resolution() {
3287        // Public typed input bypasses the XML parser, so the decryption boundary
3288        // must re-establish both content and recipient CipherValue size invariants.
3289        let key = [0x41_u8; 16];
3290        let ciphertext = crate::provider::default_provider()
3291            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
3292            .expect("test encryption must succeed");
3293        let mut encrypted = EncryptedData {
3294            id: None,
3295            encrypted_type: None,
3296            key_name: None,
3297            encryption_method: super::super::EncryptionMethod {
3298                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3299                key_size_bits: None,
3300                oaep_digest: None,
3301                mgf_algorithm: None,
3302                oaep_params: None,
3303            },
3304            encrypted_keys: Vec::new(),
3305            cipher_data: super::super::CipherData {
3306                value: STANDARD.encode(ciphertext),
3307            },
3308        };
3309        let policy = crate::policy::DecryptionPolicy {
3310            resources: crate::policy::ResourcePolicy {
3311                max_encryption_plaintext_bytes: 4,
3312                ..crate::policy::ResourcePolicy::default()
3313            },
3314            ..crate::policy::DecryptionPolicy::default()
3315        };
3316        encrypted.cipher_data.value = "A".repeat(48);
3317        assert!(matches!(
3318            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
3319                .policy(policy)
3320                .decrypt_data(&encrypted),
3321            Err(XmlEncError::Policy(
3322                crate::policy::PolicyViolation::ResourceLimit {
3323                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
3324                    ..
3325                }
3326            ))
3327        ));
3328
3329        encrypted.cipher_data.value = STANDARD.encode([0_u8; 28]);
3330        encrypted.encrypted_keys.push(EncryptedKey {
3331            id: None,
3332            recipient: None,
3333            key_name: None,
3334            encryption_method: super::super::EncryptionMethod {
3335                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
3336                key_size_bits: None,
3337                oaep_digest: None,
3338                mgf_algorithm: None,
3339                oaep_params: None,
3340            },
3341            cipher_data: super::super::CipherData {
3342                value: "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN + 4),
3343            },
3344            reference_list: None,
3345            carried_key_name: None,
3346        });
3347        let resolver = CountingResolver {
3348            candidate_calls: Cell::new(0),
3349            key: key.to_vec(),
3350        };
3351        assert!(matches!(
3352            DecryptContext::new(&resolver).decrypt_data(&encrypted),
3353            Err(XmlEncError::InvalidStructure(_))
3354        ));
3355        assert_eq!(resolver.candidate_calls.get(), 0);
3356
3357        encrypted.encrypted_keys[0].cipher_data.value = "AAAA".into();
3358        let aggregate_encoded_len =
3359            encrypted.cipher_data.value.len() + encrypted.encrypted_keys[0].cipher_data.value.len();
3360        let policy = crate::policy::DecryptionPolicy {
3361            resources: crate::policy::ResourcePolicy {
3362                max_encryption_plaintext_bytes: 4,
3363                max_xml_document_bytes: aggregate_encoded_len - 1,
3364                ..crate::policy::ResourcePolicy::default()
3365            },
3366            ..crate::policy::DecryptionPolicy::default()
3367        };
3368        let resolver = CountingResolver {
3369            candidate_calls: Cell::new(0),
3370            key: key.to_vec(),
3371        };
3372        assert!(matches!(
3373            DecryptContext::new(&resolver)
3374                .policy(policy)
3375                .decrypt_data(&encrypted),
3376            Err(XmlEncError::Policy(
3377                crate::policy::PolicyViolation::ResourceLimit {
3378                    resource:
3379                        crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
3380                    maximum,
3381                    actual,
3382                }
3383            )) if maximum == aggregate_encoded_len - 1 && actual == aggregate_encoded_len
3384        ));
3385        assert_eq!(resolver.candidate_calls.get(), 0);
3386    }
3387
3388    #[test]
3389    fn typed_legacy_oaep_accepts_explicit_mgf() {
3390        // The legacy URI defaults to MGF1-SHA1 when MGF is absent, but
3391        // libxmlsec1 also accepts the XMLEnc 1.1 child explicitly.
3392        let key = [0x43_u8; 16];
3393        let ciphertext = crate::provider::default_provider()
3394            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
3395            .expect("test encryption must succeed");
3396        let encrypted = EncryptedData {
3397            id: None,
3398            encrypted_type: None,
3399            key_name: None,
3400            encryption_method: super::super::EncryptionMethod {
3401                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3402                key_size_bits: None,
3403                oaep_digest: None,
3404                mgf_algorithm: None,
3405                oaep_params: None,
3406            },
3407            encrypted_keys: vec![EncryptedKey {
3408                id: None,
3409                recipient: None,
3410                key_name: None,
3411                encryption_method: super::super::EncryptionMethod {
3412                    algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
3413                    key_size_bits: None,
3414                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
3415                    mgf_algorithm: Some(OaepDigestAlgorithm::Sha384.mgf_uri().into()),
3416                    oaep_params: None,
3417                },
3418                cipher_data: super::super::CipherData {
3419                    value: STANDARD.encode([0_u8; 256]),
3420                },
3421                reference_list: None,
3422                carried_key_name: None,
3423            }],
3424            cipher_data: super::super::CipherData {
3425                value: STANDARD.encode(ciphertext),
3426            },
3427        };
3428        let resolver = CountingResolver {
3429            candidate_calls: Cell::new(0),
3430            key: key.to_vec(),
3431        };
3432
3433        assert_eq!(
3434            DecryptContext::new(&resolver)
3435                .decrypt_data(&encrypted)
3436                .expect("explicit legacy OAEP MGF must reach key resolution"),
3437            DecryptedContent::Bytes(b"data".to_vec())
3438        );
3439        assert_eq!(resolver.candidate_calls.get(), 1);
3440    }
3441
3442    #[test]
3443    fn typed_zero_key_size_is_rejected_before_key_resolution() {
3444        // Parsed KeySize values are positive. Caller-constructed values must
3445        // preserve the same invariant for algorithms without a fixed AES width.
3446        let key = [0x45_u8; 16];
3447        let ciphertext = crate::provider::default_provider()
3448            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
3449            .expect("test encryption must succeed");
3450        let encrypted = EncryptedData {
3451            id: None,
3452            encrypted_type: None,
3453            key_name: None,
3454            encryption_method: super::super::EncryptionMethod {
3455                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3456                key_size_bits: None,
3457                oaep_digest: None,
3458                mgf_algorithm: None,
3459                oaep_params: None,
3460            },
3461            encrypted_keys: vec![EncryptedKey {
3462                id: None,
3463                recipient: None,
3464                key_name: None,
3465                encryption_method: super::super::EncryptionMethod {
3466                    algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
3467                    key_size_bits: Some(0),
3468                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
3469                    mgf_algorithm: Some(OaepDigestAlgorithm::Sha256.mgf_uri().into()),
3470                    oaep_params: None,
3471                },
3472                cipher_data: super::super::CipherData {
3473                    value: STANDARD.encode([0_u8; 256]),
3474                },
3475                reference_list: None,
3476                carried_key_name: None,
3477            }],
3478            cipher_data: super::super::CipherData {
3479                value: STANDARD.encode(ciphertext),
3480            },
3481        };
3482        let resolver = CountingResolver {
3483            candidate_calls: Cell::new(0),
3484            key: key.to_vec(),
3485        };
3486
3487        assert!(matches!(
3488            DecryptContext::new(&resolver).decrypt_data(&encrypted),
3489            Err(XmlEncError::InvalidStructure(message))
3490                if message == "KeySize must be a positive integer"
3491        ));
3492        assert_eq!(resolver.candidate_calls.get(), 0);
3493    }
3494
3495    #[test]
3496    fn typed_content_method_is_validated_before_key_resolution() {
3497        // Caller-constructed values bypass XML parsing, so a fixed-size AES
3498        // KeySize mismatch must fail at the operation boundary.
3499        let key = [0x44_u8; 16];
3500        let ciphertext = crate::provider::default_provider()
3501            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
3502            .expect("test encryption must succeed");
3503        let encrypted = EncryptedData {
3504            id: None,
3505            encrypted_type: None,
3506            key_name: None,
3507            encryption_method: super::super::EncryptionMethod {
3508                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3509                key_size_bits: Some(256),
3510                oaep_digest: None,
3511                mgf_algorithm: None,
3512                oaep_params: None,
3513            },
3514            encrypted_keys: Vec::new(),
3515            cipher_data: super::super::CipherData {
3516                value: STANDARD.encode(ciphertext),
3517            },
3518        };
3519        let resolver = AllCallsResolver {
3520            calls: Cell::new(0),
3521            key: key.to_vec(),
3522        };
3523
3524        assert!(matches!(
3525            DecryptContext::new(&resolver).decrypt_data(&encrypted),
3526            Err(XmlEncError::InvalidStructure(message))
3527                if message.contains("requires KeySize 128, got 256")
3528        ));
3529        assert_eq!(resolver.calls.get(), 0);
3530    }
3531
3532    #[test]
3533    fn unknown_encrypted_key_algorithm_never_reaches_resolver() {
3534        // Extension URIs cannot bypass transport/wrap allowlists by relying on
3535        // an application resolver that happens to return usable key bytes.
3536        let key = [0x42_u8; 16];
3537        let ciphertext = crate::provider::default_provider()
3538            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
3539            .expect("test encryption must succeed");
3540        let encrypted = EncryptedData {
3541            id: None,
3542            encrypted_type: None,
3543            key_name: None,
3544            encryption_method: super::super::EncryptionMethod {
3545                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3546                key_size_bits: None,
3547                oaep_digest: None,
3548                mgf_algorithm: None,
3549                oaep_params: None,
3550            },
3551            encrypted_keys: vec![EncryptedKey {
3552                id: None,
3553                recipient: None,
3554                key_name: None,
3555                encryption_method: super::super::EncryptionMethod {
3556                    algorithm: "urn:example:unknown-key-algorithm".into(),
3557                    key_size_bits: None,
3558                    oaep_digest: None,
3559                    mgf_algorithm: None,
3560                    oaep_params: None,
3561                },
3562                cipher_data: super::super::CipherData {
3563                    value: STANDARD.encode([0_u8; 24]),
3564                },
3565                reference_list: None,
3566                carried_key_name: None,
3567            }],
3568            cipher_data: super::super::CipherData {
3569                value: STANDARD.encode(ciphertext),
3570            },
3571        };
3572        let resolver = CountingResolver {
3573            candidate_calls: Cell::new(0),
3574            key: key.to_vec(),
3575        };
3576
3577        assert!(matches!(
3578            DecryptContext::new(&resolver).decrypt_data(&encrypted),
3579            Err(XmlEncError::UnsupportedAlgorithm(_))
3580        ));
3581        assert_eq!(resolver.candidate_calls.get(), 0);
3582    }
3583
3584    #[test]
3585    fn cbc_padding_errors_do_not_expose_decrypted_octets() {
3586        // The error contract hides padding details, but callers still need an
3587        // authenticated envelope or a policy that rejects unauthenticated CBC.
3588        let error = map_data_decryption_error(
3589            DataEncryptionAlgorithm::Aes128Cbc,
3590            32,
3591            crate::provider::ProviderError::InvalidInput(
3592                crate::provider::ProviderInputError::AesCbcCiphertext,
3593            ),
3594        );
3595
3596        assert_eq!(error.to_string(), "invalid XMLEnc padding");
3597    }
3598
3599    #[test]
3600    fn replaces_element_and_content_in_caller_owned_documents() {
3601        // Element plaintext replaces the encrypted node itself, while Content
3602        // plaintext becomes children of the existing parent element.
3603        let key = [0x31_u8; 16];
3604        let element = encrypted_gcm_element(
3605            "http://www.w3.org/2001/04/xmlenc#Element",
3606            "<secret id=\"visible\">value</secret>",
3607            None,
3608            true,
3609            &key,
3610        );
3611        assert_eq!(
3612            decrypt_document(&element, None, &SymmetricKeyDecryptor::new(key))
3613                .expect("root Element replacement must succeed"),
3614            "<secret id=\"visible\">value</secret>"
3615        );
3616
3617        let content = encrypted_gcm_element(
3618            "http://www.w3.org/2001/04/xmlenc#Content",
3619            "before<child/>after",
3620            None,
3621            false,
3622            &key,
3623        );
3624        let document =
3625            format!("<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>{content}<suffix/></root>");
3626        let replaced = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
3627            .expect("nested Content replacement must succeed");
3628        assert_eq!(
3629            replaced,
3630            format!(
3631                "<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>before<child/>after<suffix/></root>"
3632            )
3633        );
3634    }
3635
3636    #[test]
3637    fn accepts_whitespace_and_comments_around_element_plaintext() {
3638        // Element serialization may carry harmless boundary whitespace/comments;
3639        // they must be preserved while the fragment still contains one element.
3640        let key = [0x34_u8; 16];
3641        let plaintext = "\n<!--before--><secret/><!--after-->\n";
3642        let encrypted = encrypted_gcm_element(
3643            "http://www.w3.org/2001/04/xmlenc#Element",
3644            plaintext,
3645            None,
3646            true,
3647            &key,
3648        );
3649
3650        assert_eq!(
3651            decrypt_document(&encrypted, None, &SymmetricKeyDecryptor::new(key))
3652                .expect("one element with boundary trivia must be accepted"),
3653            plaintext
3654        );
3655    }
3656
3657    #[test]
3658    fn decrypts_unknown_and_empty_type_hints_as_opaque_bytes() {
3659        // Type is an application hint, not an algorithm constraint. Unknown and
3660        // empty values must not prevent decryption of otherwise valid binary data.
3661        let key = [0x35_u8; 16];
3662        let plaintext = "\0opaque\u{ff}bytes";
3663        let unknown = encrypted_gcm_element("urn:example:binary", plaintext, None, true, &key);
3664        let empty = encrypted_gcm_element("", plaintext, None, true, &key).replacen(
3665            "<xenc:EncryptedData",
3666            "<xenc:EncryptedData Type=\"\"",
3667            1,
3668        );
3669
3670        let parsed = parse_encrypted_data(&unknown).expect("unknown Type must remain parseable");
3671        assert_eq!(
3672            parsed.encrypted_type,
3673            Some(EncryptedDataType::Other("urn:example:binary".into()))
3674        );
3675        assert!(matches!(
3676            decrypt_document(&unknown, None, &SymmetricKeyDecryptor::new(key)),
3677            Err(XmlEncError::ReplacementRequiresXml)
3678        ));
3679
3680        for encrypted in [unknown, empty] {
3681            assert_eq!(
3682                decrypt(&encrypted, &SymmetricKeyDecryptor::new(key))
3683                    .expect("opaque Type hints must not block decryption"),
3684                DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
3685            );
3686        }
3687    }
3688
3689    #[test]
3690    fn selects_document_encrypted_data_by_id_and_rejects_ambiguity() {
3691        // Selection must never decrypt an arbitrary first match when a document
3692        // contains multiple encrypted regions.
3693        let key = [0x32_u8; 16];
3694        let first = encrypted_gcm_element(
3695            "http://www.w3.org/2001/04/xmlenc#Content",
3696            "first",
3697            Some("first"),
3698            false,
3699            &key,
3700        );
3701        let second = encrypted_gcm_element(
3702            "http://www.w3.org/2001/04/xmlenc#Content",
3703            "second",
3704            Some("second"),
3705            false,
3706            &key,
3707        );
3708        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{first}{second}</root>");
3709        let resolver = SymmetricKeyDecryptor::new(key);
3710        assert!(matches!(
3711            decrypt_document(&document, None, &resolver),
3712            Err(XmlEncError::AmbiguousEncryptedData)
3713        ));
3714        let replaced = decrypt_document(&document, Some("second"), &resolver)
3715            .expect("Id selection must choose exactly one encrypted region");
3716        assert!(replaced.contains("second"));
3717        assert!(replaced.contains("Id=\"first\""));
3718        assert!(matches!(
3719            decrypt_document(&document, Some("missing"), &resolver),
3720            Err(XmlEncError::EncryptedDataNotFound)
3721        ));
3722    }
3723
3724    #[test]
3725    fn selects_encrypted_data_below_a_unique_operation_start_node() {
3726        // CLI-compatible selection starts at an arbitrary ID-bearing ancestor;
3727        // missing/duplicate IDs and multiple encrypted descendants fail closed.
3728        let key = [0x42_u8; 16];
3729        let first = encrypted_gcm_element(
3730            "http://www.w3.org/2001/04/xmlenc#Content",
3731            "first",
3732            None,
3733            false,
3734            &key,
3735        );
3736        let second = encrypted_gcm_element(
3737            "http://www.w3.org/2001/04/xmlenc#Content",
3738            "second",
3739            None,
3740            false,
3741            &key,
3742        );
3743        let document = format!(
3744            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"first\">{first}</scope><scope Id=\"second\">{second}</scope></root>"
3745        );
3746        let resolver = SymmetricKeyDecryptor::new(key);
3747        let context = DecryptContext::new(&resolver);
3748        let replaced = context
3749            .decrypt_document_from_start_node(&document, Some("second"))
3750            .expect("ancestor ID must select its encrypted descendant");
3751        assert!(replaced.contains("<scope Id=\"second\">second</scope>"));
3752        assert!(replaced.contains("<scope Id=\"first\"><xenc:EncryptedData"));
3753
3754        assert!(matches!(
3755            context.decrypt_document_from_start_node(&document, Some("missing")),
3756            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "missing"
3757        ));
3758        let duplicate = document.replace("Id=\"second\"", "Id=\"first\"");
3759        assert!(matches!(
3760            context.decrypt_document_from_start_node(&duplicate, Some("first")),
3761            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "first"
3762        ));
3763        let ambiguous = format!(
3764            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"selected\">{first}{second}</scope></root>"
3765        );
3766        assert!(matches!(
3767            context.decrypt_document_from_start_node(&ambiguous, Some("selected")),
3768            Err(XmlEncError::AmbiguousEncryptedData)
3769        ));
3770
3771        let first_replaced = context
3772            .decrypt_first_document_from_start_node(&ambiguous, Some("selected"))
3773            .expect("first-match selection must leave later encrypted descendants untouched");
3774        assert!(first_replaced.contains("<scope Id=\"selected\">first<xenc:EncryptedData"));
3775        let replaced_document =
3776            Document::parse(&first_replaced).expect("first-match output must remain valid XML");
3777        assert_eq!(
3778            replaced_document
3779                .descendants()
3780                .filter(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
3781                .count(),
3782            1
3783        );
3784    }
3785
3786    #[test]
3787    fn rejects_non_xml_or_malformed_document_replacement_plaintext() {
3788        // The document API must not expose binary content or return a document
3789        // made malformed by unauthenticated structure assumptions.
3790        let key = [0x33_u8; 16];
3791        let binary = encrypted_gcm_element("", "binary", None, true, &key);
3792        assert!(matches!(
3793            decrypt_document(&binary, None, &SymmetricKeyDecryptor::new(key)),
3794            Err(XmlEncError::ReplacementRequiresXml)
3795        ));
3796
3797        let malformed = encrypted_gcm_element(
3798            "http://www.w3.org/2001/04/xmlenc#Element",
3799            "<unclosed>",
3800            None,
3801            true,
3802            &key,
3803        );
3804        assert!(matches!(
3805            decrypt_document(&malformed, None, &SymmetricKeyDecryptor::new(key)),
3806            Err(XmlEncError::XmlParse(_))
3807        ));
3808
3809        for invalid_element in ["text-only", "<first/><second/>"] {
3810            let encrypted = encrypted_gcm_element(
3811                "http://www.w3.org/2001/04/xmlenc#Element",
3812                invalid_element,
3813                None,
3814                false,
3815                &key,
3816            );
3817            let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
3818            assert!(
3819                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
3820                "Element plaintext must contain exactly one element: {invalid_element}"
3821            );
3822        }
3823
3824        let content = encrypted_gcm_element(
3825            "http://www.w3.org/2001/04/xmlenc#Content",
3826            "plaintext",
3827            None,
3828            false,
3829            &key,
3830        );
3831        let with_dtd = format!(
3832            "<!DOCTYPE root [<!ATTLIST root Id ID #IMPLIED>]><root xmlns:xenc=\"{XMLENC_NS}\">{content}</root>"
3833        );
3834        assert!(matches!(
3835            decrypt_document(&with_dtd, None, &SymmetricKeyDecryptor::new(key)),
3836            Err(XmlEncError::XmlParse(
3837                crate::xml::dom::ParseError::DtdDetected
3838            ))
3839        ));
3840        let mut policy = crate::policy::DecryptionPolicy::default();
3841        policy.xml.allow_internal_dtd = true;
3842        assert!(
3843            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
3844                .policy(policy)
3845                .decrypt_document(&with_dtd, None)
3846                .expect("explicit internal-DTD opt-in must decrypt")
3847                .contains("plaintext")
3848        );
3849    }
3850
3851    #[test]
3852    fn rejects_plaintext_markup_that_crosses_the_encrypted_region() {
3853        // Parsing only after raw splicing is insufficient: balanced close/reopen
3854        // tags can keep the document valid while moving attacker nodes outside the
3855        // element whose encrypted child is being replaced.
3856        let key = [0x36_u8; 16];
3857        let crossing_markup = "</parent><attacker/><parent>";
3858        for type_uri in [
3859            "http://www.w3.org/2001/04/xmlenc#Content",
3860            "http://www.w3.org/2001/04/xmlenc#Element",
3861        ] {
3862            let encrypted = encrypted_gcm_element(type_uri, crossing_markup, None, false, &key);
3863            let document =
3864                format!("<outer xmlns:xenc=\"{XMLENC_NS}\"><parent>{encrypted}</parent></outer>");
3865            assert!(
3866                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
3867                "{type_uri} plaintext must not escape its replacement boundary"
3868            );
3869        }
3870    }
3871
3872    #[test]
3873    fn document_decryption_applies_byte_and_node_policy_before_parsing() {
3874        // Caller-owned XML must meet the compiled resource policy before the
3875        // initial DOM allocation; reparsed output uses the same node ceiling.
3876        let key = [0x38_u8; 16];
3877        let encrypted = encrypted_gcm_element(
3878            "http://www.w3.org/2001/04/xmlenc#Content",
3879            "plaintext",
3880            None,
3881            false,
3882            &key,
3883        );
3884        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\"><a/>{encrypted}</root>");
3885        let byte_policy = crate::policy::DecryptionPolicy {
3886            resources: crate::policy::ResourcePolicy {
3887                max_xml_document_bytes: document.len() - 1,
3888                ..crate::policy::ResourcePolicy::default()
3889            },
3890            ..crate::policy::DecryptionPolicy::default()
3891        };
3892        assert!(matches!(
3893            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
3894                .policy(byte_policy)
3895                .decrypt_document(&document, None),
3896            Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3897                resource: crate::policy::resource_name::XML_DOCUMENT,
3898                maximum,
3899                actual,
3900            })) if maximum == document.len() - 1 && actual == document.len()
3901        ));
3902
3903        let node_policy = crate::policy::DecryptionPolicy {
3904            resources: crate::policy::ResourcePolicy {
3905                max_xml_nodes: 3,
3906                ..crate::policy::ResourcePolicy::default()
3907            },
3908            ..crate::policy::DecryptionPolicy::default()
3909        };
3910        assert!(matches!(
3911            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
3912                .policy(node_policy)
3913                .decrypt_document(&document, None),
3914            Err(XmlEncError::Policy(
3915                crate::policy::PolicyViolation::ResourceLimit {
3916                    resource: crate::policy::resource_name::XML_NODES,
3917                    maximum: 3,
3918                    actual: 4,
3919                }
3920            ))
3921        ));
3922    }
3923
3924    #[test]
3925    fn decryption_entry_points_enforce_policy_depth() {
3926        // Depth validation precedes EncryptedData selection for both borrowed
3927        // and retained documents, including inputs parsed under wider defaults.
3928        let xml = "<root><child><leaf/></child></root>";
3929        let policy = crate::policy::DecryptionPolicy {
3930            resources: crate::policy::ResourcePolicy {
3931                max_xml_depth: 2,
3932                ..crate::policy::ResourcePolicy::default()
3933            },
3934            ..crate::policy::DecryptionPolicy::default()
3935        };
3936        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
3937        let mut document = XmlDocument::parse(xml).expect("wide retained fixture must parse");
3938
3939        assert!(matches!(
3940            DecryptContext::new(&resolver)
3941                .policy(policy.clone())
3942                .decrypt_document(xml, None),
3943            Err(XmlEncError::Policy(
3944                crate::policy::PolicyViolation::ResourceLimit {
3945                    resource: crate::policy::resource_name::XML_DEPTH,
3946                    maximum: 2,
3947                    actual: 3,
3948                }
3949            ))
3950        ));
3951        assert!(matches!(
3952            DecryptContext::new(&resolver)
3953                .policy(policy)
3954                .decrypt_owned_document(&mut document, None),
3955            Err(XmlEncError::Policy(
3956                crate::policy::PolicyViolation::ResourceLimit {
3957                    resource: crate::policy::resource_name::XML_DEPTH,
3958                    maximum: 2,
3959                    actual: 3,
3960                }
3961            ))
3962        ));
3963    }
3964
3965    #[test]
3966    fn fragment_validation_does_not_charge_its_internal_wrapper_node() {
3967        // The caller's node ceiling applies to input and output XML, not the
3968        // implementation-only element used to prove replacement boundaries.
3969        let key = [0x39_u8; 16];
3970        let plaintext = "<item/>".repeat(20);
3971        let encrypted = encrypted_gcm_element(
3972            "http://www.w3.org/2001/04/xmlenc#Content",
3973            &plaintext,
3974            None,
3975            false,
3976            &key,
3977        );
3978        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
3979        let resolver = SymmetricKeyDecryptor::new(key);
3980        let expected = decrypt_document(&document, None, &resolver)
3981            .expect("unbounded setup decryption must succeed");
3982        let exact_output_nodes = Document::parse(&expected)
3983            .expect("decrypted output must parse")
3984            .descendants()
3985            .count();
3986        let policy = crate::policy::DecryptionPolicy {
3987            resources: crate::policy::ResourcePolicy {
3988                max_xml_nodes: exact_output_nodes,
3989                ..crate::policy::ResourcePolicy::default()
3990            },
3991            ..crate::policy::DecryptionPolicy::default()
3992        };
3993
3994        assert_eq!(
3995            DecryptContext::new(&resolver)
3996                .policy(policy)
3997                .decrypt_document(&document, None)
3998                .expect("temporary wrapper must not consume caller node budget"),
3999            expected
4000        );
4001    }
4002
4003    #[test]
4004    fn owned_decryption_rejects_projected_node_limit_atomically() {
4005        // The owned document may have been parsed under a broader ceiling than
4006        // this operation; expanded plaintext must be bounded before mutation.
4007        let key = [0x3a_u8; 16];
4008        let plaintext = "<item/>".repeat(64);
4009        let encrypted = encrypted_gcm_element(
4010            "http://www.w3.org/2001/04/xmlenc#Content",
4011            &plaintext,
4012            None,
4013            false,
4014            &key,
4015        );
4016        let mut document = XmlDocument::parse(format!(
4017            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
4018        ))
4019        .expect("owned encrypted fixture must parse");
4020        let input_nodes = document.with_view(|view| view.node_count());
4021        let before = document.as_xml().to_owned();
4022        let policy = crate::policy::DecryptionPolicy {
4023            resources: crate::policy::ResourcePolicy {
4024                max_xml_nodes: input_nodes,
4025                ..crate::policy::ResourcePolicy::default()
4026            },
4027            ..crate::policy::DecryptionPolicy::default()
4028        };
4029
4030        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
4031            .policy(policy)
4032            .decrypt_owned_document(&mut document, None)
4033            .expect_err("expanded plaintext must exceed the operation node ceiling");
4034
4035        assert!(matches!(
4036            error,
4037            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4038                resource: crate::policy::resource_name::XML_NODES,
4039                maximum,
4040                ..
4041            }) if maximum == input_nodes
4042        ));
4043        assert_eq!(document.as_xml(), before);
4044        assert_eq!(document.generation(), 0);
4045    }
4046
4047    #[test]
4048    fn owned_decryption_reports_decrypted_depth_as_policy() {
4049        // The encrypted envelope can satisfy the active depth policy while its
4050        // plaintext replacement exceeds it. That rejection must retain the
4051        // typed policy contract and leave the owned document untouched.
4052        let key = [0x3b_u8; 16];
4053        let plaintext = format!("{}value{}", "<nested>".repeat(32), "</nested>".repeat(32));
4054        let encrypted = encrypted_gcm_element(
4055            "http://www.w3.org/2001/04/xmlenc#Element",
4056            &plaintext,
4057            None,
4058            false,
4059            &key,
4060        );
4061        let mut document = XmlDocument::parse(format!(
4062            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
4063        ))
4064        .expect("encrypted fixture must parse");
4065        let input_depth = document.with_view(|view| view.max_depth());
4066        let before = document.as_xml().to_owned();
4067        let policy = crate::policy::DecryptionPolicy {
4068            resources: crate::policy::ResourcePolicy {
4069                max_xml_depth: input_depth,
4070                ..crate::policy::ResourcePolicy::default()
4071            },
4072            ..crate::policy::DecryptionPolicy::default()
4073        };
4074
4075        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
4076            .policy(policy)
4077            .decrypt_owned_document(&mut document, None)
4078            .expect_err("deep plaintext must exceed the active depth policy");
4079
4080        assert!(matches!(
4081            error,
4082            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4083                resource: crate::policy::resource_name::XML_DEPTH,
4084                maximum,
4085                actual,
4086            }) if maximum == input_depth && actual > maximum
4087        ));
4088        assert_eq!(document.as_xml(), before);
4089        assert_eq!(document.generation(), 0);
4090    }
4091
4092    #[test]
4093    fn validates_replacement_plaintext_in_its_namespace_context() {
4094        // Decrypted fragments inherit namespaces from the encrypted node's
4095        // ancestors, so boundary validation must occur inside the source document.
4096        let key = [0x37_u8; 16];
4097        let encrypted = encrypted_gcm_element(
4098            "http://www.w3.org/2001/04/xmlenc#Content",
4099            "<shared:child/>",
4100            None,
4101            false,
4102            &key,
4103        );
4104        let document = format!(
4105            "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\">{encrypted}</root>"
4106        );
4107        let decrypted = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
4108            .expect("inherited namespace prefixes must remain valid");
4109        assert_eq!(
4110            decrypted,
4111            format!(
4112                "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\"><shared:child/></root>"
4113            )
4114        );
4115    }
4116
4117    fn encrypted_gcm_element(
4118        type_uri: &str,
4119        plaintext: &str,
4120        id: Option<&str>,
4121        declare_namespace: bool,
4122        key: &[u8; 16],
4123    ) -> String {
4124        let nonce = [0x44_u8; 12];
4125        let mut ciphertext = plaintext.as_bytes().to_vec();
4126        Aes128Gcm::new_from_slice(key)
4127            .expect("fixed content key length")
4128            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
4129            .expect("test encryption must succeed");
4130        let mut wire = nonce.to_vec();
4131        wire.extend_from_slice(&ciphertext);
4132        let namespace = declare_namespace
4133            .then_some(format!(" xmlns:xenc=\"{XMLENC_NS}\""))
4134            .unwrap_or_default();
4135        let data_type = (!type_uri.is_empty())
4136            .then_some(format!(" Type=\"{type_uri}\""))
4137            .unwrap_or_default();
4138        let id = id
4139            .map(|value| format!(" Id=\"{value}\""))
4140            .unwrap_or_default();
4141        format!(
4142            "<xenc:EncryptedData{namespace}{data_type}{id}><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
4143            STANDARD.encode(wire)
4144        )
4145    }
4146}