Skip to main content

xml_sec/xmlenc/
decrypt.rs

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