Skip to main content

xml_sec/xmlenc/
mod.rs

1//! XML Encryption (XMLEnc).
2//!
3//! Implements [XML Encryption Syntax and Processing](https://www.w3.org/TR/xmlenc-core1/).
4//!
5//! `EncryptedDataBuilder` encrypts opaque bytes, XML elements, XML content, or
6//! a selected node in a caller-owned document. It supports direct AES content
7//! keys and generated session keys wrapped independently for one or more
8//! RSA-OAEP or AES-KW recipients. The reciprocal decrypt APIs accept the same
9//! inline `CipherValue` profile.
10//!
11//! External `CipherReference` resources are outside this profile. Optional
12//! `legacy-algorithms` adds independently policy-gated RSA-1.5 and TripleDES
13//! mechanisms; it does not grant permission or authenticate CBC content.
14#![doc = include_str!("../../docs/xmlenc.md")]
15
16use crate::xml::dom::Node;
17
18mod agreement;
19mod encapsulation;
20pub use encapsulation::EncapsulationDecryptor;
21mod cipher_reference;
22pub use agreement::{AgreementDecryptor, AgreementMethod};
23mod decrypt;
24pub use cipher_reference::CipherReferenceContext;
25mod derived_key;
26pub(crate) use decrypt::validate_key_len;
27pub use derived_key::DerivedKey;
28mod encrypt;
29mod hkdf_agreement;
30mod key_derivation;
31pub use hkdf_agreement::{HkdfAgreement, parse_hkdf_agreement_method};
32mod key_establishment_budget;
33pub use key_establishment_budget::KeyEstablishmentBudget;
34mod parse;
35mod types;
36
37pub use decrypt::{
38    DecryptContext, DecryptionKeyResolver, DerivedKeyDecryptor, DerivedKeyInput,
39    DocumentDecryptionOptions, KekDecryptor, KeyCandidateBudget, KeyEncryptionKeySource,
40    OpaqueContentKeyResolver, OpaqueKekDecryptor, PrivateKeyDecryptor, SymmetricKeyDecryptor,
41    decrypt, decrypt_data, decrypt_document, decrypt_document_with_options,
42};
43pub use encrypt::{
44    EncryptedDataBuilder, validate_key_transport_recipient, validate_rsa_recipient_key,
45};
46pub use key_derivation::{
47    ConcatKdfField, KeyDerivationMethod, parse_key_derivation_method,
48    parse_key_derivation_method_with_backend,
49};
50pub use parse::{
51    EncryptedDataInspection, EncryptedDataTemplate, inspect_encrypted_data_node_with_context,
52    inspect_encrypted_data_template_node_with_context, parse_encrypted_data,
53    parse_encrypted_data_node_with_context, parse_encrypted_data_node_with_policy,
54    parse_encrypted_data_node_with_policy_and_backend,
55    parse_encrypted_data_template_node_with_policy,
56    parse_encrypted_data_template_node_with_policy_and_backend,
57};
58pub use types::{
59    ChaChaParameters, ChaChaParametersRef, CipherData, DataEncryptionAlgorithm, DecryptedContent,
60    DocumentEncryptionOptions, EncryptedData, EncryptedDataType, EncryptedKey,
61    EncryptionKeySources, EncryptionMethod, EncryptionRecipient, EncryptionResult,
62    KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm, ReferenceList, ReplacementMode,
63    RsaOaepParameters, XmlEncError,
64};
65
66fn map_document_error(
67    error: crate::document::XmlDocumentError,
68    settings: crate::document::DocumentParseSettings,
69) -> XmlEncError {
70    match error.into_policy_violation(settings) {
71        Ok(error) => XmlEncError::Policy(error),
72        Err(crate::document::XmlDocumentError::Parse(error)) => XmlEncError::XmlParse(error),
73        Err(crate::document::XmlDocumentError::ProjectedNodeLimit { maximum }) => {
74            crate::policy::PolicyViolation::ResourceLimit {
75                resource: crate::policy::resource_name::XML_NODES,
76                maximum,
77                actual: maximum.saturating_add(1),
78            }
79            .into()
80        }
81        Err(error) => XmlEncError::Document(error),
82    }
83}
84
85fn has_single_element_with_boundary_trivia(parent: Node<'_, '_>) -> bool {
86    let mut element_count = 0;
87    for node in parent.children() {
88        if node.is_element() {
89            element_count += 1;
90            if element_count > 1 {
91                return false;
92            }
93        } else if node.is_comment() {
94            continue;
95        } else if node.is_text() {
96            // XML permits boundary whitespace around a document element; processing
97            // instructions and every other node kind are unsafe replacement payloads.
98            if !node.text().is_some_and(|text| {
99                text.chars()
100                    .all(|character| matches!(character, ' ' | '\t' | '\n' | '\r'))
101            }) {
102                return false;
103            }
104        } else {
105            return false;
106        }
107    }
108    element_count == 1
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::document::{DocumentParseSettings, XmlDocumentError};
115
116    #[test]
117    fn document_errors_have_one_xmlenc_policy_mapping() {
118        // Borrowed parsing and owned mutation must expose identical typed
119        // resource failures rather than depending on their entry-point mapper.
120        let settings = DocumentParseSettings::new_with_depth(false, 8, 3, 128);
121        assert!(matches!(
122            map_document_error(
123                XmlDocumentError::DocumentTooDeep {
124                    maximum: 3,
125                    actual: 4,
126                },
127                settings,
128            ),
129            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
130                resource: crate::policy::resource_name::XML_DEPTH,
131                maximum: 3,
132                actual: 4,
133            })
134        ));
135        assert!(matches!(
136            map_document_error(
137                XmlDocumentError::ProjectedNodeLimit { maximum: 8 },
138                settings,
139            ),
140            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
141                resource: crate::policy::resource_name::XML_NODES,
142                maximum: 8,
143                actual: 9,
144            })
145        ));
146    }
147}