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 #[cfg(feature = "legacy-algorithms")]
33 TripleDesCbc,
34 #[cfg(feature = "legacy-algorithms")]
36 Aes192Cbc,
37 #[cfg(feature = "legacy-algorithms")]
39 Aes192Gcm,
40 Aes128Cbc,
42 Aes256Cbc,
44 Aes128Gcm,
46 Aes256Gcm,
48 Camellia128Cbc,
50 Camellia192Cbc,
52 Camellia256Cbc,
54 ChaCha20,
56 ChaCha20Poly1305,
58}
59
60impl DataEncryptionAlgorithm {
61 pub const fn is_authenticated(self) -> bool {
63 match self {
66 Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305 => true,
67 #[cfg(feature = "legacy-algorithms")]
68 Self::Aes192Gcm => true,
69 Self::Aes128Cbc
70 | Self::Aes256Cbc
71 | Self::Camellia128Cbc
72 | Self::Camellia192Cbc
73 | Self::Camellia256Cbc
74 | Self::ChaCha20 => false,
75 #[cfg(feature = "legacy-algorithms")]
76 Self::TripleDesCbc | Self::Aes192Cbc => false,
77 }
78 }
79
80 pub const fn key_kind(self) -> crate::key_manager::SymmetricKeyKind {
83 if matches!(self, Self::ChaCha20 | Self::ChaCha20Poly1305) {
84 return crate::key_manager::SymmetricKeyKind::ChaCha20;
85 }
86 if matches!(
87 self,
88 Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc
89 ) {
90 return crate::key_manager::SymmetricKeyKind::Camellia;
91 }
92 #[cfg(feature = "legacy-algorithms")]
93 if matches!(self, Self::TripleDesCbc) {
94 return crate::key_manager::SymmetricKeyKind::Des;
95 }
96 crate::key_manager::SymmetricKeyKind::Aes
97 }
98 pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
100 match uri {
101 #[cfg(feature = "legacy-algorithms")]
102 "http://www.w3.org/2001/04/xmlenc#tripledes-cbc" => Ok(Self::TripleDesCbc),
103 #[cfg(feature = "legacy-algorithms")]
104 "http://www.w3.org/2001/04/xmlenc#aes192-cbc" => Ok(Self::Aes192Cbc),
105 #[cfg(feature = "legacy-algorithms")]
106 "http://www.w3.org/2009/xmlenc11#aes192-gcm" => Ok(Self::Aes192Gcm),
107 "http://www.w3.org/2001/04/xmlenc#aes128-cbc" => Ok(Self::Aes128Cbc),
108 "http://www.w3.org/2001/04/xmlenc#aes256-cbc" => Ok(Self::Aes256Cbc),
109 "http://www.w3.org/2009/xmlenc11#aes128-gcm" => Ok(Self::Aes128Gcm),
110 "http://www.w3.org/2009/xmlenc11#aes256-gcm" => Ok(Self::Aes256Gcm),
111 "http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc" => Ok(Self::Camellia128Cbc),
112 "http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc" => Ok(Self::Camellia192Cbc),
113 "http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc" => Ok(Self::Camellia256Cbc),
114 "http://www.w3.org/2021/04/xmldsig-more#chacha20" => Ok(Self::ChaCha20),
115 "http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305" => Ok(Self::ChaCha20Poly1305),
116 _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
117 }
118 }
119
120 pub const fn key_len(self) -> usize {
122 match self {
123 #[cfg(feature = "legacy-algorithms")]
124 Self::TripleDesCbc | Self::Aes192Cbc | Self::Aes192Gcm => 24,
125 Self::Aes128Cbc | Self::Aes128Gcm => 16,
126 Self::Aes256Cbc | Self::Aes256Gcm => 32,
127 Self::Camellia128Cbc => 16,
128 Self::Camellia192Cbc => 24,
129 Self::Camellia256Cbc => 32,
130 Self::ChaCha20 | Self::ChaCha20Poly1305 => 32,
131 }
132 }
133
134 pub const fn uri(self) -> &'static str {
136 match self {
137 #[cfg(feature = "legacy-algorithms")]
138 Self::TripleDesCbc => "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
139 #[cfg(feature = "legacy-algorithms")]
140 Self::Aes192Cbc => "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
141 #[cfg(feature = "legacy-algorithms")]
142 Self::Aes192Gcm => "http://www.w3.org/2009/xmlenc11#aes192-gcm",
143 Self::Aes128Cbc => "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
144 Self::Aes256Cbc => "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
145 Self::Aes128Gcm => "http://www.w3.org/2009/xmlenc11#aes128-gcm",
146 Self::Aes256Gcm => "http://www.w3.org/2009/xmlenc11#aes256-gcm",
147 Self::Camellia128Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc",
148 Self::Camellia192Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc",
149 Self::Camellia256Cbc => "http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc",
150 Self::ChaCha20 => "http://www.w3.org/2021/04/xmldsig-more#chacha20",
151 Self::ChaCha20Poly1305 => "http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305",
152 }
153 }
154
155 pub(crate) const fn minimum_ciphertext_len(self) -> usize {
157 if matches!(self, Self::ChaCha20) {
158 return 0;
159 }
160 if matches!(self, Self::ChaCha20Poly1305) {
161 return 16;
162 }
163 match self.cbc_block_len() {
164 Some(block) => block * 2,
165 None => 28,
166 }
167 }
168
169 pub const fn requires_explicit_permission(self) -> bool {
172 match self {
173 Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc => true,
174 Self::ChaCha20 | Self::ChaCha20Poly1305 => true,
175 #[cfg(feature = "legacy-algorithms")]
176 Self::TripleDesCbc | Self::Aes192Cbc | Self::Aes192Gcm => true,
177 _ => false,
178 }
179 }
180
181 pub(crate) const fn cbc_block_len(self) -> Option<usize> {
182 match self {
186 Self::Aes128Cbc | Self::Aes256Cbc => Some(16),
187 Self::Camellia128Cbc | Self::Camellia192Cbc | Self::Camellia256Cbc => Some(16),
188 #[cfg(feature = "legacy-algorithms")]
189 Self::Aes192Cbc => Some(16),
190 #[cfg(feature = "legacy-algorithms")]
191 Self::TripleDesCbc => Some(8),
192 _ => None,
193 }
194 }
195
196 pub(crate) fn ciphertext_len_for_plaintext(self, plaintext_len: usize) -> Option<usize> {
198 if matches!(self, Self::ChaCha20) {
199 return Some(plaintext_len);
200 }
201 if matches!(self, Self::ChaCha20Poly1305) {
202 return plaintext_len.checked_add(16);
203 }
204 match self.cbc_block_len() {
205 Some(block) => (plaintext_len / block)
206 .checked_add(1)?
207 .checked_mul(block)?
208 .checked_add(block),
209 None => plaintext_len.checked_add(28),
210 }
211 }
212}
213
214pub(crate) fn validate_ciphertext_framing(
215 algorithm: DataEncryptionAlgorithm,
216 ciphertext_len: usize,
217) -> Result<(), XmlEncError> {
218 let minimum = algorithm.minimum_ciphertext_len();
219 if ciphertext_len < minimum {
220 let algorithm_name = match algorithm {
221 DataEncryptionAlgorithm::ChaCha20 => "ChaCha20",
222 DataEncryptionAlgorithm::ChaCha20Poly1305 => "ChaCha20-Poly1305",
223 DataEncryptionAlgorithm::Camellia128Cbc
224 | DataEncryptionAlgorithm::Camellia192Cbc
225 | DataEncryptionAlgorithm::Camellia256Cbc => "Camellia-CBC",
226 _ => match algorithm.cbc_block_len() {
227 Some(8) => "Triple DES CBC",
228 Some(_) => "AES-CBC",
229 None => "AES-GCM",
230 },
231 };
232 return Err(XmlEncError::DataTooShort {
233 algorithm: algorithm_name,
234 minimum,
235 actual: ciphertext_len,
236 });
237 }
238 if let Some(block) = algorithm.cbc_block_len()
239 && !(ciphertext_len - block).is_multiple_of(block)
240 {
241 return Err(XmlEncError::InvalidCbcCiphertextLength {
242 algorithm,
243 block,
244 actual: ciphertext_len - block,
245 });
246 }
247 Ok(())
248}
249
250impl KeyTransportAlgorithm {
251 pub const fn requires_explicit_permission(self) -> bool {
253 match self {
254 #[cfg(feature = "legacy-algorithms")]
255 Self::RsaPkcs1v15 => true,
256 _ => false,
257 }
258 }
259 pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
261 match uri {
262 "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" => Ok(Self::RsaOaepMgf1p),
263 "http://www.w3.org/2009/xmlenc11#rsa-oaep" => Ok(Self::RsaOaep11),
264 #[cfg(feature = "legacy-algorithms")]
265 "http://www.w3.org/2001/04/xmlenc#rsa-1_5" => Ok(Self::RsaPkcs1v15),
266 _ => Err(XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
267 }
268 }
269
270 pub const fn uri(self) -> &'static str {
272 match self {
273 Self::RsaOaepMgf1p => "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
274 Self::RsaOaep11 => "http://www.w3.org/2009/xmlenc11#rsa-oaep",
275 #[cfg(feature = "legacy-algorithms")]
276 Self::RsaPkcs1v15 => "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
277 }
278 }
279}
280
281impl KeyWrapAlgorithm {
282 pub const fn key_kind(self) -> crate::key_manager::SymmetricKeyKind {
284 if let Self::Cbc(algorithm) = self {
285 return algorithm.key_kind();
286 }
287 if matches!(
288 self,
289 Self::CamelliaKw128 | Self::CamelliaKw192 | Self::CamelliaKw256
290 ) {
291 return crate::key_manager::SymmetricKeyKind::Camellia;
292 }
293 #[cfg(feature = "legacy-algorithms")]
294 if matches!(self, Self::TripleDes) {
295 return crate::key_manager::SymmetricKeyKind::Des;
296 }
297 crate::key_manager::SymmetricKeyKind::Aes
298 }
299 pub fn from_uri(uri: &str) -> Result<Self, XmlEncError> {
301 match uri {
302 #[cfg(feature = "legacy-algorithms")]
303 "http://www.w3.org/2001/04/xmlenc#kw-aes192" => Ok(Self::AesKw192),
304 #[cfg(feature = "legacy-algorithms")]
305 "http://www.w3.org/2001/04/xmlenc#kw-tripledes" => Ok(Self::TripleDes),
306 "http://www.w3.org/2001/04/xmlenc#kw-aes128" => Ok(Self::AesKw128),
307 "http://www.w3.org/2001/04/xmlenc#kw-aes256" => Ok(Self::AesKw256),
308 "http://www.w3.org/2001/04/xmldsig-more#kw-camellia128" => Ok(Self::CamelliaKw128),
309 "http://www.w3.org/2001/04/xmldsig-more#kw-camellia192" => Ok(Self::CamelliaKw192),
310 "http://www.w3.org/2001/04/xmldsig-more#kw-camellia256" => Ok(Self::CamelliaKw256),
311 _ => DataEncryptionAlgorithm::from_uri(uri)
312 .ok()
313 .filter(|algorithm| algorithm.cbc_block_len().is_some())
314 .map(Self::Cbc)
315 .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned())),
316 }
317 }
318
319 pub const fn key_len(self) -> usize {
321 match self {
322 #[cfg(feature = "legacy-algorithms")]
323 Self::AesKw192 | Self::TripleDes => 24,
324 Self::AesKw128 => 16,
325 Self::AesKw256 => 32,
326 Self::CamelliaKw128 => 16,
327 Self::CamelliaKw192 => 24,
328 Self::CamelliaKw256 => 32,
329 Self::Cbc(algorithm) => algorithm.key_len(),
330 }
331 }
332
333 pub const fn uri(self) -> &'static str {
335 match self {
336 #[cfg(feature = "legacy-algorithms")]
337 Self::AesKw192 => "http://www.w3.org/2001/04/xmlenc#kw-aes192",
338 #[cfg(feature = "legacy-algorithms")]
339 Self::TripleDes => "http://www.w3.org/2001/04/xmlenc#kw-tripledes",
340 Self::AesKw128 => "http://www.w3.org/2001/04/xmlenc#kw-aes128",
341 Self::AesKw256 => "http://www.w3.org/2001/04/xmlenc#kw-aes256",
342 Self::CamelliaKw128 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia128",
343 Self::CamelliaKw192 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia192",
344 Self::CamelliaKw256 => "http://www.w3.org/2001/04/xmldsig-more#kw-camellia256",
345 Self::Cbc(algorithm) => algorithm.uri(),
346 }
347 }
348
349 pub const fn requires_explicit_permission(self) -> bool {
351 match self {
352 Self::CamelliaKw128 | Self::CamelliaKw192 | Self::CamelliaKw256 => true,
353 Self::Cbc(_) => true,
354 #[cfg(feature = "legacy-algorithms")]
355 Self::AesKw192 | Self::TripleDes => true,
356 _ => false,
357 }
358 }
359
360 pub(crate) fn wrapped_len(self, plaintext_len: usize) -> Option<usize> {
361 let overhead = match self {
365 Self::Cbc(algorithm) => {
366 return if algorithm.cbc_block_len().is_some() {
367 algorithm.ciphertext_len_for_plaintext(plaintext_len)
368 } else {
369 None
370 };
371 }
372 #[cfg(feature = "legacy-algorithms")]
373 Self::TripleDes => 16,
374 _ => 8,
375 };
376 plaintext_len.checked_add(overhead)
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
382pub enum KeyTransportAlgorithm {
383 #[cfg(feature = "legacy-algorithms")]
385 RsaPkcs1v15,
386 RsaOaepMgf1p,
391 RsaOaep11,
393}
394
395#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
397pub enum KeyWrapAlgorithm {
398 Cbc(DataEncryptionAlgorithm),
402 #[cfg(feature = "legacy-algorithms")]
404 AesKw192,
405 #[cfg(feature = "legacy-algorithms")]
407 TripleDes,
408 AesKw128,
410 AesKw256,
412 CamelliaKw128,
414 CamelliaKw192,
416 CamelliaKw256,
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
422pub enum OaepDigestAlgorithm {
423 #[cfg(feature = "legacy-algorithms")]
425 Md5,
426 #[cfg(feature = "legacy-algorithms")]
428 Ripemd160,
429 Sha1,
431 Sha224,
433 Sha256,
435 Sha384,
437 Sha512,
439 Sha3_224,
441 Sha3_256,
443 Sha3_384,
445 Sha3_512,
447}
448
449impl OaepDigestAlgorithm {
450 pub fn from_uri(uri: &str) -> Option<Self> {
452 match uri {
453 #[cfg(feature = "legacy-algorithms")]
454 "http://www.w3.org/2001/04/xmldsig-more#md5" => Some(Self::Md5),
455 #[cfg(feature = "legacy-algorithms")]
456 "http://www.w3.org/2001/04/xmlenc#ripemd160" => Some(Self::Ripemd160),
457 "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
458 "http://www.w3.org/2001/04/xmldsig-more#sha224" => Some(Self::Sha224),
459 "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
460 "http://www.w3.org/2001/04/xmlenc#sha384"
461 | "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
462 "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
463 "http://www.w3.org/2007/05/xmldsig-more#sha3-224" => Some(Self::Sha3_224),
464 "http://www.w3.org/2007/05/xmldsig-more#sha3-256" => Some(Self::Sha3_256),
465 "http://www.w3.org/2007/05/xmldsig-more#sha3-384" => Some(Self::Sha3_384),
466 "http://www.w3.org/2007/05/xmldsig-more#sha3-512" => Some(Self::Sha3_512),
467 _ => None,
468 }
469 }
470
471 pub fn from_mgf_uri(uri: &str) -> Option<Self> {
473 match uri {
474 "http://www.w3.org/2009/xmlenc11#mgf1sha1" => Some(Self::Sha1),
475 "http://www.w3.org/2009/xmlenc11#mgf1sha224" => Some(Self::Sha224),
476 "http://www.w3.org/2009/xmlenc11#mgf1sha256" => Some(Self::Sha256),
477 "http://www.w3.org/2009/xmlenc11#mgf1sha384" => Some(Self::Sha384),
478 "http://www.w3.org/2009/xmlenc11#mgf1sha512" => Some(Self::Sha512),
479 _ => None,
480 }
481 }
482
483 pub const fn uri(self) -> &'static str {
485 match self {
486 #[cfg(feature = "legacy-algorithms")]
487 Self::Md5 => "http://www.w3.org/2001/04/xmldsig-more#md5",
488 #[cfg(feature = "legacy-algorithms")]
489 Self::Ripemd160 => "http://www.w3.org/2001/04/xmlenc#ripemd160",
490 Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
491 Self::Sha224 => "http://www.w3.org/2001/04/xmldsig-more#sha224",
492 Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
493 Self::Sha384 => "http://www.w3.org/2001/04/xmlenc#sha384",
494 Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
495 Self::Sha3_224 => "http://www.w3.org/2007/05/xmldsig-more#sha3-224",
496 Self::Sha3_256 => "http://www.w3.org/2007/05/xmldsig-more#sha3-256",
497 Self::Sha3_384 => "http://www.w3.org/2007/05/xmldsig-more#sha3-384",
498 Self::Sha3_512 => "http://www.w3.org/2007/05/xmldsig-more#sha3-512",
499 }
500 }
501
502 pub const fn mgf_uri(self) -> Option<&'static str> {
505 match self {
509 Self::Sha1 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
510 Self::Sha224 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha224"),
511 Self::Sha256 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha256"),
512 Self::Sha384 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha384"),
513 Self::Sha512 => Some("http://www.w3.org/2009/xmlenc11#mgf1sha512"),
514 _ => None,
515 }
516 }
517
518 pub const fn requires_explicit_permission(self) -> bool {
520 match self {
521 #[cfg(feature = "legacy-algorithms")]
522 Self::Md5 | Self::Ripemd160 => true,
523 _ => false,
524 }
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use super::{OaepDigestAlgorithm, XmlEncError};
531
532 #[test]
533 fn operation_plan_failures_have_a_distinct_error_class() {
534 let error = XmlEncError::from(crate::operation::OperationPlanError::StaleResourceIdentity);
535 assert!(matches!(error, XmlEncError::OperationPlan(_)));
536 }
537
538 #[test]
539 fn oaep_sha384_accepts_both_interoperable_digest_uris() {
540 for uri in [
543 "http://www.w3.org/2001/04/xmlenc#sha384",
544 "http://www.w3.org/2001/04/xmldsig-more#sha384",
545 ] {
546 assert_eq!(
547 OaepDigestAlgorithm::from_uri(uri),
548 Some(OaepDigestAlgorithm::Sha384)
549 );
550 }
551 }
552
553 #[test]
554 fn oaep_mgf_uris_round_trip() {
555 for algorithm in [
556 OaepDigestAlgorithm::Sha1,
557 OaepDigestAlgorithm::Sha224,
558 OaepDigestAlgorithm::Sha256,
559 OaepDigestAlgorithm::Sha384,
560 OaepDigestAlgorithm::Sha512,
561 ] {
562 assert_eq!(
563 OaepDigestAlgorithm::from_mgf_uri(
564 algorithm.mgf_uri().expect("standard MGF1 digest URI")
565 ),
566 Some(algorithm)
567 );
568 }
569 assert_eq!(
570 OaepDigestAlgorithm::from_mgf_uri("urn:unsupported-mgf"),
571 None
572 );
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Eq)]
578pub struct RsaOaepParameters {
579 pub algorithm: KeyTransportAlgorithm,
581 pub digest: OaepDigestAlgorithm,
583 pub mgf_digest: OaepDigestAlgorithm,
585 pub label: Vec<u8>,
587}
588
589impl RsaOaepParameters {
590 pub fn legacy() -> Self {
592 Self {
593 algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
594 digest: OaepDigestAlgorithm::Sha1,
595 mgf_digest: OaepDigestAlgorithm::Sha1,
596 label: Vec::new(),
597 }
598 }
599
600 pub fn xmlenc11(digest: OaepDigestAlgorithm, mgf_digest: OaepDigestAlgorithm) -> Self {
602 Self {
603 algorithm: KeyTransportAlgorithm::RsaOaep11,
604 digest,
605 mgf_digest,
606 label: Vec::new(),
607 }
608 }
609
610 pub fn label(mut self, label: impl Into<Vec<u8>>) -> Self {
612 self.label = label.into();
613 self
614 }
615}
616
617impl Default for RsaOaepParameters {
618 fn default() -> Self {
619 Self::xmlenc11(OaepDigestAlgorithm::Sha256, OaepDigestAlgorithm::Sha256)
620 }
621}
622
623#[derive(Clone)]
625pub enum EncryptionRecipient {
626 #[cfg(feature = "legacy-algorithms")]
628 RsaPkcs1v15 {
629 public_key: Arc<dyn crate::provider::KeyTransportKey>,
631 recipient: Option<String>,
633 key_name: Option<String>,
635 },
636 RsaOaep {
638 public_key: Arc<dyn crate::provider::KeyTransportKey>,
640 parameters: RsaOaepParameters,
642 recipient: Option<String>,
644 key_name: Option<String>,
646 },
647 AesKeyWrap {
649 kek: Vec<u8>,
651 algorithm: KeyWrapAlgorithm,
653 recipient: Option<String>,
655 key_name: Option<String>,
657 },
658}
659
660impl zeroize::Zeroize for EncryptionRecipient {
661 fn zeroize(&mut self) {
662 if let Self::AesKeyWrap { kek, .. } = self {
663 kek.zeroize();
664 }
665 }
666}
667
668impl zeroize::ZeroizeOnDrop for EncryptionRecipient {}
669
670impl Drop for EncryptionRecipient {
671 fn drop(&mut self) {
672 zeroize::Zeroize::zeroize(self);
673 }
674}
675
676impl fmt::Debug for EncryptionRecipient {
677 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
678 match self {
679 #[cfg(feature = "legacy-algorithms")]
680 Self::RsaPkcs1v15 {
681 recipient,
682 key_name,
683 ..
684 } => formatter
685 .debug_struct("EncryptionRecipient::RsaPkcs1v15")
686 .field("public_key", &"[PUBLIC KEY]")
687 .field("recipient", recipient)
688 .field("key_name", key_name)
689 .finish(),
690 Self::RsaOaep {
691 parameters,
692 recipient,
693 key_name,
694 ..
695 } => formatter
696 .debug_struct("EncryptionRecipient::RsaOaep")
697 .field("public_key", &"[PUBLIC KEY]")
698 .field("parameters", parameters)
699 .field("recipient", recipient)
700 .field("key_name", key_name)
701 .finish(),
702 Self::AesKeyWrap {
703 algorithm,
704 recipient,
705 key_name,
706 ..
707 } => formatter
708 .debug_struct("EncryptionRecipient::AesKeyWrap")
709 .field("kek", &"[REDACTED]")
710 .field("algorithm", algorithm)
711 .field("recipient", recipient)
712 .field("key_name", key_name)
713 .finish(),
714 }
715 }
716}
717
718impl EncryptionRecipient {
719 #[cfg(feature = "legacy-algorithms")]
721 pub fn rsa_pkcs1v15(public_key: RsaPublicKey) -> Self {
722 Self::provider_pkcs1v15(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
723 public_key,
724 )))
725 }
726
727 #[cfg(feature = "legacy-algorithms")]
729 pub fn provider_pkcs1v15(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
730 Self::RsaPkcs1v15 {
731 public_key,
732 recipient: None,
733 key_name: None,
734 }
735 }
736 pub fn rsa_oaep(public_key: RsaPublicKey) -> Self {
742 Self::provider_key_transport(Arc::new(crate::provider::RustCryptoRsaPublicKey::new(
743 public_key,
744 )))
745 }
746
747 pub fn provider_key_transport(public_key: Arc<dyn crate::provider::KeyTransportKey>) -> Self {
749 Self::RsaOaep {
750 public_key,
751 parameters: RsaOaepParameters::default(),
752 recipient: None,
753 key_name: None,
754 }
755 }
756
757 pub fn aes_key_wrap(kek: impl Into<Vec<u8>>, algorithm: KeyWrapAlgorithm) -> Self {
759 Self::AesKeyWrap {
760 kek: kek.into(),
761 algorithm,
762 recipient: None,
763 key_name: None,
764 }
765 }
766
767 pub fn oaep_parameters(mut self, parameters: RsaOaepParameters) -> Self {
769 if let Self::RsaOaep {
770 parameters: current,
771 ..
772 } = &mut self
773 {
774 *current = parameters;
775 }
776 self
777 }
778
779 pub fn recipient(mut self, value: impl Into<String>) -> Self {
781 match &mut self {
782 #[cfg(feature = "legacy-algorithms")]
783 Self::RsaPkcs1v15 { recipient, .. } => *recipient = Some(value.into()),
784 Self::RsaOaep { recipient, .. } | Self::AesKeyWrap { recipient, .. } => {
785 *recipient = Some(value.into());
786 }
787 }
788 self
789 }
790
791 pub fn key_name(mut self, value: impl Into<String>) -> Self {
793 match &mut self {
794 #[cfg(feature = "legacy-algorithms")]
795 Self::RsaPkcs1v15 { key_name, .. } => *key_name = Some(value.into()),
796 Self::RsaOaep { key_name, .. } | Self::AesKeyWrap { key_name, .. } => {
797 *key_name = Some(value.into());
798 }
799 }
800 self
801 }
802}
803
804#[derive(Debug, Clone, Copy, PartialEq, Eq)]
806pub enum ReplacementMode {
807 ReplaceElement,
809 ReplaceContent,
811}
812
813#[derive(Debug, Clone, PartialEq, Eq)]
815pub struct EncryptionResult {
816 pub encrypted_data_xml: String,
818 pub replacement: ReplacementMode,
820}
821
822#[derive(Debug, Clone, Copy, Default)]
824pub struct DocumentEncryptionOptions<'a> {
825 pub element_id: Option<&'a str>,
827}
828
829#[derive(Debug, Clone, PartialEq, Eq)]
831pub struct EncryptionMethod {
832 pub algorithm: String,
834 pub key_size_bits: Option<usize>,
836 pub oaep_digest: Option<String>,
838 pub mgf_algorithm: Option<String>,
840 pub oaep_params: Option<Vec<u8>>,
842 pub chacha: Option<ChaChaParameters>,
844}
845
846#[derive(Debug, Clone, Default, PartialEq, Eq)]
850pub struct ChaChaParameters {
851 pub nonce: Option<[u8; 12]>,
853 pub counter: Option<[u8; 4]>,
855 pub aad: Option<String>,
857}
858
859impl ChaChaParameters {
860 pub fn borrowed(&self) -> ChaChaParametersRef<'_> {
863 ChaChaParametersRef {
864 nonce: self.nonce.as_ref(),
865 counter: self.counter,
866 aad: self.aad.as_deref(),
867 }
868 }
869 pub fn validate(&self, algorithm: DataEncryptionAlgorithm) -> Result<(), XmlEncError> {
871 self.borrowed().validate(algorithm)
872 }
873}
874
875#[derive(Debug, Clone, Copy, Default)]
877pub struct ChaChaParametersRef<'a> {
878 pub nonce: Option<&'a [u8; 12]>,
880 pub counter: Option<[u8; 4]>,
882 pub aad: Option<&'a str>,
884}
885
886impl ChaChaParametersRef<'_> {
887 pub fn validate(self, algorithm: DataEncryptionAlgorithm) -> Result<(), XmlEncError> {
889 let valid = match algorithm {
893 DataEncryptionAlgorithm::ChaCha20 => self.counter.is_some() && self.aad.is_none(),
894 DataEncryptionAlgorithm::ChaCha20Poly1305 => self.counter.is_none(),
895 _ => false,
896 };
897 if !valid {
898 return Err(XmlEncError::InvalidStructure(
899 "invalid ChaCha EncryptionMethod parameters".into(),
900 ));
901 }
902 Ok(())
903 }
904}
905
906impl EncryptionMethod {
907 pub(crate) fn validate_structure(&self) -> Result<(), XmlEncError> {
912 if let Some(parameters) = &self.chacha {
913 parameters.validate(DataEncryptionAlgorithm::from_uri(&self.algorithm)?)?;
914 } else if self.algorithm == DataEncryptionAlgorithm::ChaCha20.uri() {
915 return Err(XmlEncError::MissingRequired("ChaCha20 Counter"));
916 }
917 if self.key_size_bits == Some(0) {
918 return Err(XmlEncError::InvalidStructure(
919 "KeySize must be a positive integer".into(),
920 ));
921 }
922 let is_legacy_oaep = self.algorithm == KeyTransportAlgorithm::RsaOaepMgf1p.uri();
923 let is_oaep11 = self.algorithm == KeyTransportAlgorithm::RsaOaep11.uri();
924 if (self.oaep_params.is_some()
925 || self.oaep_digest.is_some()
926 || self.mgf_algorithm.is_some())
927 && !is_legacy_oaep
928 && !is_oaep11
929 {
930 return Err(XmlEncError::InvalidStructure(
931 "OAEP parameters are only valid for RSA-OAEP EncryptionMethod".into(),
932 ));
933 }
934 if let (Some(actual), Some(expected)) =
935 (self.key_size_bits, fixed_aes_key_size(&self.algorithm))
936 && actual != expected
937 {
938 return Err(XmlEncError::InvalidStructure(format!(
939 "EncryptionMethod {} requires KeySize {expected}, got {actual}",
940 self.algorithm
941 )));
942 }
943 Ok(())
944 }
945}
946
947fn fixed_aes_key_size(algorithm: &str) -> Option<usize> {
948 let key_len = DataEncryptionAlgorithm::from_uri(algorithm)
949 .map(DataEncryptionAlgorithm::key_len)
950 .or_else(|_| KeyWrapAlgorithm::from_uri(algorithm).map(KeyWrapAlgorithm::key_len))
951 .ok()?;
952 Some(key_len * 8)
953}
954
955#[derive(Debug, Clone, PartialEq, Eq)]
957pub enum CipherData {
958 Value {
960 value: String,
962 },
963 Reference {
965 uri: String,
967 transforms: Vec<crate::xmldsig::transforms::Transform>,
969 },
970 Bytes(Vec<u8>),
972}
973
974impl CipherData {
975 pub(super) fn octets(&self) -> Result<std::borrow::Cow<'_, [u8]>, XmlEncError> {
976 use base64::Engine as _;
977 match self {
978 Self::Value { value } => base64::engine::general_purpose::STANDARD
979 .decode(value)
980 .map(std::borrow::Cow::Owned)
981 .map_err(|error| XmlEncError::Base64(error.to_string())),
982 Self::Bytes(bytes) => Ok(std::borrow::Cow::Borrowed(bytes)),
983 Self::Reference { .. } => Err(XmlEncError::InvalidStructure(
984 "CipherReference requires its original document context".into(),
985 )),
986 }
987 }
988
989 pub fn inline_value(&self) -> Option<&str> {
991 match self {
992 Self::Value { value } => Some(value),
993 Self::Reference { .. } | Self::Bytes(_) => None,
994 }
995 }
996
997 pub fn inline_value_mut(&mut self) -> Option<&mut String> {
999 match self {
1000 Self::Value { value } => Some(value),
1001 Self::Reference { .. } | Self::Bytes(_) => None,
1002 }
1003 }
1004}
1005
1006#[derive(Debug, Clone, PartialEq, Eq)]
1008pub struct EncryptedKey {
1009 pub sources: EncryptionKeySources,
1012 pub id: Option<String>,
1014 pub recipient: Option<String>,
1016 pub key_name: Option<String>,
1018 pub encryption_method: EncryptionMethod,
1020 pub cipher_data: CipherData,
1022 pub reference_list: Option<ReferenceList>,
1024 pub carried_key_name: Option<String>,
1026}
1027
1028#[derive(Debug, Clone, PartialEq, Eq, Default)]
1031pub struct EncryptionKeySources {
1032 pub encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
1034 pub encrypted_keys: Vec<EncryptedKey>,
1036 pub derived_keys: Vec<super::DerivedKey>,
1038 pub agreement_methods: Vec<super::AgreementMethod>,
1040}
1041
1042impl EncryptionKeySources {
1043 pub(super) fn is_empty(&self) -> bool {
1044 self.encrypted_keys.is_empty()
1045 && self.encapsulation_methods.is_empty()
1046 && self.derived_keys.is_empty()
1047 && self.agreement_methods.is_empty()
1048 }
1049}
1050
1051#[derive(Debug, Clone, PartialEq, Eq)]
1053pub struct ReferenceList {
1054 pub data_references: Vec<String>,
1056 pub key_references: Vec<String>,
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq)]
1062pub struct EncryptedData {
1063 pub encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
1065 pub id: Option<String>,
1067 pub encrypted_type: Option<EncryptedDataType>,
1069 pub key_name: Option<String>,
1071 pub encryption_method: EncryptionMethod,
1073 pub encrypted_keys: Vec<EncryptedKey>,
1075 pub derived_keys: Vec<super::DerivedKey>,
1077 pub agreement_methods: Vec<super::AgreementMethod>,
1079 pub cipher_data: CipherData,
1081}
1082
1083#[derive(Debug, Clone, PartialEq, Eq)]
1085pub enum DecryptedContent {
1086 Xml(String),
1088 Bytes(Vec<u8>),
1090}
1091
1092#[derive(Debug, thiserror::Error)]
1094#[non_exhaustive]
1095pub enum XmlEncError {
1096 #[error(transparent)]
1098 KeyEstablishment(#[from] crate::key_establishment::KeyEstablishmentError),
1099 #[error("ciphertext reference error: {0}")]
1101 Transform(#[from] crate::xmldsig::TransformError),
1102 #[error("XML Encryption policy violation: {0}")]
1104 Policy(#[from] crate::policy::PolicyViolation),
1105
1106 #[error("cryptographic provider error: {0}")]
1108 Provider(#[from] crate::provider::ProviderError),
1109
1110 #[error("XML parsing error: {0}")]
1112 XmlParse(#[from] crate::xml::dom::ParseError),
1113 #[error("XML document error: {0}")]
1115 Document(#[from] crate::document::XmlDocumentError),
1116 #[error("missing required {0}")]
1118 MissingRequired(&'static str),
1119 #[error("invalid encrypted structure: {0}")]
1121 InvalidStructure(String),
1122 #[error("invalid XML Encryption operation plan: {0}")]
1124 OperationPlan(String),
1125 #[error("selected node ID is missing or ambiguous: {id}")]
1127 SelectedNodeUnavailable {
1128 id: String,
1130 },
1131 #[error("unsupported encryption algorithm: {0}")]
1133 UnsupportedAlgorithm(String),
1134 #[error("invalid base64 data: {0}")]
1136 Base64(String),
1137 #[error("{algorithm} ciphertext is too short: need at least {minimum} bytes, got {actual}")]
1139 DataTooShort {
1140 algorithm: &'static str,
1142 minimum: usize,
1144 actual: usize,
1146 },
1147 #[error(
1149 "{algorithm} ciphertext length must be a non-zero multiple of {block} bytes, got {actual}"
1150 )]
1151 InvalidCbcCiphertextLength {
1152 algorithm: DataEncryptionAlgorithm,
1154 block: usize,
1156 actual: usize,
1158 },
1159 #[error("invalid XMLEnc padding")]
1164 InvalidPadding,
1165 #[error("AEAD authentication failed")]
1167 AeadAuthenticationFailed,
1168 #[error("{algorithm:?} requires a {expected}-byte key, got {actual}")]
1170 InvalidKeySize {
1171 algorithm: DataEncryptionAlgorithm,
1173 expected: usize,
1175 actual: usize,
1177 },
1178 #[error(
1180 "{algorithm:?} cannot safely select among {actual} unordered decryption key candidates"
1181 )]
1182 AmbiguousKeyCandidates {
1183 algorithm: DataEncryptionAlgorithm,
1185 actual: usize,
1187 },
1188 #[error("{algorithm:?} requires a {expected}-byte KEK, got {actual}")]
1190 InvalidKekSize {
1191 algorithm: KeyWrapAlgorithm,
1193 expected: usize,
1195 actual: usize,
1197 },
1198 #[error("wrapped-key value must be {expected} bytes, got {actual}")]
1200 InvalidWrappedKeyLength {
1201 expected: usize,
1203 actual: usize,
1205 },
1206 #[error("invalid encryption configuration: {0}")]
1208 InvalidEncryptionConfig(String),
1209 #[error("no suitable decryption key was resolved")]
1211 KeyNotFound,
1212 #[error("no matching EncryptedData element was found")]
1214 EncryptedDataNotFound,
1215 #[error("more than one EncryptedData element matched; select one by Id")]
1217 AmbiguousEncryptedData,
1218 #[error("no matching element was found for encryption")]
1220 EncryptionTargetNotFound,
1221 #[error("more than one element matched the encryption target")]
1223 AmbiguousEncryptionTarget,
1224 #[error("EncryptedData must declare Element or Content Type for document replacement")]
1226 ReplacementRequiresXml,
1227 #[error("RSA-OAEP key unwrap failed: {0}")]
1229 Rsa(String),
1230 #[error("RSA-OAEP key wrap failed: {0}")]
1232 RsaEncrypt(String),
1233 #[error("AES key unwrap failed integrity validation")]
1235 KeyWrapIntegrity,
1236 #[error("operating-system random number generation failed: {0}")]
1238 Rng(String),
1239 #[error("XML encryption serialization failed: {0}")]
1241 XmlSerialize(String),
1242 #[error("decrypted XML is not valid UTF-8: {0}")]
1244 Utf8(#[from] std::string::FromUtf8Error),
1245}
1246
1247impl From<crate::operation::OperationPlanError> for XmlEncError {
1248 fn from(error: crate::operation::OperationPlanError) -> Self {
1249 Self::OperationPlan(error.to_string())
1250 }
1251}
1252
1253impl fmt::Display for DataEncryptionAlgorithm {
1254 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1255 formatter.write_str(match self {
1256 #[cfg(feature = "legacy-algorithms")]
1257 Self::TripleDesCbc => "Triple DES CBC",
1258 #[cfg(feature = "legacy-algorithms")]
1259 Self::Aes192Cbc => "AES-192-CBC",
1260 #[cfg(feature = "legacy-algorithms")]
1261 Self::Aes192Gcm => "AES-192-GCM",
1262 Self::Aes128Cbc => "AES-128-CBC",
1263 Self::Aes256Cbc => "AES-256-CBC",
1264 Self::Aes128Gcm => "AES-128-GCM",
1265 Self::Aes256Gcm => "AES-256-GCM",
1266 Self::Camellia128Cbc => "Camellia-128-CBC",
1267 Self::Camellia192Cbc => "Camellia-192-CBC",
1268 Self::Camellia256Cbc => "Camellia-256-CBC",
1269 Self::ChaCha20 => "ChaCha20",
1270 Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
1271 })
1272 }
1273}
1274
1275#[cfg(test)]
1276mod framing_tests {
1277 use super::*;
1278
1279 #[test]
1280 fn cbc_framing_error_names_the_selected_cipher() {
1281 let error = validate_ciphertext_framing(DataEncryptionAlgorithm::Aes128Cbc, 33)
1284 .expect_err("AES ciphertext body is not block-aligned");
1285 assert_eq!(
1286 error.to_string(),
1287 "AES-128-CBC ciphertext length must be a non-zero multiple of 16 bytes, got 17"
1288 );
1289 #[cfg(feature = "legacy-algorithms")]
1290 {
1291 let error = validate_ciphertext_framing(DataEncryptionAlgorithm::TripleDesCbc, 17)
1292 .expect_err("3DES ciphertext body is not block-aligned");
1293 assert_eq!(
1294 error.to_string(),
1295 "Triple DES CBC ciphertext length must be a non-zero multiple of 8 bytes, got 9"
1296 );
1297 }
1298 }
1299}