1use std::collections::{BTreeMap, HashSet};
4use std::io::{BufRead, Cursor};
5
6use anyhow::{Context as _, Result, bail};
7use chrono::SubsecRound;
8use deltachat_contact_tools::EmailAddress;
9use pgp::armor::BlockType;
10use pgp::composed::{
11 ArmorOptions, DecryptionOptions, Deserializable, DetachedSignature, KeyType as PgpKeyType,
12 Message, MessageBuilder, SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey,
13 SignedSecretKey, SubkeyParamsBuilder, TheRing,
14};
15use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
16use pgp::crypto::ecc_curve::ECCCurve;
17use pgp::crypto::hash::HashAlgorithm;
18use pgp::crypto::sym::SymmetricKeyAlgorithm;
19use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
20use pgp::types::{CompressionAlgorithm, KeyDetails, Password, PublicKeyTrait, StringToKey};
21use rand_old::{Rng as _, thread_rng};
22use tokio::runtime::Handle;
23
24use crate::key::{DcKey, Fingerprint};
25
26#[cfg(test)]
27pub(crate) const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
28
29pub(crate) const HEADER_SETUPCODE: &str = "passphrase-begin";
30
31const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
33
34const HASH_ALGORITHM: HashAlgorithm = HashAlgorithm::Sha256;
36
37pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>)> {
41 use std::io::Read;
42
43 let cursor = Cursor::new(buf);
44 let mut dearmor = pgp::armor::Dearmor::new(cursor);
45
46 let mut bytes = Vec::with_capacity(buf.len());
47
48 dearmor.read_to_end(&mut bytes)?;
49 let typ = dearmor.typ.context("failed to parse type")?;
50
51 let headers = dearmor
53 .headers
54 .into_iter()
55 .map(|(key, values)| {
56 (
57 key.trim().to_lowercase(),
58 values
59 .last()
60 .map_or_else(String::new, |s| s.trim().to_string()),
61 )
62 })
63 .collect();
64
65 Ok((typ, headers, bytes))
66}
67
68#[derive(Debug, Clone, Eq, PartialEq)]
73pub struct KeyPair {
74 pub public: SignedPublicKey,
76
77 pub secret: SignedSecretKey,
79}
80
81impl KeyPair {
82 pub fn new(secret: SignedSecretKey) -> Result<Self> {
86 use crate::key::DcSecretKey;
87
88 let public = secret.split_public_key()?;
89 Ok(Self { public, secret })
90 }
91}
92
93pub(crate) fn create_keypair(addr: EmailAddress) -> Result<KeyPair> {
98 let signing_key_type = PgpKeyType::Ed25519Legacy;
99 let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519);
100
101 let user_id = format!("<{addr}>");
102 let key_params = SecretKeyParamsBuilder::default()
103 .key_type(signing_key_type)
104 .can_certify(true)
105 .can_sign(true)
106 .primary_user_id(user_id)
107 .passphrase(None)
108 .preferred_symmetric_algorithms(smallvec![
109 SymmetricKeyAlgorithm::AES256,
110 SymmetricKeyAlgorithm::AES192,
111 SymmetricKeyAlgorithm::AES128,
112 ])
113 .preferred_hash_algorithms(smallvec![
114 HashAlgorithm::Sha256,
115 HashAlgorithm::Sha384,
116 HashAlgorithm::Sha512,
117 HashAlgorithm::Sha224,
118 ])
119 .preferred_compression_algorithms(smallvec![
120 CompressionAlgorithm::ZLIB,
121 CompressionAlgorithm::ZIP,
122 ])
123 .subkey(
124 SubkeyParamsBuilder::default()
125 .key_type(encryption_key_type)
126 .can_encrypt(true)
127 .passphrase(None)
128 .build()
129 .context("failed to build subkey parameters")?,
130 )
131 .build()
132 .context("failed to build key parameters")?;
133
134 let mut rng = thread_rng();
135 let secret_key = key_params
136 .generate(&mut rng)
137 .context("failed to generate the key")?
138 .sign(&mut rng, &Password::empty())
139 .context("failed to sign secret key")?;
140 secret_key
141 .verify()
142 .context("invalid secret key generated")?;
143
144 let key_pair = KeyPair::new(secret_key)?;
145 key_pair
146 .public
147 .verify()
148 .context("invalid public key generated")?;
149 Ok(key_pair)
150}
151
152fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
158 key.public_subkeys
159 .iter()
160 .find(|subkey| subkey.is_encryption_key())
161}
162
163#[derive(Debug)]
169pub enum SeipdVersion {
170 V1,
172
173 V2,
175}
176
177pub async fn pk_encrypt(
180 plain: Vec<u8>,
181 public_keys_for_encryption: Vec<SignedPublicKey>,
182 private_key_for_signing: SignedSecretKey,
183 compress: bool,
184 anonymous_recipients: bool,
185 seipd_version: SeipdVersion,
186) -> Result<String> {
187 Handle::current()
188 .spawn_blocking(move || {
189 let mut rng = thread_rng();
190
191 let pkeys = public_keys_for_encryption
192 .iter()
193 .filter_map(select_pk_for_encryption);
194
195 let msg = MessageBuilder::from_bytes("", plain);
196 let encoded_msg = match seipd_version {
197 SeipdVersion::V1 => {
198 let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
199
200 for pkey in pkeys {
201 if anonymous_recipients {
202 msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
203 } else {
204 msg.encrypt_to_key(&mut rng, &pkey)?;
205 }
206 }
207
208 msg.sign(&*private_key_for_signing, Password::empty(), HASH_ALGORITHM);
209 if compress {
210 msg.compression(CompressionAlgorithm::ZLIB);
211 }
212
213 msg.to_armored_string(&mut rng, Default::default())?
214 }
215 SeipdVersion::V2 => {
216 let mut msg = msg.seipd_v2(
217 &mut rng,
218 SYMMETRIC_KEY_ALGORITHM,
219 AeadAlgorithm::Ocb,
220 ChunkSize::C8KiB,
221 );
222
223 for pkey in pkeys {
224 if anonymous_recipients {
225 msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
226 } else {
227 msg.encrypt_to_key(&mut rng, &pkey)?;
228 }
229 }
230
231 msg.sign(&*private_key_for_signing, Password::empty(), HASH_ALGORITHM);
232 if compress {
233 msg.compression(CompressionAlgorithm::ZLIB);
234 }
235
236 msg.to_armored_string(&mut rng, Default::default())?
237 }
238 };
239
240 Ok(encoded_msg)
241 })
242 .await?
243}
244
245pub fn pk_calc_signature(
247 plain: Vec<u8>,
248 private_key_for_signing: &SignedSecretKey,
249) -> Result<String> {
250 let rng = thread_rng();
251
252 let mut config = SignatureConfig::from_key(
253 rng,
254 &private_key_for_signing.primary_key,
255 SignatureType::Binary,
256 )?;
257
258 config.hashed_subpackets = vec![
259 Subpacket::regular(SubpacketData::IssuerFingerprint(
260 private_key_for_signing.fingerprint(),
261 ))?,
262 Subpacket::critical(SubpacketData::SignatureCreationTime(
263 chrono::Utc::now().trunc_subsecs(0),
264 ))?,
265 ];
266 config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(
267 private_key_for_signing.key_id(),
268 ))?];
269
270 let signature = config.sign(
271 &private_key_for_signing.primary_key,
272 &Password::empty(),
273 plain.as_slice(),
274 )?;
275
276 let sig = DetachedSignature::new(signature);
277
278 Ok(sig.to_armored_string(ArmorOptions::default())?)
279}
280
281pub fn decrypt(
289 ctext: Vec<u8>,
290 private_keys_for_decryption: &[SignedSecretKey],
291 mut shared_secrets: &[String],
292) -> Result<pgp::composed::Message<'static>> {
293 let cursor = Cursor::new(ctext);
294 let (msg, _headers) = Message::from_armor(cursor)?;
295
296 let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
297 let empty_pw = Password::empty();
298
299 let decrypt_options = DecryptionOptions::new();
300 let symmetric_encryption_res = check_symmetric_encryption(&msg);
301 if symmetric_encryption_res.is_err() {
302 shared_secrets = &[];
303 }
304
305 let message_password: Vec<Password> = shared_secrets
310 .iter()
311 .map(|p| Password::from(p.as_str()))
312 .collect();
313 let message_password: Vec<&Password> = message_password.iter().collect();
314
315 let ring = TheRing {
316 secret_keys: skeys,
317 key_passwords: vec![&empty_pw],
318 message_password,
319 session_keys: vec![],
320 decrypt_options,
321 };
322
323 let res = msg.decrypt_the_ring(ring, true);
324
325 let (msg, _ring_result) = match res {
326 Ok(it) => it,
327 Err(err) => {
328 if let Err(reason) = symmetric_encryption_res {
329 bail!("{err:#} (Note: symmetric decryption was not tried: {reason})")
330 } else {
331 bail!("{err:#}");
332 }
333 }
334 };
335
336 let msg = msg.decompress()?;
338
339 Ok(msg)
340}
341
342fn check_symmetric_encryption(msg: &Message<'_>) -> std::result::Result<(), &'static str> {
352 let Message::Encrypted { esk, .. } = msg else {
353 return Err("not encrypted");
354 };
355
356 if esk.len() > 1 {
357 return Err("too many esks");
358 }
359
360 let [pgp::composed::Esk::SymKeyEncryptedSessionKey(esk)] = &esk[..] else {
361 return Err("not symmetrically encrypted");
362 };
363
364 match esk.s2k() {
365 Some(StringToKey::Salted { .. }) => Ok(()),
366 _ => Err("unsupported string2key algorithm"),
367 }
368}
369
370pub fn valid_signature_fingerprints(
376 msg: &pgp::composed::Message,
377 public_keys_for_validation: &[SignedPublicKey],
378) -> HashSet<Fingerprint> {
379 let mut ret_signature_fingerprints: HashSet<Fingerprint> = Default::default();
380 if msg.is_signed() {
381 for pkey in public_keys_for_validation {
382 if msg.verify(&pkey.primary_key).is_ok() {
383 let fp = pkey.dc_fingerprint();
384 ret_signature_fingerprints.insert(fp);
385 }
386 }
387 }
388 ret_signature_fingerprints
389}
390
391pub fn pk_validate(
393 content: &[u8],
394 signature: &[u8],
395 public_keys_for_validation: &[SignedPublicKey],
396) -> Result<HashSet<Fingerprint>> {
397 let mut ret: HashSet<Fingerprint> = Default::default();
398
399 let detached_signature = DetachedSignature::from_armor_single(Cursor::new(signature))?.0;
400
401 for pkey in public_keys_for_validation {
402 if detached_signature.verify(pkey, content).is_ok() {
403 let fp = pkey.dc_fingerprint();
404 ret.insert(fp);
405 }
406 }
407 Ok(ret)
408}
409
410pub async fn symm_encrypt_autocrypt_setup(passphrase: &str, plain: Vec<u8>) -> Result<String> {
412 let passphrase = Password::from(passphrase.to_string());
413
414 tokio::task::spawn_blocking(move || {
415 let mut rng = thread_rng();
416 let s2k = StringToKey::new_default(&mut rng);
417 let builder = MessageBuilder::from_bytes("", plain);
418 let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
419 builder.encrypt_with_password(s2k, &passphrase)?;
420
421 let encoded_msg = builder.to_armored_string(&mut rng, Default::default())?;
422
423 Ok(encoded_msg)
424 })
425 .await?
426}
427
428pub async fn symm_encrypt_message(
432 plain: Vec<u8>,
433 private_key_for_signing: SignedSecretKey,
434 shared_secret: &str,
435 compress: bool,
436) -> Result<String> {
437 let shared_secret = Password::from(shared_secret.to_string());
438
439 tokio::task::spawn_blocking(move || {
440 let msg = MessageBuilder::from_bytes("", plain);
441 let mut rng = thread_rng();
442 let mut salt = [0u8; 8];
443 rng.fill(&mut salt[..]);
444 let s2k = StringToKey::Salted {
445 hash_alg: HashAlgorithm::default(),
446 salt,
447 };
448 let mut msg = msg.seipd_v2(
449 &mut rng,
450 SYMMETRIC_KEY_ALGORITHM,
451 AeadAlgorithm::Ocb,
452 ChunkSize::C8KiB,
453 );
454 msg.encrypt_with_password(&mut rng, s2k, &shared_secret)?;
455
456 msg.sign(&*private_key_for_signing, Password::empty(), HASH_ALGORITHM);
457 if compress {
458 msg.compression(CompressionAlgorithm::ZLIB);
459 }
460
461 let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
462
463 Ok(encoded_msg)
464 })
465 .await?
466}
467
468pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
470 passphrase: &str,
471 ctext: T,
472) -> Result<Vec<u8>> {
473 let passphrase = passphrase.to_string();
474 tokio::task::spawn_blocking(move || {
475 let (enc_msg, _) = Message::from_armor(ctext)?;
476 let password = Password::from(passphrase);
477
478 let msg = enc_msg.decrypt_with_password(&password)?;
479 let res = msg.decompress()?.as_data_vec()?;
480 Ok(res)
481 })
482 .await?
483}
484
485#[cfg(test)]
486mod tests {
487 use std::sync::LazyLock;
488 use tokio::sync::OnceCell;
489
490 use super::*;
491 use crate::{
492 key::{load_self_public_key, load_self_secret_key},
493 test_utils::{TestContextManager, alice_keypair, bob_keypair},
494 };
495 use pgp::composed::Esk;
496 use pgp::packet::PublicKeyEncryptedSessionKey;
497
498 fn pk_decrypt_and_validate<'a>(
499 ctext: &'a [u8],
500 private_keys_for_decryption: &'a [SignedSecretKey],
501 public_keys_for_validation: &[SignedPublicKey],
502 ) -> Result<(
503 pgp::composed::Message<'static>,
504 HashSet<Fingerprint>,
505 Vec<u8>,
506 )> {
507 let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption, &[])?;
508 let content = msg.as_data_vec()?;
509 let ret_signature_fingerprints =
510 valid_signature_fingerprints(&msg, public_keys_for_validation);
511
512 Ok((msg, ret_signature_fingerprints, content))
513 }
514
515 #[test]
516 fn test_split_armored_data_1() {
517 let (typ, _headers, base64) = split_armored_data(
518 b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE-----",
519 )
520 .unwrap();
521
522 assert_eq!(typ, BlockType::Message);
523 assert!(!base64.is_empty());
524 assert_eq!(
525 std::string::String::from_utf8(base64).unwrap(),
526 "hello world"
527 );
528 }
529
530 #[test]
531 fn test_split_armored_data_2() {
532 let (typ, headers, base64) = split_armored_data(
533 b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
534 )
535 .unwrap();
536
537 assert_eq!(typ, BlockType::PrivateKey);
538 assert!(!base64.is_empty());
539 assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
540 }
541
542 #[test]
543 fn test_create_keypair() {
544 let keypair0 = create_keypair(EmailAddress::new("foo@bar.de").unwrap()).unwrap();
545 let keypair1 = create_keypair(EmailAddress::new("two@zwo.de").unwrap()).unwrap();
546 assert_ne!(keypair0.public, keypair1.public);
547 }
548
549 struct TestKeys {
552 alice_secret: SignedSecretKey,
553 alice_public: SignedPublicKey,
554 bob_secret: SignedSecretKey,
555 bob_public: SignedPublicKey,
556 }
557
558 impl TestKeys {
559 fn new() -> TestKeys {
560 let alice = alice_keypair();
561 let bob = bob_keypair();
562 TestKeys {
563 alice_secret: alice.secret.clone(),
564 alice_public: alice.public,
565 bob_secret: bob.secret.clone(),
566 bob_public: bob.public,
567 }
568 }
569 }
570
571 static CLEARTEXT: &[u8] = b"This is a test";
573
574 static KEYS: LazyLock<TestKeys> = LazyLock::new(TestKeys::new);
576
577 static CTEXT_SIGNED: OnceCell<String> = OnceCell::const_new();
578
579 async fn ctext_signed() -> &'static String {
581 let anonymous_recipients = true;
582 CTEXT_SIGNED
583 .get_or_init(|| async {
584 let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
585 let compress = true;
586
587 pk_encrypt(
588 CLEARTEXT.to_vec(),
589 keyring,
590 KEYS.alice_secret.clone(),
591 compress,
592 anonymous_recipients,
593 SeipdVersion::V2,
594 )
595 .await
596 .unwrap()
597 })
598 .await
599 }
600
601 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
602 async fn test_encrypt_signed() {
603 assert!(!ctext_signed().await.is_empty());
604 assert!(
605 ctext_signed()
606 .await
607 .starts_with("-----BEGIN PGP MESSAGE-----")
608 );
609 }
610
611 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
612 async fn test_decrypt_singed() {
613 let decrypt_keyring = vec![KEYS.alice_secret.clone()];
615 let sig_check_keyring = vec![KEYS.alice_public.clone()];
616 let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
617 ctext_signed().await.as_bytes(),
618 &decrypt_keyring,
619 &sig_check_keyring,
620 )
621 .unwrap();
622 assert_eq!(content, CLEARTEXT);
623 assert_eq!(valid_signatures.len(), 1);
624
625 let decrypt_keyring = vec![KEYS.bob_secret.clone()];
627 let sig_check_keyring = vec![KEYS.alice_public.clone()];
628 let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
629 ctext_signed().await.as_bytes(),
630 &decrypt_keyring,
631 &sig_check_keyring,
632 )
633 .unwrap();
634 assert_eq!(content, CLEARTEXT);
635 assert_eq!(valid_signatures.len(), 1);
636 }
637
638 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
639 async fn test_decrypt_no_sig_check() {
640 let keyring = vec![KEYS.alice_secret.clone()];
641 let (_msg, valid_signatures, content) =
642 pk_decrypt_and_validate(ctext_signed().await.as_bytes(), &keyring, &[]).unwrap();
643 assert_eq!(content, CLEARTEXT);
644 assert_eq!(valid_signatures.len(), 0);
645 }
646
647 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
648 async fn test_decrypt_signed_no_key() {
649 let decrypt_keyring = vec![KEYS.bob_secret.clone()];
651 let sig_check_keyring = vec![KEYS.bob_public.clone()];
652 let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
653 ctext_signed().await.as_bytes(),
654 &decrypt_keyring,
655 &sig_check_keyring,
656 )
657 .unwrap();
658 assert_eq!(content, CLEARTEXT);
659 assert_eq!(valid_signatures.len(), 0);
660 }
661
662 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
663 async fn test_decrypt_unsigned() {
664 let decrypt_keyring = vec![KEYS.bob_secret.clone()];
665 let ctext_unsigned = include_bytes!("../test-data/message/ctext_unsigned.asc");
666 let (_msg, valid_signatures, content) =
667 pk_decrypt_and_validate(ctext_unsigned, &decrypt_keyring, &[]).unwrap();
668 assert_eq!(content, CLEARTEXT);
669 assert_eq!(valid_signatures.len(), 0);
670 }
671
672 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
673 async fn test_encrypt_decrypt_broadcast() -> Result<()> {
674 let mut tcm = TestContextManager::new();
675 let alice = &tcm.alice().await;
676 let bob = &tcm.bob().await;
677
678 let plain = Vec::from(b"this is the secret message");
679 let shared_secret = "shared secret";
680 let ctext = symm_encrypt_message(
681 plain.clone(),
682 load_self_secret_key(alice).await?,
683 shared_secret,
684 true,
685 )
686 .await?;
687
688 let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
689 let mut decrypted = decrypt(
690 ctext.into(),
691 &bob_private_keyring,
692 &[shared_secret.to_string()],
693 )?;
694
695 assert_eq!(decrypted.as_data_vec()?, plain);
696
697 Ok(())
698 }
699
700 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
704 async fn test_dont_decrypt_expensive_message() -> Result<()> {
705 let mut tcm = TestContextManager::new();
706 let bob = &tcm.bob().await;
707
708 let plain = Vec::from(b"this is the secret message");
709 let shared_secret = "shared secret";
710
711 let shared_secret_pw = Password::from(shared_secret.to_string());
715 let msg = MessageBuilder::from_bytes("", plain);
716 let mut rng = thread_rng();
717 let s2k = StringToKey::new_default(&mut rng); let mut msg = msg.seipd_v2(
720 &mut rng,
721 SymmetricKeyAlgorithm::AES128,
722 AeadAlgorithm::Ocb,
723 ChunkSize::C8KiB,
724 );
725 msg.encrypt_with_password(&mut rng, s2k, &shared_secret_pw)?;
726
727 let ctext = msg.to_armored_string(&mut rng, Default::default())?;
728
729 let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
732 let error = decrypt(
733 ctext.into(),
734 &bob_private_keyring,
735 &[shared_secret.to_string()],
736 )
737 .unwrap_err();
738
739 assert_eq!(
740 error.to_string(),
741 "missing key (Note: symmetric decryption was not tried: unsupported string2key algorithm)"
742 );
743
744 Ok(())
745 }
746
747 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
748 async fn test_decryption_error_msg() -> Result<()> {
749 let mut tcm = TestContextManager::new();
750 let alice = &tcm.alice().await;
751 let bob = &tcm.bob().await;
752
753 let plain = Vec::from(b"this is the secret message");
754 let pk_for_encryption = load_self_public_key(alice).await?;
755
756 let ctext = pk_encrypt(
758 plain,
759 vec![pk_for_encryption],
760 KEYS.alice_secret.clone(),
761 true,
762 true,
763 SeipdVersion::V2,
764 )
765 .await?;
766
767 let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
769 let error = decrypt(ctext.into(), &bob_private_keyring, &[]).unwrap_err();
770
771 assert_eq!(
772 error.to_string(),
773 "missing key (Note: symmetric decryption was not tried: not symmetrically encrypted)"
774 );
775
776 Ok(())
777 }
778
779 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
782 async fn test_anonymous_recipients() -> Result<()> {
783 let ctext = ctext_signed().await.as_bytes();
784 let cursor = Cursor::new(ctext);
785 let (msg, _headers) = Message::from_armor(cursor)?;
786
787 let Message::Encrypted { esk, .. } = msg else {
788 unreachable!();
789 };
790
791 for encrypted_session_key in esk {
792 let Esk::PublicKeyEncryptedSessionKey(pkesk) = encrypted_session_key else {
793 unreachable!()
794 };
795
796 match pkesk {
797 PublicKeyEncryptedSessionKey::V3 { id, .. } => {
798 assert!(id.is_wildcard());
799 }
800 PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
801 assert!(fingerprint.is_none());
802 }
803 PublicKeyEncryptedSessionKey::Other { .. } => unreachable!(),
804 }
805 }
806 Ok(())
807 }
808}