1use std::{fmt, sync::Arc};
4
5use rsa::RsaPublicKey;
6
7pub const XMLENC_NS: &str = "http://www.w3.org/2001/04/xmlenc#";
9pub const XMLENC11_NS: &str = "http://www.w3.org/2009/xmlenc11#";
11pub const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
13
14pub const MAX_CIPHER_VALUE_BASE64_LEN: usize =
16 crate::hard_limits::ENCRYPTION_CIPHER_VALUE_BASE64_BYTE_CEILING;
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum EncryptedDataType {
20 Element,
22 Content,
24 Other(String),
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum DataEncryptionAlgorithm {
31 Aes128Cbc,
33 Aes256Cbc,
35 Aes128Gcm,
37 Aes256Gcm,
39}
40
41impl DataEncryptionAlgorithm {
42 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 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 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 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 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 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 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 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 pub const fn key_len(self) -> usize {
148 match self {
149 Self::AesKw128 => 16,
150 Self::AesKw256 => 32,
151 }
152 }
153
154 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165pub enum KeyTransportAlgorithm {
166 RsaOaepMgf1p,
171 RsaOaep11,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
177pub enum KeyWrapAlgorithm {
178 AesKw128,
180 AesKw256,
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
186pub enum OaepDigestAlgorithm {
187 Sha1,
189 Sha256,
191 Sha384,
193 Sha512,
195}
196
197impl OaepDigestAlgorithm {
198 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
289pub struct RsaOaepParameters {
290 pub algorithm: KeyTransportAlgorithm,
292 pub digest: OaepDigestAlgorithm,
294 pub mgf_digest: OaepDigestAlgorithm,
296 pub label: Vec<u8>,
298}
299
300impl RsaOaepParameters {
301 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 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 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#[derive(Clone)]
336pub enum EncryptionRecipient {
337 RsaOaep {
339 public_key: Arc<dyn crate::provider::KeyTransportKey>,
341 parameters: RsaOaepParameters,
343 recipient: Option<String>,
345 key_name: Option<String>,
347 },
348 AesKeyWrap {
350 kek: Vec<u8>,
352 algorithm: KeyWrapAlgorithm,
354 recipient: Option<String>,
356 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 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 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 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub enum ReplacementMode {
460 ReplaceElement,
462 ReplaceContent,
464}
465
466#[derive(Debug, Clone, PartialEq, Eq)]
468pub struct EncryptionResult {
469 pub encrypted_data_xml: String,
471 pub replacement: ReplacementMode,
473}
474
475#[derive(Debug, Clone, Copy, Default)]
477pub struct DocumentEncryptionOptions<'a> {
478 pub element_id: Option<&'a str>,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq)]
484pub struct EncryptionMethod {
485 pub algorithm: String,
487 pub key_size_bits: Option<usize>,
489 pub oaep_digest: Option<String>,
491 pub mgf_algorithm: Option<String>,
493 pub oaep_params: Option<Vec<u8>>,
495}
496
497impl EncryptionMethod {
498 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#[derive(Debug, Clone, PartialEq, Eq)]
543pub struct CipherData {
544 pub value: String,
546}
547
548#[derive(Debug, Clone, PartialEq, Eq)]
550pub struct EncryptedKey {
551 pub id: Option<String>,
553 pub recipient: Option<String>,
555 pub key_name: Option<String>,
557 pub encryption_method: EncryptionMethod,
559 pub cipher_data: CipherData,
561 pub reference_list: Option<ReferenceList>,
563 pub carried_key_name: Option<String>,
565}
566
567#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct ReferenceList {
570 pub data_references: Vec<String>,
572 pub key_references: Vec<String>,
574}
575
576#[derive(Debug, Clone, PartialEq, Eq)]
578pub struct EncryptedData {
579 pub id: Option<String>,
581 pub encrypted_type: Option<EncryptedDataType>,
583 pub key_name: Option<String>,
585 pub encryption_method: EncryptionMethod,
587 pub encrypted_keys: Vec<EncryptedKey>,
589 pub cipher_data: CipherData,
591}
592
593#[derive(Debug, Clone, PartialEq, Eq)]
595pub enum DecryptedContent {
596 Xml(String),
598 Bytes(Vec<u8>),
600}
601
602#[derive(Debug, thiserror::Error)]
604#[non_exhaustive]
605pub enum XmlEncError {
606 #[error("XML Encryption policy violation: {0}")]
608 Policy(#[from] crate::policy::PolicyViolation),
609
610 #[error("cryptographic provider error: {0}")]
612 Provider(#[from] crate::provider::ProviderError),
613
614 #[error("XML parsing error: {0}")]
616 XmlParse(#[from] crate::xml::dom::ParseError),
617 #[error("XML document error: {0}")]
619 Document(#[from] crate::document::XmlDocumentError),
620 #[error("missing required {0}")]
622 MissingRequired(&'static str),
623 #[error("invalid encrypted structure: {0}")]
625 InvalidStructure(String),
626 #[error("invalid XML Encryption operation plan: {0}")]
628 OperationPlan(String),
629 #[error("selected node ID is missing or ambiguous: {id}")]
631 SelectedNodeUnavailable {
632 id: String,
634 },
635 #[error("unsupported encryption algorithm: {0}")]
637 UnsupportedAlgorithm(String),
638 #[error("invalid base64 data: {0}")]
640 Base64(String),
641 #[error("{algorithm} ciphertext is too short: need at least {minimum} bytes, got {actual}")]
643 DataTooShort {
644 algorithm: &'static str,
646 minimum: usize,
648 actual: usize,
650 },
651 #[error("AES-CBC ciphertext length must be a non-zero multiple of 16 bytes, got {0}")]
653 InvalidCbcCiphertextLength(usize),
654 #[error("invalid XMLEnc padding")]
659 InvalidPadding,
660 #[error("AES-GCM authentication failed")]
662 AeadAuthenticationFailed,
663 #[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
665 InvalidKeySize {
666 algorithm: DataEncryptionAlgorithm,
668 expected: usize,
670 actual: usize,
672 },
673 #[error(
675 "{algorithm:?} cannot safely select among {actual} unordered decryption key candidates"
676 )]
677 AmbiguousKeyCandidates {
678 algorithm: DataEncryptionAlgorithm,
680 actual: usize,
682 },
683 #[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
685 InvalidKekSize {
686 algorithm: KeyWrapAlgorithm,
688 expected: usize,
690 actual: usize,
692 },
693 #[error("wrapped-key value must be {expected} bytes, got {actual}")]
695 InvalidWrappedKeyLength {
696 expected: usize,
698 actual: usize,
700 },
701 #[error("invalid encryption configuration: {0}")]
703 InvalidEncryptionConfig(String),
704 #[error("no suitable decryption key was resolved")]
706 KeyNotFound,
707 #[error("no matching EncryptedData element was found")]
709 EncryptedDataNotFound,
710 #[error("more than one EncryptedData element matched; select one by Id")]
712 AmbiguousEncryptedData,
713 #[error("no matching element was found for encryption")]
715 EncryptionTargetNotFound,
716 #[error("more than one element matched the encryption target")]
718 AmbiguousEncryptionTarget,
719 #[error("EncryptedData must declare Element or Content Type for document replacement")]
721 ReplacementRequiresXml,
722 #[error("RSA-OAEP key unwrap failed: {0}")]
724 Rsa(String),
725 #[error("RSA-OAEP key wrap failed: {0}")]
727 RsaEncrypt(String),
728 #[error("AES key unwrap failed integrity validation")]
730 KeyWrapIntegrity,
731 #[error("operating-system random number generation failed: {0}")]
733 Rng(String),
734 #[error("XML encryption serialization failed: {0}")]
736 XmlSerialize(String),
737 #[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}