Skip to main content

xml_sec/xmlenc/
decrypt.rs

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