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