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