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    /// AES-128 in CBC mode with XMLEnc padding.
32    Aes128Cbc,
33    /// AES-256 in CBC mode with XMLEnc padding.
34    Aes256Cbc,
35    /// AES-128 in GCM mode.
36    Aes128Gcm,
37    /// AES-256 in GCM mode.
38    Aes256Gcm,
39}
40
41impl DataEncryptionAlgorithm {
42    /// Parse a supported XMLEnc content-encryption URI.
43    pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
44        match uri {
45            "http://www.w3.org/2001/04/xmlenc#aes128-cbc" => Ok(Self::Aes128Cbc),
46            "http://www.w3.org/2001/04/xmlenc#aes256-cbc" => Ok(Self::Aes256Cbc),
47            "http://www.w3.org/2009/xmlenc11#aes128-gcm" => Ok(Self::Aes128Gcm),
48            "http://www.w3.org/2009/xmlenc11#aes256-gcm" => Ok(Self::Aes256Gcm),
49            _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
50        }
51    }
52
53    /// Required symmetric key length in bytes.
54    pub const fn key_len(self) -> usize {
55        match self {
56            Self::Aes128Cbc | Self::Aes128Gcm => 16,
57            Self::Aes256Cbc | Self::Aes256Gcm => 32,
58        }
59    }
60
61    /// Return the standard XMLEnc algorithm URI.
62    pub const fn uri(self) -> &'static str {
63        match self {
64            Self::Aes128Cbc => "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
65            Self::Aes256Cbc => "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
66            Self::Aes128Gcm => "http://www.w3.org/2009/xmlenc11#aes128-gcm",
67            Self::Aes256Gcm => "http://www.w3.org/2009/xmlenc11#aes256-gcm",
68        }
69    }
70
71    /// Minimum standard wire length for ciphertext produced by this algorithm.
72    pub(crate) const fn minimum_ciphertext_len(self) -> usize {
73        match self {
74            Self::Aes128Cbc | Self::Aes256Cbc => 32,
75            Self::Aes128Gcm | Self::Aes256Gcm => 28,
76        }
77    }
78
79    /// Exact wire length produced when encrypting the given plaintext length.
80    pub(crate) fn ciphertext_len_for_plaintext(self, plaintext_len: usize) -> Option<usize> {
81        match self {
82            Self::Aes128Cbc | Self::Aes256Cbc => (plaintext_len / 16)
83                .checked_add(1)?
84                .checked_mul(16)?
85                .checked_add(16),
86            Self::Aes128Gcm | Self::Aes256Gcm => plaintext_len.checked_add(28),
87        }
88    }
89}
90
91pub(crate) fn validate_ciphertext_framing(
92    algorithm: DataEncryptionAlgorithm,
93    ciphertext_len: usize,
94) -> Result<(), XmlEncError> {
95    let minimum = algorithm.minimum_ciphertext_len();
96    if ciphertext_len < minimum {
97        let algorithm_name = match algorithm {
98            DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc => "AES-CBC",
99            DataEncryptionAlgorithm::Aes128Gcm | DataEncryptionAlgorithm::Aes256Gcm => "AES-GCM",
100        };
101        return Err(XmlEncError::DataTooShort {
102            algorithm: algorithm_name,
103            minimum,
104            actual: ciphertext_len,
105        });
106    }
107    if matches!(
108        algorithm,
109        DataEncryptionAlgorithm::Aes128Cbc | DataEncryptionAlgorithm::Aes256Cbc
110    ) && !(ciphertext_len - 16).is_multiple_of(16)
111    {
112        return Err(XmlEncError::InvalidCbcCiphertextLength(ciphertext_len - 16));
113    }
114    Ok(())
115}
116
117impl KeyTransportAlgorithm {
118    /// Parse a supported XMLEnc key-transport URI.
119    pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
120        match uri {
121            "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" => Ok(Self::RsaOaepMgf1p),
122            "http://www.w3.org/2009/xmlenc11#rsa-oaep" => Ok(Self::RsaOaep11),
123            _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
124        }
125    }
126
127    /// Return the standard XMLEnc key-transport URI.
128    pub const fn uri(self) -> &'static str {
129        match self {
130            Self::RsaOaepMgf1p => "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
131            Self::RsaOaep11 => "http://www.w3.org/2009/xmlenc11#rsa-oaep",
132        }
133    }
134}
135
136impl KeyWrapAlgorithm {
137    /// Parse a supported XMLEnc symmetric key-wrap URI.
138    pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
139        match uri {
140            "http://www.w3.org/2001/04/xmlenc#kw-aes128" => Ok(Self::AesKw128),
141            "http://www.w3.org/2001/04/xmlenc#kw-aes256" => Ok(Self::AesKw256),
142            _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
143        }
144    }
145
146    /// Required key-encryption-key length in bytes.
147    pub const fn key_len(self) -> usize {
148        match self {
149            Self::AesKw128 => 16,
150            Self::AesKw256 => 32,
151        }
152    }
153
154    /// Return the standard XMLEnc key-wrap URI.
155    pub const fn uri(self) -> &'static str {
156        match self {
157            Self::AesKw128 => "http://www.w3.org/2001/04/xmlenc#kw-aes128",
158            Self::AesKw256 => "http://www.w3.org/2001/04/xmlenc#kw-aes256",
159        }
160    }
161}
162
163/// Supported asymmetric session-key transport algorithms.
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165pub enum KeyTransportAlgorithm {
166    /// XML Encryption 1.0 OAEP with SHA-1 and MGF1-SHA-1.
167    RsaOaepMgf1p,
168    /// XML Encryption 1.1 OAEP with explicitly parsed digest and MGF settings.
169    RsaOaep11,
170}
171
172/// Supported symmetric key-wrap algorithms.
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
174pub enum KeyWrapAlgorithm {
175    /// RFC 3394 AES key wrap with a 128-bit KEK.
176    AesKw128,
177    /// RFC 3394 AES key wrap with a 256-bit KEK.
178    AesKw256,
179}
180
181/// Digest algorithms accepted by RSA-OAEP encryption.
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
183pub enum OaepDigestAlgorithm {
184    /// SHA-1, retained for legacy XMLEnc OAEP interoperability.
185    Sha1,
186    /// SHA-256.
187    Sha256,
188    /// SHA-384.
189    Sha384,
190    /// SHA-512.
191    Sha512,
192}
193
194impl OaepDigestAlgorithm {
195    /// Parse a digest URI accepted by XML Encryption and libxmlsec1.
196    pub fn from_uri(uri: &str) -> Option<Self> {
197        match uri {
198            "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
199            "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
200            "http://www.w3.org/2001/04/xmlenc#sha384"
201            | "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
202            "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
203            _ => None,
204        }
205    }
206
207    /// Parse an XML Encryption 1.1 MGF1 URI.
208    pub fn from_mgf_uri(uri: &str) -> Option<Self> {
209        match uri {
210            "http://www.w3.org/2009/xmlenc11#mgf1sha1" => Some(Self::Sha1),
211            "http://www.w3.org/2009/xmlenc11#mgf1sha256" => Some(Self::Sha256),
212            "http://www.w3.org/2009/xmlenc11#mgf1sha384" => Some(Self::Sha384),
213            "http://www.w3.org/2009/xmlenc11#mgf1sha512" => Some(Self::Sha512),
214            _ => None,
215        }
216    }
217
218    /// Return the standard digest URI.
219    pub const fn uri(self) -> &'static str {
220        match self {
221            Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
222            Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
223            Self::Sha384 => "http://www.w3.org/2001/04/xmlenc#sha384",
224            Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
225        }
226    }
227
228    /// Return the XML Encryption 1.1 MGF URI for this digest.
229    pub const fn mgf_uri(self) -> &'static str {
230        match self {
231            Self::Sha1 => "http://www.w3.org/2009/xmlenc11#mgf1sha1",
232            Self::Sha256 => "http://www.w3.org/2009/xmlenc11#mgf1sha256",
233            Self::Sha384 => "http://www.w3.org/2009/xmlenc11#mgf1sha384",
234            Self::Sha512 => "http://www.w3.org/2009/xmlenc11#mgf1sha512",
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::OaepDigestAlgorithm;
242
243    #[test]
244    fn oaep_sha384_accepts_both_interoperable_digest_uris() {
245        // The canonical XML Encryption spelling and libxmlsec1's XMLDSig-more
246        // spelling identify the same OAEP digest algorithm.
247        for uri in [
248            "http://www.w3.org/2001/04/xmlenc#sha384",
249            "http://www.w3.org/2001/04/xmldsig-more#sha384",
250        ] {
251            assert_eq!(
252                OaepDigestAlgorithm::from_uri(uri),
253                Some(OaepDigestAlgorithm::Sha384)
254            );
255        }
256    }
257
258    #[test]
259    fn oaep_mgf_uris_round_trip() {
260        for algorithm in [
261            OaepDigestAlgorithm::Sha1,
262            OaepDigestAlgorithm::Sha256,
263            OaepDigestAlgorithm::Sha384,
264            OaepDigestAlgorithm::Sha512,
265        ] {
266            assert_eq!(
267                OaepDigestAlgorithm::from_mgf_uri(algorithm.mgf_uri()),
268                Some(algorithm)
269            );
270        }
271        assert_eq!(
272            OaepDigestAlgorithm::from_mgf_uri("urn:unsupported-mgf"),
273            None
274        );
275    }
276}
277
278/// RSA-OAEP parameters emitted in an `EncryptedKey`.
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct RsaOaepParameters {
281    /// XMLEnc 1.0 legacy OAEP or XMLEnc 1.1 configurable OAEP.
282    pub algorithm: KeyTransportAlgorithm,
283    /// Digest used by OAEP.
284    pub digest: OaepDigestAlgorithm,
285    /// Digest used by MGF1.
286    pub mgf_digest: OaepDigestAlgorithm,
287    /// Optional OAEP label bytes.
288    pub label: Vec<u8>,
289}
290
291impl RsaOaepParameters {
292    /// Create legacy OAEP parameters with SHA-1 and MGF1-SHA-1.
293    pub fn legacy() -> Self {
294        Self {
295            algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
296            digest: OaepDigestAlgorithm::Sha1,
297            mgf_digest: OaepDigestAlgorithm::Sha1,
298            label: Vec::new(),
299        }
300    }
301
302    /// Create XMLEnc 1.1 OAEP parameters.
303    pub fn xmlenc11(digest: OaepDigestAlgorithm, mgf_digest: OaepDigestAlgorithm) -> Self {
304        Self {
305            algorithm: KeyTransportAlgorithm::RsaOaep11,
306            digest,
307            mgf_digest,
308            label: Vec::new(),
309        }
310    }
311
312    /// Set the OAEP label bytes.
313    pub fn label(mut self, label: impl Into<Vec<u8>>) -> Self {
314        self.label = label.into();
315        self
316    }
317}
318
319impl Default for RsaOaepParameters {
320    fn default() -> Self {
321        Self::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha256)
322    }
323}
324
325/// One recipient of a generated content-encryption key.
326#[derive(Clone)]
327pub enum EncryptionRecipient {
328    /// Wrap the content key with an RSA public key and OAEP.
329    RsaOaep {
330        /// Opaque recipient public-key handle.
331        public_key: Arc<dyn crate::provider::KeyTransportKey>,
332        /// OAEP algorithm parameters.
333        parameters: RsaOaepParameters,
334        /// Optional `Recipient` attribute.
335        recipient: Option<String>,
336        /// Optional key hint inside the encrypted key's `KeyInfo`.
337        key_name: Option<String>,
338    },
339    /// Wrap the content key with a pre-shared AES KEK.
340    AesKeyWrap {
341        /// AES key-encryption key.
342        kek: Vec<u8>,
343        /// RFC 3394 key-wrap variant.
344        algorithm: KeyWrapAlgorithm,
345        /// Optional `Recipient` attribute.
346        recipient: Option<String>,
347        /// Optional key hint inside the encrypted key's `KeyInfo`.
348        key_name: Option<String>,
349    },
350}
351
352impl fmt::Debug for EncryptionRecipient {
353    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
354        match self {
355            Self::RsaOaep {
356                parameters,
357                recipient,
358                key_name,
359                ..
360            } => formatter
361                .debug_struct("EncryptionRecipient::RsaOaep")
362                .field("public_key", &"[PUBLIC KEY]")
363                .field("parameters", parameters)
364                .field("recipient", recipient)
365                .field("key_name", key_name)
366                .finish(),
367            Self::AesKeyWrap {
368                algorithm,
369                recipient,
370                key_name,
371                ..
372            } => formatter
373                .debug_struct("EncryptionRecipient::AesKeyWrap")
374                .field("kek", &"[REDACTED]")
375                .field("algorithm", algorithm)
376                .field("recipient", recipient)
377                .field("key_name", key_name)
378                .finish(),
379        }
380    }
381}
382
383impl EncryptionRecipient {
384    /// Create an RSA-OAEP recipient using SHA-256 and MGF1-SHA-256.
385    ///
386    /// XMLEnc 1.1 assigns SHA-1 and MGF1-SHA-1 when these parameters are
387    /// omitted. Serialized keys therefore include both algorithm values
388    /// explicitly instead of relying on the specification's legacy defaults.
389    pub fn rsa_oaep(public_key: RsaPublicKey) -> Self {
390        Self::provider_key_transport(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
391            public_key,
392        )))
393    }
394
395    /// Create an RSA-OAEP recipient from an opaque provider key handle.
396    pub fn provider_key_transport(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
397        Self::RsaOaep {
398            public_key,
399            parameters: RsaOaepParameters::default(),
400            recipient: None,
401            key_name: None,
402        }
403    }
404
405    /// Create an AES Key Wrap recipient.
406    pub fn aes_key_wrap(kek: impl Into<Vec<u8>>, algorithm: KeyWrapAlgorithm) -> Self {
407        Self::AesKeyWrap {
408            kek: kek.into(),
409            algorithm,
410            recipient: None,
411            key_name: None,
412        }
413    }
414
415    /// Override RSA-OAEP parameters.
416    pub fn oaep_parameters(mut self, parameters: RsaOaepParameters) -> Self {
417        if let Self::RsaOaep {
418            parameters: current,
419            ..
420        } = &mut self
421        {
422            *current = parameters;
423        }
424        self
425    }
426
427    /// Set the recipient identifier emitted on `EncryptedKey`.
428    pub fn recipient(mut self, value: impl Into<String>) -> Self {
429        match &mut self {
430            Self::RsaOaep { recipient, .. } | Self::AesKeyWrap { recipient, .. } => {
431                *recipient = Some(value.into());
432            }
433        }
434        self
435    }
436
437    /// Set the key name emitted inside the encrypted key's `KeyInfo`.
438    pub fn key_name(mut self, value: impl Into<String>) -> Self {
439        match &mut self {
440            Self::RsaOaep { key_name, .. } | Self::AesKeyWrap { key_name, .. } => {
441                *key_name = Some(value.into());
442            }
443        }
444        self
445    }
446}
447
448/// How generated `EncryptedData` replaces caller-owned XML.
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450pub enum ReplacementMode {
451    /// Replace the selected element, including its start and end tags.
452    ReplaceElement,
453    /// Replace only the selected element's child content.
454    ReplaceContent,
455}
456
457/// Result returned after encrypting bytes or XML.
458#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct EncryptionResult {
460    /// Complete `EncryptedData` XML fragment.
461    pub encrypted_data_xml: String,
462    /// Required caller-owned document replacement operation.
463    pub replacement: ReplacementMode,
464}
465
466/// Caller-owned target selection for document encryption.
467#[derive(Debug, Clone, Copy, Default)]
468pub struct DocumentEncryptionOptions<'a> {
469    /// Select an element by `Id`, `ID`, or `id`; `None` selects the document root.
470    pub element_id: Option<&'a str>,
471}
472
473/// Parsed `EncryptionMethod` data.
474#[derive(Debug, Clone, PartialEq, Eq)]
475pub struct EncryptionMethod {
476    /// Algorithm URI from the mandatory `Algorithm` attribute.
477    pub algorithm: String,
478    /// Optional explicit key size in bits.
479    pub key_size_bits: Option<usize>,
480    /// Digest URI used by XML Encryption 1.1 OAEP.
481    pub oaep_digest: Option<String>,
482    /// MGF URI used by XML Encryption 1.1 OAEP.
483    pub mgf_algorithm: Option<String>,
484    /// Decoded OAEP label bytes.
485    pub oaep_params: Option<Vec<u8>>,
486}
487
488impl EncryptionMethod {
489    /// Validate invariants imposed by the selected algorithm URI.
490    ///
491    /// Parsed XML and caller-constructed typed values share this check so the
492    /// public typed API cannot express wire structures that XML parsing rejects.
493    pub(crate) fn validate_structure(&self) -> Result<(), XmlEncError> {
494        if self.key_size_bits == Some(0) {
495            return Err(XmlEncError::InvalidStructure(
496                "KeySize must be a positive integer".into(),
497            ));
498        }
499        let is_legacy_oaep = self.algorithm == KeyTransportAlgorithm::RsaOaepMgf1p.uri();
500        let is_oaep11 = self.algorithm == KeyTransportAlgorithm::RsaOaep11.uri();
501        if (self.oaep_params.is_some()
502            || self.oaep_digest.is_some()
503            || self.mgf_algorithm.is_some())
504            && !is_legacy_oaep
505            && !is_oaep11
506        {
507            return Err(XmlEncError::InvalidStructure(
508                "OAEP parameters are only valid for RSA-OAEP EncryptionMethod".into(),
509            ));
510        }
511        if self.mgf_algorithm.is_some() && !is_oaep11 {
512            return Err(XmlEncError::InvalidStructure(
513                "MGF is only valid for XML Encryption 1.1 RSA-OAEP".into(),
514            ));
515        }
516        if let (Some(actual), Some(expected)) =
517            (self.key_size_bits, fixed_aes_key_size(&self.algorithm))
518            && actual != expected
519        {
520            return Err(XmlEncError::InvalidStructure(format!(
521                "EncryptionMethod {} requires KeySize {expected}, got {actual}",
522                self.algorithm
523            )));
524        }
525        Ok(())
526    }
527}
528
529fn fixed_aes_key_size(algorithm: &str) -> Option<usize> {
530    let key_len = DataEncryptionAlgorithm::from_uri(algorithm)
531        .map(DataEncryptionAlgorithm::key_len)
532        .or_else(|_| KeyWrapAlgorithm::from_uri(algorithm).map(KeyWrapAlgorithm::key_len))
533        .ok()?;
534    Some(key_len * 8)
535}
536
537/// Inline ciphertext data.
538#[derive(Debug, Clone, PartialEq, Eq)]
539pub struct CipherData {
540    /// Whitespace-normalized base64 text from `CipherValue`.
541    pub value: String,
542}
543
544/// Parsed embedded `EncryptedKey` used to recover a content-encryption key.
545#[derive(Debug, Clone, PartialEq, Eq)]
546pub struct EncryptedKey {
547    /// Optional XML identifier.
548    pub id: Option<String>,
549    /// Optional recipient hint.
550    pub recipient: Option<String>,
551    /// Optional direct `ds:KeyName` hint from the key's `KeyInfo`.
552    pub key_name: Option<String>,
553    /// Method which wrapped the session key.
554    pub encryption_method: EncryptionMethod,
555    /// Wrapped session-key bytes in base64 form.
556    pub cipher_data: CipherData,
557    /// Optional references identifying data or keys associated with this key.
558    pub reference_list: Option<ReferenceList>,
559    /// Optional name associated with the transported plaintext key.
560    pub carried_key_name: Option<String>,
561}
562
563/// References associated with an `EncryptedKey`.
564#[derive(Debug, Clone, PartialEq, Eq)]
565pub struct ReferenceList {
566    /// URI references to `EncryptedData` elements encrypted with this key.
567    pub data_references: Vec<String>,
568    /// URI references to other `EncryptedKey` elements encrypted with this key.
569    pub key_references: Vec<String>,
570}
571
572/// Parsed `EncryptedData` document fragment.
573#[derive(Debug, Clone, PartialEq, Eq)]
574pub struct EncryptedData {
575    /// Optional XML identifier.
576    pub id: Option<String>,
577    /// Optional plaintext representation hint.
578    pub encrypted_type: Option<EncryptedDataType>,
579    /// Optional direct `ds:KeyName` hint from `KeyInfo`.
580    pub key_name: Option<String>,
581    /// Content-encryption method.
582    pub encryption_method: EncryptionMethod,
583    /// Embedded recipient session keys in `KeyInfo` document order.
584    pub encrypted_keys: Vec<EncryptedKey>,
585    /// Content ciphertext in base64 form.
586    pub cipher_data: CipherData,
587}
588
589/// Plaintext returned from XMLEnc decryption.
590#[derive(Debug, Clone, PartialEq, Eq)]
591pub enum DecryptedContent {
592    /// XML plaintext for `Element` and `Content` encrypted data.
593    Xml(String),
594    /// Binary plaintext when the encrypted data has no standard XML type hint.
595    Bytes(Vec<u8>),
596}
597
598/// Errors raised while parsing, encrypting, resolving, or decrypting XMLEnc data.
599#[derive(Debug, thiserror::Error)]
600#[non_exhaustive]
601pub enum XmlEncError {
602    /// The compiled encryption or decryption policy rejected an operation input.
603    #[error("XML Encryption policy violation: {0}")]
604    Policy(#[from] crate::policy::PolicyViolation),
605
606    /// The selected cryptographic provider rejected or failed an operation.
607    #[error("cryptographic provider error: {0}")]
608    Provider(#[from] crate::provider::ProviderError),
609
610    /// XML document parsing failed.
611    #[error("XML parsing error: {0}")]
612    XmlParse(#[from] roxmltree::Error),
613    /// The owned XML document boundary rejected an identity or mutation.
614    #[error("XML document error: {0}")]
615    Document(#[from] crate::document::XmlDocumentError),
616    /// Required child element or attribute was absent.
617    #[error("missing required {0}")]
618    MissingRequired(&'static str),
619    /// The XML element order or namespace is invalid for the XMLEnc profile.
620    #[error("invalid encrypted structure: {0}")]
621    InvalidStructure(String),
622    /// The selected operation-start node ID is absent or resolves ambiguously.
623    #[error("selected node ID is missing or ambiguous: {id}")]
624    SelectedNodeUnavailable {
625        /// Caller-supplied node identifier.
626        id: String,
627    },
628    /// An algorithm URI is not supported by this build.
629    #[error("unsupported encryption algorithm: {0}")]
630    UnsupportedAlgorithm(String),
631    /// Base64 input is invalid or exceeds the configured input bound.
632    #[error("invalid base64 data: {0}")]
633    Base64(String),
634    /// A decoded cipher value is too short for its algorithm's framing.
635    #[error("{algorithm} ciphertext is too short: need at least {minimum} bytes, got {actual}")]
636    DataTooShort {
637        /// Algorithm name.
638        algorithm: &'static str,
639        /// Minimum valid byte length.
640        minimum: usize,
641        /// Actual byte length.
642        actual: usize,
643    },
644    /// CBC ciphertext is not a non-empty multiple of the AES block size.
645    #[error("AES-CBC ciphertext length must be a non-zero multiple of 16 bytes, got {0}")]
646    InvalidCbcCiphertextLength(usize),
647    /// XMLEnc random padding is invalid.
648    ///
649    /// No decrypted padding details are exposed. This does not authenticate CBC
650    /// ciphertexts or make success/failure safe to expose to an attacker.
651    #[error("invalid XMLEnc padding")]
652    InvalidPadding,
653    /// GCM authentication failed.
654    #[error("AES-GCM authentication failed")]
655    AeadAuthenticationFailed,
656    /// A supplied content key is not the expected size.
657    #[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
658    InvalidKeySize {
659        /// Content algorithm requiring the key.
660        algorithm: DataEncryptionAlgorithm,
661        /// Expected key size.
662        expected: usize,
663        /// Actual key size.
664        actual: usize,
665    },
666    /// An unauthenticated content algorithm cannot safely select among keys.
667    #[error(
668        "{algorithm:?} cannot safely select among {actual} unordered decryption key candidates"
669    )]
670    AmbiguousKeyCandidates {
671        /// Unauthenticated algorithm for which key success is ambiguous.
672        algorithm: DataEncryptionAlgorithm,
673        /// Number of unresolved candidate keys.
674        actual: usize,
675    },
676    /// A supplied AES key-encryption key is not the size declared by EncryptedKey.
677    #[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
678    InvalidKekSize {
679        /// Key-wrap algorithm requiring the KEK.
680        algorithm: KeyWrapAlgorithm,
681        /// Expected KEK size.
682        expected: usize,
683        /// Actual KEK size.
684        actual: usize,
685    },
686    /// A wrapped-key input or provider output has invalid algorithm framing.
687    #[error("wrapped-key value must be {expected} bytes, got {actual}")]
688    InvalidWrappedKeyLength {
689        /// Exact wrapped length required by the algorithm and key context.
690        expected: usize,
691        /// Actual input or provider output length.
692        actual: usize,
693    },
694    /// Encryption configuration is internally inconsistent.
695    #[error("invalid encryption configuration: {0}")]
696    InvalidEncryptionConfig(String),
697    /// No caller-provided resolver could supply a usable key.
698    #[error("no suitable decryption key was resolved")]
699    KeyNotFound,
700    /// No `EncryptedData` matched the requested document selection.
701    #[error("no matching EncryptedData element was found")]
702    EncryptedDataNotFound,
703    /// More than one `EncryptedData` matched the requested document selection.
704    #[error("more than one EncryptedData element matched; select one by Id")]
705    AmbiguousEncryptedData,
706    /// No source element matched the requested encryption target.
707    #[error("no matching element was found for encryption")]
708    EncryptionTargetNotFound,
709    /// More than one source element matched the requested encryption target.
710    #[error("more than one element matched the encryption target")]
711    AmbiguousEncryptionTarget,
712    /// Document replacement requires an XML `Type` declaration.
713    #[error("EncryptedData must declare Element or Content Type for document replacement")]
714    ReplacementRequiresXml,
715    /// RSA-OAEP session-key recovery failed.
716    #[error("RSA-OAEP key unwrap failed: {0}")]
717    Rsa(String),
718    /// RSA-OAEP session-key wrapping failed.
719    #[error("RSA-OAEP key wrap failed: {0}")]
720    RsaEncrypt(String),
721    /// RFC 3394 integrity validation failed while unwrapping a key.
722    #[error("AES key unwrap failed integrity validation")]
723    KeyWrapIntegrity,
724    /// Operating-system randomness was unavailable.
725    #[error("operating-system random number generation failed: {0}")]
726    Rng(String),
727    /// Generated XML could not be serialized.
728    #[error("XML encryption serialization failed: {0}")]
729    XmlSerialize(String),
730    /// XML-declared plaintext could not be decoded as UTF-8.
731    #[error("decrypted XML is not valid UTF-8: {0}")]
732    Utf8(#[from] std::string::FromUtf8Error),
733}
734
735impl fmt::Display for DataEncryptionAlgorithm {
736    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
737        formatter.write_str(match self {
738            Self::Aes128Cbc => "AES-128-CBC",
739            Self::Aes256Cbc => "AES-256-CBC",
740            Self::Aes128Gcm => "AES-128-GCM",
741            Self::Aes256Gcm => "AES-256-GCM",
742        })
743    }
744}