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,
168 RsaOaep11,
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
174pub enum KeyWrapAlgorithm {
175 AesKw128,
177 AesKw256,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
183pub enum OaepDigestAlgorithm {
184 Sha1,
186 Sha256,
188 Sha384,
190 Sha512,
192}
193
194impl OaepDigestAlgorithm {
195 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct RsaOaepParameters {
281 pub algorithm: KeyTransportAlgorithm,
283 pub digest: OaepDigestAlgorithm,
285 pub mgf_digest: OaepDigestAlgorithm,
287 pub label: Vec<u8>,
289}
290
291impl RsaOaepParameters {
292 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 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 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#[derive(Clone)]
327pub enum EncryptionRecipient {
328 RsaOaep {
330 public_key: Arc<dyn crate::provider::KeyTransportKey>,
332 parameters: RsaOaepParameters,
334 recipient: Option<String>,
336 key_name: Option<String>,
338 },
339 AesKeyWrap {
341 kek: Vec<u8>,
343 algorithm: KeyWrapAlgorithm,
345 recipient: Option<String>,
347 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 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 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 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450pub enum ReplacementMode {
451 ReplaceElement,
453 ReplaceContent,
455}
456
457#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct EncryptionResult {
460 pub encrypted_data_xml: String,
462 pub replacement: ReplacementMode,
464}
465
466#[derive(Debug, Clone, Copy, Default)]
468pub struct DocumentEncryptionOptions<'a> {
469 pub element_id: Option<&'a str>,
471}
472
473#[derive(Debug, Clone, PartialEq, Eq)]
475pub struct EncryptionMethod {
476 pub algorithm: String,
478 pub key_size_bits: Option<usize>,
480 pub oaep_digest: Option<String>,
482 pub mgf_algorithm: Option<String>,
484 pub oaep_params: Option<Vec<u8>>,
486}
487
488impl EncryptionMethod {
489 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#[derive(Debug, Clone, PartialEq, Eq)]
539pub struct CipherData {
540 pub value: String,
542}
543
544#[derive(Debug, Clone, PartialEq, Eq)]
546pub struct EncryptedKey {
547 pub id: Option<String>,
549 pub recipient: Option<String>,
551 pub key_name: Option<String>,
553 pub encryption_method: EncryptionMethod,
555 pub cipher_data: CipherData,
557 pub reference_list: Option<ReferenceList>,
559 pub carried_key_name: Option<String>,
561}
562
563#[derive(Debug, Clone, PartialEq, Eq)]
565pub struct ReferenceList {
566 pub data_references: Vec<String>,
568 pub key_references: Vec<String>,
570}
571
572#[derive(Debug, Clone, PartialEq, Eq)]
574pub struct EncryptedData {
575 pub id: Option<String>,
577 pub encrypted_type: Option<EncryptedDataType>,
579 pub key_name: Option<String>,
581 pub encryption_method: EncryptionMethod,
583 pub encrypted_keys: Vec<EncryptedKey>,
585 pub cipher_data: CipherData,
587}
588
589#[derive(Debug, Clone, PartialEq, Eq)]
591pub enum DecryptedContent {
592 Xml(String),
594 Bytes(Vec<u8>),
596}
597
598#[derive(Debug, thiserror::Error)]
600#[non_exhaustive]
601pub enum XmlEncError {
602 #[error("XML Encryption policy violation: {0}")]
604 Policy(#[from] crate::policy::PolicyViolation),
605
606 #[error("cryptographic provider error: {0}")]
608 Provider(#[from] crate::provider::ProviderError),
609
610 #[error("XML parsing error: {0}")]
612 XmlParse(#[from] roxmltree::Error),
613 #[error("XML document error: {0}")]
615 Document(#[from] crate::document::XmlDocumentError),
616 #[error("missing required {0}")]
618 MissingRequired(&'static str),
619 #[error("invalid encrypted structure: {0}")]
621 InvalidStructure(String),
622 #[error("selected node ID is missing or ambiguous: {id}")]
624 SelectedNodeUnavailable {
625 id: String,
627 },
628 #[error("unsupported encryption algorithm: {0}")]
630 UnsupportedAlgorithm(String),
631 #[error("invalid base64 data: {0}")]
633 Base64(String),
634 #[error("{algorithm} ciphertext is too short: need at least {minimum} bytes, got {actual}")]
636 DataTooShort {
637 algorithm: &'static str,
639 minimum: usize,
641 actual: usize,
643 },
644 #[error("AES-CBC ciphertext length must be a non-zero multiple of 16 bytes, got {0}")]
646 InvalidCbcCiphertextLength(usize),
647 #[error("invalid XMLEnc padding")]
652 InvalidPadding,
653 #[error("AES-GCM authentication failed")]
655 AeadAuthenticationFailed,
656 #[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
658 InvalidKeySize {
659 algorithm: DataEncryptionAlgorithm,
661 expected: usize,
663 actual: usize,
665 },
666 #[error(
668 "{algorithm:?} cannot safely select among {actual} unordered decryption key candidates"
669 )]
670 AmbiguousKeyCandidates {
671 algorithm: DataEncryptionAlgorithm,
673 actual: usize,
675 },
676 #[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
678 InvalidKekSize {
679 algorithm: KeyWrapAlgorithm,
681 expected: usize,
683 actual: usize,
685 },
686 #[error("wrapped-key value must be {expected} bytes, got {actual}")]
688 InvalidWrappedKeyLength {
689 expected: usize,
691 actual: usize,
693 },
694 #[error("invalid encryption configuration: {0}")]
696 InvalidEncryptionConfig(String),
697 #[error("no suitable decryption key was resolved")]
699 KeyNotFound,
700 #[error("no matching EncryptedData element was found")]
702 EncryptedDataNotFound,
703 #[error("more than one EncryptedData element matched; select one by Id")]
705 AmbiguousEncryptedData,
706 #[error("no matching element was found for encryption")]
708 EncryptionTargetNotFound,
709 #[error("more than one element matched the encryption target")]
711 AmbiguousEncryptionTarget,
712 #[error("EncryptedData must declare Element or Content Type for document replacement")]
714 ReplacementRequiresXml,
715 #[error("RSA-OAEP key unwrap failed: {0}")]
717 Rsa(String),
718 #[error("RSA-OAEP key wrap failed: {0}")]
720 RsaEncrypt(String),
721 #[error("AES key unwrap failed integrity validation")]
723 KeyWrapIntegrity,
724 #[error("operating-system random number generation failed: {0}")]
726 Rng(String),
727 #[error("XML encryption serialization failed: {0}")]
729 XmlSerialize(String),
730 #[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}