Skip to main content

xml_sec/xmlenc/
decrypt.rs

1//! XMLEnc decryption entry point and key resolvers.
2
3use std::{borrow::Cow, fmt, sync::Arc};
4
5#[cfg(test)]
6use crate::xml::dom::Document;
7#[cfg(test)]
8use base64::Engine as _;
9
10use crate::document::{DocumentParseSettings, XmlDocument, XmlParseWorkBudget};
11use crate::operation::{
12    OperationExecutionContext, OperationNodeId, OperationNodeKind, OperationPlanError,
13    OperationStage,
14};
15use rsa::RsaPrivateKey;
16
17use super::parse::validate_encrypted_data_metadata;
18use super::types::{MAX_CIPHER_VALUE_BASE64_LEN, XMLENC_NS, validate_ciphertext_framing};
19use super::{
20    DataEncryptionAlgorithm, DecryptedContent, EncryptedData, EncryptedDataType, EncryptedKey,
21    KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm, RsaOaepParameters, XmlEncError,
22    map_document_error,
23};
24
25#[cfg(test)]
26use super::parse_encrypted_data;
27
28/// Aggregate key-candidate work allowance for one cryptographic operation.
29///
30/// Resolver implementations must consume one unit before each key lookup or
31/// unwrap attempt. A single budget is shared across direct and recipient keys.
32#[derive(Debug)]
33pub struct KeyCandidateBudget {
34    maximum: usize,
35    remaining: usize,
36    pub(super) key_establishment: super::key_establishment_budget::KeyEstablishmentUsage,
37}
38
39impl KeyCandidateBudget {
40    /// Create the fixed implementation-wide budget for one operation.
41    pub fn for_operation() -> Self {
42        Self::with_limit(crate::hard_limits::KEY_CANDIDATE_CEILING)
43    }
44
45    /// Create a budget from a validated operation policy ceiling.
46    pub fn with_limit(maximum: usize) -> Self {
47        Self {
48            maximum,
49            remaining: maximum,
50            key_establishment: Default::default(),
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    /// Derive a key against the caller's operation snapshot, retaining all KDF
60    /// reservations in this same budget across nested sources and retries.
61    /// Resolvers must forward the snapshot received by their policy-aware entry
62    /// point; they must never choose a profile from document content.
63    pub fn derive_key(
64        &mut self,
65        provider: &dyn crate::provider::CryptoProvider,
66        policy: &crate::policy::DecryptionPolicy,
67        parameters: &crate::provider::KdfParameters<'_>,
68        secret: &[u8],
69    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
70        self.key_establishment
71            .derive_key(&policy.key_establishment, provider, parameters, secret)
72    }
73
74    /// Establish a shared secret and derive its consuming key after reserving
75    /// both outputs and checking both permissions before either provider call.
76    /// Candidate allowance includes scalar multiplication and derivation.
77    pub fn agree_and_derive(
78        &mut self,
79        provider: &dyn crate::provider::CryptoProvider,
80        policy: &crate::policy::DecryptionPolicy,
81        key: &dyn crate::provider::KeyAgreementKey,
82        agreement: &crate::provider::KeyAgreementParameters<'_>,
83        parameters: &crate::provider::KdfParameters<'_>,
84    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
85        self.consume(2)?;
86        self.key_establishment.agree_and_derive(
87            &policy.key_establishment,
88            provider,
89            key,
90            agreement,
91            parameters,
92        )
93    }
94
95    /// Charge attempted candidate work before performing it.
96    pub fn consume(&mut self, count: usize) -> Result<(), XmlEncError> {
97        self.require_available(count)?;
98        self.remaining -= count;
99        Ok(())
100    }
101
102    pub(super) fn require_available(&self, count: usize) -> Result<(), XmlEncError> {
103        if count > self.remaining {
104            return Err(crate::policy::PolicyViolation::ResourceLimit {
105                resource: crate::policy::resource_name::KEY_CANDIDATES,
106                maximum: self.maximum,
107                actual: self
108                    .maximum
109                    .saturating_sub(self.remaining)
110                    .saturating_add(count),
111            }
112            .into());
113        }
114        Ok(())
115    }
116
117    fn account_returned_candidates(
118        &mut self,
119        remaining_before: usize,
120        returned: usize,
121    ) -> Result<(), XmlEncError> {
122        let resolver_charged = remaining_before.saturating_sub(self.remaining);
123        self.consume(returned.saturating_sub(resolver_charged))
124    }
125}
126
127/// Supplies a content-encryption key for parsed XMLEnc data.
128/// A source resolved for a wrapping algorithm. Its output is never implicitly
129/// treated as a CEK or exported from a non-exportable provider handle.
130pub enum KeyEncryptionKeySource<'a> {
131    /// Experimental KEM producing this operation's wrapping key.
132    Encapsulation(&'a crate::key_establishment::EncapsulationMechanism),
133    /// Application-owned key material without transported establishment hints.
134    Direct,
135    /// A key transporting this operation's wrapping key.
136    Encrypted(&'a EncryptedKey),
137    /// Agreement producing this operation's wrapping key.
138    Agreement(&'a super::AgreementMethod),
139    /// Derivation producing this operation's wrapping key.
140    Derived(&'a super::DerivedKey),
141}
142
143/// Supplies keys for an XMLEnc operation under its immutable policy snapshot.
144pub trait DecryptionKeyResolver {
145    /// Resolve an experimental KEM source under the operation snapshot. Raw
146    /// content-key resolvers refuse by default rather than bypassing the KEM.
147    fn resolve_encapsulation_content_keys_with_policy(
148        &self,
149        _provider: &dyn crate::provider::CryptoProvider,
150        _algorithm: DataEncryptionAlgorithm,
151        _descriptor: &crate::key_establishment::EncapsulationMechanism,
152        _policy: &crate::policy::DecryptionPolicy,
153        _budget: &mut KeyCandidateBudget,
154    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
155        Err(XmlEncError::KeyNotFound)
156    }
157    /// Resolve an actual KEK purpose; default refusal prevents incorrectly
158    /// adapting a content algorithm merely because its key width happens to fit.
159    fn resolve_key_encryption_keys_with_policy(
160        &self,
161        _provider: &dyn crate::provider::CryptoProvider,
162        _algorithm: KeyWrapAlgorithm,
163        _source: KeyEncryptionKeySource<'_>,
164        _policy: &crate::policy::DecryptionPolicy,
165        _budget: &mut KeyCandidateBudget,
166    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
167        Err(XmlEncError::KeyNotFound)
168    }
169    /// Resolve an agreement under the operation snapshot and shared allowance.
170    /// Default refusal prevents a raw content key from bypassing AgreementMethod.
171    fn resolve_agreement_content_keys_with_policy(
172        &self,
173        _provider: &dyn crate::provider::CryptoProvider,
174        _algorithm: DataEncryptionAlgorithm,
175        _descriptor: &super::AgreementMethod,
176        _policy: &crate::policy::DecryptionPolicy,
177        _budget: &mut KeyCandidateBudget,
178    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
179        Err(XmlEncError::KeyNotFound)
180    }
181    /// Resolve a transported derivation descriptor using the same immutable
182    /// operation policy and cumulative candidate/KDF allowance. The default
183    /// refuses it; a raw content-key resolver must not bypass derivation.
184    fn resolve_derived_content_keys_with_policy(
185        &self,
186        _provider: &dyn crate::provider::CryptoProvider,
187        _algorithm: DataEncryptionAlgorithm,
188        _descriptor: &super::DerivedKey,
189        _policy: &crate::policy::DecryptionPolicy,
190        _budget: &mut KeyCandidateBudget,
191    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
192        Err(XmlEncError::KeyNotFound)
193    }
194    /// Resolve operation candidates without dropping implicit-rejection state.
195    /// Wrappers around recipient resolvers must forward this method; converting
196    /// recovered candidates to raw bytes loses the final content acceptance gate.
197    fn resolve_content_keys_with_policy(
198        &self,
199        provider: &dyn crate::provider::CryptoProvider,
200        algorithm: DataEncryptionAlgorithm,
201        encrypted_key: Option<&EncryptedKey>,
202        policy: &crate::policy::DecryptionPolicy,
203        budget: &mut KeyCandidateBudget,
204    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
205        self.resolve_key_candidates_with_policy(provider, algorithm, encrypted_key, policy, budget)
206            .map(|keys| {
207                keys.into_iter()
208                    .map(crate::provider::RecoveredContentKey::confirmed)
209                    .collect()
210            })
211    }
212    /// Resolve under the operation's immutable snapshot. RSA resolvers enforce
213    /// `rsa_keys` before provider recovery; wrappers must forward this snapshot.
214    ///
215    /// The default supports direct content keys only and returns
216    /// [`XmlEncError::KeyNotFound`] for recipients without invoking legacy
217    /// resolution. Recipient resolvers must override this method: recovered
218    /// symmetric bytes cannot prove the original key met the operation policy.
219    fn resolve_key_candidates_with_policy(
220        &self,
221        provider: &dyn crate::provider::CryptoProvider,
222        algorithm: DataEncryptionAlgorithm,
223        encrypted_key: Option<&EncryptedKey>,
224        policy: &crate::policy::DecryptionPolicy,
225        budget: &mut KeyCandidateBudget,
226    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
227        policy.validate()?;
228        if encrypted_key.is_some() {
229            return Err(XmlEncError::KeyNotFound);
230        }
231        self.resolve_key_candidates(provider, algorithm, None, budget)
232    }
233
234    /// Resolve the symmetric key for `algorithm`, optionally unwrapping `encrypted_key`.
235    fn resolve_key(
236        &self,
237        provider: &dyn crate::provider::CryptoProvider,
238        algorithm: DataEncryptionAlgorithm,
239        encrypted_key: Option<&EncryptedKey>,
240    ) -> Result<Vec<u8>, XmlEncError>;
241
242    /// Resolve ordered candidate keys for one prepared decryption operation.
243    ///
244    /// The default preserves single-key resolver behavior. Key rings override
245    /// this method so parsing, policy validation, and ciphertext decoding occur
246    /// once while only authenticated primitive decryption is retried. Overrides
247    /// must consume the shared budget before every lookup or unwrap attempt.
248    /// The context also accounts for any returned candidates an implementation
249    /// did not explicitly charge. Returning [`XmlEncError::Policy`] rejects the
250    /// complete operation and never advances to another key source; use a
251    /// candidate-local error such as [`XmlEncError::KeyNotFound`] when later
252    /// ordered sources are still eligible.
253    fn resolve_key_candidates(
254        &self,
255        provider: &dyn crate::provider::CryptoProvider,
256        algorithm: DataEncryptionAlgorithm,
257        encrypted_key: Option<&EncryptedKey>,
258        budget: &mut KeyCandidateBudget,
259    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
260        budget.consume(1)?;
261        self.resolve_key(provider, algorithm, encrypted_key)
262            .map(|key| vec![key])
263    }
264}
265
266/// Derive a direct content key or wrapping key from caller-owned secret
267/// material and parsed KDF parameters. The operation, not this request object,
268/// supplies permission and cumulative allowance. Secret material is borrowed.
269pub struct DerivedKeyDecryptor<'a> {
270    method: &'a super::KeyDerivationMethod,
271    input: DerivedKeyInput<'a>,
272    purpose: DerivedKeyPurpose,
273    master_key_name: Option<&'a str>,
274}
275
276/// Application-owned key-establishment input; private handles and secret bytes
277/// are borrowed, and document data cannot replace their provenance.
278pub enum DerivedKeyInput<'a> {
279    /// Caller-selected secret or password octets.
280    Secret(&'a [u8]),
281    /// Caller-selected private handle and peer parameters for agreement.
282    Agreement {
283        /// Opaque private key belonging to the selected provider.
284        key: &'a dyn crate::provider::KeyAgreementKey,
285        /// Encoded public peer and the requested agreement algorithm.
286        parameters: crate::provider::KeyAgreementParameters<'a>,
287    },
288}
289
290enum DerivedKeyPurpose {
291    Content(DataEncryptionAlgorithm),
292    Wrapping(KeyWrapAlgorithm),
293}
294
295impl<'a> DerivedKeyDecryptor<'a> {
296    /// Bind explicitly selected KDF parameters to an application-owned secret.
297    /// This does not discover keys or permit algorithms on the caller's behalf.
298    pub fn content(
299        method: &'a super::KeyDerivationMethod,
300        input: DerivedKeyInput<'a>,
301        algorithm: DataEncryptionAlgorithm,
302    ) -> Self {
303        Self {
304            method,
305            input,
306            purpose: DerivedKeyPurpose::Content(algorithm),
307            master_key_name: None,
308        }
309    }
310
311    /// Bind a wrapping-key derivation rather than a content-key derivation.
312    /// Purpose is application context, never inferred from untrusted XML widths.
313    pub fn wrapping(
314        method: &'a super::KeyDerivationMethod,
315        input: DerivedKeyInput<'a>,
316        algorithm: KeyWrapAlgorithm,
317    ) -> Self {
318        Self {
319            method,
320            input,
321            purpose: DerivedKeyPurpose::Wrapping(algorithm),
322            master_key_name: None,
323        }
324    }
325
326    /// Bind XML MasterKeyName to the caller-selected material. A transported
327    /// name never selects or substitutes private material by itself.
328    pub fn master_key_name(mut self, name: &'a str) -> Self {
329        self.master_key_name = Some(name);
330        self
331    }
332
333    fn derive(
334        &self,
335        provider: &dyn crate::provider::CryptoProvider,
336        width: usize,
337        policy: &crate::policy::DecryptionPolicy,
338        budget: &mut KeyCandidateBudget,
339    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
340        let parameters = self.method.parameters(width)?;
341        match &self.input {
342            DerivedKeyInput::Secret(secret) => {
343                budget.consume(1)?;
344                budget.derive_key(provider, policy, &parameters, secret)
345            }
346            DerivedKeyInput::Agreement {
347                key,
348                parameters: agreement,
349            } => budget.agree_and_derive(provider, policy, *key, agreement, &parameters),
350        }
351    }
352}
353
354impl fmt::Debug for DerivedKeyDecryptor<'_> {
355    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
356        formatter
357            .debug_struct("DerivedKeyDecryptor")
358            .finish_non_exhaustive()
359    }
360}
361
362impl DecryptionKeyResolver for DerivedKeyDecryptor<'_> {
363    fn resolve_key_encryption_keys_with_policy(
364        &self,
365        provider: &dyn crate::provider::CryptoProvider,
366        algorithm: KeyWrapAlgorithm,
367        source: KeyEncryptionKeySource<'_>,
368        policy: &crate::policy::DecryptionPolicy,
369        budget: &mut KeyCandidateBudget,
370    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
371        if !matches!(self.purpose, DerivedKeyPurpose::Wrapping(expected) if expected == algorithm) {
372            return Err(XmlEncError::KeyNotFound);
373        }
374        match source {
375            KeyEncryptionKeySource::Derived(descriptor)
376                if descriptor
377                    .method
378                    .as_ref()
379                    .is_none_or(|method| method == self.method)
380                    && descriptor
381                        .master_key_name
382                        .as_deref()
383                        .is_none_or(|name| Some(name) == self.master_key_name) => {}
384            KeyEncryptionKeySource::Agreement(descriptor) => {
385                let DerivedKeyInput::Agreement { parameters, .. } = &self.input else {
386                    return Err(XmlEncError::KeyNotFound);
387                };
388                if descriptor.algorithm.uri() != parameters.algorithm
389                    || descriptor
390                        .method
391                        .as_ref()
392                        .is_some_and(|method| method != self.method)
393                    || descriptor.originator.is_some()
394                    || descriptor.recipient.is_some()
395                    || !descriptor.nonce.is_empty()
396                    || descriptor.legacy_digest.is_some()
397                {
398                    return Err(XmlEncError::KeyNotFound);
399                }
400            }
401            _ => return Err(XmlEncError::KeyNotFound),
402        }
403        budget.require_available(match self.input {
404            DerivedKeyInput::Secret(_) => 2,
405            DerivedKeyInput::Agreement { .. } => 3,
406        })?;
407        let mut key = self.derive(provider, algorithm.key_len(), policy, budget)?;
408        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
409            core::mem::take(&mut *key),
410        )])
411    }
412    fn resolve_agreement_content_keys_with_policy(
413        &self,
414        provider: &dyn crate::provider::CryptoProvider,
415        algorithm: DataEncryptionAlgorithm,
416        descriptor: &super::AgreementMethod,
417        policy: &crate::policy::DecryptionPolicy,
418        budget: &mut KeyCandidateBudget,
419    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
420        let DerivedKeyInput::Agreement { parameters, .. } = &self.input else {
421            return Err(XmlEncError::KeyNotFound);
422        };
423        // This request binds both parties out of band. A transported selector
424        // requires the full expected-descriptor resolver, not silent ignoring.
425        if descriptor.algorithm.uri() != parameters.algorithm
426            || descriptor
427                .method
428                .as_ref()
429                .is_some_and(|method| method != self.method)
430            || descriptor.originator.is_some()
431            || descriptor.recipient.is_some()
432            || !descriptor.nonce.is_empty()
433            || descriptor.legacy_digest.is_some()
434        {
435            return Err(XmlEncError::KeyNotFound);
436        }
437        self.resolve_content_keys_with_policy(provider, algorithm, None, policy, budget)
438    }
439    fn resolve_derived_content_keys_with_policy(
440        &self,
441        provider: &dyn crate::provider::CryptoProvider,
442        algorithm: DataEncryptionAlgorithm,
443        descriptor: &super::DerivedKey,
444        policy: &crate::policy::DecryptionPolicy,
445        budget: &mut KeyCandidateBudget,
446    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
447        // XMLEnc 1.1 §5.4.1 leaves AlgorithmID/party validation to the
448        // application. Match its entire expected descriptor BEFORE dispatch,
449        // retaining bit boundaries and preventing document-selected context.
450        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ConcatKDF
451        if descriptor
452            .method
453            .as_ref()
454            .is_some_and(|method| method != self.method)
455            || descriptor
456                .master_key_name
457                .as_deref()
458                .is_some_and(|name| Some(name) != self.master_key_name)
459        {
460            return Err(XmlEncError::KeyNotFound);
461        }
462        self.resolve_content_keys_with_policy(provider, algorithm, None, policy, budget)
463    }
464    fn resolve_key(
465        &self,
466        _provider: &dyn crate::provider::CryptoProvider,
467        _algorithm: DataEncryptionAlgorithm,
468        _encrypted_key: Option<&EncryptedKey>,
469    ) -> Result<Vec<u8>, XmlEncError> {
470        // A policy-free legacy callback must not create a fresh KDF allowance.
471        Err(XmlEncError::KeyNotFound)
472    }
473
474    fn resolve_content_keys_with_policy(
475        &self,
476        provider: &dyn crate::provider::CryptoProvider,
477        algorithm: DataEncryptionAlgorithm,
478        encrypted_key: Option<&EncryptedKey>,
479        policy: &crate::policy::DecryptionPolicy,
480        budget: &mut KeyCandidateBudget,
481    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
482        policy.validate()?;
483        match (&self.purpose, encrypted_key) {
484            (DerivedKeyPurpose::Content(_), Some(_)) | (DerivedKeyPurpose::Wrapping(_), None) => {
485                return Err(XmlEncError::KeyNotFound);
486            }
487            (DerivedKeyPurpose::Content(expected), None) if *expected != algorithm => {
488                return Err(XmlEncError::KeyNotFound);
489            }
490            _ => {}
491        }
492        let wrap = match encrypted_key {
493            None => None,
494            Some(encrypted_key) => {
495                validate_encrypted_key_policy(encrypted_key, policy)?;
496                encrypted_key.encryption_method.validate_structure()?;
497                Some(KeyWrapAlgorithm::from_uri(
498                    &encrypted_key.encryption_method.algorithm,
499                )?)
500            }
501        };
502        if let (DerivedKeyPurpose::Wrapping(expected), Some(actual)) = (&self.purpose, wrap)
503            && *expected != actual
504        {
505            return Err(XmlEncError::KeyNotFound);
506        }
507        let width = match wrap {
508            Some(wrap) => wrap.key_len(),
509            None => algorithm.key_len(),
510        };
511        // Check the complete attempt before expensive password or scalar work.
512        // Individual stages still charge themselves, retaining failed work and
513        // forwarding this same budget into the existing unwrap implementation.
514        let derivation_attempts = match self.input {
515            DerivedKeyInput::Secret(_) => 1,
516            DerivedKeyInput::Agreement { .. } => 2,
517        };
518        let required = if wrap.is_some() {
519            derivation_attempts + 1
520        } else {
521            derivation_attempts
522        };
523        budget.require_available(required)?;
524        let mut key = self.derive(provider, width, policy, budget)?;
525        if let Some(wrap) = wrap {
526            // Reuse the existing policy-aware recipient path rather than keeping
527            // a second implementation of wrap-family, framing and key checks.
528            return KekDecryptor::borrowed_with_kind(&key, wrap.key_kind())
529                .resolve_content_keys_with_policy(
530                    provider,
531                    algorithm,
532                    encrypted_key,
533                    policy,
534                    budget,
535                );
536        }
537        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
538            core::mem::take(&mut *key),
539        )])
540    }
541}
542
543/// Direct non-exportable content key, with no private-key or symmetric-byte export.
544pub struct OpaqueContentKeyResolver {
545    key: std::sync::Arc<dyn crate::provider::ContentDecryptionKey>,
546}
547
548impl OpaqueContentKeyResolver {
549    /// Retain a caller-selected handle; policy remains in the operation context.
550    pub fn new(key: std::sync::Arc<dyn crate::provider::ContentDecryptionKey>) -> Self {
551        Self { key }
552    }
553}
554
555impl DecryptionKeyResolver for OpaqueContentKeyResolver {
556    fn resolve_key(
557        &self,
558        _provider: &dyn crate::provider::CryptoProvider,
559        _algorithm: DataEncryptionAlgorithm,
560        _encrypted_key: Option<&EncryptedKey>,
561    ) -> Result<Vec<u8>, XmlEncError> {
562        Err(crate::provider::ProviderError::KeyNotExportable.into())
563    }
564    fn resolve_content_keys_with_policy(
565        &self,
566        provider: &dyn crate::provider::CryptoProvider,
567        algorithm: DataEncryptionAlgorithm,
568        encrypted_key: Option<&EncryptedKey>,
569        policy: &crate::policy::DecryptionPolicy,
570        budget: &mut KeyCandidateBudget,
571    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
572        policy.validate()?;
573        if encrypted_key.is_some() {
574            return Err(XmlEncError::KeyNotFound);
575        }
576        budget.consume(1)?;
577        provider.require_capability(crate::provider::ProviderCapability::Decrypt(algorithm))?;
578        validate_content_key_len(algorithm, self.key.key_len())?;
579        Ok(vec![crate::provider::RecoveredContentKey::opaque(
580            self.key.clone(),
581        )])
582    }
583}
584
585/// Opaque KEK recipient resolver that retains the unwrapped CEK in its provider.
586pub struct OpaqueKekDecryptor {
587    key: std::sync::Arc<dyn crate::provider::KeyUnwrappingKey>,
588}
589
590impl OpaqueKekDecryptor {
591    /// Select a caller-owned KEK without adding a second policy surface.
592    pub fn new(key: std::sync::Arc<dyn crate::provider::KeyUnwrappingKey>) -> Self {
593        Self { key }
594    }
595}
596
597impl DecryptionKeyResolver for OpaqueKekDecryptor {
598    fn resolve_key(
599        &self,
600        _provider: &dyn crate::provider::CryptoProvider,
601        _algorithm: DataEncryptionAlgorithm,
602        _encrypted_key: Option<&EncryptedKey>,
603    ) -> Result<Vec<u8>, XmlEncError> {
604        Err(crate::provider::ProviderError::KeyNotExportable.into())
605    }
606    fn resolve_content_keys_with_policy(
607        &self,
608        provider: &dyn crate::provider::CryptoProvider,
609        algorithm: DataEncryptionAlgorithm,
610        encrypted_key: Option<&EncryptedKey>,
611        policy: &crate::policy::DecryptionPolicy,
612        budget: &mut KeyCandidateBudget,
613    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
614        policy.validate()?;
615        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
616        validate_encrypted_key_policy(encrypted_key, policy)?;
617        encrypted_key.encryption_method.validate_structure()?;
618        let wrap = KeyWrapAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
619        provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(wrap))?;
620        budget.consume(1)?;
621        let wrapped = encrypted_key.cipher_data.octets()?;
622        if wrapped.len() != algorithm.key_len() + wrap.overhead() {
623            return Err(XmlEncError::InvalidWrappedKeyLength {
624                expected: algorithm.key_len() + wrap.overhead(),
625                actual: wrapped.len(),
626            });
627        }
628        let key = provider.unwrap_content_key(self.key.as_ref(), wrap, algorithm, &wrapped)?;
629        validate_content_key_len(algorithm, key.key_len())?;
630        Ok(vec![key])
631    }
632}
633
634/// Caller-owned target selection for document decryption.
635#[derive(Debug, Clone, Copy, Default)]
636pub struct DocumentDecryptionOptions<'a> {
637    /// Select a specific `EncryptedData` by its `Id` attribute.
638    pub encrypted_data_id: Option<&'a str>,
639}
640
641/// Immutable XMLEnc decryption operation context.
642pub struct DecryptContext<'a> {
643    resolver: &'a dyn DecryptionKeyResolver,
644    policy: crate::policy::DecryptionPolicy,
645    provider: &'a dyn crate::provider::CryptoProvider,
646    xml_backend: crate::XmlBackend,
647    id_attributes: &'a [crate::IdAttributeRegistration],
648    external_resources: Option<&'a std::collections::HashMap<String, Vec<u8>>>,
649}
650
651struct DecryptionPlanNodes {
652    document: OperationNodeId,
653    ciphertext: OperationNodeId,
654    key: OperationNodeId,
655    crypto: OperationNodeId,
656    evidence: OperationNodeId,
657    mutation: Option<OperationNodeId>,
658}
659
660struct DecryptionOperationBudgets {
661    key_candidates: std::cell::RefCell<KeyCandidateBudget>,
662    xml_parse: XmlParseWorkBudget,
663}
664
665struct DecryptionReferenceGate<'a> {
666    operation:
667        &'a OperationExecutionContext<crate::policy::DecryptionPolicy, DecryptionOperationBudgets>,
668    document: OperationNodeId,
669    ciphertext: OperationNodeId,
670}
671
672impl super::cipher_reference::ReferenceOperationGate for DecryptionReferenceGate<'_> {
673    fn run_resource(
674        &self,
675        identity: &crate::operation::OperationResourceIdentity,
676        action: &mut dyn FnMut() -> Result<Vec<u8>, XmlEncError>,
677    ) -> Result<Vec<u8>, XmlEncError> {
678        self.operation
679            .run_discovered_resource(self.document, self.ciphertext, identity, action)
680    }
681}
682
683impl DecryptionOperationBudgets {
684    fn from_policy(policy: &crate::policy::DecryptionPolicy) -> Self {
685        Self {
686            key_candidates: std::cell::RefCell::new(KeyCandidateBudget::with_limit(
687                policy.resources.max_key_candidates,
688            )),
689            xml_parse: XmlParseWorkBudget::from_resources(&policy.resources),
690        }
691    }
692}
693
694fn compile_decryption_plan(
695    operation: &mut OperationExecutionContext<
696        crate::policy::DecryptionPolicy,
697        DecryptionOperationBudgets,
698    >,
699    mutation: bool,
700) -> Result<DecryptionPlanNodes, XmlEncError> {
701    let document = operation.add_node(OperationNodeKind::Document, OperationStage::Parse, None);
702    let ciphertext =
703        operation.add_node(OperationNodeKind::Ciphertext, OperationStage::Resolve, None);
704    operation
705        .add_dependency(ciphertext, document)
706        .map_err(map_decryption_plan_error)?;
707    let key = operation.add_node(
708        OperationNodeKind::Key { index: 0 },
709        OperationStage::Resolve,
710        None,
711    );
712    operation
713        .add_dependency(key, ciphertext)
714        .map_err(map_decryption_plan_error)?;
715    let crypto = operation.add_node(OperationNodeKind::Crypto, OperationStage::Crypto, None);
716    operation
717        .add_dependency(crypto, key)
718        .map_err(map_decryption_plan_error)?;
719    let evidence = operation.add_node(OperationNodeKind::Evidence, OperationStage::Evidence, None);
720    operation
721        .add_dependency(evidence, crypto)
722        .map_err(map_decryption_plan_error)?;
723    let mutation = mutation.then(|| {
724        let node = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
725        operation
726            .add_dependency(node, evidence)
727            .expect("evidence-to-mutation stage order is fixed");
728        node
729    });
730    operation.compile().map_err(map_decryption_plan_error)?;
731    Ok(DecryptionPlanNodes {
732        document,
733        ciphertext,
734        key,
735        crypto,
736        evidence,
737        mutation,
738    })
739}
740
741struct ProcessedDecryption<T> {
742    output: T,
743    operation:
744        OperationExecutionContext<crate::policy::DecryptionPolicy, DecryptionOperationBudgets>,
745    mutation: Option<OperationNodeId>,
746}
747
748#[derive(Clone, Copy)]
749enum DecryptionInput<'a> {
750    Xml {
751        source: &'a str,
752        node_start: Option<usize>,
753    },
754    Parsed(&'a EncryptedData),
755}
756
757fn cipher_reference_node<'doc, 'input>(
758    node: crate::xml::dom::Node<'doc, 'input>,
759) -> Result<crate::xml::dom::Node<'doc, 'input>, XmlEncError> {
760    node.children()
761        .find(|child| child.has_tag_name((XMLENC_NS, "CipherData")))
762        .and_then(|child| {
763            child
764                .children()
765                .find(|node| node.has_tag_name((XMLENC_NS, "CipherReference")))
766        })
767        .ok_or(XmlEncError::MissingRequired("CipherReference"))
768}
769
770fn selected_encrypted_node<'doc, 'input>(
771    document: &'doc crate::xml::dom::Document<'input>,
772    start: Option<usize>,
773) -> Result<crate::xml::dom::Node<'doc, 'input>, XmlEncError> {
774    match start {
775        Some(start) => document
776            .descendants()
777            .find(|node| node.is_element() && node.range().start == start)
778            .ok_or_else(|| XmlEncError::OperationPlan("selected encrypted node is absent".into())),
779        None => Ok(document.root_element()),
780    }
781}
782
783fn map_decryption_plan_error(error: OperationPlanError) -> XmlEncError {
784    XmlEncError::from(error)
785}
786
787impl<'a> DecryptContext<'a> {
788    /// Create a context with the default decryption policy and RustCrypto provider.
789    pub fn new(resolver: &'a dyn DecryptionKeyResolver) -> Self {
790        Self {
791            resolver,
792            policy: crate::policy::DecryptionPolicy::default(),
793            provider: crate::provider::default_provider(),
794            xml_backend: crate::XmlBackend::default(),
795            id_attributes: &[],
796            external_resources: None,
797        }
798    }
799
800    /// Replace the complete immutable decryption policy snapshot.
801    pub fn policy(mut self, policy: crate::policy::DecryptionPolicy) -> Self {
802        self.policy = policy;
803        self
804    }
805
806    /// Select the cryptographic provider for this decryption operation.
807    pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
808        self.provider = provider;
809        self
810    }
811
812    /// Select the compiled XML parser backend for this decryption operation.
813    pub fn xml_backend(mut self, backend: crate::XmlBackend) -> Self {
814        self.xml_backend = backend;
815        self
816    }
817
818    fn document_parse_settings(&self) -> DocumentParseSettings {
819        DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources)
820            .with_backend(self.xml_backend)
821    }
822
823    /// Add caller-declared ID attributes for operation start-node lookup.
824    pub fn id_attributes(mut self, registrations: &'a [crate::IdAttributeRegistration]) -> Self {
825        self.id_attributes = registrations;
826        self
827    }
828
829    /// Supply immutable external ciphertext resources. No implicit I/O occurs.
830    pub fn external_resources(
831        mut self,
832        resources: &'a std::collections::HashMap<String, Vec<u8>>,
833    ) -> Self {
834        self.external_resources = Some(resources);
835        self
836    }
837
838    /// Parse and decrypt a standalone `EncryptedData` XML fragment.
839    pub fn decrypt(&self, xml: &str) -> Result<DecryptedContent, XmlEncError> {
840        let budgets = DecryptionOperationBudgets::from_policy(&self.policy);
841        self.process_decryption_input(
842            DecryptionInput::Xml {
843                source: xml,
844                node_start: None,
845            },
846            None,
847            false,
848            budgets,
849            |content, _| Ok(content),
850        )
851        .map(|processed| processed.output)
852    }
853
854    /// Decrypt an already parsed `EncryptedData` value.
855    pub fn decrypt_data(&self, encrypted: &EncryptedData) -> Result<DecryptedContent, XmlEncError> {
856        self.process_decryption_candidates(
857            encrypted,
858            None,
859            false,
860            DecryptionOperationBudgets::from_policy(&self.policy),
861            |content, _| Ok(content),
862        )
863        .map(|processed| processed.output)
864    }
865
866    fn process_decryption_candidates<T>(
867        &self,
868        encrypted: &EncryptedData,
869        document_binding: Option<(crate::DocumentIdentity, u64)>,
870        mutates_document: bool,
871        budgets: DecryptionOperationBudgets,
872        accept: impl FnMut(DecryptedContent, &XmlParseWorkBudget) -> Result<T, XmlEncError>,
873    ) -> Result<ProcessedDecryption<T>, XmlEncError> {
874        self.process_decryption_input(
875            DecryptionInput::Parsed(encrypted),
876            document_binding,
877            mutates_document,
878            budgets,
879            accept,
880        )
881    }
882
883    fn process_decryption_input<T>(
884        &self,
885        input: DecryptionInput<'_>,
886        document_binding: Option<(crate::DocumentIdentity, u64)>,
887        mutates_document: bool,
888        budgets: DecryptionOperationBudgets,
889        mut accept: impl FnMut(DecryptedContent, &XmlParseWorkBudget) -> Result<T, XmlEncError>,
890    ) -> Result<ProcessedDecryption<T>, XmlEncError> {
891        let mut operation =
892            OperationExecutionContext::new(self.policy.clone(), budgets, document_binding);
893        operation.policy().resources.validate()?;
894        let plan_nodes = compile_decryption_plan(&mut operation, mutates_document)?;
895        let source_document = operation.run(plan_nodes.document, || match input {
896            DecryptionInput::Xml { source, node_start } => {
897                let settings = self.document_parse_settings();
898                let document = crate::document::parse_borrowed_with_settings_and_budget(
899                    source,
900                    settings,
901                    Some(&operation.budgets().xml_parse),
902                )
903                .map_err(|error| map_document_error(error, settings))?;
904                Ok::<_, XmlEncError>(Some((document, node_start)))
905            }
906            DecryptionInput::Parsed(_) => Ok(None),
907        })?;
908        let reference_gate = DecryptionReferenceGate {
909            operation: &operation,
910            document: plan_nodes.document,
911            ciphertext: plan_nodes.ciphertext,
912        };
913        let reference_context = super::CipherReferenceContext::new(
914            operation.policy(),
915            self.external_resources,
916            self.xml_backend,
917            self.id_attributes,
918        )?
919        .with_operation(&reference_gate);
920        let parsed = operation.run(plan_nodes.ciphertext, || {
921            // The graph gates parser work as well as cryptographic callbacks.
922            // Typed inputs stay borrowed; XML inputs consume the same operation
923            // parse allowance used later for plaintext and controlled mutation.
924            let (mut encrypted, origins) = match input {
925                DecryptionInput::Xml { .. } => {
926                    let (document, node_start) =
927                        source_document.as_ref().expect("XML input parsed once");
928                    let selected = match *node_start {
929                        Some(start) => document
930                            .descendants()
931                            .find(|node| node.is_element() && node.range().start == start)
932                            .ok_or_else(|| {
933                                XmlEncError::OperationPlan(
934                                    "selected encrypted node changed during policy parsing".into(),
935                                )
936                            })?,
937                        None => document.root_element(),
938                    };
939                    let (encrypted, origins) =
940                        super::parse::parse_encrypted_data_node_with_origins(
941                            selected,
942                            operation.policy().into(),
943                            false,
944                            self.id_attributes,
945                            self.provider,
946                            Some(&reference_context),
947                            Some(&operation.budgets().xml_parse),
948                        )?;
949                    (Cow::Owned(encrypted), origins)
950                }
951                DecryptionInput::Parsed(encrypted) => (Cow::Borrowed(encrypted), Vec::new()),
952            };
953            validate_encrypted_data_metadata(&encrypted, operation.policy())?;
954            encrypted.encryption_method.validate_structure()?;
955            validate_recipient_count(
956                encrypted.encrypted_keys.len(),
957                operation.policy().resources.max_encryption_recipients,
958            )?;
959            let algorithm =
960                DataEncryptionAlgorithm::from_uri(&encrypted.encryption_method.algorithm)?;
961            crate::policy::check_content_algorithm(
962                operation.policy().data_algorithms.as_ref(),
963                algorithm,
964                "decryption",
965            )?;
966            self.provider
967                .require_capability(crate::provider::ProviderCapability::Decrypt(algorithm))?;
968            validate_typed_cipher_values(
969                &encrypted,
970                algorithm,
971                operation.policy().resources.max_encryption_plaintext_bytes,
972                operation.policy().resources.max_xml_document_bytes,
973            )?;
974            let bound_references = source_document
975                .as_ref()
976                .map(|(document, _)| reference_context.bind_document(document));
977            let ciphertext = match (&encrypted.cipher_data, source_document.as_ref()) {
978                (super::CipherData::Reference { uri, transforms }, Some((document, start))) => {
979                    let source = selected_encrypted_node(document, *start)?;
980                    bound_references
981                        .as_ref()
982                        .expect("XML source has a bound reference context")
983                        .resolve_parsed(
984                            cipher_reference_node(source)?,
985                            uri,
986                            transforms,
987                            &operation.budgets().xml_parse,
988                        )?
989                }
990                _ => encrypted.cipher_data.octets()?.into_owned(),
991            };
992            if let Some((document, _)) = source_document.as_ref() {
993                let mut origins = origins.iter();
994                resolve_nested_cipher_references(
995                    &mut encrypted.to_mut().encrypted_keys,
996                    document,
997                    &mut origins,
998                    bound_references
999                        .as_ref()
1000                        .expect("XML source has a bound reference context"),
1001                    &operation.budgets().xml_parse,
1002                )?;
1003                if origins.next().is_some() {
1004                    return Err(XmlEncError::OperationPlan(
1005                        "unused encrypted key origin".into(),
1006                    ));
1007                }
1008            }
1009            validate_typed_cipher_values(
1010                &encrypted,
1011                algorithm,
1012                operation.policy().resources.max_encryption_plaintext_bytes,
1013                operation.policy().resources.max_xml_document_bytes,
1014            )?;
1015            validate_content_framing_before_resolution(
1016                algorithm,
1017                ciphertext.len(),
1018                &encrypted.encrypted_keys,
1019                operation.policy(),
1020            )?;
1021            validate_possible_plaintext_len(
1022                algorithm,
1023                ciphertext.len(),
1024                operation.policy().resources.max_encryption_plaintext_bytes,
1025            )?;
1026            Ok::<_, XmlEncError>((encrypted, algorithm, ciphertext, origins, bound_references))
1027        });
1028        let (encrypted, algorithm, ciphertext, origins, bound_references) = parsed?;
1029        let keys = operation.run(plan_nodes.key, || {
1030            let document = match (source_document.as_ref(), bound_references.as_ref()) {
1031                (Some((document, start)), Some(bound)) => Some(KeySourceDocument {
1032                    references: bound,
1033                    target: selected_encrypted_node(document, *start)?.id(),
1034                    origins: &origins,
1035                }),
1036                _ => None,
1037            };
1038            resolve_content_key_candidates(
1039                self.provider,
1040                algorithm,
1041                &encrypted,
1042                self.resolver,
1043                operation.policy(),
1044                &mut operation.budgets().key_candidates.borrow_mut(),
1045                document,
1046            )
1047        })?;
1048        let keys = compatible_decryption_key_candidates(algorithm, keys)?;
1049        validate_decryption_key_candidates(algorithm, keys.len())?;
1050        let output = operation.run(plan_nodes.crypto, || {
1051            let mut last_error = None;
1052            for key in keys {
1053                let attempt = (|| {
1054                    validate_content_key_len(algorithm, key.key_len())?;
1055                    let result = key.decrypt(self.provider, algorithm, &ciphertext);
1056                    // XMLEnc 1.1 §6.1.2: fallback recovery must still perform
1057                    // content work. Never release a CBC result merely because
1058                    // its unauthenticated padding happened to be valid.
1059                    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-bleichenbacher-attack
1060                    if !key.valid() {
1061                        if let Ok(mut plaintext) = result {
1062                            zeroize::Zeroize::zeroize(&mut plaintext);
1063                        }
1064                        return Err(if algorithm.cbc_block_len().is_some() {
1065                            XmlEncError::InvalidPadding
1066                        } else {
1067                            XmlEncError::AeadAuthenticationFailed
1068                        });
1069                    }
1070                    let plaintext = result.map_err(|error| {
1071                        map_data_decryption_error(algorithm, ciphertext.len(), error)
1072                    })?;
1073                    validate_provider_plaintext_len(algorithm, ciphertext.len(), plaintext.len())?;
1074                    validate_plaintext_len(
1075                        plaintext.len(),
1076                        operation.policy().resources.max_encryption_plaintext_bytes,
1077                    )?;
1078                    let content = match encrypted.encrypted_type.as_ref() {
1079                        Some(EncryptedDataType::Element | EncryptedDataType::Content) => {
1080                            DecryptedContent::Xml(String::from_utf8(plaintext)?)
1081                        }
1082                        Some(EncryptedDataType::Other(_)) | None => {
1083                            DecryptedContent::Bytes(plaintext)
1084                        }
1085                    };
1086                    accept(content, &operation.budgets().xml_parse)
1087                })();
1088                match attempt {
1089                    Ok(output) => return Ok(output),
1090                    Err(error) => last_error = Some(error),
1091                }
1092            }
1093            Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
1094        })?;
1095        let output = operation.run(plan_nodes.evidence, || Ok::<_, XmlEncError>(output))?;
1096        Ok(ProcessedDecryption {
1097            output,
1098            operation,
1099            mutation: plan_nodes.mutation,
1100        })
1101    }
1102
1103    /// Decrypt and replace one selected `EncryptedData` in a caller-owned document.
1104    pub fn decrypt_document(
1105        &self,
1106        xml: &str,
1107        encrypted_data_id: Option<&str>,
1108    ) -> Result<String, XmlEncError> {
1109        decrypt_document_with_context(
1110            xml,
1111            DocumentEncryptedDataSelector::EncryptedDataId(encrypted_data_id),
1112            self,
1113        )
1114    }
1115
1116    /// Decrypt and replace one selected `EncryptedData` in an owned document.
1117    pub fn decrypt_owned_document(
1118        &self,
1119        document: &mut XmlDocument,
1120        encrypted_data_id: Option<&str>,
1121    ) -> Result<(), XmlEncError> {
1122        let budgets = DecryptionOperationBudgets::from_policy(&self.policy);
1123        decrypt_owned_document_with_context(
1124            document,
1125            DocumentEncryptedDataSelector::EncryptedDataId(encrypted_data_id),
1126            self,
1127            budgets,
1128        )
1129    }
1130
1131    /// Decrypt and replace the sole `EncryptedData` below an operation start
1132    /// node selected by ID.
1133    pub fn decrypt_document_from_start_node(
1134        &self,
1135        xml: &str,
1136        start_node_id: Option<&str>,
1137    ) -> Result<String, XmlEncError> {
1138        decrypt_document_with_context(
1139            xml,
1140            DocumentEncryptedDataSelector::UniqueBelowStartNode(start_node_id),
1141            self,
1142        )
1143    }
1144
1145    /// Decrypt and replace the first `EncryptedData` below an operation start
1146    /// node selected by ID, leaving later encrypted descendants untouched.
1147    pub fn decrypt_first_document_from_start_node(
1148        &self,
1149        xml: &str,
1150        start_node_id: Option<&str>,
1151    ) -> Result<String, XmlEncError> {
1152        decrypt_document_with_context(
1153            xml,
1154            DocumentEncryptedDataSelector::FirstBelowStartNode(start_node_id),
1155            self,
1156        )
1157    }
1158}
1159
1160/// Resolver for direct, pre-shared AES content keys.
1161#[derive(Clone)]
1162pub struct SymmetricKeyDecryptor {
1163    key: Vec<u8>,
1164    kind: crate::key_manager::SymmetricKeyKind,
1165}
1166
1167impl fmt::Debug for SymmetricKeyDecryptor {
1168    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1169        formatter
1170            .debug_struct("SymmetricKeyDecryptor")
1171            .field("key", &"[REDACTED]")
1172            .finish()
1173    }
1174}
1175
1176impl SymmetricKeyDecryptor {
1177    /// Create a direct AES-key resolver. Key length never selects another family.
1178    pub fn new(key: impl Into<Vec<u8>>) -> Self {
1179        Self::with_kind(key, crate::key_manager::SymmetricKeyKind::Aes)
1180    }
1181
1182    /// Bind a pre-shared key to its trusted family before reading an algorithm
1183    /// from the document. XMLEnc 1.1 §6.1.3 recommends key separation:
1184    /// https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-backwards-compatibility-attacks
1185    pub fn with_kind(key: impl Into<Vec<u8>>, kind: crate::key_manager::SymmetricKeyKind) -> Self {
1186        Self {
1187            key: key.into(),
1188            kind,
1189        }
1190    }
1191}
1192
1193impl DecryptionKeyResolver for SymmetricKeyDecryptor {
1194    fn resolve_content_keys_with_policy(
1195        &self,
1196        provider: &dyn crate::provider::CryptoProvider,
1197        algorithm: DataEncryptionAlgorithm,
1198        encrypted_key: Option<&EncryptedKey>,
1199        policy: &crate::policy::DecryptionPolicy,
1200        budget: &mut KeyCandidateBudget,
1201    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1202        policy.validate()?;
1203        if encrypted_key.is_some() {
1204            return Err(XmlEncError::KeyNotFound);
1205        }
1206        budget.consume(1)?;
1207        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1208            self.resolve_key(provider, algorithm, None)?,
1209        )])
1210    }
1211
1212    fn resolve_key(
1213        &self,
1214        _provider: &dyn crate::provider::CryptoProvider,
1215        algorithm: DataEncryptionAlgorithm,
1216        _encrypted_key: Option<&EncryptedKey>,
1217    ) -> Result<Vec<u8>, XmlEncError> {
1218        if self.kind != algorithm.key_kind() {
1219            return Err(XmlEncError::KeyNotFound);
1220        }
1221        validate_key_len(algorithm, &self.key)?;
1222        Ok(self.key.clone())
1223    }
1224}
1225
1226/// Resolver backed by an RSA private key for OAEP-wrapped session keys.
1227#[derive(Clone)]
1228pub struct PrivateKeyDecryptor {
1229    key: Arc<dyn crate::provider::KeyRecoveryKey>,
1230}
1231
1232/// Resolver backed by a pre-shared, algorithm-family-bound key-encryption key.
1233#[derive(Clone)]
1234pub struct KekDecryptor<'a> {
1235    kek: std::borrow::Cow<'a, [u8]>,
1236    kind: crate::key_manager::SymmetricKeyKind,
1237}
1238
1239impl fmt::Debug for KekDecryptor<'_> {
1240    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1241        formatter
1242            .debug_struct("KekDecryptor")
1243            .field("kek", &"[REDACTED]")
1244            .field("kind", &self.kind)
1245            .finish()
1246    }
1247}
1248
1249impl KekDecryptor<'static> {
1250    /// Create a resolver for RFC 3394 AES key-wrap `EncryptedKey` elements.
1251    pub fn new(kek: impl Into<Vec<u8>>) -> Self {
1252        Self::with_kind(kek, crate::key_manager::SymmetricKeyKind::Aes)
1253    }
1254
1255    /// Bind owned key material to its trusted family, independently of input XML.
1256    pub fn with_kind(kek: impl Into<Vec<u8>>, kind: crate::key_manager::SymmetricKeyKind) -> Self {
1257        Self {
1258            kek: std::borrow::Cow::Owned(kek.into()),
1259            kind,
1260        }
1261    }
1262}
1263
1264impl<'a> KekDecryptor<'a> {
1265    /// Borrow a KEK for operation-scoped recovery without copying secret material.
1266    pub fn borrowed(kek: &'a [u8]) -> Self {
1267        Self::borrowed_with_kind(kek, crate::key_manager::SymmetricKeyKind::Aes)
1268    }
1269
1270    /// Borrow key material while retaining its caller-declared algorithm family.
1271    pub fn borrowed_with_kind(kek: &'a [u8], kind: crate::key_manager::SymmetricKeyKind) -> Self {
1272        Self {
1273            kek: std::borrow::Cow::Borrowed(kek),
1274            kind,
1275        }
1276    }
1277}
1278
1279impl DecryptionKeyResolver for KekDecryptor<'_> {
1280    fn resolve_key_encryption_keys_with_policy(
1281        &self,
1282        provider: &dyn crate::provider::CryptoProvider,
1283        algorithm: KeyWrapAlgorithm,
1284        source: KeyEncryptionKeySource<'_>,
1285        policy: &crate::policy::DecryptionPolicy,
1286        budget: &mut KeyCandidateBudget,
1287    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1288        match source {
1289            KeyEncryptionKeySource::Direct => {
1290                if self.kind != algorithm.key_kind() || self.kek.len() != algorithm.key_len() {
1291                    return Err(XmlEncError::KeyNotFound);
1292                }
1293                budget.consume(1)?;
1294                Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1295                    self.kek.to_vec(),
1296                )])
1297            }
1298            KeyEncryptionKeySource::Encrypted(key) => {
1299                validate_encrypted_key_policy(key, policy)?;
1300                budget.consume(2)?;
1301                Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1302                    self.unwrap(provider, algorithm.key_len(), key)?,
1303                )])
1304            }
1305            _ => Err(XmlEncError::KeyNotFound),
1306        }
1307    }
1308    fn resolve_content_keys_with_policy(
1309        &self,
1310        provider: &dyn crate::provider::CryptoProvider,
1311        algorithm: DataEncryptionAlgorithm,
1312        encrypted_key: Option<&EncryptedKey>,
1313        policy: &crate::policy::DecryptionPolicy,
1314        budget: &mut KeyCandidateBudget,
1315    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1316        policy.validate()?;
1317        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1318        validate_encrypted_key_policy(encrypted_key, policy)?;
1319        budget.consume(1)?;
1320        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1321            self.resolve_key(provider, algorithm, Some(encrypted_key))?,
1322        )])
1323    }
1324
1325    fn resolve_key_candidates_with_policy(
1326        &self,
1327        provider: &dyn crate::provider::CryptoProvider,
1328        algorithm: DataEncryptionAlgorithm,
1329        encrypted_key: Option<&EncryptedKey>,
1330        policy: &crate::policy::DecryptionPolicy,
1331        budget: &mut KeyCandidateBudget,
1332    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1333        self.resolve_content_keys_with_policy(provider, algorithm, encrypted_key, policy, budget)?
1334            .into_iter()
1335            .map(|key| key.into_key().map_err(XmlEncError::from))
1336            .collect()
1337    }
1338
1339    fn resolve_key(
1340        &self,
1341        provider: &dyn crate::provider::CryptoProvider,
1342        algorithm: DataEncryptionAlgorithm,
1343        encrypted_key: Option<&EncryptedKey>,
1344    ) -> Result<Vec<u8>, XmlEncError> {
1345        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1346        let key = self.unwrap(provider, algorithm.key_len(), encrypted_key)?;
1347        validate_key_len(algorithm, &key)?;
1348        Ok(key)
1349    }
1350}
1351
1352impl KekDecryptor<'_> {
1353    fn unwrap(
1354        &self,
1355        provider: &dyn crate::provider::CryptoProvider,
1356        output_len: usize,
1357        encrypted_key: &EncryptedKey,
1358    ) -> Result<Vec<u8>, XmlEncError> {
1359        encrypted_key.encryption_method.validate_structure()?;
1360        let wrap_algorithm =
1361            KeyWrapAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
1362        // XMLEnc 1.1 section 6.1.3 recommends key separation between algorithms:
1363        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-backwards-compatibility-attacks
1364        // Equal AES-192 and TripleDES widths do not establish key identity.
1365        if self.kind != wrap_algorithm.key_kind() {
1366            return Err(XmlEncError::KeyNotFound);
1367        }
1368        let wrapped = encrypted_key.cipher_data.octets()?;
1369        let expected_kek_len = wrap_algorithm.key_len();
1370        if self.kek.len() != expected_kek_len {
1371            return Err(XmlEncError::InvalidKekSize {
1372                algorithm: wrap_algorithm,
1373                expected: expected_kek_len,
1374                actual: self.kek.len(),
1375            });
1376        }
1377        let expected_wrapped_len = output_len + wrap_algorithm.overhead();
1378        if wrapped.len() != expected_wrapped_len {
1379            return Err(XmlEncError::InvalidWrappedKeyLength {
1380                expected: expected_wrapped_len,
1381                actual: wrapped.len(),
1382            });
1383        }
1384        provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(
1385            wrap_algorithm,
1386        ))?;
1387        let key = provider
1388            .unwrap_key(wrap_algorithm, &self.kek, &wrapped)
1389            .map_err(|error| match error {
1390                crate::provider::ProviderError::InvalidKeySize { expected, actual } => {
1391                    XmlEncError::InvalidKekSize {
1392                        algorithm: wrap_algorithm,
1393                        expected,
1394                        actual,
1395                    }
1396                }
1397                crate::provider::ProviderError::AuthenticationFailed
1398                | crate::provider::ProviderError::InvalidInput(
1399                    crate::provider::ProviderInputError::AesKeyWrapFraming,
1400                ) => XmlEncError::KeyWrapIntegrity,
1401                error => XmlEncError::Provider(error),
1402            })?;
1403        if key.len() != output_len {
1404            return Err(XmlEncError::InvalidWrappedKeyLength {
1405                expected: output_len,
1406                actual: key.len(),
1407            });
1408        }
1409        Ok(key)
1410    }
1411}
1412
1413impl PrivateKeyDecryptor {
1414    /// Create a resolver from an already-parsed RSA private key.
1415    pub fn new(key: RsaPrivateKey) -> Self {
1416        Self::provider_key(Arc::new(crate::provider::RustCryptoRsaPrivateKey::new(key)))
1417    }
1418
1419    /// Create a resolver from an opaque provider-owned recovery key.
1420    pub fn provider_key(key: Arc<dyn crate::provider::KeyRecoveryKey>) -> Self {
1421        Self { key }
1422    }
1423}
1424
1425impl DecryptionKeyResolver for PrivateKeyDecryptor {
1426    fn resolve_key_encryption_keys_with_policy(
1427        &self,
1428        provider: &dyn crate::provider::CryptoProvider,
1429        algorithm: KeyWrapAlgorithm,
1430        source: KeyEncryptionKeySource<'_>,
1431        policy: &crate::policy::DecryptionPolicy,
1432        budget: &mut KeyCandidateBudget,
1433    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1434        let KeyEncryptionKeySource::Encrypted(key) = source else {
1435            return Err(XmlEncError::KeyNotFound);
1436        };
1437        validate_encrypted_key_policy(key, policy)?;
1438        let width = policy.rsa_keys.validate_public_metadata(
1439            "decryption",
1440            self.key.rsa_modulus_bits(),
1441            self.key.rsa_public_exponent(),
1442        )?;
1443        let ciphertext = key.cipher_data.octets()?;
1444        if width != self.key.ciphertext_len() || ciphertext.len() != width {
1445            return Err(XmlEncError::InvalidWrappedKeyLength {
1446                expected: width,
1447                actual: ciphertext.len(),
1448            });
1449        }
1450        let transport = KeyTransportAlgorithm::from_uri(&key.encryption_method.algorithm)?;
1451        budget.consume(2)?;
1452        #[cfg(feature = "legacy-algorithms")]
1453        if transport == KeyTransportAlgorithm::RsaPkcs1v15 {
1454            provider.require_capability(crate::provider::ProviderCapability::Pkcs1v15Recovery)?;
1455            return Ok(vec![provider.recover_pkcs1v15(
1456                self.key.as_ref(),
1457                &ciphertext,
1458                algorithm.key_len(),
1459            )?]);
1460        }
1461        let method = &key.encryption_method;
1462        let parameters = RsaOaepParameters {
1463            algorithm: transport,
1464            digest: parse_oaep_digest(method.oaep_digest.as_deref())?,
1465            mgf_digest: parse_oaep_mgf_digest(method.mgf_algorithm.as_deref())?,
1466            label: method.oaep_params.clone().unwrap_or_default(),
1467        };
1468        provider.require_capability(crate::provider::ProviderCapability::KeyRecovery(
1469            &parameters,
1470        ))?;
1471        let key = zeroize::Zeroizing::new(provider.recover_key(
1472            self.key.as_ref(),
1473            &parameters,
1474            &ciphertext,
1475        )?);
1476        if key.len() != algorithm.key_len() {
1477            return Err(XmlEncError::InvalidKekSize {
1478                algorithm,
1479                expected: algorithm.key_len(),
1480                actual: key.len(),
1481            });
1482        }
1483        let mut key = key;
1484        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1485            core::mem::take(&mut *key),
1486        )])
1487    }
1488    fn resolve_content_keys_with_policy(
1489        &self,
1490        provider: &dyn crate::provider::CryptoProvider,
1491        algorithm: DataEncryptionAlgorithm,
1492        encrypted_key: Option<&EncryptedKey>,
1493        policy: &crate::policy::DecryptionPolicy,
1494        budget: &mut KeyCandidateBudget,
1495    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1496        policy.validate()?;
1497        budget.consume(1)?;
1498        self.resolve_key_with_policy(provider, algorithm, encrypted_key, policy)
1499            .map(|key| vec![key])
1500    }
1501    fn resolve_key_candidates_with_policy(
1502        &self,
1503        provider: &dyn crate::provider::CryptoProvider,
1504        algorithm: DataEncryptionAlgorithm,
1505        encrypted_key: Option<&EncryptedKey>,
1506        policy: &crate::policy::DecryptionPolicy,
1507        budget: &mut KeyCandidateBudget,
1508    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1509        policy.validate()?;
1510        budget.consume(1)?;
1511        self.resolve_key_with_policy(provider, algorithm, encrypted_key, policy)
1512            .and_then(|key| key.into_key().map_err(XmlEncError::Provider))
1513            .map(|key| vec![key])
1514    }
1515
1516    fn resolve_key(
1517        &self,
1518        provider: &dyn crate::provider::CryptoProvider,
1519        algorithm: DataEncryptionAlgorithm,
1520        encrypted_key: Option<&EncryptedKey>,
1521    ) -> Result<Vec<u8>, XmlEncError> {
1522        self.resolve_key_with_policy(
1523            provider,
1524            algorithm,
1525            encrypted_key,
1526            &crate::policy::DecryptionPolicy::default(),
1527        )
1528        .and_then(|key| key.into_key().map_err(XmlEncError::Provider))
1529    }
1530}
1531
1532impl PrivateKeyDecryptor {
1533    /// Recover one session key using the exact operation policy. This avoids
1534    /// a temporary candidate collection when composing ordered RSA key rings.
1535    pub fn resolve_key_with_policy(
1536        &self,
1537        provider: &dyn crate::provider::CryptoProvider,
1538        algorithm: DataEncryptionAlgorithm,
1539        encrypted_key: Option<&EncryptedKey>,
1540        policy: &crate::policy::DecryptionPolicy,
1541    ) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1542        policy.validate()?;
1543        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1544        validate_encrypted_key_policy(encrypted_key, policy)?;
1545        let width = policy.rsa_keys.validate_public_metadata(
1546            "decryption",
1547            self.key.rsa_modulus_bits(),
1548            self.key.rsa_public_exponent(),
1549        )?;
1550        if width != self.key.ciphertext_len() {
1551            return Err(crate::policy::PolicyViolation::InvalidKeyMaterial {
1552                operation: "decryption",
1553                key_type: "RSA",
1554                reason: "ciphertext width disagrees with modulus",
1555            }
1556            .into());
1557        }
1558        encrypted_key.encryption_method.validate_structure()?;
1559        let wrapped = encrypted_key.cipher_data.octets()?;
1560        let label = encrypted_key
1561            .encryption_method
1562            .oaep_params
1563            .clone()
1564            .unwrap_or_default();
1565        let transport =
1566            KeyTransportAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
1567        let key = match transport {
1568            #[cfg(feature = "legacy-algorithms")]
1569            KeyTransportAlgorithm::RsaPkcs1v15 => {
1570                if wrapped.len() != self.key.ciphertext_len() {
1571                    return Err(XmlEncError::InvalidWrappedKeyLength {
1572                        expected: self.key.ciphertext_len(),
1573                        actual: wrapped.len(),
1574                    });
1575                }
1576                provider
1577                    .require_capability(crate::provider::ProviderCapability::Pkcs1v15Recovery)?;
1578                // Recovery conceals padding/range rejection with a random CEK;
1579                // only operational provider failures propagate here. The RFC
1580                // padding-error rule is RFC 8017 §7.2.2's note:
1581                // https://www.rfc-editor.org/rfc/rfc8017#section-7.2.2.
1582                provider
1583                    .recover_pkcs1v15(self.key.as_ref(), &wrapped, algorithm.key_len())
1584                    .map_err(XmlEncError::Provider)
1585            }
1586            KeyTransportAlgorithm::RsaOaepMgf1p => self.decrypt_oaep_mgf1p(
1587                provider,
1588                encrypted_key.encryption_method.oaep_digest.as_deref(),
1589                encrypted_key.encryption_method.mgf_algorithm.as_deref(),
1590                label,
1591                &wrapped,
1592                algorithm,
1593            ),
1594            KeyTransportAlgorithm::RsaOaep11 => self.decrypt_oaep11(
1595                provider,
1596                encrypted_key.encryption_method.oaep_digest.as_deref(),
1597                encrypted_key.encryption_method.mgf_algorithm.as_deref(),
1598                label,
1599                &wrapped,
1600                algorithm,
1601            ),
1602        }?;
1603        validate_content_key_len(algorithm, key.key_len())?;
1604        Ok(key)
1605    }
1606}
1607
1608impl PrivateKeyDecryptor {
1609    fn decrypt_oaep_mgf1p(
1610        &self,
1611        provider: &dyn crate::provider::CryptoProvider,
1612        digest: Option<&str>,
1613        mgf: Option<&str>,
1614        label: Vec<u8>,
1615        wrapped: &[u8],
1616        algorithm: DataEncryptionAlgorithm,
1617    ) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1618        let parameters = RsaOaepParameters {
1619            algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
1620            digest: parse_oaep_digest(digest)?,
1621            mgf_digest: parse_oaep_mgf_digest(mgf)?,
1622            label,
1623        };
1624        recover_rsa_oaep(provider, self.key.as_ref(), &parameters, wrapped, algorithm)
1625    }
1626
1627    fn decrypt_oaep11(
1628        &self,
1629        provider: &dyn crate::provider::CryptoProvider,
1630        digest: Option<&str>,
1631        mgf: Option<&str>,
1632        label: Vec<u8>,
1633        wrapped: &[u8],
1634        algorithm: DataEncryptionAlgorithm,
1635    ) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1636        let parameters = RsaOaepParameters {
1637            algorithm: KeyTransportAlgorithm::RsaOaep11,
1638            digest: parse_oaep_digest(digest)?,
1639            mgf_digest: parse_oaep_mgf_digest(mgf)?,
1640            label,
1641        };
1642        recover_rsa_oaep(provider, self.key.as_ref(), &parameters, wrapped, algorithm)
1643    }
1644}
1645
1646fn parse_oaep_digest(uri: Option<&str>) -> Result<OaepDigestAlgorithm, XmlEncError> {
1647    let uri = uri.unwrap_or("http://www.w3.org/2000/09/xmldsig#sha1");
1648    OaepDigestAlgorithm::from_uri(uri)
1649        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned()))
1650}
1651
1652fn parse_oaep_mgf_digest(uri: Option<&str>) -> Result<OaepDigestAlgorithm, XmlEncError> {
1653    let uri = uri.unwrap_or("http://www.w3.org/2009/xmlenc11#mgf1sha1");
1654    OaepDigestAlgorithm::from_mgf_uri(uri)
1655        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned()))
1656}
1657
1658fn recover_rsa_oaep(
1659    provider: &dyn crate::provider::CryptoProvider,
1660    key: &dyn crate::provider::KeyRecoveryKey,
1661    parameters: &RsaOaepParameters,
1662    wrapped: &[u8],
1663    algorithm: DataEncryptionAlgorithm,
1664) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1665    let expected = key.ciphertext_len();
1666    if wrapped.len() != expected {
1667        return Err(XmlEncError::InvalidWrappedKeyLength {
1668            expected,
1669            actual: wrapped.len(),
1670        });
1671    }
1672    provider.require_capability(crate::provider::ProviderCapability::KeyRecovery(parameters))?;
1673    provider
1674        .recover_content_key(key, parameters, algorithm, wrapped)
1675        .map_err(|error| match error {
1676            crate::provider::ProviderError::Random(message) => XmlEncError::Rng(message),
1677            error @ (crate::provider::ProviderError::AuthenticationFailed
1678            | crate::provider::ProviderError::InvalidInput(_)) => {
1679                XmlEncError::Rsa(error.to_string())
1680            }
1681            error => XmlEncError::Provider(error),
1682        })
1683}
1684
1685/// Parse and decrypt a standalone `EncryptedData` XML fragment.
1686pub fn decrypt(
1687    xml: &str,
1688    resolver: &dyn DecryptionKeyResolver,
1689) -> Result<DecryptedContent, XmlEncError> {
1690    DecryptContext::new(resolver).decrypt(xml)
1691}
1692
1693/// Decrypt and replace one `EncryptedData` element in a caller-owned XML document.
1694///
1695/// When `encrypted_data_id` is `None`, the document must contain exactly one
1696/// `EncryptedData`. The decrypted value must declare either the XMLEnc `Element`
1697/// or `Content` type. Plaintext is parsed inside a bounded replacement wrapper
1698/// before insertion, and the returned document is parsed again before exposure.
1699pub fn decrypt_document(
1700    xml: &str,
1701    encrypted_data_id: Option<&str>,
1702    resolver: &dyn DecryptionKeyResolver,
1703) -> Result<String, XmlEncError> {
1704    decrypt_document_with_options(
1705        xml,
1706        DocumentDecryptionOptions { encrypted_data_id },
1707        resolver,
1708    )
1709}
1710
1711/// Decrypt and replace one `EncryptedData` using the default decryption policy.
1712///
1713/// Use [`DecryptContext`] when XML-input or resource policy must differ from
1714/// the secure defaults; options here contain request selection only.
1715pub fn decrypt_document_with_options(
1716    xml: &str,
1717    options: DocumentDecryptionOptions<'_>,
1718    resolver: &dyn DecryptionKeyResolver,
1719) -> Result<String, XmlEncError> {
1720    DecryptContext::new(resolver).decrypt_document(xml, options.encrypted_data_id)
1721}
1722
1723#[derive(Clone, Copy)]
1724enum DocumentEncryptedDataSelector<'a> {
1725    EncryptedDataId(Option<&'a str>),
1726    UniqueBelowStartNode(Option<&'a str>),
1727    FirstBelowStartNode(Option<&'a str>),
1728}
1729
1730fn decrypt_document_with_context(
1731    xml: &str,
1732    selector: DocumentEncryptedDataSelector<'_>,
1733    context: &DecryptContext<'_>,
1734) -> Result<String, XmlEncError> {
1735    context.policy.resources.validate()?;
1736    validate_encryption_document_len(xml.len(), &context.policy)?;
1737    let budgets = DecryptionOperationBudgets::from_policy(&context.policy);
1738    let settings = context.document_parse_settings();
1739    let mut document =
1740        XmlDocument::parse_with_settings_and_budget(xml.to_owned(), settings, &budgets.xml_parse)
1741            .map_err(|error| map_document_error(error, settings))?;
1742    decrypt_owned_document_with_context(&mut document, selector, context, budgets)?;
1743    Ok(document.into_xml())
1744}
1745
1746fn decrypt_owned_document_with_context(
1747    document: &mut XmlDocument,
1748    selector: DocumentEncryptedDataSelector<'_>,
1749    context: &DecryptContext<'_>,
1750    budgets: DecryptionOperationBudgets,
1751) -> Result<(), XmlEncError> {
1752    context.policy.resources.validate()?;
1753    document.validate_operation_policy(&context.policy.xml, &context.policy.resources)?;
1754    let (target, target_len, target_start, replacement_type) = document.with_view(|view| {
1755        let start = match selector {
1756            DocumentEncryptedDataSelector::UniqueBelowStartNode(Some(id))
1757            | DocumentEncryptedDataSelector::FirstBelowStartNode(Some(id)) => view
1758                .node_for_id(id, context.id_attributes)
1759                .ok_or_else(|| XmlEncError::SelectedNodeUnavailable { id: id.to_owned() })
1760                .and_then(|identity| view.resolve_node(identity).map_err(XmlEncError::from))?,
1761            DocumentEncryptedDataSelector::UniqueBelowStartNode(None)
1762            | DocumentEncryptedDataSelector::FirstBelowStartNode(None)
1763            | DocumentEncryptedDataSelector::EncryptedDataId(_) => view.document().root(),
1764        };
1765        let encrypted_data_id = match selector {
1766            DocumentEncryptedDataSelector::EncryptedDataId(id) => id,
1767            DocumentEncryptedDataSelector::UniqueBelowStartNode(_)
1768            | DocumentEncryptedDataSelector::FirstBelowStartNode(_) => None,
1769        };
1770        let mut matches = start.descendants().filter(|node| {
1771            node.has_tag_name((XMLENC_NS, "EncryptedData"))
1772                && encrypted_data_id.is_none_or(|id| node.attribute("Id") == Some(id))
1773        });
1774        let selected = matches.next().ok_or(XmlEncError::EncryptedDataNotFound)?;
1775        if matches!(
1776            selector,
1777            DocumentEncryptedDataSelector::EncryptedDataId(_)
1778                | DocumentEncryptedDataSelector::UniqueBelowStartNode(_)
1779        ) && matches.next().is_some()
1780        {
1781            return Err(XmlEncError::AmbiguousEncryptedData);
1782        }
1783        Ok::<_, XmlEncError>((
1784            view.node_identity(selected),
1785            selected.range().len(),
1786            selected.range().start,
1787            match selected.attribute("Type") {
1788                Some("http://www.w3.org/2001/04/xmlenc#Element") => {
1789                    Some(EncryptedDataType::Element)
1790                }
1791                Some("http://www.w3.org/2001/04/xmlenc#Content") => {
1792                    Some(EncryptedDataType::Content)
1793                }
1794                _ => None,
1795            },
1796        ))
1797    })?;
1798    let document_binding = Some((document.identity(), document.generation()));
1799    let mut processed = context.process_decryption_input(
1800        DecryptionInput::Xml {
1801            source: document.as_xml(),
1802            node_start: Some(target_start),
1803        },
1804        document_binding,
1805        true,
1806        budgets,
1807        |candidate, operation_parse_budget| {
1808            let DecryptedContent::Xml(plaintext) = candidate else {
1809                return Err(XmlEncError::ReplacementRequiresXml);
1810            };
1811            validate_encryption_document_len(
1812                document
1813                    .as_xml()
1814                    .len()
1815                    .saturating_sub(target_len)
1816                    .saturating_add(plaintext.len()),
1817                &context.policy,
1818            )?;
1819            let settings = context.document_parse_settings();
1820            match replacement_type.as_ref() {
1821                Some(EncryptedDataType::Element) => document
1822                    .prepare_element_replacement_with_budget(
1823                        target,
1824                        &plaintext,
1825                        settings,
1826                        operation_parse_budget,
1827                    )
1828                    .map_err(|error| map_document_error(error, settings)),
1829                Some(EncryptedDataType::Content) => document
1830                    .prepare_node_fragment_replacement_with_budget(
1831                        target,
1832                        &plaintext,
1833                        settings,
1834                        operation_parse_budget,
1835                    )
1836                    .map_err(|error| map_document_error(error, settings)),
1837                Some(EncryptedDataType::Other(_)) | None => {
1838                    Err(XmlEncError::ReplacementRequiresXml)
1839                }
1840            }
1841        },
1842    )?;
1843    let mutation = processed.mutation.ok_or_else(|| {
1844        XmlEncError::OperationPlan("decryption mutation node is unavailable".into())
1845    })?;
1846    processed
1847        .operation
1848        .run_document_transition(mutation, document, |document, _| {
1849            document
1850                .commit_prepared(processed.output)
1851                .map_err(|error| map_document_error(error, context.document_parse_settings()))
1852        })
1853}
1854
1855fn validate_encryption_document_len(
1856    actual: usize,
1857    policy: &crate::policy::DecryptionPolicy,
1858) -> Result<(), XmlEncError> {
1859    policy.resources.validate_xml_document_len(actual)?;
1860    Ok(())
1861}
1862
1863fn validate_recipient_count(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
1864    if actual > maximum {
1865        return Err(crate::policy::PolicyViolation::ResourceLimit {
1866            resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
1867            maximum,
1868            actual,
1869        }
1870        .into());
1871    }
1872    Ok(())
1873}
1874
1875/// Decrypt an already parsed `EncryptedData` value.
1876pub fn decrypt_data(
1877    encrypted: &EncryptedData,
1878    resolver: &dyn DecryptionKeyResolver,
1879) -> Result<DecryptedContent, XmlEncError> {
1880    DecryptContext::new(resolver).decrypt_data(encrypted)
1881}
1882
1883fn resolve_content_key_candidates(
1884    provider: &dyn crate::provider::CryptoProvider,
1885    algorithm: DataEncryptionAlgorithm,
1886    encrypted: &EncryptedData,
1887    resolver: &dyn DecryptionKeyResolver,
1888    policy: &crate::policy::DecryptionPolicy,
1889    budget: &mut KeyCandidateBudget,
1890    mut document: Option<KeySourceDocument<'_, '_>>,
1891) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1892    let mut last_error = None;
1893    let mut candidates = if encrypted.derived_keys.is_empty()
1894        && encrypted.agreement_methods.is_empty()
1895        && encrypted.encapsulation_methods.is_empty()
1896    {
1897        match resolve_candidates_with_budget(resolver, provider, algorithm, None, policy, budget) {
1898            Ok(keys) => keys,
1899            Err(error) => {
1900                record_candidate_source_error_or_fail_operation(error, &mut last_error)?;
1901                Vec::new()
1902            }
1903        }
1904    } else {
1905        Vec::new()
1906    };
1907    for descriptor in &encrypted.encapsulation_methods {
1908        policy
1909            .key_establishment
1910            .check_encapsulation(descriptor.algorithm)?;
1911        let remaining_before = budget.remaining();
1912        budget.require_available(1)?;
1913        match resolver.resolve_encapsulation_content_keys_with_policy(
1914            provider, algorithm, descriptor, policy, budget,
1915        ) {
1916            Ok(keys) => {
1917                budget.account_returned_candidates(remaining_before, keys.len())?;
1918                candidates.extend(keys);
1919            }
1920            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
1921        }
1922    }
1923    for descriptor in &encrypted.agreement_methods {
1924        let remaining_before = budget.remaining();
1925        match resolver.resolve_agreement_content_keys_with_policy(
1926            provider, algorithm, descriptor, policy, budget,
1927        ) {
1928            Ok(keys) => {
1929                budget.account_returned_candidates(remaining_before, keys.len())?;
1930                candidates.extend(keys);
1931            }
1932            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
1933        }
1934    }
1935    for descriptor in &encrypted.derived_keys {
1936        if !reference_list_applies_to_target(
1937            descriptor.reference_list.as_ref(),
1938            ReferenceTarget::new(
1939                document,
1940                encrypted.id.as_deref(),
1941                policy.transforms.same_document_id_semantics,
1942            ),
1943            ReferenceKind::Data,
1944        ) {
1945            continue;
1946        }
1947        let remaining_before = budget.remaining();
1948        match resolver.resolve_derived_content_keys_with_policy(
1949            provider, algorithm, descriptor, policy, budget,
1950        ) {
1951            Ok(keys) => {
1952                budget.account_returned_candidates(remaining_before, keys.len())?;
1953                candidates.extend(keys);
1954            }
1955            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
1956        }
1957    }
1958    for encrypted_key in &encrypted.encrypted_keys {
1959        let source = KeySourceView {
1960            key: encrypted_key,
1961            document: take_key_document(&mut document, encrypted_key)?,
1962        };
1963        if !encrypted_key_applies_to_data(
1964            encrypted_key,
1965            encrypted,
1966            ReferenceTarget::new(
1967                document,
1968                encrypted.id.as_deref(),
1969                policy.transforms.same_document_id_semantics,
1970            ),
1971        ) {
1972            continue;
1973        }
1974        if let Err(error) = validate_encrypted_key_policy(encrypted_key, policy) {
1975            last_error = Some(error);
1976            continue;
1977        }
1978        let resolution = if encrypted_key.sources.is_empty() {
1979            resolve_candidates_with_budget(
1980                resolver,
1981                provider,
1982                algorithm,
1983                Some(encrypted_key),
1984                policy,
1985                budget,
1986            )
1987        } else {
1988            resolve_nested_key(
1989                provider,
1990                source,
1991                algorithm.key_len(),
1992                resolver,
1993                policy,
1994                budget,
1995                1,
1996            )
1997        };
1998        match resolution {
1999            Ok(keys) => candidates.extend(keys),
2000            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
2001        }
2002    }
2003    if candidates.is_empty() {
2004        Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
2005    } else {
2006        Ok(candidates)
2007    }
2008}
2009
2010pub(super) fn resolve_nested_cipher_references<'a>(
2011    keys: &mut [EncryptedKey],
2012    document: &crate::XmlDomDocument<'_>,
2013    origins: &mut core::slice::Iter<'_, Option<crate::NodeId>>,
2014    context: &super::cipher_reference::BoundCipherReferenceContext<'a, 'a>,
2015    parse: &XmlParseWorkBudget,
2016) -> Result<(), XmlEncError> {
2017    // The metadata pass already enforces the non-configurable key recursion
2018    // ceiling. Keep original nodes and one shared transform/resource context.
2019    for key in keys {
2020        let origin = origins
2021            .next()
2022            .ok_or_else(|| XmlEncError::OperationPlan("missing encrypted key origin".into()))?;
2023        if let super::CipherData::Reference { uri, transforms } = &key.cipher_data {
2024            let node = origin.and_then(|id| document.get_node(id)).ok_or_else(|| {
2025                XmlEncError::OperationPlan("unresolved foreign encrypted key origin".into())
2026            })?;
2027            key.cipher_data = super::CipherData::Bytes(context.resolve_parsed(
2028                cipher_reference_node(node)?,
2029                uri,
2030                transforms,
2031                parse,
2032            )?);
2033        }
2034        resolve_nested_cipher_references(
2035            &mut key.sources.encrypted_keys,
2036            document,
2037            origins,
2038            context,
2039            parse,
2040        )?;
2041    }
2042    Ok(())
2043}
2044
2045fn resolve_nested_key(
2046    provider: &dyn crate::provider::CryptoProvider,
2047    source: KeySourceView<'_, '_>,
2048    output_len: usize,
2049    resolver: &dyn DecryptionKeyResolver,
2050    policy: &crate::policy::DecryptionPolicy,
2051    budget: &mut KeyCandidateBudget,
2052    depth: usize,
2053) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
2054    let key = source.key;
2055    let mut document = source.document;
2056    let target = ReferenceTarget::new(
2057        document,
2058        key.id.as_deref(),
2059        policy.transforms.same_document_id_semantics,
2060    );
2061    policy.resources.validate_key_info_reference_depth(depth)?;
2062    validate_encrypted_key_policy(key, policy)?;
2063    let wrap = KeyWrapAlgorithm::from_uri(&key.encryption_method.algorithm)?;
2064    provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(wrap))?;
2065    let ciphertext = key.cipher_data.octets()?;
2066    let expected = output_len + wrap.overhead();
2067    if ciphertext.len() != expected {
2068        return Err(XmlEncError::InvalidWrappedKeyLength {
2069            expected,
2070            actual: ciphertext.len(),
2071        });
2072    }
2073    let mut keys = Vec::new();
2074    let mut last_error = None;
2075    let mut resolve_source = |source| -> Result<(), XmlEncError> {
2076        budget.require_available(1)?;
2077        let remaining = budget.remaining();
2078        match resolver
2079            .resolve_key_encryption_keys_with_policy(provider, wrap, source, policy, budget)
2080        {
2081            Ok(resolved) => {
2082                budget.account_returned_candidates(remaining, resolved.len())?;
2083                keys.extend(resolved);
2084                Ok(())
2085            }
2086            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error),
2087        }
2088    };
2089    for mechanism in &key.sources.encapsulation_methods {
2090        resolve_source(KeyEncryptionKeySource::Encapsulation(mechanism))?;
2091    }
2092    for agreement in &key.sources.agreement_methods {
2093        resolve_source(KeyEncryptionKeySource::Agreement(agreement))?;
2094    }
2095    for derived in &key.sources.derived_keys {
2096        if !reference_list_applies_to_target(
2097            derived.reference_list.as_ref(),
2098            target,
2099            ReferenceKind::Key,
2100        ) {
2101            continue;
2102        }
2103        resolve_source(KeyEncryptionKeySource::Derived(derived))?;
2104    }
2105    for nested in &key.sources.encrypted_keys {
2106        let source = KeySourceView {
2107            key: nested,
2108            document: take_key_document(&mut document, nested)?,
2109        };
2110        if !reference_list_applies_to_target(
2111            nested.reference_list.as_ref(),
2112            target,
2113            ReferenceKind::Key,
2114        ) {
2115            continue;
2116        }
2117        let resolved = if nested.sources.is_empty() {
2118            budget.require_available(3)?;
2119            let remaining = budget.remaining();
2120            resolver
2121                .resolve_key_encryption_keys_with_policy(
2122                    provider,
2123                    wrap,
2124                    KeyEncryptionKeySource::Encrypted(nested),
2125                    policy,
2126                    budget,
2127                )
2128                .and_then(|resolved| {
2129                    budget.account_returned_candidates(remaining, resolved.len())?;
2130                    Ok(resolved)
2131                })
2132        } else {
2133            resolve_nested_key(
2134                provider,
2135                source,
2136                wrap.key_len(),
2137                resolver,
2138                policy,
2139                budget,
2140                depth + 1,
2141            )
2142        };
2143        match resolved {
2144            Ok(resolved) => keys.extend(resolved),
2145            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
2146        }
2147    }
2148    let mut outputs = Vec::new();
2149    // XMLEnc §3.5.1 supplies recovered octets directly to the consuming
2150    // EncryptionMethod. No content-algorithm proxy or base64 round trip.
2151    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-EncryptedKey
2152    for kek in keys {
2153        if kek.key_len() != wrap.key_len() {
2154            last_error = Some(XmlEncError::InvalidKekSize {
2155                algorithm: wrap,
2156                expected: wrap.key_len(),
2157                actual: kek.key_len(),
2158            });
2159            continue;
2160        }
2161        budget.consume(1)?;
2162        match kek.unwrap_nested(provider, wrap, &ciphertext, output_len) {
2163            Ok(result) if result.key_len() == output_len => outputs.push(result),
2164            Ok(result) => {
2165                last_error = Some(XmlEncError::InvalidWrappedKeyLength {
2166                    expected: output_len,
2167                    actual: result.key_len(),
2168                })
2169            }
2170            Err(error) => last_error = Some(XmlEncError::Provider(error)),
2171        }
2172    }
2173    if outputs.is_empty() {
2174        Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
2175    } else {
2176        Ok(outputs)
2177    }
2178}
2179
2180#[derive(Clone, Copy)]
2181struct KeySourceDocument<'a, 'doc> {
2182    references: &'a super::cipher_reference::BoundCipherReferenceContext<'doc, 'doc>,
2183    target: crate::NodeId,
2184    origins: &'a [Option<crate::NodeId>],
2185}
2186
2187struct KeySourceView<'a, 'doc> {
2188    key: &'a EncryptedKey,
2189    document: Option<KeySourceDocument<'a, 'doc>>,
2190}
2191
2192fn key_origin_count(key: &EncryptedKey) -> usize {
2193    // Metadata validation has already bounded total candidates and recursion.
2194    // Each identity is counted at most once per bounded key-indirection level.
2195    1 + key
2196        .sources
2197        .encrypted_keys
2198        .iter()
2199        .map(key_origin_count)
2200        .sum::<usize>()
2201}
2202
2203fn take_key_document<'a, 'doc>(
2204    document: &mut Option<KeySourceDocument<'a, 'doc>>,
2205    key: &EncryptedKey,
2206) -> Result<Option<KeySourceDocument<'a, 'doc>>, XmlEncError> {
2207    let Some(parent) = document else {
2208        return Ok(None);
2209    };
2210    let count = key_origin_count(key);
2211    if count > parent.origins.len() {
2212        return Err(XmlEncError::OperationPlan(
2213            "missing key association origin".into(),
2214        ));
2215    }
2216    let (subtree, remaining) = parent.origins.split_at(count);
2217    parent.origins = remaining;
2218    Ok(subtree[0].map(|target| KeySourceDocument {
2219        references: parent.references,
2220        target,
2221        origins: &subtree[1..],
2222    }))
2223}
2224
2225#[derive(Clone, Copy)]
2226enum ReferenceTarget<'a, 'doc> {
2227    Document {
2228        references: &'a super::cipher_reference::BoundCipherReferenceContext<'doc, 'doc>,
2229        node: crate::NodeId,
2230    },
2231    TypedId {
2232        id: Option<&'a str>,
2233        semantics: crate::policy::SameDocumentIdSemantics,
2234    },
2235}
2236
2237impl<'a, 'doc> ReferenceTarget<'a, 'doc> {
2238    fn new(
2239        document: Option<KeySourceDocument<'a, 'doc>>,
2240        id: Option<&'a str>,
2241        semantics: crate::policy::SameDocumentIdSemantics,
2242    ) -> Self {
2243        match document {
2244            Some(source) => Self::Document {
2245                references: source.references,
2246                node: source.target,
2247            },
2248            None => Self::TypedId { id, semantics },
2249        }
2250    }
2251
2252    fn matches(self, uri: &str) -> bool {
2253        // XMLEnc 1.1 §3.6 references objects through URIs, not specifically the
2254        // lexical Id attribute. XML operations retain the original document's
2255        // registered IDs, ambiguity checks and configured fragment grammar.
2256        // Typed-only requests have no DOM and can use only their explicit Id.
2257        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ReferenceList
2258        match self {
2259            Self::Document { references, node } => references
2260                .resolver()
2261                .same_document_reference_targets(uri, node),
2262            Self::TypedId { id, semantics } => {
2263                id.is_some_and(|id| reference_targets_id(uri, id, semantics))
2264            }
2265        }
2266    }
2267}
2268
2269enum ReferenceKind {
2270    Data,
2271    Key,
2272}
2273
2274fn record_candidate_source_error_or_fail_operation(
2275    error: XmlEncError,
2276    last_error: &mut Option<XmlEncError>,
2277) -> Result<(), XmlEncError> {
2278    // Candidate-specific failures permit the next ordered key source. The
2279    // shared work ceiling is operation-wide and must never be recoverable by
2280    // advancing to another recipient.
2281    if matches!(
2282        &error,
2283        XmlEncError::Policy(_)
2284            | XmlEncError::KeyEstablishment(
2285                crate::key_establishment::KeyEstablishmentError::Policy(_)
2286            )
2287    ) {
2288        return Err(error);
2289    }
2290    *last_error = Some(error);
2291    Ok(())
2292}
2293
2294fn resolve_candidates_with_budget(
2295    resolver: &dyn DecryptionKeyResolver,
2296    provider: &dyn crate::provider::CryptoProvider,
2297    algorithm: DataEncryptionAlgorithm,
2298    encrypted_key: Option<&EncryptedKey>,
2299    policy: &crate::policy::DecryptionPolicy,
2300    budget: &mut KeyCandidateBudget,
2301) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
2302    let remaining_before = budget.remaining();
2303    let keys = resolver.resolve_content_keys_with_policy(
2304        provider,
2305        algorithm,
2306        encrypted_key,
2307        policy,
2308        budget,
2309    )?;
2310    budget.account_returned_candidates(remaining_before, keys.len())?;
2311    Ok(keys)
2312}
2313
2314fn encrypted_key_applies_to_data(
2315    encrypted_key: &EncryptedKey,
2316    encrypted_data: &EncryptedData,
2317    target: ReferenceTarget<'_, '_>,
2318) -> bool {
2319    // XMLEnc association metadata is optional, but authoritative when present:
2320    // DataReference identifies encrypted objects and CarriedKeyName identifies
2321    // the transported key referenced by the enclosing ds:KeyName.
2322    if !reference_list_applies_to_target(
2323        encrypted_key.reference_list.as_ref(),
2324        target,
2325        ReferenceKind::Data,
2326    ) {
2327        return false;
2328    }
2329    if let (Some(carried), Some(expected)) = (
2330        encrypted_key.carried_key_name.as_deref(),
2331        encrypted_data.key_name.as_deref(),
2332    ) && carried != expected
2333    {
2334        return false;
2335    }
2336    true
2337}
2338
2339pub(super) struct EncapsulationRecipient {
2340    key_path: [usize; crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING],
2341    depth: usize,
2342    method: usize,
2343}
2344
2345impl EncapsulationRecipient {
2346    pub(super) fn mechanism<'a>(
2347        &self,
2348        data: &'a EncryptedData,
2349    ) -> &'a crate::key_establishment::EncapsulationMechanism {
2350        let mut methods = &data.encapsulation_methods;
2351        let mut keys = &data.encrypted_keys;
2352        for index in &self.key_path[..self.depth] {
2353            let sources = &keys[*index].sources;
2354            methods = &sources.encapsulation_methods;
2355            keys = &sources.encrypted_keys;
2356        }
2357        &methods[self.method]
2358    }
2359}
2360
2361pub(super) fn select_encapsulation_recipient<'doc>(
2362    data: &EncryptedData,
2363    references: &super::cipher_reference::BoundCipherReferenceContext<'doc, 'doc>,
2364    target: crate::NodeId,
2365    origins: &[Option<crate::NodeId>],
2366    policy: &crate::policy::DecryptionPolicy,
2367) -> Result<Option<EncapsulationRecipient>, XmlEncError> {
2368    struct Selection<'a> {
2369        data: &'a EncryptedData,
2370        semantics: crate::policy::SameDocumentIdSemantics,
2371        path: [usize; crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING],
2372        depth: usize,
2373        selected: Option<EncapsulationRecipient>,
2374    }
2375    fn visit(
2376        methods: &[crate::key_establishment::EncapsulationMechanism],
2377        keys: &[EncryptedKey],
2378        mut document: Option<KeySourceDocument<'_, '_>>,
2379        parent: Option<&EncryptedKey>,
2380        selection: &mut Selection<'_>,
2381    ) -> Result<(), XmlEncError> {
2382        for method in 0..methods.len() {
2383            if selection.selected.is_some() {
2384                return Err(XmlEncError::InvalidStructure(
2385                    "multiple KEM recipients require explicit application key resolution".into(),
2386                ));
2387            }
2388            selection.selected = Some(EncapsulationRecipient {
2389                key_path: selection.path,
2390                depth: selection.depth,
2391                method,
2392            });
2393        }
2394        for (index, key) in keys.iter().enumerate() {
2395            let source = take_key_document(&mut document, key)?;
2396            let target = ReferenceTarget::new(
2397                document,
2398                match parent {
2399                    Some(parent) => parent.id.as_deref(),
2400                    None => selection.data.id.as_deref(),
2401                },
2402                selection.semantics,
2403            );
2404            // XMLEnc 1.1 §3.6: use the execution predicates for DataReference/CarriedKeyName
2405            // at the content-key boundary, KeyReference at wrapping-key boundaries.
2406            // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ReferenceList
2407            let applies = match parent {
2408                None => encrypted_key_applies_to_data(key, selection.data, target),
2409                Some(_) => reference_list_applies_to_target(
2410                    key.reference_list.as_ref(),
2411                    target,
2412                    ReferenceKind::Key,
2413                ),
2414            };
2415            if !applies {
2416                continue;
2417            }
2418            // Parsing bounds every key-indirection path by this absolute
2419            // ceiling; selection needs no heap allocation or ciphertext copy.
2420            debug_assert!(selection.depth < selection.path.len());
2421            selection.path[selection.depth] = index;
2422            selection.depth += 1;
2423            visit(
2424                &key.sources.encapsulation_methods,
2425                &key.sources.encrypted_keys,
2426                source,
2427                Some(key),
2428                selection,
2429            )?;
2430            selection.depth -= 1;
2431        }
2432        Ok(())
2433    }
2434    let mut selection = Selection {
2435        data,
2436        semantics: policy.transforms.same_document_id_semantics,
2437        path: [0; crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING],
2438        depth: 0,
2439        selected: None,
2440    };
2441    visit(
2442        &data.encapsulation_methods,
2443        &data.encrypted_keys,
2444        Some(KeySourceDocument {
2445            references,
2446            target,
2447            origins,
2448        }),
2449        None,
2450        &mut selection,
2451    )?;
2452    Ok(selection.selected)
2453}
2454
2455fn reference_list_applies_to_target(
2456    references: Option<&super::ReferenceList>,
2457    target: ReferenceTarget<'_, '_>,
2458    kind: ReferenceKind,
2459) -> bool {
2460    let Some(references) = references else {
2461        return true;
2462    };
2463    let uris = match kind {
2464        ReferenceKind::Data => &references.data_references,
2465        ReferenceKind::Key => &references.key_references,
2466    };
2467    if uris.is_empty() {
2468        return true;
2469    }
2470    for uri in uris {
2471        if target.matches(uri) {
2472            return true;
2473        }
2474    }
2475    false
2476}
2477
2478fn reference_targets_id(
2479    uri: &str,
2480    id: &str,
2481    semantics: crate::policy::SameDocumentIdSemantics,
2482) -> bool {
2483    let Some(fragment) = uri.strip_prefix('#') else {
2484        return false;
2485    };
2486    // Share XMLDSig's XPointer grammar rather than treating every fragment as
2487    // an ID string; XMLEnc §3.6 uses URI references for both object classes.
2488    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ReferenceList
2489    let Ok(fragment) =
2490        crate::xmldsig::uri::UriReferenceResolver::normalize_fragment(fragment, None, semantics)
2491    else {
2492        return false;
2493    };
2494    crate::xmldsig::uri::UriReferenceResolver::id_fragment(&fragment, semantics)
2495        .is_some_and(|(target, _)| target == id)
2496}
2497
2498fn compatible_decryption_key_candidates(
2499    algorithm: DataEncryptionAlgorithm,
2500    mut keys: Vec<crate::provider::RecoveredContentKey>,
2501) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
2502    let mut accepted = 0;
2503    let mut last_error = None;
2504    for index in 0..keys.len() {
2505        let key = &keys[index];
2506        match validate_content_key_len(algorithm, key.key_len()) {
2507            Ok(())
2508                if !keys[..accepted]
2509                    .iter()
2510                    .any(|existing| existing.same_candidate(key)) =>
2511            {
2512                // Accepted entries form a stable prefix. Swapping an earlier
2513                // rejected slot never moves an unvisited entry, and avoids a
2514                // second allocation without changing candidate precedence.
2515                keys.swap(accepted, index);
2516                accepted += 1;
2517            }
2518            Ok(()) => {}
2519            Err(error) => last_error = Some(error),
2520        }
2521    }
2522    keys.truncate(accepted);
2523    if keys.is_empty() {
2524        return Err(last_error.unwrap_or(XmlEncError::KeyNotFound));
2525    }
2526    Ok(keys)
2527}
2528
2529fn validate_decryption_key_candidates(
2530    algorithm: DataEncryptionAlgorithm,
2531    actual: usize,
2532) -> Result<(), XmlEncError> {
2533    let maximum = crate::hard_limits::KEY_CANDIDATE_CEILING;
2534    if actual > maximum {
2535        return Err(crate::policy::PolicyViolation::ResourceLimit {
2536            resource: crate::policy::resource_name::KEY_CANDIDATES,
2537            maximum,
2538            actual,
2539        }
2540        .into());
2541    }
2542    if actual > 1 && algorithm.cbc_block_len().is_some() {
2543        return Err(XmlEncError::AmbiguousKeyCandidates { algorithm, actual });
2544    }
2545    Ok(())
2546}
2547
2548fn validate_encrypted_key_policy(
2549    encrypted_key: &EncryptedKey,
2550    policy: &crate::policy::DecryptionPolicy,
2551) -> Result<(), XmlEncError> {
2552    validate_key_encryption_method_policy(&encrypted_key.encryption_method, policy)
2553}
2554
2555pub(super) fn validate_key_encryption_method_policy(
2556    method: &super::EncryptionMethod,
2557    policy: &crate::policy::DecryptionPolicy,
2558) -> Result<(), XmlEncError> {
2559    method.validate_structure()?;
2560    let uri = &method.algorithm;
2561    if let Ok(transport) = KeyTransportAlgorithm::from_uri(uri) {
2562        if policy
2563            .key_transport_algorithms
2564            .as_ref()
2565            .map_or(transport.requires_explicit_permission(), |allowed| {
2566                !allowed.contains(&transport)
2567            })
2568        {
2569            return Err(crate::policy::PolicyViolation::Algorithm {
2570                operation: "decryption",
2571                algorithm: uri.clone(),
2572            }
2573            .into());
2574        }
2575        #[cfg(feature = "legacy-algorithms")]
2576        if transport == KeyTransportAlgorithm::RsaPkcs1v15 {
2577            return Ok(());
2578        }
2579        let digest = parse_oaep_digest(method.oaep_digest.as_deref())?;
2580        let mgf_digest = parse_oaep_mgf_digest(method.mgf_algorithm.as_deref())?;
2581        let selected_algorithms = [
2582            (
2583                digest,
2584                method.oaep_digest.as_deref().unwrap_or(digest.uri()),
2585            ),
2586            (
2587                mgf_digest,
2588                method
2589                    .mgf_algorithm
2590                    .as_deref()
2591                    .unwrap_or(mgf_digest.mgf_uri()),
2592            ),
2593        ];
2594        for (selected, wire_uri) in selected_algorithms {
2595            if policy
2596                .oaep_digests
2597                .as_ref()
2598                .is_some_and(|allowed| !allowed.contains(&selected))
2599            {
2600                return Err(crate::policy::PolicyViolation::Algorithm {
2601                    operation: "decryption",
2602                    algorithm: wire_uri.to_owned(),
2603                }
2604                .into());
2605            }
2606        }
2607    } else {
2608        let wrap = KeyWrapAlgorithm::from_uri(uri)?;
2609        if policy
2610            .key_wrap_algorithms
2611            .as_ref()
2612            .map_or(wrap.requires_explicit_permission(), |allowed| {
2613                !allowed.contains(&wrap)
2614            })
2615        {
2616            return Err(crate::policy::PolicyViolation::Algorithm {
2617                operation: "decryption",
2618                algorithm: uri.clone(),
2619            }
2620            .into());
2621        }
2622    }
2623    Ok(())
2624}
2625
2626fn validate_content_framing_before_resolution(
2627    algorithm: DataEncryptionAlgorithm,
2628    ciphertext_len: usize,
2629    encrypted_keys: &[EncryptedKey],
2630    policy: &crate::policy::DecryptionPolicy,
2631) -> Result<(), XmlEncError> {
2632    let Err(framing_error) = validate_ciphertext_framing(algorithm, ciphertext_len) else {
2633        return Ok(());
2634    };
2635
2636    // If no embedded key uses a supported transport, that envelope error is
2637    // more specific than content framing: the ciphertext cannot be interpreted
2638    // under any supported key path. This inspection performs no key resolution
2639    // and never dispatches malformed content to a cryptographic provider.
2640    if !encrypted_keys.is_empty() {
2641        let mut last_key_error = None;
2642        for encrypted_key in encrypted_keys {
2643            match validate_encrypted_key_policy(encrypted_key, policy) {
2644                Ok(()) => return Err(framing_error),
2645                Err(error) => last_key_error = Some(error),
2646            }
2647        }
2648        if let Some(error) = last_key_error {
2649            return Err(error);
2650        }
2651    }
2652    Err(framing_error)
2653}
2654
2655fn validate_typed_cipher_values(
2656    encrypted: &EncryptedData,
2657    algorithm: DataEncryptionAlgorithm,
2658    maximum_plaintext: usize,
2659    maximum_cipher_values: usize,
2660) -> Result<(), XmlEncError> {
2661    let maximum_ciphertext = algorithm
2662        .ciphertext_len_for_plaintext(maximum_plaintext)
2663        .ok_or(XmlEncError::InvalidEncryptionConfig(
2664            "ciphertext size overflow".into(),
2665        ))?;
2666    let projected = validate_cipher_data_len(&encrypted.cipher_data, maximum_ciphertext)?;
2667    if projected > maximum_ciphertext {
2668        return Err(crate::policy::PolicyViolation::ResourceLimit {
2669            resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2670            maximum: maximum_plaintext,
2671            actual: projected.saturating_sub(algorithm.minimum_ciphertext_len()),
2672        }
2673        .into());
2674    }
2675
2676    let mut aggregate_encoded = cipher_data_storage_len(&encrypted.cipher_data);
2677    if aggregate_encoded > maximum_cipher_values {
2678        return Err(crate::policy::PolicyViolation::ResourceLimit {
2679            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2680            maximum: maximum_cipher_values,
2681            actual: aggregate_encoded,
2682        }
2683        .into());
2684    }
2685
2686    let maximum_wrapped_key = projected_decoded_len_for_encoded_len(MAX_CIPHER_VALUE_BASE64_LEN);
2687    validate_encapsulation_cipher_values(
2688        &encrypted.encapsulation_methods,
2689        maximum_cipher_values,
2690        &mut aggregate_encoded,
2691    )?;
2692    validate_wrapped_cipher_values(
2693        &encrypted.encrypted_keys,
2694        maximum_wrapped_key,
2695        maximum_cipher_values,
2696        &mut aggregate_encoded,
2697    )
2698}
2699
2700fn validate_wrapped_cipher_values(
2701    keys: &[EncryptedKey],
2702    maximum_wrapped_key: usize,
2703    maximum_cipher_values: usize,
2704    aggregate_encoded: &mut usize,
2705) -> Result<(), XmlEncError> {
2706    for encrypted_key in keys {
2707        validate_cipher_data_len(&encrypted_key.cipher_data, maximum_wrapped_key)?;
2708        *aggregate_encoded =
2709            aggregate_encoded.saturating_add(cipher_data_storage_len(&encrypted_key.cipher_data));
2710        if *aggregate_encoded > maximum_cipher_values {
2711            return Err(crate::policy::PolicyViolation::ResourceLimit {
2712                resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2713                maximum: maximum_cipher_values,
2714                actual: *aggregate_encoded,
2715            }
2716            .into());
2717        }
2718        validate_encapsulation_cipher_values(
2719            &encrypted_key.sources.encapsulation_methods,
2720            maximum_cipher_values,
2721            aggregate_encoded,
2722        )?;
2723        validate_wrapped_cipher_values(
2724            &encrypted_key.sources.encrypted_keys,
2725            maximum_wrapped_key,
2726            maximum_cipher_values,
2727            aggregate_encoded,
2728        )?;
2729    }
2730    Ok(())
2731}
2732
2733fn validate_encapsulation_cipher_values(
2734    methods: &[crate::key_establishment::EncapsulationMechanism],
2735    maximum: usize,
2736    aggregate: &mut usize,
2737) -> Result<(), XmlEncError> {
2738    for method in methods {
2739        // Match XML parsing's encoded CipherValue accounting, even though
2740        // typed descriptors retain decoded public ciphertext bytes.
2741        let encoded = method.ciphertext.len().div_ceil(3).checked_mul(4);
2742        let total = encoded.and_then(|encoded| aggregate.checked_add(encoded));
2743        let total = total.ok_or(crate::policy::PolicyViolation::ResourceLimitExceeded {
2744            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2745            maximum,
2746        })?;
2747        if total > maximum {
2748            return Err(crate::policy::PolicyViolation::ResourceLimit {
2749                resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2750                maximum,
2751                actual: total,
2752            }
2753            .into());
2754        }
2755        *aggregate = total;
2756    }
2757    Ok(())
2758}
2759
2760fn cipher_data_storage_len(cipher: &super::CipherData) -> usize {
2761    match cipher {
2762        super::CipherData::Value { value } => value.len(),
2763        super::CipherData::Bytes(bytes) => bytes.len(),
2764        super::CipherData::Reference { .. } => 0,
2765    }
2766}
2767
2768fn validate_cipher_data_len(
2769    cipher: &super::CipherData,
2770    maximum: usize,
2771) -> Result<usize, XmlEncError> {
2772    match cipher {
2773        super::CipherData::Value { value } => validate_cipher_value_len(value, maximum),
2774        super::CipherData::Bytes(bytes) if bytes.len() <= maximum => Ok(bytes.len()),
2775        super::CipherData::Bytes(bytes) => Err(crate::policy::PolicyViolation::ResourceLimit {
2776            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2777            maximum,
2778            actual: bytes.len(),
2779        }
2780        .into()),
2781        super::CipherData::Reference { .. } => Ok(0),
2782    }
2783}
2784
2785fn validate_cipher_value_len(value: &str, maximum_decoded: usize) -> Result<usize, XmlEncError> {
2786    if value.len() > MAX_CIPHER_VALUE_BASE64_LEN {
2787        return Err(XmlEncError::InvalidStructure(format!(
2788            "CipherValue exceeds {MAX_CIPHER_VALUE_BASE64_LEN}-byte limit"
2789        )));
2790    }
2791    Ok(projected_decoded_len(value).min(maximum_decoded.saturating_add(1)))
2792}
2793
2794fn projected_decoded_len(value: &str) -> usize {
2795    let padding = value
2796        .as_bytes()
2797        .iter()
2798        .rev()
2799        .take(2)
2800        .take_while(|byte| **byte == b'=')
2801        .count();
2802    projected_decoded_len_for_encoded_len(value.len()).saturating_sub(padding)
2803}
2804
2805fn projected_decoded_len_for_encoded_len(encoded_len: usize) -> usize {
2806    encoded_len
2807        .checked_add(3)
2808        .map(|length| length / 4)
2809        .and_then(|quanta| quanta.checked_mul(3))
2810        .unwrap_or(usize::MAX)
2811}
2812
2813pub(crate) fn validate_key_len(
2814    algorithm: DataEncryptionAlgorithm,
2815    key: &[u8],
2816) -> Result<(), XmlEncError> {
2817    validate_content_key_len(algorithm, key.len())
2818}
2819
2820fn validate_content_key_len(
2821    algorithm: DataEncryptionAlgorithm,
2822    actual: usize,
2823) -> Result<(), XmlEncError> {
2824    if actual == algorithm.key_len() {
2825        Ok(())
2826    } else {
2827        Err(XmlEncError::InvalidKeySize {
2828            algorithm,
2829            expected: algorithm.key_len(),
2830            actual,
2831        })
2832    }
2833}
2834
2835fn validate_possible_plaintext_len(
2836    algorithm: DataEncryptionAlgorithm,
2837    ciphertext_len: usize,
2838    maximum: usize,
2839) -> Result<(), XmlEncError> {
2840    // CBC's minimum includes an IV and one padded block; using that
2841    // maximum-padding case yields the safe pre-decryption plaintext lower bound.
2842    let framing = algorithm.minimum_ciphertext_len();
2843    validate_plaintext_len(ciphertext_len.saturating_sub(framing), maximum)
2844}
2845
2846fn validate_provider_plaintext_len(
2847    algorithm: DataEncryptionAlgorithm,
2848    ciphertext_len: usize,
2849    plaintext_len: usize,
2850) -> Result<(), XmlEncError> {
2851    use crate::provider::{ProviderError, ProviderOperation};
2852
2853    match algorithm.cbc_block_len() {
2854        None => {
2855            let expected = ciphertext_len - algorithm.minimum_ciphertext_len();
2856            if plaintext_len != expected {
2857                return Err(ProviderError::InvalidOutputSize {
2858                    operation: ProviderOperation::Decrypt,
2859                    expected,
2860                    actual: plaintext_len,
2861                }
2862                .into());
2863            }
2864        }
2865        Some(block) => {
2866            let padded_len = ciphertext_len - block;
2867            let minimum = padded_len - block;
2868            let maximum = padded_len - 1;
2869            if !(minimum..=maximum).contains(&plaintext_len) {
2870                return Err(ProviderError::InvalidOutputSizeRange {
2871                    operation: ProviderOperation::Decrypt,
2872                    minimum,
2873                    maximum,
2874                    actual: plaintext_len,
2875                }
2876                .into());
2877            }
2878        }
2879    }
2880    Ok(())
2881}
2882
2883fn validate_plaintext_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
2884    if actual <= maximum {
2885        Ok(())
2886    } else {
2887        Err(crate::policy::PolicyViolation::ResourceLimit {
2888            resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2889            maximum,
2890            actual,
2891        }
2892        .into())
2893    }
2894}
2895
2896fn map_data_decryption_error(
2897    algorithm: DataEncryptionAlgorithm,
2898    ciphertext_len: usize,
2899    error: crate::provider::ProviderError,
2900) -> XmlEncError {
2901    use crate::provider::ProviderError;
2902
2903    match error {
2904        ProviderError::AuthenticationFailed if algorithm.cbc_block_len().is_none() => {
2905            XmlEncError::AeadAuthenticationFailed
2906        }
2907        ProviderError::InvalidInput(crate::provider::ProviderInputError::AesGcmFraming)
2908            if algorithm.cbc_block_len().is_none() =>
2909        {
2910            XmlEncError::DataTooShort {
2911                algorithm: "AES-GCM",
2912                minimum: 28,
2913                actual: ciphertext_len,
2914            }
2915        }
2916        error @ ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcFraming)
2917            if algorithm.cbc_block_len().is_some() =>
2918        {
2919            match super::types::validate_ciphertext_framing(algorithm, ciphertext_len) {
2920                Err(error) => error,
2921                Ok(()) => XmlEncError::Provider(error),
2922            }
2923        }
2924        ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcCiphertext)
2925            if algorithm.cbc_block_len().is_some() =>
2926        {
2927            XmlEncError::InvalidPadding
2928        }
2929        error => XmlEncError::Provider(error),
2930    }
2931}
2932
2933#[cfg(test)]
2934mod tests {
2935    #[test]
2936    fn typed_reference_association_obeys_id_semantics() {
2937        // A typed decryption input must not bypass the URI policy enforced by
2938        // the retained-document path, including escaping and Visa3D opt-in.
2939        use crate::policy::SameDocumentIdSemantics as Semantics;
2940        for semantics in [
2941            Semantics::Specification,
2942            Semantics::XmlSecBarename,
2943            Semantics::XmlSecVisa3d,
2944        ] {
2945            assert!(!super::reference_targets_id("#", "", semantics));
2946        }
2947        assert!(super::reference_targets_id(
2948            "#tar%67et",
2949            "target",
2950            Semantics::Specification
2951        ));
2952        assert!(super::reference_targets_id(
2953            "#xpointer(id('target'))",
2954            "target",
2955            Semantics::Specification
2956        ));
2957        assert!(!super::reference_targets_id(
2958            "#123",
2959            "123",
2960            Semantics::Specification
2961        ));
2962        assert!(super::reference_targets_id(
2963            "#123",
2964            "123",
2965            Semantics::XmlSecVisa3d
2966        ));
2967        assert!(!super::reference_targets_id(
2968            "#xpointer(id('a' or 'b'))",
2969            "a' or 'b",
2970            Semantics::Specification
2971        ));
2972    }
2973    #[cfg(feature = "legacy-algorithms")]
2974    #[test]
2975    fn legacy_cbc_rejects_ambiguous_candidates_before_decryption() {
2976        // CBC has no authentication tag: selecting a candidate by padding
2977        // success is unsafe for DES and AES-192 just as for existing AES modes.
2978        for algorithm in [
2979            super::DataEncryptionAlgorithm::TripleDesCbc,
2980            super::DataEncryptionAlgorithm::Aes192Cbc,
2981        ] {
2982            assert!(matches!(
2983                super::validate_decryption_key_candidates(algorithm, 2),
2984                Err(super::XmlEncError::AmbiguousKeyCandidates { actual: 2, .. })
2985            ));
2986        }
2987    }
2988    use std::cell::{Cell, RefCell};
2989    use std::sync::atomic::{AtomicUsize, Ordering};
2990
2991    use crate::rsa_encoding::RsaPrivateKeyEncoding as _;
2992    use aes_gcm::{
2993        Aes128Gcm,
2994        aead::{AeadInOut, KeyInit},
2995    };
2996    use aes_kw::KwAes128;
2997    use base64::engine::general_purpose::STANDARD;
2998    use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
2999    use rsa::{Oaep, RsaPublicKey};
3000    use sha1::Sha1;
3001    use sha2::{Sha256, Sha384};
3002
3003    use super::*;
3004    use crate::xmlenc::{CipherData, EncryptionMethod};
3005
3006    struct RecipientKeyResolver {
3007        recipient: &'static str,
3008        key: Vec<u8>,
3009    }
3010
3011    struct CountingResolver {
3012        candidate_calls: Cell<usize>,
3013        key: Vec<u8>,
3014    }
3015
3016    struct AllCallsResolver {
3017        calls: Cell<usize>,
3018        key: Vec<u8>,
3019    }
3020
3021    struct CandidateResolver {
3022        keys: Vec<Vec<u8>>,
3023    }
3024
3025    #[test]
3026    fn policy_unaware_recipient_resolver_is_not_dispatched() {
3027        // An already-recovered AES key cannot prove that its RSA source met
3028        // the operation's minimum. Reject the legacy recipient path before
3029        // lookup, while keeping direct symmetric resolution available.
3030        struct LegacyRecipient {
3031            recipient_calls: Cell<usize>,
3032            key: Vec<u8>,
3033        }
3034        impl DecryptionKeyResolver for LegacyRecipient {
3035            fn resolve_key(
3036                &self,
3037                _provider: &dyn crate::provider::CryptoProvider,
3038                _algorithm: DataEncryptionAlgorithm,
3039                encrypted_key: Option<&EncryptedKey>,
3040            ) -> Result<Vec<u8>, XmlEncError> {
3041                if encrypted_key.is_none() {
3042                    return Err(XmlEncError::KeyNotFound);
3043                }
3044                self.recipient_calls.set(self.recipient_calls.get() + 1);
3045                Ok(self.key.clone())
3046            }
3047        }
3048        let resolver = LegacyRecipient {
3049            recipient_calls: Cell::new(0),
3050            key: vec![0x63; 16],
3051        };
3052        let encrypted = encrypted_data_with_recipients(
3053            &resolver.key,
3054            vec![associated_encrypted_key("recipient", None, None)],
3055            None,
3056        );
3057        assert!(matches!(
3058            DecryptContext::new(&resolver).decrypt_data(&encrypted),
3059            Err(XmlEncError::KeyNotFound)
3060        ));
3061        assert_eq!(resolver.recipient_calls.get(), 0);
3062
3063        let direct = SymmetricKeyDecryptor::new(resolver.key.clone());
3064        let mut budget = KeyCandidateBudget::with_limit(1);
3065        assert!(matches!(
3066            resolver.resolve_key_candidates_with_policy(
3067                crate::provider::default_provider(),
3068                DataEncryptionAlgorithm::Aes128Gcm,
3069                encrypted.encrypted_keys.first(),
3070                &crate::policy::DecryptionPolicy::default(),
3071                &mut budget,
3072            ),
3073            Err(XmlEncError::KeyNotFound)
3074        ));
3075        assert_eq!(budget.remaining(), 1, "no recipient work was performed");
3076        assert_eq!(
3077            DecryptContext::new(&direct)
3078                .decrypt_data(&encrypted)
3079                .expect("direct AES does not require RSA metadata"),
3080            DecryptedContent::Bytes(b"payload".to_vec())
3081        );
3082    }
3083
3084    #[test]
3085    fn document_decryption_initial_parse_uses_the_policy_work_budget() {
3086        // Candidate retries and replacement validation must inherit the same
3087        // allowance consumed by the caller document's initial parse.
3088        let xml = "<root/>";
3089        let policy = crate::policy::DecryptionPolicy {
3090            resources: crate::policy::ResourcePolicy {
3091                max_xml_parse_work_bytes: 0,
3092                ..crate::policy::ResourcePolicy::default()
3093            },
3094            ..crate::policy::DecryptionPolicy::default()
3095        };
3096        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
3097
3098        let error = DecryptContext::new(&resolver)
3099            .policy(policy)
3100            .decrypt_document(xml, None)
3101            .expect_err("a zero parse-work budget must reject the input parse");
3102
3103        assert!(matches!(
3104            error,
3105            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3106                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3107                maximum: 0,
3108                actual,
3109            }) if actual == xml.len()
3110        ));
3111    }
3112
3113    struct AggregateRecipientResolver {
3114        attempts: Cell<usize>,
3115        key: Vec<u8>,
3116    }
3117
3118    struct AssociationRecordingResolver {
3119        visited: RefCell<Vec<String>>,
3120        key: Vec<u8>,
3121    }
3122
3123    struct OrderedRecipientResolver {
3124        wrong: Vec<u8>,
3125        correct: Vec<u8>,
3126    }
3127
3128    struct DirectAndRecipientResolver {
3129        direct: Vec<u8>,
3130        recipient: Vec<u8>,
3131    }
3132
3133    struct FailingDirectResolver {
3134        recipient: Vec<u8>,
3135    }
3136
3137    struct PolicyRejectingDirectResolver {
3138        recipient: Vec<u8>,
3139    }
3140
3141    struct MislabelledExhaustionResolver {
3142        direct: Vec<u8>,
3143    }
3144
3145    impl DecryptionKeyResolver for DirectAndRecipientResolver {
3146        fn resolve_key_candidates_with_policy(
3147            &self,
3148            provider: &dyn crate::provider::CryptoProvider,
3149            algorithm: DataEncryptionAlgorithm,
3150            encrypted_key: Option<&EncryptedKey>,
3151            policy: &crate::policy::DecryptionPolicy,
3152            budget: &mut KeyCandidateBudget,
3153        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3154            // Prepared test keys simulate source ordering, not RSA recovery.
3155            policy.validate()?;
3156            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3157        }
3158
3159        fn resolve_key(
3160            &self,
3161            _provider: &dyn crate::provider::CryptoProvider,
3162            _algorithm: DataEncryptionAlgorithm,
3163            encrypted_key: Option<&EncryptedKey>,
3164        ) -> Result<Vec<u8>, XmlEncError> {
3165            Ok(if encrypted_key.is_some() {
3166                self.recipient.clone()
3167            } else {
3168                self.direct.clone()
3169            })
3170        }
3171    }
3172
3173    impl DecryptionKeyResolver for FailingDirectResolver {
3174        fn resolve_key_candidates_with_policy(
3175            &self,
3176            provider: &dyn crate::provider::CryptoProvider,
3177            algorithm: DataEncryptionAlgorithm,
3178            encrypted_key: Option<&EncryptedKey>,
3179            policy: &crate::policy::DecryptionPolicy,
3180            budget: &mut KeyCandidateBudget,
3181        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3182            // Prepared test keys simulate lookup errors, not RSA recovery.
3183            policy.validate()?;
3184            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3185        }
3186
3187        fn resolve_key(
3188            &self,
3189            _provider: &dyn crate::provider::CryptoProvider,
3190            algorithm: DataEncryptionAlgorithm,
3191            encrypted_key: Option<&EncryptedKey>,
3192        ) -> Result<Vec<u8>, XmlEncError> {
3193            if encrypted_key.is_some() {
3194                Ok(self.recipient.clone())
3195            } else {
3196                Err(XmlEncError::InvalidKeySize {
3197                    algorithm,
3198                    expected: 16,
3199                    actual: 8,
3200                })
3201            }
3202        }
3203    }
3204
3205    impl DecryptionKeyResolver for PolicyRejectingDirectResolver {
3206        fn resolve_key(
3207            &self,
3208            _provider: &dyn crate::provider::CryptoProvider,
3209            _algorithm: DataEncryptionAlgorithm,
3210            encrypted_key: Option<&EncryptedKey>,
3211        ) -> Result<Vec<u8>, XmlEncError> {
3212            if encrypted_key.is_some() {
3213                Ok(self.recipient.clone())
3214            } else {
3215                Err(crate::policy::PolicyViolation::KeyTrust {
3216                    reason: "test resolver rejected the operation",
3217                }
3218                .into())
3219            }
3220        }
3221    }
3222
3223    impl DecryptionKeyResolver for MislabelledExhaustionResolver {
3224        fn resolve_key(
3225            &self,
3226            _provider: &dyn crate::provider::CryptoProvider,
3227            _algorithm: DataEncryptionAlgorithm,
3228            _encrypted_key: Option<&EncryptedKey>,
3229        ) -> Result<Vec<u8>, XmlEncError> {
3230            Err(XmlEncError::KeyNotFound)
3231        }
3232
3233        fn resolve_key_candidates_with_policy(
3234            &self,
3235            _provider: &dyn crate::provider::CryptoProvider,
3236            _algorithm: DataEncryptionAlgorithm,
3237            encrypted_key: Option<&EncryptedKey>,
3238            policy: &crate::policy::DecryptionPolicy,
3239            budget: &mut KeyCandidateBudget,
3240        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3241            // Prepared test keys exercise operation-wide candidate accounting.
3242            policy.validate()?;
3243            if encrypted_key.is_none() {
3244                budget.consume(1)?;
3245                return Ok(vec![self.direct.clone()]);
3246            }
3247            budget.consume(budget.remaining().saturating_add(1))?;
3248            unreachable!("candidate budget exhaustion must return first")
3249        }
3250    }
3251
3252    impl DecryptionKeyResolver for OrderedRecipientResolver {
3253        fn resolve_key(
3254            &self,
3255            _provider: &dyn crate::provider::CryptoProvider,
3256            _algorithm: DataEncryptionAlgorithm,
3257            _encrypted_key: Option<&EncryptedKey>,
3258        ) -> Result<Vec<u8>, XmlEncError> {
3259            Err(XmlEncError::KeyNotFound)
3260        }
3261
3262        fn resolve_key_candidates_with_policy(
3263            &self,
3264            _provider: &dyn crate::provider::CryptoProvider,
3265            _algorithm: DataEncryptionAlgorithm,
3266            encrypted_key: Option<&EncryptedKey>,
3267            policy: &crate::policy::DecryptionPolicy,
3268            budget: &mut KeyCandidateBudget,
3269        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3270            // Prepared test keys simulate authenticated candidate ordering.
3271            policy.validate()?;
3272            budget.consume(1)?;
3273            match encrypted_key.and_then(|key| key.id.as_deref()) {
3274                Some("first") => Ok(vec![self.wrong.clone()]),
3275                Some("second") => Ok(vec![self.correct.clone()]),
3276                _ => Err(XmlEncError::KeyNotFound),
3277            }
3278        }
3279    }
3280
3281    impl DecryptionKeyResolver for CandidateResolver {
3282        fn resolve_key(
3283            &self,
3284            _provider: &dyn crate::provider::CryptoProvider,
3285            _algorithm: DataEncryptionAlgorithm,
3286            _encrypted_key: Option<&EncryptedKey>,
3287        ) -> Result<Vec<u8>, XmlEncError> {
3288            Err(XmlEncError::KeyNotFound)
3289        }
3290
3291        fn resolve_key_candidates(
3292            &self,
3293            _provider: &dyn crate::provider::CryptoProvider,
3294            _algorithm: DataEncryptionAlgorithm,
3295            encrypted_key: Option<&EncryptedKey>,
3296            budget: &mut KeyCandidateBudget,
3297        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3298            if encrypted_key.is_none() {
3299                budget.consume(self.keys.len())?;
3300                Ok(self.keys.clone())
3301            } else {
3302                Err(XmlEncError::KeyNotFound)
3303            }
3304        }
3305    }
3306
3307    impl DecryptionKeyResolver for AggregateRecipientResolver {
3308        fn resolve_key(
3309            &self,
3310            _provider: &dyn crate::provider::CryptoProvider,
3311            _algorithm: DataEncryptionAlgorithm,
3312            _encrypted_key: Option<&EncryptedKey>,
3313        ) -> Result<Vec<u8>, XmlEncError> {
3314            Err(XmlEncError::KeyNotFound)
3315        }
3316
3317        fn resolve_key_candidates_with_policy(
3318            &self,
3319            _provider: &dyn crate::provider::CryptoProvider,
3320            _algorithm: DataEncryptionAlgorithm,
3321            encrypted_key: Option<&EncryptedKey>,
3322            policy: &crate::policy::DecryptionPolicy,
3323            budget: &mut KeyCandidateBudget,
3324        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3325            // Prepared test keys isolate aggregate work from RSA primitives.
3326            policy.validate()?;
3327            let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
3328            let attempts = budget.remaining();
3329            if attempts == 0 {
3330                budget.consume(1)?;
3331            }
3332            budget.consume(attempts)?;
3333            self.attempts.set(self.attempts.get() + attempts);
3334            if encrypted_key.id.as_deref() == Some("first") {
3335                Err(XmlEncError::KeyNotFound)
3336            } else {
3337                Ok(vec![self.key.clone()])
3338            }
3339        }
3340    }
3341
3342    impl DecryptionKeyResolver for AssociationRecordingResolver {
3343        fn resolve_key(
3344            &self,
3345            _provider: &dyn crate::provider::CryptoProvider,
3346            _algorithm: DataEncryptionAlgorithm,
3347            _encrypted_key: Option<&EncryptedKey>,
3348        ) -> Result<Vec<u8>, XmlEncError> {
3349            Err(XmlEncError::KeyNotFound)
3350        }
3351
3352        fn resolve_key_candidates_with_policy(
3353            &self,
3354            _provider: &dyn crate::provider::CryptoProvider,
3355            _algorithm: DataEncryptionAlgorithm,
3356            encrypted_key: Option<&EncryptedKey>,
3357            policy: &crate::policy::DecryptionPolicy,
3358            budget: &mut KeyCandidateBudget,
3359        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3360            // Prepared test keys isolate XMLEnc association selection.
3361            policy.validate()?;
3362            let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
3363            budget.consume(1)?;
3364            self.visited
3365                .borrow_mut()
3366                .push(encrypted_key.id.clone().unwrap_or_default());
3367            Ok(vec![self.key.clone()])
3368        }
3369    }
3370
3371    fn associated_encrypted_key(
3372        id: &str,
3373        data_reference: Option<&str>,
3374        carried_key_name: Option<&str>,
3375    ) -> EncryptedKey {
3376        EncryptedKey {
3377            id: Some(id.into()),
3378            sources: Default::default(),
3379            recipient: None,
3380            key_name: None,
3381            encryption_method: EncryptionMethod {
3382                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
3383                key_size_bits: None,
3384                oaep_digest: None,
3385                mgf_algorithm: None,
3386                oaep_params: None,
3387            },
3388            cipher_data: CipherData::Value {
3389                value: STANDARD.encode([0_u8; 256]),
3390            },
3391            reference_list: data_reference.map(|uri| crate::xmlenc::ReferenceList {
3392                data_references: vec![uri.into()],
3393                key_references: Vec::new(),
3394            }),
3395            carried_key_name: carried_key_name.map(str::to_owned),
3396        }
3397    }
3398
3399    fn encrypted_data_with_recipients(
3400        key: &[u8],
3401        encrypted_keys: Vec<EncryptedKey>,
3402        key_name: Option<&str>,
3403    ) -> EncryptedData {
3404        EncryptedData {
3405            encapsulation_methods: Vec::new(),
3406            id: Some("target".into()),
3407            derived_keys: Vec::new(),
3408            agreement_methods: Vec::new(),
3409            encrypted_type: None,
3410            encryption_method: EncryptionMethod {
3411                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3412                key_size_bits: None,
3413                oaep_digest: None,
3414                mgf_algorithm: None,
3415                oaep_params: None,
3416            },
3417            key_name: key_name.map(str::to_owned),
3418            encrypted_keys,
3419            cipher_data: CipherData::Value {
3420                value: STANDARD.encode(
3421                    crate::provider::default_provider()
3422                        .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, key, b"payload")
3423                        .expect("test encryption must succeed"),
3424                ),
3425            },
3426        }
3427    }
3428
3429    #[derive(Debug, Default)]
3430    struct PermissiveUnwrapProvider {
3431        decrypt_calls: AtomicUsize,
3432        unwrap_calls: AtomicUsize,
3433        recover_calls: AtomicUsize,
3434        plaintext: Vec<u8>,
3435        candidate_plaintexts: Vec<Vec<u8>>,
3436    }
3437
3438    struct OpaqueRecoveryKey;
3439
3440    impl crate::provider::KeyRecoveryKey for OpaqueRecoveryKey {
3441        fn rsa_modulus_bits(&self) -> usize {
3442            2048
3443        }
3444        fn rsa_public_exponent(&self) -> Option<u64> {
3445            Some(65537)
3446        }
3447        fn ciphertext_len(&self) -> usize {
3448            256
3449        }
3450
3451        fn recover_with_provider(
3452            &self,
3453            _provider: &dyn crate::provider::CryptoProvider,
3454            _parameters: &RsaOaepParameters,
3455            _ciphertext: &[u8],
3456        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3457            panic!("custom provider must own recovery for its opaque key")
3458        }
3459    }
3460
3461    impl crate::provider::CryptoProvider for PermissiveUnwrapProvider {
3462        #[cfg(feature = "legacy-algorithms")]
3463        fn recover_pkcs1v15(
3464            &self,
3465            key: &dyn crate::provider::KeyRecoveryKey,
3466            ciphertext: &[u8],
3467            key_len: usize,
3468        ) -> Result<crate::provider::RecoveredContentKey, crate::provider::ProviderError> {
3469            key.recover_pkcs1v15(self, ciphertext, key_len)
3470        }
3471        fn name(&self) -> &'static str {
3472            "permissive-unwrap-test"
3473        }
3474
3475        fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
3476            crate::provider::CryptoProvider::supports(
3477                &crate::provider::RustCryptoProvider,
3478                capability,
3479            )
3480        }
3481
3482        fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
3483            crate::provider::CryptoProvider::fill_random(
3484                &crate::provider::RustCryptoProvider,
3485                output,
3486            )
3487        }
3488
3489        fn derive_key(
3490            &self,
3491            parameters: &crate::provider::KdfParameters<'_>,
3492            secret: &[u8],
3493        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3494            crate::provider::RustCryptoProvider.derive_key(parameters, secret)
3495        }
3496
3497        #[cfg(feature = "xmldsig")]
3498        fn digest(
3499            &self,
3500            algorithm: crate::xmldsig::DigestAlgorithm,
3501            data: &[u8],
3502        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3503            crate::provider::CryptoProvider::digest(
3504                &crate::provider::RustCryptoProvider,
3505                algorithm,
3506                data,
3507            )
3508        }
3509
3510        #[cfg(feature = "xmldsig")]
3511        fn sign(
3512            &self,
3513            key: &dyn crate::xmldsig::SigningKey,
3514            algorithm: crate::xmldsig::SignatureAlgorithm,
3515            data: &[u8],
3516        ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
3517            crate::provider::CryptoProvider::sign(
3518                &crate::provider::RustCryptoProvider,
3519                key,
3520                algorithm,
3521                data,
3522            )
3523        }
3524
3525        #[cfg(feature = "xmldsig")]
3526        fn verify(
3527            &self,
3528            key: &dyn crate::xmldsig::VerifyingKey,
3529            algorithm: crate::xmldsig::SignatureAlgorithm,
3530            data: &[u8],
3531            signature: &[u8],
3532        ) -> Result<bool, crate::xmldsig::DsigError> {
3533            crate::provider::CryptoProvider::verify(
3534                &crate::provider::RustCryptoProvider,
3535                key,
3536                algorithm,
3537                data,
3538                signature,
3539            )
3540        }
3541
3542        fn encrypt_data(
3543            &self,
3544            algorithm: DataEncryptionAlgorithm,
3545            key: &[u8],
3546            plaintext: &[u8],
3547        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3548            crate::provider::CryptoProvider::encrypt_data(
3549                &crate::provider::RustCryptoProvider,
3550                algorithm,
3551                key,
3552                plaintext,
3553            )
3554        }
3555
3556        fn decrypt_data(
3557            &self,
3558            _algorithm: DataEncryptionAlgorithm,
3559            _key: &[u8],
3560            _ciphertext: &[u8],
3561        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3562            let index = self.decrypt_calls.fetch_add(1, Ordering::Relaxed);
3563            Ok(self
3564                .candidate_plaintexts
3565                .get(index)
3566                .unwrap_or(&self.plaintext)
3567                .clone())
3568        }
3569
3570        fn wrap_key(
3571            &self,
3572            algorithm: KeyWrapAlgorithm,
3573            kek: &[u8],
3574            key: &[u8],
3575        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3576            crate::provider::CryptoProvider::wrap_key(
3577                &crate::provider::RustCryptoProvider,
3578                algorithm,
3579                kek,
3580                key,
3581            )
3582        }
3583
3584        fn unwrap_key(
3585            &self,
3586            _algorithm: KeyWrapAlgorithm,
3587            _kek: &[u8],
3588            _wrapped: &[u8],
3589        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3590            self.unwrap_calls.fetch_add(1, Ordering::Relaxed);
3591            Ok(vec![0_u8; 16])
3592        }
3593
3594        fn transport_key(
3595            &self,
3596            key: &dyn crate::provider::KeyTransportKey,
3597            parameters: &RsaOaepParameters,
3598            plaintext: &[u8],
3599        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3600            crate::provider::CryptoProvider::transport_key(
3601                &crate::provider::RustCryptoProvider,
3602                key,
3603                parameters,
3604                plaintext,
3605            )
3606        }
3607
3608        fn recover_key(
3609            &self,
3610            _key: &dyn crate::provider::KeyRecoveryKey,
3611            _parameters: &RsaOaepParameters,
3612            _ciphertext: &[u8],
3613        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3614            self.recover_calls.fetch_add(1, Ordering::Relaxed);
3615            Ok(vec![0_u8; 16])
3616        }
3617    }
3618
3619    impl DecryptionKeyResolver for CountingResolver {
3620        fn resolve_encapsulation_content_keys_with_policy(
3621            &self,
3622            _: &dyn crate::provider::CryptoProvider,
3623            _: DataEncryptionAlgorithm,
3624            _: &crate::key_establishment::EncapsulationMechanism,
3625            _: &crate::policy::DecryptionPolicy,
3626            _: &mut KeyCandidateBudget,
3627        ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
3628            self.candidate_calls.set(self.candidate_calls.get() + 1);
3629            Ok(vec![crate::provider::RecoveredContentKey::confirmed(
3630                self.key.clone(),
3631            )])
3632        }
3633
3634        fn resolve_key_candidates_with_policy(
3635            &self,
3636            provider: &dyn crate::provider::CryptoProvider,
3637            algorithm: DataEncryptionAlgorithm,
3638            encrypted_key: Option<&EncryptedKey>,
3639            policy: &crate::policy::DecryptionPolicy,
3640            budget: &mut KeyCandidateBudget,
3641        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3642            // Prepared test keys isolate validation-before-lookup ordering.
3643            policy.validate()?;
3644            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3645        }
3646
3647        fn resolve_key(
3648            &self,
3649            _provider: &dyn crate::provider::CryptoProvider,
3650            _algorithm: DataEncryptionAlgorithm,
3651            encrypted_key: Option<&EncryptedKey>,
3652        ) -> Result<Vec<u8>, XmlEncError> {
3653            if encrypted_key.is_some() {
3654                self.candidate_calls.set(self.candidate_calls.get() + 1);
3655                Ok(self.key.clone())
3656            } else {
3657                Err(XmlEncError::KeyNotFound)
3658            }
3659        }
3660    }
3661
3662    impl DecryptionKeyResolver for AllCallsResolver {
3663        fn resolve_key(
3664            &self,
3665            _provider: &dyn crate::provider::CryptoProvider,
3666            _algorithm: DataEncryptionAlgorithm,
3667            _encrypted_key: Option<&EncryptedKey>,
3668        ) -> Result<Vec<u8>, XmlEncError> {
3669            self.calls.set(self.calls.get() + 1);
3670            Ok(self.key.clone())
3671        }
3672    }
3673
3674    impl DecryptionKeyResolver for RecipientKeyResolver {
3675        fn resolve_key_candidates_with_policy(
3676            &self,
3677            provider: &dyn crate::provider::CryptoProvider,
3678            algorithm: DataEncryptionAlgorithm,
3679            encrypted_key: Option<&EncryptedKey>,
3680            policy: &crate::policy::DecryptionPolicy,
3681            budget: &mut KeyCandidateBudget,
3682        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3683            // Prepared test keys simulate recipient labels, not RSA recovery.
3684            policy.validate()?;
3685            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3686        }
3687
3688        fn resolve_key(
3689            &self,
3690            _provider: &dyn crate::provider::CryptoProvider,
3691            _algorithm: DataEncryptionAlgorithm,
3692            encrypted_key: Option<&EncryptedKey>,
3693        ) -> Result<Vec<u8>, XmlEncError> {
3694            if encrypted_key.and_then(|key| key.recipient.as_deref()) == Some(self.recipient) {
3695                Ok(self.key.clone())
3696            } else {
3697                Err(XmlEncError::KeyNotFound)
3698            }
3699        }
3700    }
3701
3702    #[test]
3703    fn decrypts_gcm_and_rejects_tampering() {
3704        // Authentication must cover the complete ciphertext and tag before plaintext returns.
3705        let key = [7_u8; 16];
3706        let nonce = [9_u8; 12];
3707        let mut ciphertext = b"<Assertion>trusted</Assertion>".to_vec();
3708        Aes128Gcm::new_from_slice(&key)
3709            .expect("fixed key length")
3710            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
3711            .expect("test encryption must succeed");
3712        let mut wire = nonce.to_vec();
3713        wire.extend_from_slice(&ciphertext);
3714        let xml = format!(
3715            "<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>",
3716            STANDARD.encode(&wire)
3717        );
3718        let decrypted = decrypt(&xml, &SymmetricKeyDecryptor::new(key))
3719            .expect("valid AES-GCM XML must decrypt");
3720        assert_eq!(
3721            decrypted,
3722            DecryptedContent::Xml("<Assertion>trusted</Assertion>".into())
3723        );
3724        let last = wire.len() - 1;
3725        wire[last] ^= 1;
3726        let tampered = xml.replace(&STANDARD.encode(ciphertext), &STANDARD.encode(&wire[12..]));
3727        assert!(matches!(
3728            decrypt(&tampered, &SymmetricKeyDecryptor::new(key)),
3729            Err(XmlEncError::AeadAuthenticationFailed)
3730        ));
3731    }
3732
3733    #[test]
3734    fn candidate_keys_retry_only_authenticated_decryption() {
3735        // Candidate selection belongs inside one prepared decryption operation:
3736        // structural validation and ciphertext decoding must not be repeated.
3737        let key = [7_u8; 16];
3738        let nonce = [9_u8; 12];
3739        let mut ciphertext = b"candidate plaintext".to_vec();
3740        Aes128Gcm::new_from_slice(&key)
3741            .expect("fixed key length")
3742            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
3743            .expect("test encryption must succeed");
3744        let mut wire = nonce.to_vec();
3745        wire.extend_from_slice(&ciphertext);
3746        let encrypted = EncryptedData {
3747            encapsulation_methods: Vec::new(),
3748            id: None,
3749            derived_keys: Vec::new(),
3750            agreement_methods: Vec::new(),
3751            encrypted_type: None,
3752            encryption_method: EncryptionMethod {
3753                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3754                key_size_bits: None,
3755                oaep_digest: None,
3756                mgf_algorithm: None,
3757                oaep_params: None,
3758            },
3759            key_name: None,
3760            encrypted_keys: Vec::new(),
3761            cipher_data: CipherData::Value {
3762                value: STANDARD.encode(wire),
3763            },
3764        };
3765        let resolver = CandidateResolver {
3766            keys: vec![vec![1_u8; 16], key.to_vec()],
3767        };
3768
3769        let decrypted = DecryptContext::new(&resolver)
3770            .decrypt_data(&encrypted)
3771            .expect("a later authenticated candidate must decrypt");
3772
3773        assert_eq!(
3774            decrypted,
3775            DecryptedContent::Bytes(b"candidate plaintext".to_vec())
3776        );
3777    }
3778
3779    #[test]
3780    fn standalone_decryption_stops_after_first_successful_candidate() {
3781        // A successful authenticated candidate is the final standalone result;
3782        // later keys must not cause redundant decryptions or retained plaintexts.
3783        let provider = PermissiveUnwrapProvider {
3784            plaintext: b"accepted".to_vec(),
3785            ..PermissiveUnwrapProvider::default()
3786        };
3787        let resolver = CandidateResolver {
3788            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
3789        };
3790        let encrypted = EncryptedData {
3791            encapsulation_methods: Vec::new(),
3792            id: None,
3793            derived_keys: Vec::new(),
3794            agreement_methods: Vec::new(),
3795            encrypted_type: None,
3796            encryption_method: EncryptionMethod {
3797                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3798                key_size_bits: None,
3799                oaep_digest: None,
3800                mgf_algorithm: None,
3801                oaep_params: None,
3802            },
3803            key_name: None,
3804            encrypted_keys: Vec::new(),
3805            cipher_data: CipherData::Value {
3806                value: STANDARD.encode([0_u8; 36]),
3807            },
3808        };
3809
3810        let result = DecryptContext::new(&resolver)
3811            .provider(&provider)
3812            .decrypt_data(&encrypted)
3813            .expect("the first successful candidate must be returned");
3814
3815        assert_eq!(result, DecryptedContent::Bytes(b"accepted".to_vec()));
3816        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
3817    }
3818
3819    #[test]
3820    fn document_decryption_discards_rejected_plaintext_before_next_candidate() {
3821        // Replacement validation may reject authenticated plaintext. The next
3822        // key is then tried, but candidates after the first valid XML stay unused.
3823        let provider = PermissiveUnwrapProvider {
3824            candidate_plaintexts: vec![b"<bad".to_vec(), b"<x/>".to_vec(), b"<u/>".to_vec()],
3825            ..PermissiveUnwrapProvider::default()
3826        };
3827        let resolver = CandidateResolver {
3828            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
3829        };
3830        let encrypted = format!(
3831            "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\" Type=\"{XMLENC_NS}Element\"><xenc:EncryptionMethod Algorithm=\"{}\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
3832            DataEncryptionAlgorithm::Aes128Gcm.uri(),
3833            STANDARD.encode([0_u8; 32]),
3834        );
3835
3836        let result = DecryptContext::new(&resolver)
3837            .provider(&provider)
3838            .decrypt_document(&encrypted, None)
3839            .expect("a later candidate with valid replacement XML must succeed");
3840
3841        assert_eq!(result, "<x/>");
3842        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 2);
3843    }
3844
3845    #[test]
3846    fn cbc_rejects_multiple_unordered_key_candidates() {
3847        // CBC padding cannot authenticate which candidate key is correct. A
3848        // resolver must select one key from trusted metadata before decryption.
3849        let encrypted = EncryptedData {
3850            encapsulation_methods: Vec::new(),
3851            id: None,
3852            derived_keys: Vec::new(),
3853            agreement_methods: Vec::new(),
3854            encrypted_type: None,
3855            encryption_method: EncryptionMethod {
3856                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
3857                key_size_bits: None,
3858                oaep_digest: None,
3859                mgf_algorithm: None,
3860                oaep_params: None,
3861            },
3862            key_name: None,
3863            encrypted_keys: Vec::new(),
3864            cipher_data: CipherData::Value {
3865                value: STANDARD.encode(
3866                    crate::provider::default_provider()
3867                        .encrypt_data(
3868                            DataEncryptionAlgorithm::Aes128Cbc,
3869                            &[7_u8; 16],
3870                            b"opaque plaintext",
3871                        )
3872                        .expect("test encryption must succeed"),
3873                ),
3874            },
3875        };
3876        let resolver = CandidateResolver {
3877            keys: vec![vec![1_u8; 16], vec![7_u8; 16]],
3878        };
3879
3880        let error = DecryptContext::new(&resolver)
3881            .decrypt_data(&encrypted)
3882            .expect_err("unauthenticated CBC must not guess among candidate keys");
3883
3884        assert!(matches!(
3885            error,
3886            XmlEncError::AmbiguousKeyCandidates {
3887                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
3888                actual: 2,
3889            }
3890        ));
3891    }
3892
3893    #[test]
3894    fn cbc_accepts_duplicate_copies_of_one_key_identity() {
3895        // Repeated sources containing the same key do not create the ambiguity
3896        // that unauthenticated CBC must reject between distinct key identities.
3897        let key = vec![0x27_u8; 16];
3898        let encrypted = EncryptedData {
3899            encapsulation_methods: Vec::new(),
3900            id: None,
3901            derived_keys: Vec::new(),
3902            agreement_methods: Vec::new(),
3903            encrypted_type: None,
3904            encryption_method: EncryptionMethod {
3905                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
3906                key_size_bits: None,
3907                oaep_digest: None,
3908                mgf_algorithm: None,
3909                oaep_params: None,
3910            },
3911            key_name: None,
3912            encrypted_keys: Vec::new(),
3913            cipher_data: CipherData::Value {
3914                value: STANDARD.encode(
3915                    crate::provider::default_provider()
3916                        .encrypt_data(
3917                            DataEncryptionAlgorithm::Aes128Cbc,
3918                            &key,
3919                            b"duplicate identity",
3920                        )
3921                        .expect("test encryption must succeed"),
3922                ),
3923            },
3924        };
3925        let resolver = CandidateResolver {
3926            keys: vec![key.clone(), key],
3927        };
3928
3929        assert_eq!(
3930            DecryptContext::new(&resolver)
3931                .decrypt_data(&encrypted)
3932                .expect("one distinct CBC key identity must decrypt"),
3933            DecryptedContent::Bytes(b"duplicate identity".to_vec())
3934        );
3935    }
3936
3937    #[test]
3938    fn candidate_keys_are_bounded_before_cryptographic_processing() {
3939        // A resolver is caller-controlled; its result cannot multiply one
3940        // prepared operation beyond the implementation safety ceiling.
3941        let encrypted = EncryptedData {
3942            encapsulation_methods: Vec::new(),
3943            id: None,
3944            derived_keys: Vec::new(),
3945            agreement_methods: Vec::new(),
3946            encrypted_type: None,
3947            encryption_method: EncryptionMethod {
3948                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3949                key_size_bits: None,
3950                oaep_digest: None,
3951                mgf_algorithm: None,
3952                oaep_params: None,
3953            },
3954            key_name: None,
3955            encrypted_keys: Vec::new(),
3956            cipher_data: CipherData::Value {
3957                value: STANDARD.encode(vec![0_u8; 28]),
3958            },
3959        };
3960        let actual = crate::hard_limits::KEY_CANDIDATE_CEILING + 1;
3961        let resolver = CandidateResolver {
3962            keys: vec![vec![0_u8; 16]; actual],
3963        };
3964
3965        let error = DecryptContext::new(&resolver)
3966            .decrypt_data(&encrypted)
3967            .expect_err("oversized candidate sets must fail before decryption");
3968
3969        assert!(matches!(
3970            error,
3971            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3972                resource: crate::policy::resource_name::KEY_CANDIDATES,
3973                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
3974                actual: observed,
3975            }) if observed == actual
3976        ));
3977    }
3978
3979    #[test]
3980    fn operation_policy_controls_candidate_budget() {
3981        // A deployment-selected ceiling must reach resolver accounting; the
3982        // hard implementation ceiling is not the effective runtime policy.
3983        let encrypted = EncryptedData {
3984            encapsulation_methods: Vec::new(),
3985            id: None,
3986            derived_keys: Vec::new(),
3987            agreement_methods: Vec::new(),
3988            encrypted_type: None,
3989            encryption_method: EncryptionMethod {
3990                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3991                key_size_bits: None,
3992                oaep_digest: None,
3993                mgf_algorithm: None,
3994                oaep_params: None,
3995            },
3996            key_name: None,
3997            encrypted_keys: Vec::new(),
3998            cipher_data: CipherData::Value {
3999                value: STANDARD.encode(vec![0_u8; 28]),
4000            },
4001        };
4002        let resolver = CandidateResolver {
4003            keys: vec![vec![0_u8; 16]; 3],
4004        };
4005        let mut policy = crate::policy::DecryptionPolicy::default();
4006        policy.resources.max_key_candidates = 2;
4007
4008        let error = DecryptContext::new(&resolver)
4009            .policy(policy)
4010            .decrypt_data(&encrypted)
4011            .expect_err("candidate accounting must use the operation policy ceiling");
4012
4013        assert!(matches!(
4014            error,
4015            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4016                resource: crate::policy::resource_name::KEY_CANDIDATES,
4017                maximum: 2,
4018                actual: 3,
4019            })
4020        ));
4021    }
4022
4023    #[test]
4024    fn candidate_work_ceiling_is_shared_across_recipients() {
4025        // The candidate ceiling bounds the complete decryption operation, not
4026        // each EncryptedKey independently.
4027        let key = vec![0x39_u8; 16];
4028        let resolver = AggregateRecipientResolver {
4029            attempts: Cell::new(0),
4030            key: key.clone(),
4031        };
4032        let encrypted = encrypted_data_with_recipients(
4033            &key,
4034            vec![
4035                associated_encrypted_key("first", None, None),
4036                associated_encrypted_key("second", None, None),
4037            ],
4038            None,
4039        );
4040
4041        DecryptContext::new(&resolver)
4042            .decrypt_data(&encrypted)
4043            .expect_err("a second recipient must not receive a fresh candidate allowance");
4044        assert_eq!(
4045            resolver.attempts.get(),
4046            crate::hard_limits::KEY_CANDIDATE_CEILING
4047        );
4048    }
4049
4050    #[test]
4051    fn candidate_budget_exhaustion_is_fatal_after_a_key_was_found() {
4052        // Candidate exhaustion must remain terminal even when it occurs in a
4053        // resolver path dedicated to one particular key family.
4054        let key = vec![0x49_u8; 16];
4055        let resolver = MislabelledExhaustionResolver {
4056            direct: key.clone(),
4057        };
4058        let encrypted = encrypted_data_with_recipients(
4059            &key,
4060            vec![associated_encrypted_key("recipient", None, None)],
4061            None,
4062        );
4063
4064        let error = DecryptContext::new(&resolver)
4065            .decrypt_data(&encrypted)
4066            .expect_err("candidate exhaustion must override an earlier usable key");
4067
4068        assert!(matches!(
4069            error,
4070            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4071                resource: "key candidates",
4072                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
4073                actual,
4074            }) if actual == crate::hard_limits::KEY_CANDIDATE_CEILING + 1
4075        ));
4076    }
4077
4078    #[test]
4079    fn authenticated_decryption_continues_after_wrong_unwrapped_recipient_key() {
4080        // A same-width key can unwrap successfully yet fail GCM authentication;
4081        // later applicable recipients must remain available to the data cipher.
4082        let correct = vec![0x53_u8; 16];
4083        let encrypted = encrypted_data_with_recipients(
4084            &correct,
4085            vec![
4086                associated_encrypted_key("first", None, None),
4087                associated_encrypted_key("second", None, None),
4088            ],
4089            None,
4090        );
4091        let resolver = OrderedRecipientResolver {
4092            wrong: vec![0x11_u8; 16],
4093            correct,
4094        };
4095
4096        let plaintext = DecryptContext::new(&resolver)
4097            .decrypt_data(&encrypted)
4098            .expect("the second recipient key must authenticate");
4099        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4100    }
4101
4102    #[test]
4103    fn authenticated_decryption_continues_from_direct_key_to_recipient() {
4104        // Direct candidates and embedded recipients are one ordered lookup
4105        // space; a wrong direct GCM key must not hide a valid wrapped key.
4106        let correct = vec![0x63_u8; 16];
4107        let encrypted = encrypted_data_with_recipients(
4108            &correct,
4109            vec![associated_encrypted_key("recipient", None, None)],
4110            None,
4111        );
4112        let resolver = DirectAndRecipientResolver {
4113            direct: vec![0x19_u8; 16],
4114            recipient: correct,
4115        };
4116
4117        let plaintext = DecryptContext::new(&resolver)
4118            .decrypt_data(&encrypted)
4119            .expect("the embedded recipient must remain available after a direct candidate");
4120
4121        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4122    }
4123
4124    #[test]
4125    fn authenticated_decryption_continues_after_direct_lookup_error() {
4126        // A candidate-local direct lookup failure must not suppress a valid
4127        // embedded recipient from the same ordered key-resolution operation.
4128        let correct = vec![0x64_u8; 16];
4129        let encrypted = encrypted_data_with_recipients(
4130            &correct,
4131            vec![associated_encrypted_key("recipient", None, None)],
4132            None,
4133        );
4134        let resolver = FailingDirectResolver { recipient: correct };
4135
4136        let plaintext = DecryptContext::new(&resolver)
4137            .decrypt_data(&encrypted)
4138            .expect("recipient lookup must follow a candidate-local direct error");
4139
4140        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4141    }
4142
4143    #[test]
4144    fn resolver_policy_rejection_stops_before_later_recipient() {
4145        // A custom resolver's typed policy rejection is operation-wide; trying
4146        // a later recipient after it would allow key-source policy bypass.
4147        let correct = vec![0x65_u8; 16];
4148        let encrypted = encrypted_data_with_recipients(
4149            &correct,
4150            vec![associated_encrypted_key("recipient", None, None)],
4151            None,
4152        );
4153        let resolver = PolicyRejectingDirectResolver { recipient: correct };
4154
4155        let error = DecryptContext::new(&resolver)
4156            .decrypt_data(&encrypted)
4157            .expect_err("operation policy rejection must be fatal");
4158
4159        assert!(matches!(
4160            error,
4161            XmlEncError::Policy(crate::policy::PolicyViolation::KeyTrust {
4162                reason: "test resolver rejected the operation",
4163            })
4164        ));
4165    }
4166
4167    #[test]
4168    fn cbc_rejects_distinct_direct_and_recipient_candidates() {
4169        // Combining lookup sources must not make unauthenticated CBC choose the
4170        // direct key merely because it was resolved before the recipient key.
4171        let recipient = vec![0x73_u8; 16];
4172        let mut encrypted = encrypted_data_with_recipients(
4173            &recipient,
4174            vec![associated_encrypted_key("recipient", None, None)],
4175            None,
4176        );
4177        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes128Cbc.uri().into();
4178        *encrypted
4179            .cipher_data
4180            .inline_value_mut()
4181            .expect("inline ciphertext") = STANDARD.encode(
4182            crate::provider::default_provider()
4183                .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &recipient, b"payload")
4184                .expect("test encryption must succeed"),
4185        );
4186        let resolver = DirectAndRecipientResolver {
4187            direct: vec![0x29_u8; 16],
4188            recipient,
4189        };
4190
4191        let error = DecryptContext::new(&resolver)
4192            .decrypt_data(&encrypted)
4193            .expect_err("CBC must not guess between direct and recipient keys");
4194
4195        assert!(matches!(
4196            error,
4197            XmlEncError::AmbiguousKeyCandidates {
4198                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
4199                actual: 2,
4200            }
4201        ));
4202    }
4203
4204    #[test]
4205    fn cbc_ambiguity_ignores_algorithm_incompatible_key_widths() {
4206        // A wrong-width key cannot reach AES-CBC and therefore cannot make one
4207        // width-compatible candidate ambiguous.
4208        let key = vec![0x47_u8; 16];
4209        let ciphertext = crate::provider::default_provider()
4210            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &key, b"payload")
4211            .expect("test encryption must succeed");
4212        let encrypted = EncryptedData {
4213            encapsulation_methods: Vec::new(),
4214            id: None,
4215            derived_keys: Vec::new(),
4216            agreement_methods: Vec::new(),
4217            encrypted_type: None,
4218            encryption_method: EncryptionMethod {
4219                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
4220                key_size_bits: None,
4221                oaep_digest: None,
4222                mgf_algorithm: None,
4223                oaep_params: None,
4224            },
4225            key_name: None,
4226            encrypted_keys: Vec::new(),
4227            cipher_data: CipherData::Value {
4228                value: STANDARD.encode(ciphertext),
4229            },
4230        };
4231        let resolver = CandidateResolver {
4232            keys: vec![vec![0_u8; 32], key],
4233        };
4234
4235        assert_eq!(
4236            DecryptContext::new(&resolver)
4237                .decrypt_data(&encrypted)
4238                .expect("the sole width-compatible CBC key must be selected"),
4239            DecryptedContent::Bytes(b"payload".to_vec())
4240        );
4241    }
4242
4243    #[test]
4244    fn data_reference_selects_the_associated_encrypted_key() {
4245        // An explicit DataReference to another object contradicts this
4246        // EncryptedData even when that recipient can be unwrapped.
4247        let key = vec![0x51_u8; 16];
4248        let resolver = AssociationRecordingResolver {
4249            visited: RefCell::new(Vec::new()),
4250            key: key.clone(),
4251        };
4252        let encrypted = encrypted_data_with_recipients(
4253            &key,
4254            vec![
4255                associated_encrypted_key("unrelated", Some("#other"), None),
4256                associated_encrypted_key("matching", Some("#target"), None),
4257            ],
4258            None,
4259        );
4260
4261        DecryptContext::new(&resolver)
4262            .decrypt_data(&encrypted)
4263            .expect("the associated recipient must decrypt");
4264        assert_eq!(resolver.visited.into_inner(), ["matching"]);
4265    }
4266
4267    #[test]
4268    fn carried_key_name_selects_the_named_content_key() {
4269        // CarriedKeyName identifies the transported key named by the enclosing
4270        // EncryptedData KeyInfo; a contradictory label must be skipped.
4271        let key = vec![0x52_u8; 16];
4272        let resolver = AssociationRecordingResolver {
4273            visited: RefCell::new(Vec::new()),
4274            key: key.clone(),
4275        };
4276        let encrypted = encrypted_data_with_recipients(
4277            &key,
4278            vec![
4279                associated_encrypted_key("unrelated", None, Some("other")),
4280                associated_encrypted_key("matching", None, Some("wanted")),
4281            ],
4282            Some("wanted"),
4283        );
4284
4285        DecryptContext::new(&resolver)
4286            .decrypt_data(&encrypted)
4287            .expect("the matching carried key name must decrypt");
4288        assert_eq!(resolver.visited.into_inner(), ["matching"]);
4289    }
4290
4291    #[test]
4292    fn contradictory_encrypted_key_associations_fail_closed() {
4293        // Association metadata is authoritative when present. The resolver must
4294        // not see a recipient that explicitly names another encrypted object.
4295        let key = vec![0x53_u8; 16];
4296        let resolver = AssociationRecordingResolver {
4297            visited: RefCell::new(Vec::new()),
4298            key: key.clone(),
4299        };
4300        let encrypted = encrypted_data_with_recipients(
4301            &key,
4302            vec![associated_encrypted_key("unrelated", Some("#other"), None)],
4303            None,
4304        );
4305
4306        assert!(matches!(
4307            DecryptContext::new(&resolver).decrypt_data(&encrypted),
4308            Err(XmlEncError::KeyNotFound)
4309        ));
4310        assert!(resolver.visited.into_inner().is_empty());
4311    }
4312
4313    #[test]
4314    fn direct_symmetric_key_ignores_embedded_key_hints() {
4315        // A caller-supplied content key is authoritative for this resolver;
4316        // unrelated recipient hints must not disable direct-key decryption.
4317        let key = [0x28_u8; 16];
4318        let unrelated = EncryptedKey {
4319            sources: Default::default(),
4320            id: None,
4321            recipient: Some("other-recipient".into()),
4322            key_name: None,
4323            encryption_method: super::super::EncryptionMethod {
4324                algorithm: "urn:unrelated:key-transport".into(),
4325                key_size_bits: None,
4326                oaep_digest: None,
4327                mgf_algorithm: None,
4328                oaep_params: None,
4329            },
4330            cipher_data: super::super::CipherData::Value {
4331                value: STANDARD.encode([0_u8; 24]),
4332            },
4333            reference_list: None,
4334            carried_key_name: None,
4335        };
4336
4337        assert_eq!(
4338            SymmetricKeyDecryptor::new(key)
4339                .resolve_key(
4340                    crate::provider::default_provider(),
4341                    DataEncryptionAlgorithm::Aes128Gcm,
4342                    Some(&unrelated)
4343                )
4344                .expect("direct key must ignore unrelated embedded hints"),
4345            key
4346        );
4347    }
4348
4349    #[test]
4350    fn decrypts_with_the_matching_recipient_key() {
4351        // Multi-recipient KeyInfo must retain document order and continue after a
4352        // malformed unrelated key before accepting the intended one.
4353        let key = [0x29_u8; 16];
4354        let plaintext = "recipient-specific plaintext";
4355        let encrypted = encrypted_gcm_element("", plaintext, None, true, &key);
4356        let recipient_key = |recipient: &str, method: &str| {
4357            format!(
4358                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"{method}\">{}</xenc:EncryptionMethod><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>",
4359                if recipient == "alice" {
4360                    "<ds:DigestMethod Algorithm=\"urn:unsupported:digest\"/>"
4361                } else {
4362                    ""
4363                }
4364            )
4365        };
4366        let key_info = format!(
4367            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
4368            crate::xmlenc::types::XMLDSIG_NS,
4369            recipient_key("alice", KeyTransportAlgorithm::RsaOaep11.uri()),
4370            recipient_key("bob", KeyWrapAlgorithm::AesKw128.uri())
4371        );
4372        let xml = encrypted.replacen(
4373            "<xenc:CipherData>",
4374            &format!("{key_info}<xenc:CipherData>"),
4375            1,
4376        );
4377        let resolver = RecipientKeyResolver {
4378            recipient: "bob",
4379            key: key.to_vec(),
4380        };
4381
4382        assert_eq!(
4383            decrypt(&xml, &resolver).expect("second recipient key must be tried"),
4384            DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
4385        );
4386    }
4387
4388    #[test]
4389    fn decryption_policy_bounds_recipients_before_key_resolution() {
4390        // Both XML parsing and caller-constructed typed input must reject an
4391        // oversized recipient set before any resolver can inspect candidates.
4392        let key = [0x29_u8; 16];
4393        let encrypted = encrypted_gcm_element("", "bounded recipients", None, true, &key);
4394        let recipient_key = |recipient: &str| {
4395            format!(
4396                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"urn:test:key\"/><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>"
4397            )
4398        };
4399        let key_info = format!(
4400            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
4401            crate::xmlenc::types::XMLDSIG_NS,
4402            recipient_key("alice"),
4403            recipient_key("bob")
4404        );
4405        let xml = encrypted.replacen(
4406            "<xenc:CipherData>",
4407            &format!("{key_info}<xenc:CipherData>"),
4408            1,
4409        );
4410        let parsed = parse_encrypted_data(&xml).expect("default parser accepts two recipients");
4411        let policy = crate::policy::DecryptionPolicy {
4412            resources: crate::policy::ResourcePolicy {
4413                max_encryption_recipients: 1,
4414                ..crate::policy::ResourcePolicy::default()
4415            },
4416            ..crate::policy::DecryptionPolicy::default()
4417        };
4418        let resolver = SymmetricKeyDecryptor::new(key);
4419        let context = DecryptContext::new(&resolver).policy(policy);
4420
4421        for error in [
4422            context
4423                .decrypt(&xml)
4424                .expect_err("XML recipient collection must be bounded"),
4425            context
4426                .decrypt_data(&parsed)
4427                .expect_err("typed recipient collection must be bounded"),
4428        ] {
4429            assert!(matches!(
4430                error,
4431                XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4432                    resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
4433                    maximum: 1,
4434                    actual: 2,
4435                })
4436            ));
4437        }
4438    }
4439
4440    #[test]
4441    fn decrypts_session_key_wrapped_with_aes_kw() {
4442        // RFC 3394 unwrap must recover exactly the content algorithm's key length.
4443        let kek = [3_u8; 16];
4444        let session_key = [4_u8; 16];
4445        let mut wrapped = [0_u8; 24];
4446        KwAes128::new_from_slice(&kek)
4447            .expect("fixed KEK length")
4448            .wrap_key(&session_key, &mut wrapped)
4449            .expect("RFC 3394 test wrapping must succeed");
4450        let encrypted_key = EncryptedKey {
4451            id: None,
4452            sources: Default::default(),
4453            recipient: None,
4454            key_name: None,
4455            encryption_method: super::super::EncryptionMethod {
4456                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
4457                key_size_bits: None,
4458                oaep_digest: None,
4459                mgf_algorithm: None,
4460                oaep_params: None,
4461            },
4462            cipher_data: super::super::CipherData::Value {
4463                value: STANDARD.encode(wrapped),
4464            },
4465            reference_list: None,
4466            carried_key_name: None,
4467        };
4468        let resolved = KekDecryptor::new(kek)
4469            .resolve_key(
4470                crate::provider::default_provider(),
4471                DataEncryptionAlgorithm::Aes128Gcm,
4472                Some(&encrypted_key),
4473            )
4474            .expect("wrapped session key must resolve");
4475        assert_eq!(resolved, session_key);
4476    }
4477
4478    #[test]
4479    fn rejects_invalid_kek_before_custom_provider_dispatch() {
4480        // KEK length is part of the XMLEnc algorithm contract, not a provider
4481        // preference. A permissive provider must not bypass facade validation.
4482        let encrypted_key = EncryptedKey {
4483            id: None,
4484            sources: Default::default(),
4485            recipient: None,
4486            key_name: None,
4487            encryption_method: super::super::EncryptionMethod {
4488                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
4489                key_size_bits: None,
4490                oaep_digest: None,
4491                mgf_algorithm: None,
4492                oaep_params: None,
4493            },
4494            cipher_data: super::super::CipherData::Value {
4495                value: STANDARD.encode([0_u8; 24]),
4496            },
4497            reference_list: None,
4498            carried_key_name: None,
4499        };
4500        let provider = PermissiveUnwrapProvider::default();
4501
4502        assert!(matches!(
4503            KekDecryptor::new([0_u8; 32]).resolve_key(
4504                &provider,
4505                DataEncryptionAlgorithm::Aes128Gcm,
4506                Some(&encrypted_key),
4507            ),
4508            Err(XmlEncError::InvalidKekSize {
4509                algorithm: KeyWrapAlgorithm::AesKw128,
4510                expected: 16,
4511                actual: 32,
4512            })
4513        ));
4514        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
4515    }
4516
4517    #[test]
4518    fn rejects_content_ciphertext_framing_before_resolution_or_provider_dispatch() {
4519        // Algorithm framing belongs to the XMLEnc facade. A permissive provider
4520        // and resolver must never observe malformed standard CipherValue bytes.
4521        for (algorithm, ciphertext_len) in [
4522            (DataEncryptionAlgorithm::Aes128Gcm, 27),
4523            (DataEncryptionAlgorithm::Aes128Cbc, 33),
4524        ] {
4525            let resolver = AllCallsResolver {
4526                calls: Cell::new(0),
4527                key: vec![0_u8; algorithm.key_len()],
4528            };
4529            let provider = PermissiveUnwrapProvider::default();
4530            let encrypted = EncryptedData {
4531                encapsulation_methods: Vec::new(),
4532                id: None,
4533                derived_keys: Vec::new(),
4534                agreement_methods: Vec::new(),
4535                encrypted_type: None,
4536                key_name: None,
4537                encryption_method: super::super::EncryptionMethod {
4538                    algorithm: algorithm.uri().into(),
4539                    key_size_bits: None,
4540                    oaep_digest: None,
4541                    mgf_algorithm: None,
4542                    oaep_params: None,
4543                },
4544                encrypted_keys: Vec::new(),
4545                cipher_data: super::super::CipherData::Value {
4546                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
4547                },
4548            };
4549
4550            assert!(
4551                DecryptContext::new(&resolver)
4552                    .provider(&provider)
4553                    .decrypt_data(&encrypted)
4554                    .is_err()
4555            );
4556            assert_eq!(resolver.calls.get(), 0);
4557            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 0);
4558        }
4559    }
4560
4561    #[test]
4562    fn rejects_custom_provider_plaintext_outside_algorithm_bounds() {
4563        // A provider success result is still untrusted: GCM fixes the plaintext
4564        // length exactly, while CBC padding permits only one block-sized range.
4565        for (algorithm, ciphertext_len, plaintext_len) in [
4566            (DataEncryptionAlgorithm::Aes128Gcm, 32, 5),
4567            (DataEncryptionAlgorithm::Aes128Cbc, 32, 16),
4568        ] {
4569            let resolver = AllCallsResolver {
4570                calls: Cell::new(0),
4571                key: vec![0_u8; algorithm.key_len()],
4572            };
4573            let provider = PermissiveUnwrapProvider {
4574                plaintext: vec![0_u8; plaintext_len],
4575                ..PermissiveUnwrapProvider::default()
4576            };
4577            let encrypted = EncryptedData {
4578                encapsulation_methods: Vec::new(),
4579                id: None,
4580                derived_keys: Vec::new(),
4581                agreement_methods: Vec::new(),
4582                encrypted_type: None,
4583                key_name: None,
4584                encryption_method: super::super::EncryptionMethod {
4585                    algorithm: algorithm.uri().into(),
4586                    key_size_bits: None,
4587                    oaep_digest: None,
4588                    mgf_algorithm: None,
4589                    oaep_params: None,
4590                },
4591                encrypted_keys: Vec::new(),
4592                cipher_data: super::super::CipherData::Value {
4593                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
4594                },
4595            };
4596
4597            let error = DecryptContext::new(&resolver)
4598                .provider(&provider)
4599                .decrypt_data(&encrypted)
4600                .expect_err("impossible provider output length must fail");
4601            match algorithm {
4602                DataEncryptionAlgorithm::Aes128Gcm => assert!(matches!(
4603                    error,
4604                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSize {
4605                        operation: crate::provider::ProviderOperation::Decrypt,
4606                        expected: 4,
4607                        actual: 5,
4608                    })
4609                )),
4610                DataEncryptionAlgorithm::Aes128Cbc => assert!(matches!(
4611                    error,
4612                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSizeRange {
4613                        operation: crate::provider::ProviderOperation::Decrypt,
4614                        minimum: 0,
4615                        maximum: 15,
4616                        actual: 16,
4617                    })
4618                )),
4619                _ => unreachable!("the regression table covers one GCM and one CBC algorithm"),
4620            }
4621            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
4622        }
4623    }
4624
4625    #[test]
4626    fn rejects_malformed_aes_kw_before_custom_provider_dispatch() {
4627        // RFC 3394 adds exactly eight bytes to the transported content key;
4628        // permissive custom providers must not redefine that wire contract.
4629        let provider = PermissiveUnwrapProvider::default();
4630        for actual in [0, 23, 25] {
4631            let encrypted_key = EncryptedKey {
4632                sources: Default::default(),
4633                id: None,
4634                recipient: None,
4635                key_name: None,
4636                encryption_method: super::super::EncryptionMethod {
4637                    algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
4638                    key_size_bits: None,
4639                    oaep_digest: None,
4640                    mgf_algorithm: None,
4641                    oaep_params: None,
4642                },
4643                cipher_data: super::super::CipherData::Value {
4644                    value: STANDARD.encode(vec![0_u8; actual]),
4645                },
4646                reference_list: None,
4647                carried_key_name: None,
4648            };
4649            assert!(matches!(
4650                KekDecryptor::new([0_u8; 16]).resolve_key(
4651                    &provider,
4652                    DataEncryptionAlgorithm::Aes128Gcm,
4653                    Some(&encrypted_key),
4654                ),
4655                Err(XmlEncError::InvalidWrappedKeyLength {
4656                    expected: 24,
4657                    actual: output_len,
4658                }) if output_len == actual
4659            ));
4660        }
4661        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
4662    }
4663
4664    #[test]
4665    fn rejects_malformed_rsa_oaep_before_custom_provider_dispatch() {
4666        // RSA ciphertext width is the private modulus width, so malformed
4667        // transport bytes must be rejected before provider-owned recovery.
4668        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4669            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4670        ))
4671        .expect("RSA donor private key must parse");
4672        let provider = PermissiveUnwrapProvider::default();
4673        for actual in [0, 255, 257] {
4674            assert!(matches!(
4675                recover_rsa_oaep(
4676                    &provider,
4677                    &private_key,
4678                    &RsaOaepParameters::default(),
4679                    &vec![0_u8; actual],
4680                    DataEncryptionAlgorithm::Aes128Gcm,
4681                ),
4682                Err(XmlEncError::InvalidWrappedKeyLength {
4683                    expected: 256,
4684                    actual: output_len,
4685                }) if output_len == actual
4686            ));
4687        }
4688        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 0);
4689    }
4690
4691    #[cfg(feature = "legacy-algorithms")]
4692    #[test]
4693    fn invalid_rsa_recovery_cannot_release_successful_cbc_plaintext() {
4694        // Force the CBC provider to accept its padding, making this regression
4695        // deterministic instead of waiting for random fallback plaintext.
4696        let key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4697            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4698        ))
4699        .expect("valid RSA fixture");
4700        let resolver = PrivateKeyDecryptor::new(key);
4701        for algorithm in [
4702            DataEncryptionAlgorithm::Aes128Cbc,
4703            DataEncryptionAlgorithm::Aes192Cbc,
4704            DataEncryptionAlgorithm::Aes256Cbc,
4705            DataEncryptionAlgorithm::TripleDesCbc,
4706            DataEncryptionAlgorithm::Aes128Gcm,
4707        ] {
4708            let provider = PermissiveUnwrapProvider {
4709                plaintext: b"accepted CBC plaintext".to_vec(),
4710                ..PermissiveUnwrapProvider::default()
4711            };
4712            let mut encrypted = encrypted_data_with_recipients(
4713                &[0; 16],
4714                vec![associated_encrypted_key("recipient", None, None)],
4715                None,
4716            );
4717            encrypted.encryption_method.algorithm = algorithm.uri().into();
4718            *encrypted
4719                .cipher_data
4720                .inline_value_mut()
4721                .expect("inline ciphertext") =
4722                STANDARD.encode(vec![
4723                    0;
4724                    algorithm
4725                        .ciphertext_len_for_plaintext(provider.plaintext.len())
4726                        .expect("bounded test frame")
4727                ]);
4728            encrypted.encrypted_keys[0].encryption_method.algorithm =
4729                KeyTransportAlgorithm::RsaPkcs1v15.uri().into();
4730            *encrypted.encrypted_keys[0]
4731                .cipher_data
4732                .inline_value_mut()
4733                .expect("inline wrapped key") = STANDARD.encode([0; 256]);
4734            let result = DecryptContext::new(&resolver)
4735                .provider(&provider)
4736                .policy(crate::policy::DecryptionPolicy {
4737                    data_algorithms: Some([algorithm].into()),
4738                    key_transport_algorithms: Some([KeyTransportAlgorithm::RsaPkcs1v15].into()),
4739                    ..crate::policy::DecryptionPolicy::default()
4740                })
4741                .decrypt_data(&encrypted);
4742            assert!(
4743                result.is_err(),
4744                "invalid RSA padding must never release fallback plaintext"
4745            );
4746            assert_eq!(
4747                provider.decrypt_calls.load(Ordering::Relaxed),
4748                1,
4749                "content decryption must run before rejecting the recovery"
4750            );
4751        }
4752    }
4753
4754    #[test]
4755    fn candidate_filter_reuses_storage_and_preserves_precedence() {
4756        // Compact invalid widths and non-adjacent duplicates in place without
4757        // reordering distinct candidates or allocating a second candidate list.
4758        let keys = [
4759            vec![0; 8],
4760            vec![2; 16],
4761            vec![3; 16],
4762            vec![2; 16],
4763            vec![4; 32],
4764            vec![5; 16],
4765        ]
4766        .into_iter()
4767        .map(crate::provider::RecoveredContentKey::confirmed)
4768        .collect::<Vec<_>>();
4769        let allocation = keys.as_ptr();
4770        let keys = compatible_decryption_key_candidates(DataEncryptionAlgorithm::Aes128Gcm, keys)
4771            .expect("compatible candidates survive");
4772        assert_eq!(keys.as_ptr(), allocation);
4773        assert_eq!(keys.len(), 3);
4774        for (key, expected) in keys.iter().zip([2, 3, 5]) {
4775            assert_eq!(key.bytes(), &[expected; 16]);
4776        }
4777    }
4778
4779    #[test]
4780    fn custom_provider_recovers_with_an_opaque_private_key() {
4781        // The resolver knows only the public ciphertext width; private key
4782        // material remains entirely behind the provider/key-handle boundary.
4783        let encrypted_key = EncryptedKey {
4784            id: None,
4785            sources: Default::default(),
4786            recipient: None,
4787            key_name: None,
4788            encryption_method: super::super::EncryptionMethod {
4789                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
4790                key_size_bits: None,
4791                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
4792                mgf_algorithm: Some(OaepDigestAlgorithm::Sha256.mgf_uri().into()),
4793                oaep_params: None,
4794            },
4795            cipher_data: super::super::CipherData::Value {
4796                value: STANDARD.encode(vec![0x5a; 256]),
4797            },
4798            reference_list: None,
4799            carried_key_name: None,
4800        };
4801        let provider = PermissiveUnwrapProvider::default();
4802        let decryptor = PrivateKeyDecryptor::provider_key(Arc::new(OpaqueRecoveryKey));
4803
4804        let key = decryptor
4805            .resolve_key(
4806                &provider,
4807                DataEncryptionAlgorithm::Aes128Gcm,
4808                Some(&encrypted_key),
4809            )
4810            .expect("custom provider must recover through its opaque private key");
4811
4812        assert_eq!(key, vec![0_u8; 16]);
4813        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 1);
4814    }
4815
4816    #[test]
4817    fn rejects_truncated_gcm_and_invalid_wrapped_key() {
4818        // Framing and key-wrap integrity failures must occur before content is exposed.
4819        assert!(matches!(
4820            crate::provider::default_provider().decrypt_data(
4821                DataEncryptionAlgorithm::Aes128Gcm,
4822                &[0_u8; 16],
4823                &[0_u8; 27],
4824            ),
4825            Err(crate::provider::ProviderError::InvalidInput(
4826                crate::provider::ProviderInputError::AesGcmFraming
4827            ))
4828        ));
4829        let truncated = EncryptedData {
4830            encapsulation_methods: Vec::new(),
4831            id: None,
4832            derived_keys: Vec::new(),
4833            agreement_methods: Vec::new(),
4834            encrypted_type: None,
4835            key_name: None,
4836            encryption_method: super::super::EncryptionMethod {
4837                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
4838                key_size_bits: None,
4839                oaep_digest: None,
4840                mgf_algorithm: None,
4841                oaep_params: None,
4842            },
4843            encrypted_keys: Vec::new(),
4844            cipher_data: super::super::CipherData::Value {
4845                value: STANDARD.encode([0_u8; 27]),
4846            },
4847        };
4848        assert!(matches!(
4849            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16])).decrypt_data(&truncated),
4850            Err(XmlEncError::DataTooShort {
4851                algorithm: "AES-GCM",
4852                actual: 27,
4853                ..
4854            })
4855        ));
4856        let encrypted_key = EncryptedKey {
4857            id: None,
4858            sources: Default::default(),
4859            recipient: None,
4860            key_name: None,
4861            encryption_method: super::super::EncryptionMethod {
4862                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
4863                key_size_bits: None,
4864                oaep_digest: None,
4865                mgf_algorithm: None,
4866                oaep_params: None,
4867            },
4868            cipher_data: super::super::CipherData::Value {
4869                value: STANDARD.encode([0_u8; 24]),
4870            },
4871            reference_list: None,
4872            carried_key_name: None,
4873        };
4874        assert!(matches!(
4875            KekDecryptor::new([0_u8; 16]).resolve_key(
4876                crate::provider::default_provider(),
4877                DataEncryptionAlgorithm::Aes128Gcm,
4878                Some(&encrypted_key)
4879            ),
4880            Err(XmlEncError::KeyWrapIntegrity)
4881        ));
4882        assert!(matches!(
4883            KekDecryptor::new([0_u8; 32]).resolve_key(
4884                crate::provider::default_provider(),
4885                DataEncryptionAlgorithm::Aes128Gcm,
4886                Some(&encrypted_key)
4887            ),
4888            Err(XmlEncError::InvalidKekSize {
4889                algorithm: KeyWrapAlgorithm::AesKw128,
4890                expected: 16,
4891                actual: 32
4892            })
4893        ));
4894    }
4895
4896    #[test]
4897    fn decrypts_oaep11_with_independent_digest_and_mgf() {
4898        // XMLEnc 1.1 permits the OAEP digest and MGF1 digest to differ.
4899        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4900            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4901        ))
4902        .expect("RSA donor private key must parse");
4903        let public_key = RsaPublicKey::from(&private_key);
4904        let session_key = [6_u8; 16];
4905        let label = b"xmlenc-label".to_vec();
4906        let wrapped = public_key
4907            .encrypt(
4908                &mut ChaCha20Rng::from_seed([17_u8; 32]),
4909                Oaep::<Sha256, Sha384>::new_with_mgf_hash_and_label(label.clone()),
4910                &session_key,
4911            )
4912            .expect("OAEP test wrapping must succeed");
4913        let encrypted_key = EncryptedKey {
4914            id: Some("wrapped-key".into()),
4915            sources: Default::default(),
4916            recipient: Some("recipient-a".into()),
4917            key_name: None,
4918            encryption_method: super::super::EncryptionMethod {
4919                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
4920                key_size_bits: None,
4921                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
4922                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha384".into()),
4923                oaep_params: Some(label),
4924            },
4925            cipher_data: super::super::CipherData::Value {
4926                value: STANDARD.encode(wrapped),
4927            },
4928            reference_list: None,
4929            carried_key_name: None,
4930        };
4931        let resolved = PrivateKeyDecryptor::new(private_key)
4932            .resolve_key(
4933                crate::provider::default_provider(),
4934                DataEncryptionAlgorithm::Aes128Gcm,
4935                Some(&encrypted_key),
4936            )
4937            .expect("OAEP 1.1 wrapped key must resolve");
4938        assert_eq!(resolved, session_key);
4939    }
4940
4941    #[test]
4942    fn decrypts_legacy_oaep_uri_with_sha256_digest() {
4943        // The legacy URI defaults MGF1 to SHA-1 when no xenc11:MGF child is present.
4944        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4945            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4946        ))
4947        .expect("RSA donor private key must parse");
4948        let public_key = RsaPublicKey::from(&private_key);
4949        let session_key = [8_u8; 16];
4950        let wrapped = public_key
4951            .encrypt(
4952                &mut ChaCha20Rng::from_seed([19_u8; 32]),
4953                Oaep::<Sha256, Sha1>::new_with_mgf_hash(),
4954                &session_key,
4955            )
4956            .expect("legacy OAEP URI test wrapping must succeed");
4957        let encrypted_key = EncryptedKey {
4958            id: None,
4959            sources: Default::default(),
4960            recipient: None,
4961            key_name: None,
4962            encryption_method: super::super::EncryptionMethod {
4963                algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p".into(),
4964                key_size_bits: None,
4965                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
4966                mgf_algorithm: None,
4967                oaep_params: None,
4968            },
4969            cipher_data: super::super::CipherData::Value {
4970                value: STANDARD.encode(wrapped),
4971            },
4972            reference_list: None,
4973            carried_key_name: None,
4974        };
4975        let resolved = PrivateKeyDecryptor::new(private_key)
4976            .resolve_key(
4977                crate::provider::default_provider(),
4978                DataEncryptionAlgorithm::Aes128Gcm,
4979                Some(&encrypted_key),
4980            )
4981            .expect("legacy OAEP URI with SHA-256 must resolve");
4982        assert_eq!(resolved, session_key);
4983    }
4984
4985    #[test]
4986    fn decrypts_sha384_oaep_with_the_xmlenc_digest_uri() {
4987        // XML Encryption 1.1 reserves xmlenc#sha384 for SHA-384. Exercise both
4988        // OAEP algorithm URIs with their absent/explicit MGF1-SHA1 forms.
4989        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4990            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4991        ))
4992        .expect("RSA donor private key must parse");
4993        let public_key = RsaPublicKey::from(&private_key);
4994        let session_key = [9_u8; 16];
4995        let digest = "http://www.w3.org/2001/04/xmlenc#sha384";
4996
4997        for (algorithm, mgf_algorithm) in [
4998            ("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", None),
4999            (
5000                "http://www.w3.org/2009/xmlenc11#rsa-oaep",
5001                Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
5002            ),
5003        ] {
5004            let wrapped = public_key
5005                .encrypt(
5006                    &mut ChaCha20Rng::from_seed([23_u8; 32]),
5007                    Oaep::<Sha384, Sha1>::new_with_mgf_hash(),
5008                    &session_key,
5009                )
5010                .expect("SHA-384 OAEP test wrapping must succeed");
5011            let encrypted_key = EncryptedKey {
5012                id: None,
5013                sources: Default::default(),
5014                recipient: None,
5015                key_name: None,
5016                encryption_method: super::super::EncryptionMethod {
5017                    algorithm: algorithm.into(),
5018                    key_size_bits: None,
5019                    oaep_digest: Some(digest.into()),
5020                    mgf_algorithm: mgf_algorithm.map(str::to_owned),
5021                    oaep_params: None,
5022                },
5023                cipher_data: super::super::CipherData::Value {
5024                    value: STANDARD.encode(wrapped),
5025                },
5026                reference_list: None,
5027                carried_key_name: None,
5028            };
5029            let resolved = PrivateKeyDecryptor::new(private_key.clone())
5030                .resolve_key(
5031                    crate::provider::default_provider(),
5032                    DataEncryptionAlgorithm::Aes128Gcm,
5033                    Some(&encrypted_key),
5034                )
5035                .expect("official XMLENC SHA-384 URI must resolve");
5036            assert_eq!(resolved, session_key);
5037        }
5038    }
5039
5040    #[test]
5041    fn rejects_unknown_oaep_digest_and_mgf_as_unsupported() {
5042        // Unknown algorithm URIs are declaration errors, not generic RSA failures.
5043        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5044            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5045        ))
5046        .expect("RSA donor private key must parse");
5047        let decryptor = PrivateKeyDecryptor::new(private_key);
5048        let mut encrypted_key = EncryptedKey {
5049            sources: Default::default(),
5050            id: None,
5051            recipient: None,
5052            key_name: None,
5053            encryption_method: super::super::EncryptionMethod {
5054                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
5055                key_size_bits: None,
5056                oaep_digest: Some("urn:unsupported:digest".into()),
5057                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha1".into()),
5058                oaep_params: None,
5059            },
5060            cipher_data: super::super::CipherData::Value {
5061                value: STANDARD.encode([0_u8; 256]),
5062            },
5063            reference_list: None,
5064            carried_key_name: None,
5065        };
5066        assert!(matches!(
5067            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
5068            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:digest"
5069        ));
5070
5071        encrypted_key.encryption_method.oaep_digest = None;
5072        encrypted_key.encryption_method.mgf_algorithm = Some("urn:unsupported:mgf".into());
5073        assert!(matches!(
5074            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
5075            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:mgf"
5076        ));
5077    }
5078
5079    #[test]
5080    fn decryption_policy_enforces_oaep_digest_and_plaintext_limits() {
5081        // Algorithm and allocation policies are checked before key resolution
5082        // or plaintext materialization, including the document-declared MGF.
5083        let encrypted_key = EncryptedKey {
5084            id: None,
5085            recipient: Some("selected".into()),
5086            sources: Default::default(),
5087            key_name: None,
5088            encryption_method: super::super::EncryptionMethod {
5089                algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
5090                key_size_bits: None,
5091                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5092                mgf_algorithm: Some(OaepDigestAlgorithm::Sha384.mgf_uri().into()),
5093                oaep_params: None,
5094            },
5095            cipher_data: super::super::CipherData::Value {
5096                value: STANDARD.encode([0_u8; 256]),
5097            },
5098            reference_list: None,
5099            carried_key_name: None,
5100        };
5101        let encrypted = EncryptedData {
5102            encapsulation_methods: Vec::new(),
5103            id: None,
5104            derived_keys: Vec::new(),
5105            agreement_methods: Vec::new(),
5106            encrypted_type: None,
5107            key_name: None,
5108            encryption_method: super::super::EncryptionMethod {
5109                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5110                key_size_bits: None,
5111                oaep_digest: None,
5112                mgf_algorithm: None,
5113                oaep_params: None,
5114            },
5115            encrypted_keys: vec![encrypted_key],
5116            cipher_data: super::super::CipherData::Value {
5117                value: STANDARD.encode([0_u8; 28]),
5118            },
5119        };
5120        let policy = crate::policy::DecryptionPolicy {
5121            oaep_digests: Some(std::collections::HashSet::from([
5122                OaepDigestAlgorithm::Sha256,
5123            ])),
5124            ..crate::policy::DecryptionPolicy::default()
5125        };
5126        assert!(matches!(
5127            DecryptContext::new(&RecipientKeyResolver {
5128                recipient: "selected",
5129                key: vec![0_u8; 16],
5130            })
5131            .policy(policy)
5132            .decrypt_data(&encrypted),
5133            Err(XmlEncError::Policy(
5134                crate::policy::PolicyViolation::Algorithm { algorithm, .. }
5135            )) if algorithm == OaepDigestAlgorithm::Sha384.mgf_uri()
5136        ));
5137
5138        let ciphertext = crate::provider::default_provider()
5139            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"four")
5140            .expect("test encryption must succeed");
5141        let bounded = EncryptedData {
5142            encrypted_keys: Vec::new(),
5143            cipher_data: super::super::CipherData::Value {
5144                value: STANDARD.encode(ciphertext),
5145            },
5146            ..encrypted
5147        };
5148        let policy = crate::policy::DecryptionPolicy {
5149            resources: crate::policy::ResourcePolicy {
5150                max_encryption_plaintext_bytes: 3,
5151                ..crate::policy::ResourcePolicy::default()
5152            },
5153            ..crate::policy::DecryptionPolicy::default()
5154        };
5155        assert!(matches!(
5156            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
5157                .policy(policy)
5158                .decrypt_data(&bounded),
5159            Err(XmlEncError::Policy(
5160                crate::policy::PolicyViolation::ResourceLimit {
5161                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
5162                    maximum: 3,
5163                    actual: 4
5164                }
5165            ))
5166        ));
5167
5168        let cbc_ciphertext = crate::provider::default_provider()
5169            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &[0_u8; 16], b"four")
5170            .expect("test CBC encryption must succeed");
5171        let bounded_cbc = EncryptedData {
5172            encryption_method: super::super::EncryptionMethod {
5173                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
5174                key_size_bits: None,
5175                oaep_digest: None,
5176                mgf_algorithm: None,
5177                oaep_params: None,
5178            },
5179            encrypted_keys: Vec::new(),
5180            cipher_data: super::super::CipherData::Value {
5181                value: STANDARD.encode(cbc_ciphertext),
5182            },
5183            ..bounded
5184        };
5185        let policy = crate::policy::DecryptionPolicy {
5186            resources: crate::policy::ResourcePolicy {
5187                max_encryption_plaintext_bytes: 4,
5188                ..crate::policy::ResourcePolicy::default()
5189            },
5190            ..crate::policy::DecryptionPolicy::default()
5191        };
5192        assert_eq!(
5193            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
5194                .policy(policy)
5195                .decrypt_data(&bounded_cbc)
5196                .expect("CBC plaintext at the configured limit must decrypt"),
5197            DecryptedContent::Bytes(b"four".to_vec())
5198        );
5199    }
5200
5201    #[test]
5202    fn typed_decryption_input_cannot_bypass_metadata_policy() {
5203        // Callers may construct EncryptedData directly instead of using the XML
5204        // parser, so the operation boundary must enforce the same metadata cap.
5205        let ciphertext = crate::provider::default_provider()
5206            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"data")
5207            .expect("test encryption must succeed");
5208        let encrypted = EncryptedData {
5209            encapsulation_methods: Vec::new(),
5210            id: Some("oversized".into()),
5211            derived_keys: Vec::new(),
5212            agreement_methods: Vec::new(),
5213            encrypted_type: None,
5214            key_name: None,
5215            encryption_method: super::super::EncryptionMethod {
5216                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5217                key_size_bits: None,
5218                oaep_digest: None,
5219                mgf_algorithm: None,
5220                oaep_params: None,
5221            },
5222            encrypted_keys: Vec::new(),
5223            cipher_data: super::super::CipherData::Value {
5224                value: STANDARD.encode(ciphertext),
5225            },
5226        };
5227        let policy = crate::policy::DecryptionPolicy {
5228            resources: crate::policy::ResourcePolicy {
5229                max_encryption_metadata_bytes: 8,
5230                ..crate::policy::ResourcePolicy::default()
5231            },
5232            ..crate::policy::DecryptionPolicy::default()
5233        };
5234
5235        let resolver = AllCallsResolver {
5236            calls: Cell::new(0),
5237            key: vec![0_u8; 16],
5238        };
5239        assert!(matches!(
5240            DecryptContext::new(&resolver)
5241                .policy(policy)
5242                .decrypt_data(&encrypted),
5243            Err(XmlEncError::Policy(
5244                crate::policy::PolicyViolation::ResourceLimit {
5245                    resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
5246                    maximum: 8,
5247                    actual: 9,
5248                }
5249            ))
5250        ));
5251        assert_eq!(
5252            resolver.calls.get(),
5253            0,
5254            "metadata preflight must gate resolver-controlled work"
5255        );
5256    }
5257
5258    #[test]
5259    fn typed_cipher_values_are_bounded_before_decode_or_resolution() {
5260        // Public typed input bypasses the XML parser, so the decryption boundary
5261        // must re-establish both content and recipient CipherValue size invariants.
5262        let key = [0x41_u8; 16];
5263        let ciphertext = crate::provider::default_provider()
5264            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5265            .expect("test encryption must succeed");
5266        let mut encrypted = EncryptedData {
5267            encapsulation_methods: Vec::new(),
5268            id: None,
5269            derived_keys: Vec::new(),
5270            agreement_methods: Vec::new(),
5271            encrypted_type: None,
5272            key_name: None,
5273            encryption_method: super::super::EncryptionMethod {
5274                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5275                key_size_bits: None,
5276                oaep_digest: None,
5277                mgf_algorithm: None,
5278                oaep_params: None,
5279            },
5280            encrypted_keys: Vec::new(),
5281            cipher_data: super::super::CipherData::Value {
5282                value: STANDARD.encode(ciphertext),
5283            },
5284        };
5285        let policy = crate::policy::DecryptionPolicy {
5286            resources: crate::policy::ResourcePolicy {
5287                max_encryption_plaintext_bytes: 4,
5288                ..crate::policy::ResourcePolicy::default()
5289            },
5290            ..crate::policy::DecryptionPolicy::default()
5291        };
5292        *encrypted
5293            .cipher_data
5294            .inline_value_mut()
5295            .expect("inline ciphertext") = "A".repeat(48);
5296        assert!(matches!(
5297            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
5298                .policy(policy)
5299                .decrypt_data(&encrypted),
5300            Err(XmlEncError::Policy(
5301                crate::policy::PolicyViolation::ResourceLimit {
5302                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
5303                    ..
5304                }
5305            ))
5306        ));
5307
5308        *encrypted
5309            .cipher_data
5310            .inline_value_mut()
5311            .expect("inline ciphertext") = STANDARD.encode([0_u8; 28]);
5312        encrypted.encrypted_keys.push(EncryptedKey {
5313            sources: Default::default(),
5314            id: None,
5315            recipient: None,
5316            key_name: None,
5317            encryption_method: super::super::EncryptionMethod {
5318                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5319                key_size_bits: None,
5320                oaep_digest: None,
5321                mgf_algorithm: None,
5322                oaep_params: None,
5323            },
5324            cipher_data: super::super::CipherData::Value {
5325                value: "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN + 4),
5326            },
5327            reference_list: None,
5328            carried_key_name: None,
5329        });
5330        let resolver = CountingResolver {
5331            candidate_calls: Cell::new(0),
5332            key: key.to_vec(),
5333        };
5334        assert!(matches!(
5335            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5336            Err(XmlEncError::InvalidStructure(_))
5337        ));
5338        assert_eq!(resolver.candidate_calls.get(), 0);
5339
5340        *encrypted.encrypted_keys[0]
5341            .cipher_data
5342            .inline_value_mut()
5343            .expect("inline wrapped key") = "AAAA".into();
5344        let aggregate_encoded_len = encrypted
5345            .cipher_data
5346            .inline_value()
5347            .expect("inline ciphertext")
5348            .len()
5349            + encrypted.encrypted_keys[0]
5350                .cipher_data
5351                .inline_value()
5352                .expect("inline wrapped key")
5353                .len();
5354        let policy = crate::policy::DecryptionPolicy {
5355            resources: crate::policy::ResourcePolicy {
5356                max_encryption_plaintext_bytes: 4,
5357                max_xml_document_bytes: aggregate_encoded_len - 1,
5358                ..crate::policy::ResourcePolicy::default()
5359            },
5360            ..crate::policy::DecryptionPolicy::default()
5361        };
5362        let resolver = CountingResolver {
5363            candidate_calls: Cell::new(0),
5364            key: key.to_vec(),
5365        };
5366        assert!(matches!(
5367            DecryptContext::new(&resolver)
5368                .policy(policy)
5369                .decrypt_data(&encrypted),
5370            Err(XmlEncError::Policy(
5371                crate::policy::PolicyViolation::ResourceLimit {
5372                    resource:
5373                        crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
5374                    maximum,
5375                    actual,
5376                }
5377            )) if maximum == aggregate_encoded_len - 1 && actual == aggregate_encoded_len
5378        ));
5379        assert_eq!(resolver.candidate_calls.get(), 0);
5380    }
5381
5382    #[cfg(feature = "experimental-pq")]
5383    #[test]
5384    fn typed_kem_width_is_checked_before_any_resolver_dispatch() {
5385        // Exercise the existing facade preflight with a permissive custom
5386        // resolver, including nested KEKs, rather than built-in KEM recovery.
5387        let document = crate::XmlDomDocument::parse(include_str!(
5388            "../../tests/fixtures/xmlenc/aleksey-xmlenc-01/enc-aes128gcm-em-ml-kem-512.xml"
5389        ))
5390        .expect("valid donor XML");
5391        let node = document
5392            .descendants()
5393            .find(|node| node.has_tag_name((crate::xmlenc::types::XMLENC_NS, "EncryptedData")))
5394            .expect("donor EncryptedData");
5395        let mut policy = crate::policy::DecryptionPolicy::default();
5396        policy
5397            .key_establishment
5398            .encapsulation_algorithms
5399            .insert(crate::provider::KeyEncapsulationAlgorithm::MlKem512);
5400        let data = super::super::parse_encrypted_data_node_with_policy(node, &policy)
5401            .expect("valid donor encryption metadata");
5402        for width in [0, 767, 769] {
5403            for nested in [false, true] {
5404                let mut encrypted = data.clone();
5405                encrypted.encapsulation_methods[0]
5406                    .ciphertext
5407                    .resize(width, 0);
5408                if nested {
5409                    encrypted.encrypted_keys.push(EncryptedKey {
5410                        sources: super::super::EncryptionKeySources {
5411                            encapsulation_methods: std::mem::take(
5412                                &mut encrypted.encapsulation_methods,
5413                            ),
5414                            ..Default::default()
5415                        },
5416                        id: None,
5417                        recipient: None,
5418                        key_name: None,
5419                        encryption_method: super::super::EncryptionMethod {
5420                            algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5421                            key_size_bits: None,
5422                            oaep_digest: None,
5423                            mgf_algorithm: None,
5424                            oaep_params: None,
5425                        },
5426                        cipher_data: super::super::CipherData::Bytes(vec![0; 24]),
5427                        reference_list: None,
5428                        carried_key_name: None,
5429                    });
5430                }
5431                let resolver = CountingResolver {
5432                    candidate_calls: Cell::new(0),
5433                    key: vec![0; 16],
5434                };
5435                let error = DecryptContext::new(&resolver)
5436                    .policy(policy.clone())
5437                    .decrypt_data(&encrypted)
5438                    .expect_err("malformed typed KEM width rejected");
5439                assert!(
5440                    error
5441                        .to_string()
5442                        .contains("ciphertext size does not match Algorithm"),
5443                    "{error}"
5444                );
5445                assert_eq!(resolver.candidate_calls.get(), 0);
5446            }
5447        }
5448        // A custom resolver cannot bypass the facade's default KEM/CBC gate.
5449        let mut encrypted = data;
5450        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes256Cbc.uri().into();
5451        encrypted.cipher_data = super::super::CipherData::Bytes(vec![0; 32]);
5452        let resolver = CountingResolver {
5453            candidate_calls: Cell::new(0),
5454            key: vec![0; 32],
5455        };
5456        assert!(matches!(
5457            DecryptContext::new(&resolver)
5458                .policy(policy)
5459                .decrypt_data(&encrypted),
5460            Err(XmlEncError::Policy(
5461                crate::policy::PolicyViolation::Algorithm {
5462                    operation: "direct KEM content without external authentication",
5463                    ..
5464                }
5465            ))
5466        ));
5467        assert_eq!(resolver.candidate_calls.get(), 0);
5468    }
5469
5470    #[cfg(feature = "experimental-pq")]
5471    #[test]
5472    fn typed_kem_ciphertext_shares_aggregate_cipher_value_budget() {
5473        // Typed input must charge the same base64-projected KEM ciphertext as
5474        // XML input, including descriptors nested under an EncryptedKey.
5475        let xml = include_str!(
5476            "../../tests/fixtures/xmlenc/aleksey-xmlenc-01/enc-aes128gcm-em-ml-kem-512.xml"
5477        );
5478        let document = crate::XmlDomDocument::parse(xml).expect("donor XML parses");
5479        let node = document
5480            .descendants()
5481            .find(|node| node.has_tag_name((crate::xmlenc::types::XMLENC_NS, "EncryptedData")))
5482            .expect("donor EncryptedData");
5483        let mut policy = crate::policy::DecryptionPolicy::default();
5484        policy
5485            .key_establishment
5486            .encapsulation_algorithms
5487            .insert(crate::provider::KeyEncapsulationAlgorithm::MlKem512);
5488        let mut encrypted = super::super::parse_encrypted_data_node_with_policy(node, &policy)
5489            .expect("permitted donor recipient");
5490        let content = cipher_data_storage_len(&encrypted.cipher_data);
5491        let kem_encoded = 4 * encrypted.encapsulation_methods[0]
5492            .ciphertext
5493            .len()
5494            .div_ceil(3);
5495        assert!(
5496            validate_typed_cipher_values(
5497                &encrypted,
5498                DataEncryptionAlgorithm::Aes128Gcm,
5499                usize::MAX / 2,
5500                content + kem_encoded
5501            )
5502            .is_ok()
5503        );
5504        for nested in [false, true] {
5505            if nested {
5506                encrypted.encrypted_keys.push(EncryptedKey {
5507                    sources: super::super::EncryptionKeySources {
5508                        encapsulation_methods: std::mem::take(&mut encrypted.encapsulation_methods),
5509                        ..Default::default()
5510                    },
5511                    id: None,
5512                    recipient: None,
5513                    key_name: None,
5514                    encryption_method: super::super::EncryptionMethod {
5515                        algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5516                        key_size_bits: None,
5517                        oaep_digest: None,
5518                        mgf_algorithm: None,
5519                        oaep_params: None,
5520                    },
5521                    cipher_data: super::super::CipherData::Bytes(vec![0; 24]),
5522                    reference_list: None,
5523                    carried_key_name: None,
5524                });
5525            }
5526            let total = content + kem_encoded + if nested { 24 } else { 0 };
5527            policy.resources.max_xml_document_bytes = total - 1;
5528            let resolver = CountingResolver {
5529                candidate_calls: Cell::new(0),
5530                key: vec![0; 16],
5531            };
5532            assert!(
5533                matches!(DecryptContext::new(&resolver).policy(policy.clone()).decrypt_data(&encrypted), Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit { resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES, maximum, actual })) if maximum == total - 1 && actual == total)
5534            );
5535            assert_eq!(resolver.candidate_calls.get(), 0);
5536        }
5537    }
5538
5539    #[test]
5540    fn typed_legacy_oaep_accepts_explicit_mgf() {
5541        // The legacy URI defaults to MGF1-SHA1 when MGF is absent, but
5542        // libxmlsec1 also accepts the XMLEnc 1.1 child explicitly.
5543        let key = [0x43_u8; 16];
5544        let ciphertext = crate::provider::default_provider()
5545            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5546            .expect("test encryption must succeed");
5547        let encrypted = EncryptedData {
5548            encapsulation_methods: Vec::new(),
5549            id: None,
5550            derived_keys: Vec::new(),
5551            agreement_methods: Vec::new(),
5552            encrypted_type: None,
5553            key_name: None,
5554            encryption_method: super::super::EncryptionMethod {
5555                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5556                key_size_bits: None,
5557                oaep_digest: None,
5558                mgf_algorithm: None,
5559                oaep_params: None,
5560            },
5561            encrypted_keys: vec![EncryptedKey {
5562                sources: Default::default(),
5563                id: None,
5564                recipient: None,
5565                key_name: None,
5566                encryption_method: super::super::EncryptionMethod {
5567                    algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
5568                    key_size_bits: None,
5569                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5570                    mgf_algorithm: Some(OaepDigestAlgorithm::Sha384.mgf_uri().into()),
5571                    oaep_params: None,
5572                },
5573                cipher_data: super::super::CipherData::Value {
5574                    value: STANDARD.encode([0_u8; 256]),
5575                },
5576                reference_list: None,
5577                carried_key_name: None,
5578            }],
5579            cipher_data: super::super::CipherData::Value {
5580                value: STANDARD.encode(ciphertext),
5581            },
5582        };
5583        let resolver = CountingResolver {
5584            candidate_calls: Cell::new(0),
5585            key: key.to_vec(),
5586        };
5587
5588        assert_eq!(
5589            DecryptContext::new(&resolver)
5590                .decrypt_data(&encrypted)
5591                .expect("explicit legacy OAEP MGF must reach key resolution"),
5592            DecryptedContent::Bytes(b"data".to_vec())
5593        );
5594        assert_eq!(resolver.candidate_calls.get(), 1);
5595    }
5596
5597    #[test]
5598    fn typed_zero_key_size_is_rejected_before_key_resolution() {
5599        // Parsed KeySize values are positive. Caller-constructed values must
5600        // preserve the same invariant for algorithms without a fixed AES width.
5601        let key = [0x45_u8; 16];
5602        let ciphertext = crate::provider::default_provider()
5603            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5604            .expect("test encryption must succeed");
5605        let encrypted = EncryptedData {
5606            encapsulation_methods: Vec::new(),
5607            id: None,
5608            derived_keys: Vec::new(),
5609            agreement_methods: Vec::new(),
5610            encrypted_type: None,
5611            key_name: None,
5612            encryption_method: super::super::EncryptionMethod {
5613                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5614                key_size_bits: None,
5615                oaep_digest: None,
5616                mgf_algorithm: None,
5617                oaep_params: None,
5618            },
5619            encrypted_keys: vec![EncryptedKey {
5620                id: None,
5621                sources: Default::default(),
5622                recipient: None,
5623                key_name: None,
5624                encryption_method: super::super::EncryptionMethod {
5625                    algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
5626                    key_size_bits: Some(0),
5627                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5628                    mgf_algorithm: Some(OaepDigestAlgorithm::Sha256.mgf_uri().into()),
5629                    oaep_params: None,
5630                },
5631                cipher_data: super::super::CipherData::Value {
5632                    value: STANDARD.encode([0_u8; 256]),
5633                },
5634                reference_list: None,
5635                carried_key_name: None,
5636            }],
5637            cipher_data: super::super::CipherData::Value {
5638                value: STANDARD.encode(ciphertext),
5639            },
5640        };
5641        let resolver = CountingResolver {
5642            candidate_calls: Cell::new(0),
5643            key: key.to_vec(),
5644        };
5645
5646        assert!(matches!(
5647            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5648            Err(XmlEncError::InvalidStructure(message))
5649                if message == "KeySize must be a positive integer"
5650        ));
5651        assert_eq!(resolver.candidate_calls.get(), 0);
5652    }
5653
5654    #[test]
5655    fn typed_content_method_is_validated_before_key_resolution() {
5656        // Caller-constructed values bypass XML parsing, so a fixed-size AES
5657        // KeySize mismatch must fail at the operation boundary.
5658        let key = [0x44_u8; 16];
5659        let ciphertext = crate::provider::default_provider()
5660            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5661            .expect("test encryption must succeed");
5662        let encrypted = EncryptedData {
5663            encapsulation_methods: Vec::new(),
5664            id: None,
5665            derived_keys: Vec::new(),
5666            agreement_methods: Vec::new(),
5667            encrypted_type: None,
5668            key_name: None,
5669            encryption_method: super::super::EncryptionMethod {
5670                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5671                key_size_bits: Some(256),
5672                oaep_digest: None,
5673                mgf_algorithm: None,
5674                oaep_params: None,
5675            },
5676            encrypted_keys: Vec::new(),
5677            cipher_data: super::super::CipherData::Value {
5678                value: STANDARD.encode(ciphertext),
5679            },
5680        };
5681        let resolver = AllCallsResolver {
5682            calls: Cell::new(0),
5683            key: key.to_vec(),
5684        };
5685
5686        assert!(matches!(
5687            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5688            Err(XmlEncError::InvalidStructure(message))
5689                if message.contains("requires KeySize 128, got 256")
5690        ));
5691        assert_eq!(resolver.calls.get(), 0);
5692    }
5693
5694    #[test]
5695    fn unknown_encrypted_key_algorithm_never_reaches_resolver() {
5696        // Extension URIs cannot bypass transport/wrap allowlists by relying on
5697        // an application resolver that happens to return usable key bytes.
5698        let key = [0x42_u8; 16];
5699        let ciphertext = crate::provider::default_provider()
5700            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5701            .expect("test encryption must succeed");
5702        let encrypted = EncryptedData {
5703            encapsulation_methods: Vec::new(),
5704            id: None,
5705            derived_keys: Vec::new(),
5706            agreement_methods: Vec::new(),
5707            encrypted_type: None,
5708            key_name: None,
5709            encryption_method: super::super::EncryptionMethod {
5710                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5711                key_size_bits: None,
5712                oaep_digest: None,
5713                mgf_algorithm: None,
5714                oaep_params: None,
5715            },
5716            encrypted_keys: vec![EncryptedKey {
5717                id: None,
5718                recipient: None,
5719                sources: Default::default(),
5720                key_name: None,
5721                encryption_method: super::super::EncryptionMethod {
5722                    algorithm: "urn:example:unknown-key-algorithm".into(),
5723                    key_size_bits: None,
5724                    oaep_digest: None,
5725                    mgf_algorithm: None,
5726                    oaep_params: None,
5727                },
5728                cipher_data: super::super::CipherData::Value {
5729                    value: STANDARD.encode([0_u8; 24]),
5730                },
5731                reference_list: None,
5732                carried_key_name: None,
5733            }],
5734            cipher_data: super::super::CipherData::Value {
5735                value: STANDARD.encode(ciphertext),
5736            },
5737        };
5738        let resolver = CountingResolver {
5739            candidate_calls: Cell::new(0),
5740            key: key.to_vec(),
5741        };
5742
5743        assert!(matches!(
5744            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5745            Err(XmlEncError::UnsupportedAlgorithm(_))
5746        ));
5747        assert_eq!(resolver.candidate_calls.get(), 0);
5748    }
5749
5750    #[test]
5751    fn cbc_padding_errors_do_not_expose_decrypted_octets() {
5752        // The error contract hides padding details, but callers still need an
5753        // authenticated envelope or a policy that rejects unauthenticated CBC.
5754        let error = map_data_decryption_error(
5755            DataEncryptionAlgorithm::Aes128Cbc,
5756            32,
5757            crate::provider::ProviderError::InvalidInput(
5758                crate::provider::ProviderInputError::AesCbcCiphertext,
5759            ),
5760        );
5761
5762        assert_eq!(error.to_string(), "invalid XMLEnc padding");
5763    }
5764
5765    #[test]
5766    fn replaces_element_and_content_in_caller_owned_documents() {
5767        // Element plaintext replaces the encrypted node itself, while Content
5768        // plaintext becomes children of the existing parent element.
5769        let key = [0x31_u8; 16];
5770        let element = encrypted_gcm_element(
5771            "http://www.w3.org/2001/04/xmlenc#Element",
5772            "<secret id=\"visible\">value</secret>",
5773            None,
5774            true,
5775            &key,
5776        );
5777        assert_eq!(
5778            decrypt_document(&element, None, &SymmetricKeyDecryptor::new(key))
5779                .expect("root Element replacement must succeed"),
5780            "<secret id=\"visible\">value</secret>"
5781        );
5782
5783        let content = encrypted_gcm_element(
5784            "http://www.w3.org/2001/04/xmlenc#Content",
5785            "before<child/>after",
5786            None,
5787            false,
5788            &key,
5789        );
5790        let document =
5791            format!("<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>{content}<suffix/></root>");
5792        let replaced = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
5793            .expect("nested Content replacement must succeed");
5794        assert_eq!(
5795            replaced,
5796            format!(
5797                "<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>before<child/>after<suffix/></root>"
5798            )
5799        );
5800    }
5801
5802    #[test]
5803    fn accepts_whitespace_and_comments_around_element_plaintext() {
5804        // Element serialization may carry harmless boundary whitespace/comments;
5805        // they must be preserved while the fragment still contains one element.
5806        let key = [0x34_u8; 16];
5807        let plaintext = "\n<!--before--><secret/><!--after-->\n";
5808        let encrypted = encrypted_gcm_element(
5809            "http://www.w3.org/2001/04/xmlenc#Element",
5810            plaintext,
5811            None,
5812            true,
5813            &key,
5814        );
5815
5816        assert_eq!(
5817            decrypt_document(&encrypted, None, &SymmetricKeyDecryptor::new(key))
5818                .expect("one element with boundary trivia must be accepted"),
5819            plaintext
5820        );
5821    }
5822
5823    #[test]
5824    fn decrypts_unknown_and_empty_type_hints_as_opaque_bytes() {
5825        // Type is an application hint, not an algorithm constraint. Unknown and
5826        // empty values must not prevent decryption of otherwise valid binary data.
5827        let key = [0x35_u8; 16];
5828        let plaintext = "\0opaque\u{ff}bytes";
5829        let unknown = encrypted_gcm_element("urn:example:binary", plaintext, None, true, &key);
5830        let empty = encrypted_gcm_element("", plaintext, None, true, &key).replacen(
5831            "<xenc:EncryptedData",
5832            "<xenc:EncryptedData Type=\"\"",
5833            1,
5834        );
5835
5836        let parsed = parse_encrypted_data(&unknown).expect("unknown Type must remain parseable");
5837        assert_eq!(
5838            parsed.encrypted_type,
5839            Some(EncryptedDataType::Other("urn:example:binary".into()))
5840        );
5841        assert!(matches!(
5842            decrypt_document(&unknown, None, &SymmetricKeyDecryptor::new(key)),
5843            Err(XmlEncError::ReplacementRequiresXml)
5844        ));
5845
5846        for encrypted in [unknown, empty] {
5847            assert_eq!(
5848                decrypt(&encrypted, &SymmetricKeyDecryptor::new(key))
5849                    .expect("opaque Type hints must not block decryption"),
5850                DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
5851            );
5852        }
5853    }
5854
5855    #[test]
5856    fn selects_document_encrypted_data_by_id_and_rejects_ambiguity() {
5857        // Selection must never decrypt an arbitrary first match when a document
5858        // contains multiple encrypted regions.
5859        let key = [0x32_u8; 16];
5860        let first = encrypted_gcm_element(
5861            "http://www.w3.org/2001/04/xmlenc#Content",
5862            "first",
5863            Some("first"),
5864            false,
5865            &key,
5866        );
5867        let second = encrypted_gcm_element(
5868            "http://www.w3.org/2001/04/xmlenc#Content",
5869            "second",
5870            Some("second"),
5871            false,
5872            &key,
5873        );
5874        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{first}{second}</root>");
5875        let resolver = SymmetricKeyDecryptor::new(key);
5876        assert!(matches!(
5877            decrypt_document(&document, None, &resolver),
5878            Err(XmlEncError::AmbiguousEncryptedData)
5879        ));
5880        let replaced = decrypt_document(&document, Some("second"), &resolver)
5881            .expect("Id selection must choose exactly one encrypted region");
5882        assert!(replaced.contains("second"));
5883        assert!(replaced.contains("Id=\"first\""));
5884        assert!(matches!(
5885            decrypt_document(&document, Some("missing"), &resolver),
5886            Err(XmlEncError::EncryptedDataNotFound)
5887        ));
5888    }
5889
5890    #[test]
5891    fn selects_encrypted_data_below_a_unique_operation_start_node() {
5892        // CLI-compatible selection starts at an arbitrary ID-bearing ancestor;
5893        // missing/duplicate IDs and multiple encrypted descendants fail closed.
5894        let key = [0x42_u8; 16];
5895        let first = encrypted_gcm_element(
5896            "http://www.w3.org/2001/04/xmlenc#Content",
5897            "first",
5898            None,
5899            false,
5900            &key,
5901        );
5902        let second = encrypted_gcm_element(
5903            "http://www.w3.org/2001/04/xmlenc#Content",
5904            "second",
5905            None,
5906            false,
5907            &key,
5908        );
5909        let document = format!(
5910            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"first\">{first}</scope><scope Id=\"second\">{second}</scope></root>"
5911        );
5912        let resolver = SymmetricKeyDecryptor::new(key);
5913        let context = DecryptContext::new(&resolver);
5914        let replaced = context
5915            .decrypt_document_from_start_node(&document, Some("second"))
5916            .expect("ancestor ID must select its encrypted descendant");
5917        assert!(replaced.contains("<scope Id=\"second\">second</scope>"));
5918        assert!(replaced.contains("<scope Id=\"first\"><xenc:EncryptedData"));
5919
5920        assert!(matches!(
5921            context.decrypt_document_from_start_node(&document, Some("missing")),
5922            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "missing"
5923        ));
5924        let duplicate = document.replace("Id=\"second\"", "Id=\"first\"");
5925        assert!(matches!(
5926            context.decrypt_document_from_start_node(&duplicate, Some("first")),
5927            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "first"
5928        ));
5929        let ambiguous = format!(
5930            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"selected\">{first}{second}</scope></root>"
5931        );
5932        assert!(matches!(
5933            context.decrypt_document_from_start_node(&ambiguous, Some("selected")),
5934            Err(XmlEncError::AmbiguousEncryptedData)
5935        ));
5936
5937        let first_replaced = context
5938            .decrypt_first_document_from_start_node(&ambiguous, Some("selected"))
5939            .expect("first-match selection must leave later encrypted descendants untouched");
5940        assert!(first_replaced.contains("<scope Id=\"selected\">first<xenc:EncryptedData"));
5941        let replaced_document =
5942            Document::parse(&first_replaced).expect("first-match output must remain valid XML");
5943        assert_eq!(
5944            replaced_document
5945                .descendants()
5946                .filter(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
5947                .count(),
5948            1
5949        );
5950    }
5951
5952    #[test]
5953    fn rejects_non_xml_or_malformed_document_replacement_plaintext() {
5954        // The document API must not expose binary content or return a document
5955        // made malformed by unauthenticated structure assumptions.
5956        let key = [0x33_u8; 16];
5957        let binary = encrypted_gcm_element("", "binary", None, true, &key);
5958        assert!(matches!(
5959            decrypt_document(&binary, None, &SymmetricKeyDecryptor::new(key)),
5960            Err(XmlEncError::ReplacementRequiresXml)
5961        ));
5962
5963        let malformed = encrypted_gcm_element(
5964            "http://www.w3.org/2001/04/xmlenc#Element",
5965            "<unclosed>",
5966            None,
5967            true,
5968            &key,
5969        );
5970        assert!(matches!(
5971            decrypt_document(&malformed, None, &SymmetricKeyDecryptor::new(key)),
5972            Err(XmlEncError::XmlParse(_))
5973        ));
5974
5975        for invalid_element in ["text-only", "<first/><second/>"] {
5976            let encrypted = encrypted_gcm_element(
5977                "http://www.w3.org/2001/04/xmlenc#Element",
5978                invalid_element,
5979                None,
5980                false,
5981                &key,
5982            );
5983            let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
5984            assert!(
5985                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
5986                "Element plaintext must contain exactly one element: {invalid_element}"
5987            );
5988        }
5989
5990        let content = encrypted_gcm_element(
5991            "http://www.w3.org/2001/04/xmlenc#Content",
5992            "plaintext",
5993            None,
5994            false,
5995            &key,
5996        );
5997        let with_dtd = format!(
5998            "<!DOCTYPE root [<!ATTLIST root Id ID #IMPLIED>]><root xmlns:xenc=\"{XMLENC_NS}\">{content}</root>"
5999        );
6000        assert!(matches!(
6001            decrypt_document(&with_dtd, None, &SymmetricKeyDecryptor::new(key)),
6002            Err(XmlEncError::XmlParse(
6003                crate::xml::dom::ParseError::DtdDetected
6004            ))
6005        ));
6006        let mut policy = crate::policy::DecryptionPolicy::default();
6007        policy.xml.allow_internal_dtd = true;
6008        assert!(
6009            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6010                .policy(policy)
6011                .decrypt_document(&with_dtd, None)
6012                .expect("explicit internal-DTD opt-in must decrypt")
6013                .contains("plaintext")
6014        );
6015    }
6016
6017    #[test]
6018    fn rejects_plaintext_markup_that_crosses_the_encrypted_region() {
6019        // Parsing only after raw splicing is insufficient: balanced close/reopen
6020        // tags can keep the document valid while moving attacker nodes outside the
6021        // element whose encrypted child is being replaced.
6022        let key = [0x36_u8; 16];
6023        let crossing_markup = "</parent><attacker/><parent>";
6024        for type_uri in [
6025            "http://www.w3.org/2001/04/xmlenc#Content",
6026            "http://www.w3.org/2001/04/xmlenc#Element",
6027        ] {
6028            let encrypted = encrypted_gcm_element(type_uri, crossing_markup, None, false, &key);
6029            let document =
6030                format!("<outer xmlns:xenc=\"{XMLENC_NS}\"><parent>{encrypted}</parent></outer>");
6031            assert!(
6032                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
6033                "{type_uri} plaintext must not escape its replacement boundary"
6034            );
6035        }
6036    }
6037
6038    #[test]
6039    fn document_decryption_applies_byte_and_node_policy_before_parsing() {
6040        // Caller-owned XML must meet the compiled resource policy before the
6041        // initial DOM allocation; reparsed output uses the same node ceiling.
6042        let key = [0x38_u8; 16];
6043        let encrypted = encrypted_gcm_element(
6044            "http://www.w3.org/2001/04/xmlenc#Content",
6045            "plaintext",
6046            None,
6047            false,
6048            &key,
6049        );
6050        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\"><a/>{encrypted}</root>");
6051        let byte_policy = crate::policy::DecryptionPolicy {
6052            resources: crate::policy::ResourcePolicy {
6053                max_xml_document_bytes: document.len() - 1,
6054                ..crate::policy::ResourcePolicy::default()
6055            },
6056            ..crate::policy::DecryptionPolicy::default()
6057        };
6058        assert!(matches!(
6059            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6060                .policy(byte_policy)
6061                .decrypt_document(&document, None),
6062            Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6063                resource: crate::policy::resource_name::XML_DOCUMENT,
6064                maximum,
6065                actual,
6066            })) if maximum == document.len() - 1 && actual == document.len()
6067        ));
6068
6069        let node_policy = crate::policy::DecryptionPolicy {
6070            resources: crate::policy::ResourcePolicy {
6071                max_xml_nodes: 3,
6072                ..crate::policy::ResourcePolicy::default()
6073            },
6074            ..crate::policy::DecryptionPolicy::default()
6075        };
6076        assert!(matches!(
6077            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6078                .policy(node_policy)
6079                .decrypt_document(&document, None),
6080            Err(XmlEncError::Policy(
6081                crate::policy::PolicyViolation::ResourceLimit {
6082                    resource: crate::policy::resource_name::XML_NODES,
6083                    maximum: 3,
6084                    actual: 4,
6085                }
6086            ))
6087        ));
6088    }
6089
6090    #[test]
6091    fn decryption_entry_points_enforce_policy_depth() {
6092        // Depth validation precedes EncryptedData selection for both borrowed
6093        // and retained documents, including inputs parsed under wider defaults.
6094        let xml = "<root><child><leaf/></child></root>";
6095        let policy = crate::policy::DecryptionPolicy {
6096            resources: crate::policy::ResourcePolicy {
6097                max_xml_depth: 2,
6098                ..crate::policy::ResourcePolicy::default()
6099            },
6100            ..crate::policy::DecryptionPolicy::default()
6101        };
6102        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
6103        let mut document = XmlDocument::parse(xml).expect("wide retained fixture must parse");
6104
6105        assert!(matches!(
6106            DecryptContext::new(&resolver)
6107                .policy(policy.clone())
6108                .decrypt_document(xml, None),
6109            Err(XmlEncError::Policy(
6110                crate::policy::PolicyViolation::ResourceLimit {
6111                    resource: crate::policy::resource_name::XML_DEPTH,
6112                    maximum: 2,
6113                    actual: 3,
6114                }
6115            ))
6116        ));
6117        assert!(matches!(
6118            DecryptContext::new(&resolver)
6119                .policy(policy)
6120                .decrypt_owned_document(&mut document, None),
6121            Err(XmlEncError::Policy(
6122                crate::policy::PolicyViolation::ResourceLimit {
6123                    resource: crate::policy::resource_name::XML_DEPTH,
6124                    maximum: 2,
6125                    actual: 3,
6126                }
6127            ))
6128        ));
6129    }
6130
6131    #[test]
6132    fn fragment_validation_does_not_charge_its_internal_wrapper_node() {
6133        // The caller's node ceiling applies to input and output XML, not the
6134        // implementation-only element used to prove replacement boundaries.
6135        let key = [0x39_u8; 16];
6136        let plaintext = "<item/>".repeat(20);
6137        let encrypted = encrypted_gcm_element(
6138            "http://www.w3.org/2001/04/xmlenc#Content",
6139            &plaintext,
6140            None,
6141            false,
6142            &key,
6143        );
6144        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
6145        let resolver = SymmetricKeyDecryptor::new(key);
6146        let expected = decrypt_document(&document, None, &resolver)
6147            .expect("unbounded setup decryption must succeed");
6148        let exact_output_nodes = Document::parse(&expected)
6149            .expect("decrypted output must parse")
6150            .descendants()
6151            .count();
6152        let policy = crate::policy::DecryptionPolicy {
6153            resources: crate::policy::ResourcePolicy {
6154                max_xml_nodes: exact_output_nodes,
6155                ..crate::policy::ResourcePolicy::default()
6156            },
6157            ..crate::policy::DecryptionPolicy::default()
6158        };
6159
6160        assert_eq!(
6161            DecryptContext::new(&resolver)
6162                .policy(policy)
6163                .decrypt_document(&document, None)
6164                .expect("temporary wrapper must not consume caller node budget"),
6165            expected
6166        );
6167    }
6168
6169    #[test]
6170    fn owned_decryption_rejects_projected_node_limit_atomically() {
6171        // The owned document may have been parsed under a broader ceiling than
6172        // this operation; expanded plaintext must be bounded before mutation.
6173        let key = [0x3a_u8; 16];
6174        let plaintext = "<item/>".repeat(64);
6175        let encrypted = encrypted_gcm_element(
6176            "http://www.w3.org/2001/04/xmlenc#Content",
6177            &plaintext,
6178            None,
6179            false,
6180            &key,
6181        );
6182        let mut document = XmlDocument::parse(format!(
6183            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
6184        ))
6185        .expect("owned encrypted fixture must parse");
6186        let input_nodes = document.with_view(|view| view.node_count());
6187        let before = document.as_xml().to_owned();
6188        let policy = crate::policy::DecryptionPolicy {
6189            resources: crate::policy::ResourcePolicy {
6190                max_xml_nodes: input_nodes,
6191                ..crate::policy::ResourcePolicy::default()
6192            },
6193            ..crate::policy::DecryptionPolicy::default()
6194        };
6195
6196        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6197            .policy(policy)
6198            .decrypt_owned_document(&mut document, None)
6199            .expect_err("expanded plaintext must exceed the operation node ceiling");
6200
6201        assert!(matches!(
6202            error,
6203            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6204                resource: crate::policy::resource_name::XML_NODES,
6205                maximum,
6206                ..
6207            }) if maximum == input_nodes
6208        ));
6209        assert_eq!(document.as_xml(), before);
6210        assert_eq!(document.generation(), 0);
6211    }
6212
6213    #[test]
6214    fn owned_decryption_reports_decrypted_depth_as_policy() {
6215        // The encrypted envelope can satisfy the active depth policy while its
6216        // plaintext replacement exceeds it. That rejection must retain the
6217        // typed policy contract and leave the owned document untouched.
6218        let key = [0x3b_u8; 16];
6219        let plaintext = format!("{}value{}", "<nested>".repeat(32), "</nested>".repeat(32));
6220        let encrypted = encrypted_gcm_element(
6221            "http://www.w3.org/2001/04/xmlenc#Element",
6222            &plaintext,
6223            None,
6224            false,
6225            &key,
6226        );
6227        let mut document = XmlDocument::parse(format!(
6228            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
6229        ))
6230        .expect("encrypted fixture must parse");
6231        let input_depth = document.with_view(|view| view.max_depth());
6232        let before = document.as_xml().to_owned();
6233        let policy = crate::policy::DecryptionPolicy {
6234            resources: crate::policy::ResourcePolicy {
6235                max_xml_depth: input_depth,
6236                ..crate::policy::ResourcePolicy::default()
6237            },
6238            ..crate::policy::DecryptionPolicy::default()
6239        };
6240
6241        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6242            .policy(policy)
6243            .decrypt_owned_document(&mut document, None)
6244            .expect_err("deep plaintext must exceed the active depth policy");
6245
6246        assert!(matches!(
6247            error,
6248            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6249                resource: crate::policy::resource_name::XML_DEPTH,
6250                maximum,
6251                actual,
6252            }) if maximum == input_depth && actual > maximum
6253        ));
6254        assert_eq!(document.as_xml(), before);
6255        assert_eq!(document.generation(), 0);
6256    }
6257
6258    #[test]
6259    fn validates_replacement_plaintext_in_its_namespace_context() {
6260        // Decrypted fragments inherit namespaces from the encrypted node's
6261        // ancestors, so boundary validation must occur inside the source document.
6262        let key = [0x37_u8; 16];
6263        let encrypted = encrypted_gcm_element(
6264            "http://www.w3.org/2001/04/xmlenc#Content",
6265            "<shared:child/>",
6266            None,
6267            false,
6268            &key,
6269        );
6270        let document = format!(
6271            "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\">{encrypted}</root>"
6272        );
6273        let decrypted = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
6274            .expect("inherited namespace prefixes must remain valid");
6275        assert_eq!(
6276            decrypted,
6277            format!(
6278                "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\"><shared:child/></root>"
6279            )
6280        );
6281    }
6282
6283    fn encrypted_gcm_element(
6284        type_uri: &str,
6285        plaintext: &str,
6286        id: Option<&str>,
6287        declare_namespace: bool,
6288        key: &[u8; 16],
6289    ) -> String {
6290        let nonce = [0x44_u8; 12];
6291        let mut ciphertext = plaintext.as_bytes().to_vec();
6292        Aes128Gcm::new_from_slice(key)
6293            .expect("fixed content key length")
6294            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
6295            .expect("test encryption must succeed");
6296        let mut wire = nonce.to_vec();
6297        wire.extend_from_slice(&ciphertext);
6298        let namespace = declare_namespace
6299            .then_some(format!(" xmlns:xenc=\"{XMLENC_NS}\""))
6300            .unwrap_or_default();
6301        let data_type = (!type_uri.is_empty())
6302            .then_some(format!(" Type=\"{type_uri}\""))
6303            .unwrap_or_default();
6304        let id = id
6305            .map(|value| format!(" Id=\"{value}\""))
6306            .unwrap_or_default();
6307        format!(
6308            "<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>",
6309            STANDARD.encode(wire)
6310        )
6311    }
6312}