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