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