Skip to main content

xml_sec/xmlenc/
decrypt.rs

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