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