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, RSA PKCS#1 v1.5 key transport, and
12//! unauthenticated legacy ciphers are intentionally outside this profile.
13#![doc = include_str!("../../docs/xmlenc.md")]
14
15use crate::xml::dom::Node;
16
17mod decrypt;
18pub(crate) use decrypt::validate_key_len;
19mod encrypt;
20mod parse;
21mod types;
22
23pub use decrypt::{
24    DecryptContext, DecryptionKeyResolver, DocumentDecryptionOptions, KekDecryptor,
25    KeyCandidateBudget, PrivateKeyDecryptor, SymmetricKeyDecryptor, decrypt, decrypt_data,
26    decrypt_document, decrypt_document_with_options,
27};
28pub use encrypt::{
29    EncryptedDataBuilder, validate_key_transport_recipient, validate_rsa_recipient_key,
30};
31pub use parse::{
32    parse_encrypted_data, parse_encrypted_data_node_with_policy,
33    parse_encrypted_data_node_with_policy_and_backend,
34    parse_encrypted_data_template_node_with_policy,
35    parse_encrypted_data_template_node_with_policy_and_backend,
36};
37pub use types::{
38    CipherData, DataEncryptionAlgorithm, DecryptedContent, DocumentEncryptionOptions,
39    EncryptedData, EncryptedDataType, EncryptedKey, EncryptionMethod, EncryptionRecipient,
40    EncryptionResult, KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm, ReferenceList,
41    ReplacementMode, RsaOaepParameters, XmlEncError,
42};
43
44fn map_document_error(
45    error: crate::document::XmlDocumentError,
46    settings: crate::document::DocumentParseSettings,
47) -> XmlEncError {
48    match error.into_policy_violation(settings) {
49        Ok(error) => XmlEncError::Policy(error),
50        Err(crate::document::XmlDocumentError::Parse(error)) => XmlEncError::XmlParse(error),
51        Err(crate::document::XmlDocumentError::ProjectedNodeLimit { maximum }) => {
52            crate::policy::PolicyViolation::ResourceLimit {
53                resource: crate::policy::resource_name::XML_NODES,
54                maximum,
55                actual: maximum.saturating_add(1),
56            }
57            .into()
58        }
59        Err(error) => XmlEncError::Document(error),
60    }
61}
62
63fn has_single_element_with_boundary_trivia(parent: Node<'_, '_>) -> bool {
64    let mut element_count = 0;
65    for node in parent.children() {
66        if node.is_element() {
67            element_count += 1;
68            if element_count > 1 {
69                return false;
70            }
71        } else if node.is_comment() {
72            continue;
73        } else if node.is_text() {
74            // XML permits boundary whitespace around a document element; processing
75            // instructions and every other node kind are unsafe replacement payloads.
76            if !node.text().is_some_and(|text| {
77                text.chars()
78                    .all(|character| matches!(character, ' ' | '\t' | '\n' | '\r'))
79            }) {
80                return false;
81            }
82        } else {
83            return false;
84        }
85    }
86    element_count == 1
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::document::{DocumentParseSettings, XmlDocumentError};
93
94    #[test]
95    fn document_errors_have_one_xmlenc_policy_mapping() {
96        // Borrowed parsing and owned mutation must expose identical typed
97        // resource failures rather than depending on their entry-point mapper.
98        let settings = DocumentParseSettings::new_with_depth(false, 8, 3, 128);
99        assert!(matches!(
100            map_document_error(
101                XmlDocumentError::DocumentTooDeep {
102                    maximum: 3,
103                    actual: 4,
104                },
105                settings,
106            ),
107            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
108                resource: crate::policy::resource_name::XML_DEPTH,
109                maximum: 3,
110                actual: 4,
111            })
112        ));
113        assert!(matches!(
114            map_document_error(
115                XmlDocumentError::ProjectedNodeLimit { maximum: 8 },
116                settings,
117            ),
118            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
119                resource: crate::policy::resource_name::XML_NODES,
120                maximum: 8,
121                actual: 9,
122            })
123        ));
124    }
125}