Skip to main content

xml_sec/xmlenc/
types.rs

1//! Public XMLEnc data structures and errors.
2
3use std::{fmt, sync::Arc};
4
5use rsa::RsaPublicKey;
6
7/// XML Encryption 1.0 namespace.
8pub const XMLENC_NS: &str = "http://www.w3.org/2001/04/xmlenc#";
9/// XML Encryption 1.1 namespace.
10pub const XMLENC11_NS: &str = "http://www.w3.org/2009/xmlenc11#";
11/// XML Signature namespace, used by OAEP parameter elements.
12pub const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
13
14/// Maximum normalized base64 text accepted from a `CipherValue`.
15pub const MAX_CIPHER_VALUE_BASE64_LEN: usize =
16    crate::hard_limits::ENCRYPTION_CIPHER_VALUE_BASE64_BYTE_CEILING;
17/// The `Type` attribute on an `EncryptedData` element.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum EncryptedDataType {
20    /// The plaintext contains one complete XML element.
21    Element,
22    /// The plaintext contains the encrypted element's child content.
23    Content,
24    /// An application-defined or empty type hint whose plaintext remains opaque.
25    Other(String),
26}
27
28/// Supported content-encryption algorithms.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum DataEncryptionAlgorithm {
31    /// Legacy three-key Triple DES CBC, available only with explicit policy permission.
32    #[cfg(feature = "legacy-algorithms")]
33    TripleDesCbc,
34    /// AES-192 CBC compatibility capability.
35    #[cfg(feature = "legacy-algorithms")]
36    Aes192Cbc,
37    /// AES-192 GCM compatibility capability.
38    #[cfg(feature = "legacy-algorithms")]
39    Aes192Gcm,
40    /// AES-128 in CBC mode with XMLEnc padding.
41    Aes128Cbc,
42    /// AES-256 in CBC mode with XMLEnc padding.
43    Aes256Cbc,
44    /// AES-128 in GCM mode.
45    Aes128Gcm,
46    /// AES-256 in GCM mode.
47    Aes256Gcm,
48    /// Camellia CBC with a 128-bit key (RFC 9231 §2.6.2).
49    Camellia128Cbc,
50    /// Camellia CBC with a 192-bit key.
51    Camellia192Cbc,
52    /// Camellia CBC with a 256-bit key.
53    Camellia256Cbc,
54    /// RFC 9231 ChaCha20 profile; unauthenticated and opt-in.
55    ChaCha20,
56    /// ChaCha20-Poly1305 with XML-carried nonce and optional AAD.
57    ChaCha20Poly1305,
58}
59
60impl DataEncryptionAlgorithm {
61    /// Whether successful decryption cryptographically authenticates the key.
62    pub const fn is_authenticated(self) -> bool {
63        // RFC 9231 §§2.6.7-2.6.8 distinguishes the stream cipher from AEAD.
64        // https://www.rfc-editor.org/rfc/rfc9231.html#section-2.6.8
65        match self {
66            Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305 => true,
67            #[cfg(feature = "legacy-algorithms")]
68            Self::Aes192Gcm => true,
69            Self::Aes128Cbc
70            | Self::Aes256Cbc
71            | Self::Camellia128Cbc
72            | Self::Camellia192Cbc
73            | Self::Camellia256Cbc
74            | Self::ChaCha20 => false,
75            #[cfg(feature = "legacy-algorithms")]
76            Self::TripleDesCbc | Self::Aes192Cbc => false,
77        }
78    }
79
80    /// Key family required independently of the byte length (AES-192 and
81    /// three-key Triple DES both use 24 bytes).
82    pub const fn key_kind(self) -> crate::key_manager::SymmetricKeyKind {
83        if matches!(self, Self::ChaCha20 | Self::ChaCha20Poly1305) {
84            return crate::key_manager::SymmetricKeyKind::ChaCha20;
85        }
86        if matches!(
87            self,
88            Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc
89        ) {
90            return crate::key_manager::SymmetricKeyKind::Camellia;
91        }
92        #[cfg(feature = "legacy-algorithms")]
93        if matches!(self, Self::TripleDesCbc) {
94            return crate::key_manager::SymmetricKeyKind::Des;
95        }
96        crate::key_manager::SymmetricKeyKind::Aes
97    }
98    /// Parse a supported XMLEnc content-encryption URI.
99    pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
100        match uri {
101            #[cfg(feature = "legacy-algorithms")]
102            "http://www.w3.org/2001/04/xmlenc#tripledes-cbc" => Ok(Self::TripleDesCbc),
103            #[cfg(feature = "legacy-algorithms")]
104            "http://www.w3.org/2001/04/xmlenc#aes192-cbc" => Ok(Self::Aes192Cbc),
105            #[cfg(feature = "legacy-algorithms")]
106            "http://www.w3.org/2009/xmlenc11#aes192-gcm" => Ok(Self::Aes192Gcm),
107            "http://www.w3.org/2001/04/xmlenc#aes128-cbc" => Ok(Self::Aes128Cbc),
108            "http://www.w3.org/2001/04/xmlenc#aes256-cbc" => Ok(Self::Aes256Cbc),
109            "http://www.w3.org/2009/xmlenc11#aes128-gcm" => Ok(Self::Aes128Gcm),
110            "http://www.w3.org/2009/xmlenc11#aes256-gcm" => Ok(Self::Aes256Gcm),
111            "http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc" => Ok(Self::Camellia128Cbc),
112            "http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc" => Ok(Self::Camellia192Cbc),
113            "http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc" => Ok(Self::Camellia256Cbc),
114            "http://www.w3.org/2021/04/xmldsig-more#chacha20" => Ok(Self::ChaCha20),
115            "http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305" => Ok(Self::ChaCha20Poly1305),
116            _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
117        }
118    }
119
120    /// Required symmetric key length in bytes.
121    pub const fn key_len(self) -> usize {
122        match self {
123            #[cfg(feature = "legacy-algorithms")]
124            Self::TripleDesCbc | Self::Aes192Cbc | Self::Aes192Gcm => 24,
125            Self::Aes128Cbc | Self::Aes128Gcm => 16,
126            Self::Aes256Cbc | Self::Aes256Gcm => 32,
127            Self::Camellia128Cbc => 16,
128            Self::Camellia192Cbc => 24,
129            Self::Camellia256Cbc => 32,
130            Self::ChaCha20 | Self::ChaCha20Poly1305 => 32,
131        }
132    }
133
134    /// Return the standard XMLEnc algorithm URI.
135    pub const fn uri(self) -> &'static str {
136        match self {
137            #[cfg(feature = "legacy-algorithms")]
138            Self::TripleDesCbc => "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
139            #[cfg(feature = "legacy-algorithms")]
140            Self::Aes192Cbc => "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
141            #[cfg(feature = "legacy-algorithms")]
142            Self::Aes192Gcm => "http://www.w3.org/2009/xmlenc11#aes192-gcm",
143            Self::Aes128Cbc => "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
144            Self::Aes256Cbc => "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
145            Self::Aes128Gcm => "http://www.w3.org/2009/xmlenc11#aes128-gcm",
146            Self::Aes256Gcm => "http://www.w3.org/2009/xmlenc11#aes256-gcm",
147            Self::Camellia128Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc",
148            Self::Camellia192Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc",
149            Self::Camellia256Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc",
150            Self::ChaCha20 => "http://www.w3.org/2021/04/xmldsig-more#chacha20",
151            Self::ChaCha20Poly1305 => "http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305",
152        }
153    }
154
155    /// Minimum standard wire length for ciphertext produced by this algorithm.
156    pub(crate) const fn minimum_ciphertext_len(self) -> usize {
157        if matches!(self, Self::ChaCha20) {
158            return 0;
159        }
160        if matches!(self, Self::ChaCha20Poly1305) {
161            return 16;
162        }
163        match self.cbc_block_len() {
164            Some(block) => block * 2,
165            None => 28,
166        }
167    }
168
169    /// Compile-time capability is not the operation's permission.
170    /// Whether this capability must be explicitly selected in the operation allowlist.
171    pub const fn requires_explicit_permission(self) -> bool {
172        match self {
173            Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc => true,
174            Self::ChaCha20 | Self::ChaCha20Poly1305 => true,
175            #[cfg(feature = "legacy-algorithms")]
176            Self::TripleDesCbc | Self::Aes192Cbc | Self::Aes192Gcm => true,
177            _ => false,
178        }
179    }
180
181    pub(crate) const fn cbc_block_len(self) -> Option<usize> {
182        // XMLEnc 1.1 §§5.2.2-5.2.4: CBC frames prefix one cipher block;
183        // GCM instead prefixes a 96-bit nonce and appends a 128-bit tag.
184        // https://www.w3.org/TR/xmlenc-core1/#sec-Block-Encryption
185        match self {
186            Self::Aes128Cbc | Self::Aes256Cbc => Some(16),
187            Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc => Some(16),
188            #[cfg(feature = "legacy-algorithms")]
189            Self::Aes192Cbc => Some(16),
190            #[cfg(feature = "legacy-algorithms")]
191            Self::TripleDesCbc => Some(8),
192            _ => None,
193        }
194    }
195
196    /// Exact wire length produced when encrypting the given plaintext length.
197    pub(crate) fn ciphertext_len_for_plaintext(self, plaintext_len: usize) -> Option<usize> {
198        if matches!(self, Self::ChaCha20) {
199            return Some(plaintext_len);
200        }
201        if matches!(self, Self::ChaCha20Poly1305) {
202            return plaintext_len.checked_add(16);
203        }
204        match self.cbc_block_len() {
205            Some(block) => (plaintext_len / block)
206                .checked_add(1)?
207                .checked_mul(block)?
208                .checked_add(block),
209            None => plaintext_len.checked_add(28),
210        }
211    }
212}
213
214pub(crate) fn validate_ciphertext_framing(
215    algorithm: DataEncryptionAlgorithm,
216    ciphertext_len: usize,
217) -> Result<(), XmlEncError> {
218    let minimum = algorithm.minimum_ciphertext_len();
219    if ciphertext_len < minimum {
220        let algorithm_name = match algorithm {
221            DataEncryptionAlgorithm::ChaCha20 => "ChaCha20",
222            DataEncryptionAlgorithm::ChaCha20Poly1305 => "ChaCha20-Poly1305",
223            DataEncryptionAlgorithm::Camellia128Cbc
224            | DataEncryptionAlgorithm::Camellia192Cbc
225            | DataEncryptionAlgorithm::Camellia256Cbc => "Camellia-CBC",
226            _ => match algorithm.cbc_block_len() {
227                Some(8) => "Triple DES CBC",
228                Some(_) => "AES-CBC",
229                None => "AES-GCM",
230            },
231        };
232        return Err(XmlEncError::DataTooShort {
233            algorithm: algorithm_name,
234            minimum,
235            actual: ciphertext_len,
236        });
237    }
238    if let Some(block) = algorithm.cbc_block_len()
239        && !(ciphertext_len - block).is_multiple_of(block)
240    {
241        return Err(XmlEncError::InvalidCbcCiphertextLength {
242            algorithm,
243            block,
244            actual: ciphertext_len - block,
245        });
246    }
247    Ok(())
248}
249
250impl KeyTransportAlgorithm {
251    /// Historical transport is never enabled merely by compiling its primitive.
252    pub const fn requires_explicit_permission(self) -> bool {
253        match self {
254            #[cfg(feature = "legacy-algorithms")]
255            Self::RsaPkcs1v15 => true,
256            _ => false,
257        }
258    }
259    /// Parse a supported XMLEnc key-transport URI.
260    pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
261        match uri {
262            "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" => Ok(Self::RsaOaepMgf1p),
263            "http://www.w3.org/2009/xmlenc11#rsa-oaep" => Ok(Self::RsaOaep11),
264            #[cfg(feature = "legacy-algorithms")]
265            "http://www.w3.org/2001/04/xmlenc#rsa-1_5" => Ok(Self::RsaPkcs1v15),
266            _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
267        }
268    }
269
270    /// Return the standard XMLEnc key-transport URI.
271    pub const fn uri(self) -> &'static str {
272        match self {
273            Self::RsaOaepMgf1p => "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
274            Self::RsaOaep11 => "http://www.w3.org/2009/xmlenc11#rsa-oaep",
275            #[cfg(feature = "legacy-algorithms")]
276            Self::RsaPkcs1v15 => "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
277        }
278    }
279}
280
281impl KeyWrapAlgorithm {
282    /// Family of the wrapping key, not of the wrapped content key.
283    pub const fn key_kind(self) -> crate::key_manager::SymmetricKeyKind {
284        if let Self::Cbc(algorithm) = self {
285            return algorithm.key_kind();
286        }
287        if matches!(
288            self,
289            Self::CamelliaKw128 | Self::CamelliaKw192 | Self::CamelliaKw256
290        ) {
291            return crate::key_manager::SymmetricKeyKind::Camellia;
292        }
293        #[cfg(feature = "legacy-algorithms")]
294        if matches!(self, Self::TripleDes) {
295            return crate::key_manager::SymmetricKeyKind::Des;
296        }
297        crate::key_manager::SymmetricKeyKind::Aes
298    }
299    /// Parse a supported XMLEnc symmetric key-wrap URI.
300    pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
301        match uri {
302            #[cfg(feature = "legacy-algorithms")]
303            "http://www.w3.org/2001/04/xmlenc#kw-aes192" => Ok(Self::AesKw192),
304            #[cfg(feature = "legacy-algorithms")]
305            "http://www.w3.org/2001/04/xmlenc#kw-tripledes" => Ok(Self::TripleDes),
306            "http://www.w3.org/2001/04/xmlenc#kw-aes128" => Ok(Self::AesKw128),
307            "http://www.w3.org/2001/04/xmlenc#kw-aes256" => Ok(Self::AesKw256),
308            "http://www.w3.org/2001/04/xmldsig-more#kw-camellia128" => Ok(Self::CamelliaKw128),
309            "http://www.w3.org/2001/04/xmldsig-more#kw-camellia192" => Ok(Self::CamelliaKw192),
310            "http://www.w3.org/2001/04/xmldsig-more#kw-camellia256" => Ok(Self::CamelliaKw256),
311            _ => DataEncryptionAlgorithm::from_uri(uri)
312                .ok()
313                .filter(|algorithm| algorithm.cbc_block_len().is_some())
314                .map(Self::Cbc)
315                .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
316        }
317    }
318
319    /// Required key-encryption-key length in bytes.
320    pub const fn key_len(self) -> usize {
321        match self {
322            #[cfg(feature = "legacy-algorithms")]
323            Self::AesKw192 | Self::TripleDes => 24,
324            Self::AesKw128 => 16,
325            Self::AesKw256 => 32,
326            Self::CamelliaKw128 => 16,
327            Self::CamelliaKw192 => 24,
328            Self::CamelliaKw256 => 32,
329            Self::Cbc(algorithm) => algorithm.key_len(),
330        }
331    }
332
333    /// Return the standard XMLEnc key-wrap URI.
334    pub const fn uri(self) -> &'static str {
335        match self {
336            #[cfg(feature = "legacy-algorithms")]
337            Self::AesKw192 => "http://www.w3.org/2001/04/xmlenc#kw-aes192",
338            #[cfg(feature = "legacy-algorithms")]
339            Self::TripleDes => "http://www.w3.org/2001/04/xmlenc#kw-tripledes",
340            Self::AesKw128 => "http://www.w3.org/2001/04/xmlenc#kw-aes128",
341            Self::AesKw256 => "http://www.w3.org/2001/04/xmlenc#kw-aes256",
342            Self::CamelliaKw128 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia128",
343            Self::CamelliaKw192 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia192",
344            Self::CamelliaKw256 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia256",
345            Self::Cbc(algorithm) => algorithm.uri(),
346        }
347    }
348
349    /// Whether use requires an explicit compiled-policy allowlist entry.
350    pub const fn requires_explicit_permission(self) -> bool {
351        match self {
352            Self::CamelliaKw128 | Self::CamelliaKw192 | Self::CamelliaKw256 => true,
353            Self::Cbc(_) => true,
354            #[cfg(feature = "legacy-algorithms")]
355            Self::AesKw192 | Self::TripleDes => true,
356            _ => false,
357        }
358    }
359
360    pub(crate) fn wrapped_len(self, plaintext_len: usize) -> Option<usize> {
361        // XMLEnc 1.1 §5.7.1 / RFC 3217 §§2-3: CMS wrapping includes
362        // an 8-byte checksum and an 8-byte IV, unlike RFC 3394's A register.
363        // https://www.w3.org/TR/xmlenc-core1/#sec-CMS-3DES
364        let overhead = match self {
365            Self::Cbc(algorithm) => {
366                return if algorithm.cbc_block_len().is_some() {
367                    algorithm.ciphertext_len_for_plaintext(plaintext_len)
368                } else {
369                    None
370                };
371            }
372            #[cfg(feature = "legacy-algorithms")]
373            Self::TripleDes => 16,
374            _ => 8,
375        };
376        plaintext_len.checked_add(overhead)
377    }
378}
379
380/// Supported asymmetric session-key transport algorithms.
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
382pub enum KeyTransportAlgorithm {
383    /// Historical RSAES-PKCS1-v1_5 transport; explicit policy permission is required.
384    #[cfg(feature = "legacy-algorithms")]
385    RsaPkcs1v15,
386    /// XML Encryption 1.0 OAEP URI with SHA-1/MGF1-SHA1 absent-field defaults.
387    ///
388    /// An explicit DigestMethod or XMLEnc 1.1 MGF child overrides the corresponding default,
389    /// matching libxmlsec1 1.3.13 behavior.
390    RsaOaepMgf1p,
391    /// XML Encryption 1.1 OAEP with explicitly parsed digest and MGF settings.
392    RsaOaep11,
393}
394
395/// Supported symmetric key-wrap algorithms.
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
397pub enum KeyWrapAlgorithm {
398    /// CBC encryption of key bytes. Unlike RFC 3394 this is not authenticated;
399    /// permission must name this exact algorithm. Only CBC ciphers are valid.
400    /// XMLEnc 1.1 §3.4 permits EncryptedKey's inherited EncryptionMethod.
401    Cbc(DataEncryptionAlgorithm),
402    /// AES-192 RFC 3394 key wrap, explicitly permitted by compatibility policy.
403    #[cfg(feature = "legacy-algorithms")]
404    AesKw192,
405    /// CMS Triple DES key wrap, including XMLEnc's optional non-DES key inputs.
406    #[cfg(feature = "legacy-algorithms")]
407    TripleDes,
408    /// RFC 3394 AES key wrap with a 128-bit KEK.
409    AesKw128,
410    /// RFC 3394 AES key wrap with a 256-bit KEK.
411    AesKw256,
412    /// RFC 9231 §2.6.3 Camellia wrapping with a 128-bit KEK.
413    CamelliaKw128,
414    /// Camellia wrapping with a 192-bit KEK.
415    CamelliaKw192,
416    /// Camellia wrapping with a 256-bit KEK.
417    CamelliaKw256,
418}
419
420/// Digest algorithms accepted by RSA-OAEP encryption.
421#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
422pub enum OaepDigestAlgorithm {
423    /// MD5 compatibility digest; requires explicit legacy permission.
424    #[cfg(feature = "legacy-algorithms")]
425    Md5,
426    /// RIPEMD-160 compatibility digest; requires explicit legacy permission.
427    #[cfg(feature = "legacy-algorithms")]
428    Ripemd160,
429    /// SHA-1, retained for legacy XMLEnc OAEP interoperability.
430    Sha1,
431    /// SHA-224, including the XML Encryption 1.1 MGF1-SHA224 identifier.
432    Sha224,
433    /// SHA-256.
434    Sha256,
435    /// SHA-384.
436    Sha384,
437    /// SHA-512.
438    Sha512,
439    /// SHA3-224.
440    Sha3_224,
441    /// SHA3-256.
442    Sha3_256,
443    /// SHA3-384.
444    Sha3_384,
445    /// SHA3-512.
446    Sha3_512,
447}
448
449impl OaepDigestAlgorithm {
450    /// Parse a digest URI accepted by XML Encryption and libxmlsec1.
451    pub fn from_uri(uri: &str) -> Option<Self> {
452        match uri {
453            #[cfg(feature = "legacy-algorithms")]
454            "http://www.w3.org/2001/04/xmldsig-more#md5" => Some(Self::Md5),
455            #[cfg(feature = "legacy-algorithms")]
456            "http://www.w3.org/2001/04/xmlenc#ripemd160" => Some(Self::Ripemd160),
457            "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
458            "http://www.w3.org/2001/04/xmldsig-more#sha224" => Some(Self::Sha224),
459            "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
460            "http://www.w3.org/2001/04/xmlenc#sha384"
461            | "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
462            "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
463            "http://www.w3.org/2007/05/xmldsig-more#sha3-224" => Some(Self::Sha3_224),
464            "http://www.w3.org/2007/05/xmldsig-more#sha3-256" => Some(Self::Sha3_256),
465            "http://www.w3.org/2007/05/xmldsig-more#sha3-384" => Some(Self::Sha3_384),
466            "http://www.w3.org/2007/05/xmldsig-more#sha3-512" => Some(Self::Sha3_512),
467            _ => None,
468        }
469    }
470
471    /// Parse an XML Encryption 1.1 MGF1 URI.
472    pub fn from_mgf_uri(uri: &str) -> Option<Self> {
473        match uri {
474            "http://www.w3.org/2009/xmlenc11#mgf1sha1" => Some(Self::Sha1),
475            "http://www.w3.org/2009/xmlenc11#mgf1sha224" => Some(Self::Sha224),
476            "http://www.w3.org/2009/xmlenc11#mgf1sha256" => Some(Self::Sha256),
477            "http://www.w3.org/2009/xmlenc11#mgf1sha384" => Some(Self::Sha384),
478            "http://www.w3.org/2009/xmlenc11#mgf1sha512" => Some(Self::Sha512),
479            _ => None,
480        }
481    }
482
483    /// Return the standard digest URI.
484    pub const fn uri(self) -> &'static str {
485        match self {
486            #[cfg(feature = "legacy-algorithms")]
487            Self::Md5 => "http://www.w3.org/2001/04/xmldsig-more#md5",
488            #[cfg(feature = "legacy-algorithms")]
489            Self::Ripemd160 => "http://www.w3.org/2001/04/xmlenc#ripemd160",
490            Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
491            Self::Sha224 => "http://www.w3.org/2001/04/xmldsig-more#sha224",
492            Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
493            Self::Sha384 => "http://www.w3.org/2001/04/xmlenc#sha384",
494            Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
495            Self::Sha3_224 => "http://www.w3.org/2007/05/xmldsig-more#sha3-224",
496            Self::Sha3_256 => "http://www.w3.org/2007/05/xmldsig-more#sha3-256",
497            Self::Sha3_384 => "http://www.w3.org/2007/05/xmldsig-more#sha3-384",
498            Self::Sha3_512 => "http://www.w3.org/2007/05/xmldsig-more#sha3-512",
499        }
500    }
501
502    /// Return an XML Encryption 1.1 MGF URI, if one exists for this digest.
503    /// DigestMethod support does not invent additional wire MGF identifiers.
504    pub const fn mgf_uri(self) -> Option<&'static str> {
505        // XML Encryption 1.1 §5.5.2 defines five MGF1 URIs. RFC 8017's
506        // arbitrary Hash parameter does not register further XML identifiers.
507        // https://www.w3.org/TR/xmlenc-core1/#sec-RSA-OAEP
508        match self {
509            Self::Sha1 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
510            Self::Sha224 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha224"),
511            Self::Sha256 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha256"),
512            Self::Sha384 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha384"),
513            Self::Sha512 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha512"),
514            _ => None,
515        }
516    }
517
518    /// Compatibility digest capability never implicitly permits its use.
519    pub const fn requires_explicit_permission(self) -> bool {
520        match self {
521            #[cfg(feature = "legacy-algorithms")]
522            Self::Md5 | Self::Ripemd160 => true,
523            _ => false,
524        }
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::{OaepDigestAlgorithm, XmlEncError};
531
532    #[test]
533    fn operation_plan_failures_have_a_distinct_error_class() {
534        let error = XmlEncError::from(crate::operation::OperationPlanError::StaleResourceIdentity);
535        assert!(matches!(error, XmlEncError::OperationPlan(_)));
536    }
537
538    #[test]
539    fn oaep_sha384_accepts_both_interoperable_digest_uris() {
540        // The canonical XML Encryption spelling and libxmlsec1's XMLDSig-more
541        // spelling identify the same OAEP digest algorithm.
542        for uri in [
543            "http://www.w3.org/2001/04/xmlenc#sha384",
544            "http://www.w3.org/2001/04/xmldsig-more#sha384",
545        ] {
546            assert_eq!(
547                OaepDigestAlgorithm::from_uri(uri),
548                Some(OaepDigestAlgorithm::Sha384)
549            );
550        }
551    }
552
553    #[test]
554    fn oaep_mgf_uris_round_trip() {
555        for algorithm in [
556            OaepDigestAlgorithm::Sha1,
557            OaepDigestAlgorithm::Sha224,
558            OaepDigestAlgorithm::Sha256,
559            OaepDigestAlgorithm::Sha384,
560            OaepDigestAlgorithm::Sha512,
561        ] {
562            assert_eq!(
563                OaepDigestAlgorithm::from_mgf_uri(
564                    algorithm.mgf_uri().expect("standard MGF1 digest URI")
565                ),
566                Some(algorithm)
567            );
568        }
569        assert_eq!(
570            OaepDigestAlgorithm::from_mgf_uri("urn:unsupported-mgf"),
571            None
572        );
573    }
574}
575
576/// RSA-OAEP parameters emitted in an `EncryptedKey`.
577#[derive(Debug, Clone, PartialEq, Eq)]
578pub struct RsaOaepParameters {
579    /// XMLEnc 1.0 legacy OAEP or XMLEnc 1.1 configurable OAEP.
580    pub algorithm: KeyTransportAlgorithm,
581    /// Digest used by OAEP.
582    pub digest: OaepDigestAlgorithm,
583    /// Digest used by MGF1.
584    pub mgf_digest: OaepDigestAlgorithm,
585    /// Optional OAEP label bytes.
586    pub label: Vec<u8>,
587}
588
589impl RsaOaepParameters {
590    /// Create legacy OAEP parameters with SHA-1 and MGF1-SHA-1.
591    pub fn legacy() -> Self {
592        Self {
593            algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
594            digest: OaepDigestAlgorithm::Sha1,
595            mgf_digest: OaepDigestAlgorithm::Sha1,
596            label: Vec::new(),
597        }
598    }
599
600    /// Create XMLEnc 1.1 OAEP parameters.
601    pub fn xmlenc11(digest: OaepDigestAlgorithm, mgf_digest: OaepDigestAlgorithm) -> Self {
602        Self {
603            algorithm: KeyTransportAlgorithm::RsaOaep11,
604            digest,
605            mgf_digest,
606            label: Vec::new(),
607        }
608    }
609
610    /// Set the OAEP label bytes.
611    pub fn label(mut self, label: impl Into<Vec<u8>>) -> Self {
612        self.label = label.into();
613        self
614    }
615}
616
617impl Default for RsaOaepParameters {
618    fn default() -> Self {
619        Self::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha256)
620    }
621}
622
623/// One recipient of a generated content-encryption key.
624#[derive(Clone)]
625pub enum EncryptionRecipient {
626    /// Historical RSA transport, admitted only by explicit compiled policy.
627    #[cfg(feature = "legacy-algorithms")]
628    RsaPkcs1v15 {
629        /// Opaque RSA public key used by the selected provider.
630        public_key: Arc<dyn crate::provider::KeyTransportKey>,
631        /// Optional recipient identifier.
632        recipient: Option<String>,
633        /// Optional key hint.
634        key_name: Option<String>,
635    },
636    /// Wrap the content key with an RSA public key and OAEP.
637    RsaOaep {
638        /// Opaque recipient public-key handle.
639        public_key: Arc<dyn crate::provider::KeyTransportKey>,
640        /// OAEP algorithm parameters.
641        parameters: RsaOaepParameters,
642        /// Optional `Recipient` attribute.
643        recipient: Option<String>,
644        /// Optional key hint inside the encrypted key's `KeyInfo`.
645        key_name: Option<String>,
646    },
647    /// Wrap the content key with a pre-shared AES KEK.
648    AesKeyWrap {
649        /// AES key-encryption key.
650        kek: Vec<u8>,
651        /// RFC 3394 key-wrap variant.
652        algorithm: KeyWrapAlgorithm,
653        /// Optional `Recipient` attribute.
654        recipient: Option<String>,
655        /// Optional key hint inside the encrypted key's `KeyInfo`.
656        key_name: Option<String>,
657    },
658}
659
660impl fmt::Debug for EncryptionRecipient {
661    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
662        match self {
663            #[cfg(feature = "legacy-algorithms")]
664            Self::RsaPkcs1v15 {
665                recipient,
666                key_name,
667                ..
668            } => formatter
669                .debug_struct("EncryptionRecipient::RsaPkcs1v15")
670                .field("public_key", &"[PUBLIC KEY]")
671                .field("recipient", recipient)
672                .field("key_name", key_name)
673                .finish(),
674            Self::RsaOaep {
675                parameters,
676                recipient,
677                key_name,
678                ..
679            } => formatter
680                .debug_struct("EncryptionRecipient::RsaOaep")
681                .field("public_key", &"[PUBLIC KEY]")
682                .field("parameters", parameters)
683                .field("recipient", recipient)
684                .field("key_name", key_name)
685                .finish(),
686            Self::AesKeyWrap {
687                algorithm,
688                recipient,
689                key_name,
690                ..
691            } => formatter
692                .debug_struct("EncryptionRecipient::AesKeyWrap")
693                .field("kek", &"[REDACTED]")
694                .field("algorithm", algorithm)
695                .field("recipient", recipient)
696                .field("key_name", key_name)
697                .finish(),
698        }
699    }
700}
701
702impl EncryptionRecipient {
703    /// Create a historical RSAES-PKCS1-v1_5 recipient. Defaults still deny it.
704    #[cfg(feature = "legacy-algorithms")]
705    pub fn rsa_pkcs1v15(public_key: RsaPublicKey) -> Self {
706        Self::provider_pkcs1v15(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
707            public_key,
708        )))
709    }
710
711    /// Create a historical RSA recipient without extracting provider-owned keys.
712    #[cfg(feature = "legacy-algorithms")]
713    pub fn provider_pkcs1v15(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
714        Self::RsaPkcs1v15 {
715            public_key,
716            recipient: None,
717            key_name: None,
718        }
719    }
720    /// Create an RSA-OAEP recipient using SHA-256 and MGF1-SHA-256.
721    ///
722    /// XMLEnc 1.1 assigns SHA-1 and MGF1-SHA-1 when these parameters are
723    /// omitted. Serialized keys therefore include both algorithm values
724    /// explicitly instead of relying on the specification's legacy defaults.
725    pub fn rsa_oaep(public_key: RsaPublicKey) -> Self {
726        Self::provider_key_transport(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
727            public_key,
728        )))
729    }
730
731    /// Create an RSA-OAEP recipient from an opaque provider key handle.
732    pub fn provider_key_transport(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
733        Self::RsaOaep {
734            public_key,
735            parameters: RsaOaepParameters::default(),
736            recipient: None,
737            key_name: None,
738        }
739    }
740
741    /// Create an AES Key Wrap recipient.
742    pub fn aes_key_wrap(kek: impl Into<Vec<u8>>, algorithm: KeyWrapAlgorithm) -> Self {
743        Self::AesKeyWrap {
744            kek: kek.into(),
745            algorithm,
746            recipient: None,
747            key_name: None,
748        }
749    }
750
751    /// Override RSA-OAEP parameters.
752    pub fn oaep_parameters(mut self, parameters: RsaOaepParameters) -> Self {
753        if let Self::RsaOaep {
754            parameters: current,
755            ..
756        } = &mut self
757        {
758            *current = parameters;
759        }
760        self
761    }
762
763    /// Set the recipient identifier emitted on `EncryptedKey`.
764    pub fn recipient(mut self, value: impl Into<String>) -> Self {
765        match &mut self {
766            #[cfg(feature = "legacy-algorithms")]
767            Self::RsaPkcs1v15 { recipient, .. } => *recipient = Some(value.into()),
768            Self::RsaOaep { recipient, .. } | Self::AesKeyWrap { recipient, .. } => {
769                *recipient = Some(value.into());
770            }
771        }
772        self
773    }
774
775    /// Set the key name emitted inside the encrypted key's `KeyInfo`.
776    pub fn key_name(mut self, value: impl Into<String>) -> Self {
777        match &mut self {
778            #[cfg(feature = "legacy-algorithms")]
779            Self::RsaPkcs1v15 { key_name, .. } => *key_name = Some(value.into()),
780            Self::RsaOaep { key_name, .. } | Self::AesKeyWrap { key_name, .. } => {
781                *key_name = Some(value.into());
782            }
783        }
784        self
785    }
786}
787
788/// How generated `EncryptedData` replaces caller-owned XML.
789#[derive(Debug, Clone, Copy, PartialEq, Eq)]
790pub enum ReplacementMode {
791    /// Replace the selected element, including its start and end tags.
792    ReplaceElement,
793    /// Replace only the selected element's child content.
794    ReplaceContent,
795}
796
797/// Result returned after encrypting bytes or XML.
798#[derive(Debug, Clone, PartialEq, Eq)]
799pub struct EncryptionResult {
800    /// Complete `EncryptedData` XML fragment.
801    pub encrypted_data_xml: String,
802    /// Required caller-owned document replacement operation.
803    pub replacement: ReplacementMode,
804}
805
806/// Caller-owned target selection for document encryption.
807#[derive(Debug, Clone, Copy, Default)]
808pub struct DocumentEncryptionOptions<'a> {
809    /// Select an element by `Id`, `ID`, or `id`; `None` selects the document root.
810    pub element_id: Option<&'a str>,
811}
812
813/// Parsed `EncryptionMethod` data.
814#[derive(Debug, Clone, PartialEq, Eq)]
815pub struct EncryptionMethod {
816    /// Algorithm URI from the mandatory `Algorithm` attribute.
817    pub algorithm: String,
818    /// Optional explicit key size in bits.
819    pub key_size_bits: Option<usize>,
820    /// Digest URI used by XML Encryption 1.1 OAEP.
821    pub oaep_digest: Option<String>,
822    /// MGF URI used by XML Encryption 1.1 OAEP.
823    pub mgf_algorithm: Option<String>,
824    /// Decoded OAEP label bytes.
825    pub oaep_params: Option<Vec<u8>>,
826    /// ChaCha profile parameters, separate from RSA-OAEP labels.
827    pub chacha: Option<ChaChaParameters>,
828}
829
830/// Request/wire data for the RFC 9231 ChaCha profiles, not policy.
831/// A missing nonce is only allowed in an encryption template; decryption
832/// requires an explicit nonce, and encryption generates it through its provider.
833#[derive(Debug, Clone, Default, PartialEq, Eq)]
834pub struct ChaChaParameters {
835    /// 96-bit nonce encoded as hexBinary in EncryptionMethod.
836    pub nonce: Option<[u8; 12]>,
837    /// Raw ChaCha's four little-endian counter bytes; absent for Poly1305.
838    pub counter: Option<[u8; 4]>,
839    /// Poly1305 additional authenticated data: UTF-8 XML text, not base64.
840    pub aad: Option<String>,
841}
842
843impl ChaChaParameters {
844    /// Borrow metadata for cryptographic dispatch and serialization without
845    /// copying AAD when the operation supplies a generated nonce.
846    pub fn borrowed(&self) -> ChaChaParametersRef<'_> {
847        ChaChaParametersRef {
848            nonce: self.nonce.as_ref(),
849            counter: self.counter,
850            aad: self.aad.as_deref(),
851        }
852    }
853    /// Check the selected profile before any resolver or provider work.
854    pub fn validate(&self, algorithm: DataEncryptionAlgorithm) -> Result<(), XmlEncError> {
855        self.borrowed().validate(algorithm)
856    }
857}
858
859/// Borrowed request parameters; no metadata allocation at provider dispatch.
860#[derive(Debug, Clone, Copy, Default)]
861pub struct ChaChaParametersRef<'a> {
862    /// Explicit or operation-generated nonce.
863    pub nonce: Option<&'a [u8; 12]>,
864    /// Raw ChaCha's encoded initial block counter.
865    pub counter: Option<[u8; 4]>,
866    /// Borrowed UTF-8 additional authenticated data.
867    pub aad: Option<&'a str>,
868}
869
870impl ChaChaParametersRef<'_> {
871    /// Enforce the same profile invariants as owned parsed parameters.
872    pub fn validate(self, algorithm: DataEncryptionAlgorithm) -> Result<(), XmlEncError> {
873        // draft-eastlake-rfc9231bis-xmlsec-uris-06 §§3.6.7–3.6.8 separates
874        // Counter from AAD. These experimental identifiers are not RFC 9231.
875        // https://www.ietf.org/archive/id/draft-eastlake-rfc9231bis-xmlsec-uris-06.html#section-3.6.7
876        let valid = match algorithm {
877            DataEncryptionAlgorithm::ChaCha20 => self.counter.is_some() && self.aad.is_none(),
878            DataEncryptionAlgorithm::ChaCha20Poly1305 => self.counter.is_none(),
879            _ => false,
880        };
881        if !valid {
882            return Err(XmlEncError::InvalidStructure(
883                "invalid ChaCha EncryptionMethod parameters".into(),
884            ));
885        }
886        Ok(())
887    }
888}
889
890impl EncryptionMethod {
891    /// Validate invariants imposed by the selected algorithm URI.
892    ///
893    /// Parsed XML and caller-constructed typed values share this check so the
894    /// public typed API cannot express wire structures that XML parsing rejects.
895    pub(crate) fn validate_structure(&self) -> Result<(), XmlEncError> {
896        if let Some(parameters) = &self.chacha {
897            parameters.validate(DataEncryptionAlgorithm::from_uri(&self.algorithm)?)?;
898        } else if self.algorithm == DataEncryptionAlgorithm::ChaCha20.uri() {
899            return Err(XmlEncError::MissingRequired("ChaCha20 Counter"));
900        }
901        if self.key_size_bits == Some(0) {
902            return Err(XmlEncError::InvalidStructure(
903                "KeySize must be a positive integer".into(),
904            ));
905        }
906        let is_legacy_oaep = self.algorithm == KeyTransportAlgorithm::RsaOaepMgf1p.uri();
907        let is_oaep11 = self.algorithm == KeyTransportAlgorithm::RsaOaep11.uri();
908        if (self.oaep_params.is_some()
909            || self.oaep_digest.is_some()
910            || self.mgf_algorithm.is_some())
911            && !is_legacy_oaep
912            && !is_oaep11
913        {
914            return Err(XmlEncError::InvalidStructure(
915                "OAEP parameters are only valid for RSA-OAEP EncryptionMethod".into(),
916            ));
917        }
918        if let (Some(actual), Some(expected)) =
919            (self.key_size_bits, fixed_aes_key_size(&self.algorithm))
920            && actual != expected
921        {
922            return Err(XmlEncError::InvalidStructure(format!(
923                "EncryptionMethod {} requires KeySize {expected}, got {actual}",
924                self.algorithm
925            )));
926        }
927        Ok(())
928    }
929}
930
931fn fixed_aes_key_size(algorithm: &str) -> Option<usize> {
932    let key_len = DataEncryptionAlgorithm::from_uri(algorithm)
933        .map(DataEncryptionAlgorithm::key_len)
934        .or_else(|_| KeyWrapAlgorithm::from_uri(algorithm).map(KeyWrapAlgorithm::key_len))
935        .ok()?;
936    Some(key_len * 8)
937}
938
939/// Ciphertext storage specified by XMLEnc 1.1 section 3.3.
940#[derive(Debug, Clone, PartialEq, Eq)]
941pub enum CipherData {
942    /// Whitespace-normalized base64 text from `CipherValue`.
943    Value {
944        /// Encoded ciphertext, not plaintext or key material.
945        value: String,
946    },
947    /// A source-anchored reference, resolved before cryptographic dispatch.
948    Reference {
949        /// Required URI, including an explicitly empty URI.
950        uri: String,
951        /// Ordered transforms retained with their original XPath source identity.
952        transforms: Vec<crate::xmldsig::transforms::Transform>,
953    },
954    /// Ciphertext octets resolved by the operation or supplied by the caller.
955    Bytes(Vec<u8>),
956}
957
958impl CipherData {
959    pub(super) fn octets(&self) -> Result<std::borrow::Cow<'_, [u8]>, XmlEncError> {
960        use base64::Engine as _;
961        match self {
962            Self::Value { value } => base64::engine::general_purpose::STANDARD
963                .decode(value)
964                .map(std::borrow::Cow::Owned)
965                .map_err(|error| XmlEncError::Base64(error.to_string())),
966            Self::Bytes(bytes) => Ok(std::borrow::Cow::Borrowed(bytes)),
967            Self::Reference { .. } => Err(XmlEncError::InvalidStructure(
968                "CipherReference requires its original document context".into(),
969            )),
970        }
971    }
972
973    /// Return an inline base64 value, if this ciphertext is enveloped.
974    pub fn inline_value(&self) -> Option<&str> {
975        match self {
976            Self::Value { value } => Some(value),
977            Self::Reference { .. } | Self::Bytes(_) => None,
978        }
979    }
980
981    /// Mutably access an inline value without changing its storage variant.
982    pub fn inline_value_mut(&mut self) -> Option<&mut String> {
983        match self {
984            Self::Value { value } => Some(value),
985            Self::Reference { .. } | Self::Bytes(_) => None,
986        }
987    }
988}
989
990/// Parsed embedded `EncryptedKey` used to recover a content-encryption key.
991#[derive(Debug, Clone, PartialEq, Eq)]
992pub struct EncryptedKey {
993    /// Sources of the key which protects this transported key. These are not
994    /// alternative content keys: their output feeds this key's EncryptionMethod.
995    pub sources: EncryptionKeySources,
996    /// Optional XML identifier.
997    pub id: Option<String>,
998    /// Optional recipient hint.
999    pub recipient: Option<String>,
1000    /// Optional direct `ds:KeyName` hint from the key's `KeyInfo`.
1001    pub key_name: Option<String>,
1002    /// Method which wrapped the session key.
1003    pub encryption_method: EncryptionMethod,
1004    /// Wrapped session-key bytes in base64 form.
1005    pub cipher_data: CipherData,
1006    /// Optional references identifying data or keys associated with this key.
1007    pub reference_list: Option<ReferenceList>,
1008    /// Optional name associated with the transported plaintext key.
1009    pub carried_key_name: Option<String>,
1010}
1011
1012/// Typed establishment sources inside an encrypted key's `ds:KeyInfo`.
1013/// Recursion is checked against the operation's depth and candidate allowances.
1014#[derive(Debug, Clone, PartialEq, Eq, Default)]
1015pub struct EncryptionKeySources {
1016    /// Experimental KEM sources producing this wrapping key.
1017    pub encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
1018    /// Keys transporting the wrapping key, rather than the final content key.
1019    pub encrypted_keys: Vec<EncryptedKey>,
1020    /// Derivations producing the wrapping key.
1021    pub derived_keys: Vec<super::DerivedKey>,
1022    /// Agreements producing the wrapping key.
1023    pub agreement_methods: Vec<super::AgreementMethod>,
1024}
1025
1026impl EncryptionKeySources {
1027    pub(super) fn is_empty(&self) -> bool {
1028        self.encrypted_keys.is_empty()
1029            && self.encapsulation_methods.is_empty()
1030            && self.derived_keys.is_empty()
1031            && self.agreement_methods.is_empty()
1032    }
1033}
1034
1035/// References associated with an `EncryptedKey`.
1036#[derive(Debug, Clone, PartialEq, Eq)]
1037pub struct ReferenceList {
1038    /// URI references to `EncryptedData` elements encrypted with this key.
1039    pub data_references: Vec<String>,
1040    /// URI references to other `EncryptedKey` elements encrypted with this key.
1041    pub key_references: Vec<String>,
1042}
1043
1044/// Parsed `EncryptedData` document fragment.
1045#[derive(Debug, Clone, PartialEq, Eq)]
1046pub struct EncryptedData {
1047    /// Experimental KEM sources; never silently treated as raw content keys.
1048    pub encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
1049    /// Optional XML identifier.
1050    pub id: Option<String>,
1051    /// Optional plaintext representation hint.
1052    pub encrypted_type: Option<EncryptedDataType>,
1053    /// Optional direct `ds:KeyName` hint from `KeyInfo`.
1054    pub key_name: Option<String>,
1055    /// Content-encryption method.
1056    pub encryption_method: EncryptionMethod,
1057    /// Embedded recipient session keys in `KeyInfo` document order.
1058    pub encrypted_keys: Vec<EncryptedKey>,
1059    /// Ordered XML derivation candidates. Master material remains request-owned.
1060    pub derived_keys: Vec<super::DerivedKey>,
1061    /// Agreement candidates; trusted private keys remain in request context.
1062    pub agreement_methods: Vec<super::AgreementMethod>,
1063    /// Content ciphertext in base64 form.
1064    pub cipher_data: CipherData,
1065}
1066
1067/// Plaintext returned from XMLEnc decryption.
1068#[derive(Debug, Clone, PartialEq, Eq)]
1069pub enum DecryptedContent {
1070    /// XML plaintext for `Element` and `Content` encrypted data.
1071    Xml(String),
1072    /// Binary plaintext when the encrypted data has no standard XML type hint.
1073    Bytes(Vec<u8>),
1074}
1075
1076/// Errors raised while parsing, encrypting, resolving, or decrypting XMLEnc data.
1077#[derive(Debug, thiserror::Error)]
1078#[non_exhaustive]
1079pub enum XmlEncError {
1080    /// Shared key establishment failed before content encryption/decryption.
1081    #[error(transparent)]
1082    KeyEstablishment(#[from] crate::key_establishment::KeyEstablishmentError),
1083    /// URI resolution or ciphertext transforms failed.
1084    #[error("ciphertext reference error: {0}")]
1085    Transform(#[from] crate::xmldsig::TransformError),
1086    /// The compiled encryption or decryption policy rejected an operation input.
1087    #[error("XML Encryption policy violation: {0}")]
1088    Policy(#[from] crate::policy::PolicyViolation),
1089
1090    /// The selected cryptographic provider rejected or failed an operation.
1091    #[error("cryptographic provider error: {0}")]
1092    Provider(#[from] crate::provider::ProviderError),
1093
1094    /// XML document parsing failed.
1095    #[error("XML parsing error: {0}")]
1096    XmlParse(#[from] crate::xml::dom::ParseError),
1097    /// The owned XML document boundary rejected an identity or mutation.
1098    #[error("XML document error: {0}")]
1099    Document(#[from] crate::document::XmlDocumentError),
1100    /// Required child element or attribute was absent.
1101    #[error("missing required {0}")]
1102    MissingRequired(&'static str),
1103    /// The XML element order or namespace is invalid for the XMLEnc profile.
1104    #[error("invalid encrypted structure: {0}")]
1105    InvalidStructure(String),
1106    /// The compiled operation graph rejected an identity, dependency, or generation invariant.
1107    #[error("invalid XML Encryption operation plan: {0}")]
1108    OperationPlan(String),
1109    /// The selected operation-start node ID is absent or resolves ambiguously.
1110    #[error("selected node ID is missing or ambiguous: {id}")]
1111    SelectedNodeUnavailable {
1112        /// Caller-supplied node identifier.
1113        id: String,
1114    },
1115    /// An algorithm URI is not supported by this build.
1116    #[error("unsupported encryption algorithm: {0}")]
1117    UnsupportedAlgorithm(String),
1118    /// Base64 input is invalid or exceeds the configured input bound.
1119    #[error("invalid base64 data: {0}")]
1120    Base64(String),
1121    /// A decoded cipher value is too short for its algorithm's framing.
1122    #[error("{algorithm} ciphertext is too short: need at least {minimum} bytes, got {actual}")]
1123    DataTooShort {
1124        /// Algorithm name.
1125        algorithm: &'static str,
1126        /// Minimum valid byte length.
1127        minimum: usize,
1128        /// Actual byte length.
1129        actual: usize,
1130    },
1131    /// CBC ciphertext is not a non-empty multiple of the selected cipher's block size.
1132    #[error(
1133        "{algorithm} ciphertext length must be a non-zero multiple of {block} bytes, got {actual}"
1134    )]
1135    InvalidCbcCiphertextLength {
1136        /// Selected content cipher.
1137        algorithm: DataEncryptionAlgorithm,
1138        /// Cipher's block width in bytes.
1139        block: usize,
1140        /// Ciphertext body width, excluding the IV.
1141        actual: usize,
1142    },
1143    /// XMLEnc random padding is invalid.
1144    ///
1145    /// No decrypted padding details are exposed. This does not authenticate CBC
1146    /// ciphertexts or make success/failure safe to expose to an attacker.
1147    #[error("invalid XMLEnc padding")]
1148    InvalidPadding,
1149    /// Authenticated content decryption failed (GCM or ChaCha20-Poly1305).
1150    #[error("AEAD authentication failed")]
1151    AeadAuthenticationFailed,
1152    /// A supplied content key is not the expected size.
1153    #[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
1154    InvalidKeySize {
1155        /// Content algorithm requiring the key.
1156        algorithm: DataEncryptionAlgorithm,
1157        /// Expected key size.
1158        expected: usize,
1159        /// Actual key size.
1160        actual: usize,
1161    },
1162    /// An unauthenticated content algorithm cannot safely select among keys.
1163    #[error(
1164        "{algorithm:?} cannot safely select among {actual} unordered decryption key candidates"
1165    )]
1166    AmbiguousKeyCandidates {
1167        /// Unauthenticated algorithm for which key success is ambiguous.
1168        algorithm: DataEncryptionAlgorithm,
1169        /// Number of unresolved candidate keys.
1170        actual: usize,
1171    },
1172    /// A supplied AES key-encryption key is not the size declared by EncryptedKey.
1173    #[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
1174    InvalidKekSize {
1175        /// Key-wrap algorithm requiring the KEK.
1176        algorithm: KeyWrapAlgorithm,
1177        /// Expected KEK size.
1178        expected: usize,
1179        /// Actual KEK size.
1180        actual: usize,
1181    },
1182    /// A wrapped-key input or provider output has invalid algorithm framing.
1183    #[error("wrapped-key value must be {expected} bytes, got {actual}")]
1184    InvalidWrappedKeyLength {
1185        /// Exact wrapped length required by the algorithm and key context.
1186        expected: usize,
1187        /// Actual input or provider output length.
1188        actual: usize,
1189    },
1190    /// Encryption configuration is internally inconsistent.
1191    #[error("invalid encryption configuration: {0}")]
1192    InvalidEncryptionConfig(String),
1193    /// No caller-provided resolver could supply a usable key.
1194    #[error("no suitable decryption key was resolved")]
1195    KeyNotFound,
1196    /// No `EncryptedData` matched the requested document selection.
1197    #[error("no matching EncryptedData element was found")]
1198    EncryptedDataNotFound,
1199    /// More than one `EncryptedData` matched the requested document selection.
1200    #[error("more than one EncryptedData element matched; select one by Id")]
1201    AmbiguousEncryptedData,
1202    /// No source element matched the requested encryption target.
1203    #[error("no matching element was found for encryption")]
1204    EncryptionTargetNotFound,
1205    /// More than one source element matched the requested encryption target.
1206    #[error("more than one element matched the encryption target")]
1207    AmbiguousEncryptionTarget,
1208    /// Document replacement requires an XML `Type` declaration.
1209    #[error("EncryptedData must declare Element or Content Type for document replacement")]
1210    ReplacementRequiresXml,
1211    /// RSA-OAEP session-key recovery failed.
1212    #[error("RSA-OAEP key unwrap failed: {0}")]
1213    Rsa(String),
1214    /// RSA-OAEP session-key wrapping failed.
1215    #[error("RSA-OAEP key wrap failed: {0}")]
1216    RsaEncrypt(String),
1217    /// RFC 3394 integrity validation failed while unwrapping a key.
1218    #[error("AES key unwrap failed integrity validation")]
1219    KeyWrapIntegrity,
1220    /// Operating-system randomness was unavailable.
1221    #[error("operating-system random number generation failed: {0}")]
1222    Rng(String),
1223    /// Generated XML could not be serialized.
1224    #[error("XML encryption serialization failed: {0}")]
1225    XmlSerialize(String),
1226    /// XML-declared plaintext could not be decoded as UTF-8.
1227    #[error("decrypted XML is not valid UTF-8: {0}")]
1228    Utf8(#[from] std::string::FromUtf8Error),
1229}
1230
1231impl From<crate::operation::OperationPlanError> for XmlEncError {
1232    fn from(error: crate::operation::OperationPlanError) -> Self {
1233        Self::OperationPlan(error.to_string())
1234    }
1235}
1236
1237impl fmt::Display for DataEncryptionAlgorithm {
1238    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1239        formatter.write_str(match self {
1240            #[cfg(feature = "legacy-algorithms")]
1241            Self::TripleDesCbc => "Triple DES CBC",
1242            #[cfg(feature = "legacy-algorithms")]
1243            Self::Aes192Cbc => "AES-192-CBC",
1244            #[cfg(feature = "legacy-algorithms")]
1245            Self::Aes192Gcm => "AES-192-GCM",
1246            Self::Aes128Cbc => "AES-128-CBC",
1247            Self::Aes256Cbc => "AES-256-CBC",
1248            Self::Aes128Gcm => "AES-128-GCM",
1249            Self::Aes256Gcm => "AES-256-GCM",
1250            Self::Camellia128Cbc => "Camellia-128-CBC",
1251            Self::Camellia192Cbc => "Camellia-192-CBC",
1252            Self::Camellia256Cbc => "Camellia-256-CBC",
1253            Self::ChaCha20 => "ChaCha20",
1254            Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
1255        })
1256    }
1257}
1258
1259#[cfg(test)]
1260mod framing_tests {
1261    use super::*;
1262
1263    #[test]
1264    fn cbc_framing_error_names_the_selected_cipher() {
1265        // Diagnostics must identify the actual cipher and its block width,
1266        // rather than reporting AES framing for a DES input.
1267        let error = validate_ciphertext_framing(DataEncryptionAlgorithm::Aes128Cbc, 33)
1268            .expect_err("AES ciphertext body is not block-aligned");
1269        assert_eq!(
1270            error.to_string(),
1271            "AES-128-CBC ciphertext length must be a non-zero multiple of 16 bytes, got 17"
1272        );
1273        #[cfg(feature = "legacy-algorithms")]
1274        {
1275            let error = validate_ciphertext_framing(DataEncryptionAlgorithm::TripleDesCbc, 17)
1276                .expect_err("3DES ciphertext body is not block-aligned");
1277            assert_eq!(
1278                error.to_string(),
1279                "Triple DES CBC ciphertext length must be a non-zero multiple of 8 bytes, got 9"
1280            );
1281        }
1282    }
1283}