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