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