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
163pub async fn pk_encrypt(
166 plain: Vec<u8>,
167 public_keys_for_encryption: Vec<SignedPublicKey>,
168 private_key_for_signing: Option<SignedSecretKey>,
169 compress: bool,
170 anonymous_recipients: bool,
171) -> Result<String> {
172 Handle::current()
173 .spawn_blocking(move || {
174 let mut rng = thread_rng();
175
176 let pkeys = public_keys_for_encryption
177 .iter()
178 .filter_map(select_pk_for_encryption);
179
180 let msg = MessageBuilder::from_bytes("", plain);
181 let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
182 for pkey in pkeys {
183 if anonymous_recipients {
184 msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
185 } else {
186 msg.encrypt_to_key(&mut rng, &pkey)?;
187 }
188 }
189
190 if let Some(ref skey) = private_key_for_signing {
191 msg.sign(&**skey, Password::empty(), HASH_ALGORITHM);
192 if compress {
193 msg.compression(CompressionAlgorithm::ZLIB);
194 }
195 }
196
197 let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
198
199 Ok(encoded_msg)
200 })
201 .await?
202}
203
204pub fn pk_calc_signature(
206 plain: Vec<u8>,
207 private_key_for_signing: &SignedSecretKey,
208) -> Result<String> {
209 let rng = thread_rng();
210
211 let mut config = SignatureConfig::from_key(
212 rng,
213 &private_key_for_signing.primary_key,
214 SignatureType::Binary,
215 )?;
216
217 config.hashed_subpackets = vec![
218 Subpacket::regular(SubpacketData::IssuerFingerprint(
219 private_key_for_signing.fingerprint(),
220 ))?,
221 Subpacket::critical(SubpacketData::SignatureCreationTime(
222 chrono::Utc::now().trunc_subsecs(0),
223 ))?,
224 ];
225 config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(
226 private_key_for_signing.key_id(),
227 ))?];
228
229 let signature = config.sign(
230 &private_key_for_signing.primary_key,
231 &Password::empty(),
232 plain.as_slice(),
233 )?;
234
235 let sig = DetachedSignature::new(signature);
236
237 Ok(sig.to_armored_string(ArmorOptions::default())?)
238}
239
240pub fn decrypt(
248 ctext: Vec<u8>,
249 private_keys_for_decryption: &[SignedSecretKey],
250 mut shared_secrets: &[String],
251) -> Result<pgp::composed::Message<'static>> {
252 let cursor = Cursor::new(ctext);
253 let (msg, _headers) = Message::from_armor(cursor)?;
254
255 let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
256 let empty_pw = Password::empty();
257
258 let decrypt_options = DecryptionOptions::new();
259 let symmetric_encryption_res = check_symmetric_encryption(&msg);
260 if symmetric_encryption_res.is_err() {
261 shared_secrets = &[];
262 }
263
264 let message_password: Vec<Password> = shared_secrets
269 .iter()
270 .map(|p| Password::from(p.as_str()))
271 .collect();
272 let message_password: Vec<&Password> = message_password.iter().collect();
273
274 let ring = TheRing {
275 secret_keys: skeys,
276 key_passwords: vec![&empty_pw],
277 message_password,
278 session_keys: vec![],
279 decrypt_options,
280 };
281
282 let res = msg.decrypt_the_ring(ring, true);
283
284 let (msg, _ring_result) = match res {
285 Ok(it) => it,
286 Err(err) => {
287 if let Err(reason) = symmetric_encryption_res {
288 bail!("{err:#} (Note: symmetric decryption was not tried: {reason})")
289 } else {
290 bail!("{err:#}");
291 }
292 }
293 };
294
295 let msg = msg.decompress()?;
297
298 Ok(msg)
299}
300
301fn check_symmetric_encryption(msg: &Message<'_>) -> std::result::Result<(), &'static str> {
311 let Message::Encrypted { esk, .. } = msg else {
312 return Err("not encrypted");
313 };
314
315 if esk.len() > 1 {
316 return Err("too many esks");
317 }
318
319 let [pgp::composed::Esk::SymKeyEncryptedSessionKey(esk)] = &esk[..] else {
320 return Err("not symmetrically encrypted");
321 };
322
323 match esk.s2k() {
324 Some(StringToKey::Salted { .. }) => Ok(()),
325 _ => Err("unsupported string2key algorithm"),
326 }
327}
328
329pub fn valid_signature_fingerprints(
335 msg: &pgp::composed::Message,
336 public_keys_for_validation: &[SignedPublicKey],
337) -> HashSet<Fingerprint> {
338 let mut ret_signature_fingerprints: HashSet<Fingerprint> = Default::default();
339 if msg.is_signed() {
340 for pkey in public_keys_for_validation {
341 if msg.verify(&pkey.primary_key).is_ok() {
342 let fp = pkey.dc_fingerprint();
343 ret_signature_fingerprints.insert(fp);
344 }
345 }
346 }
347 ret_signature_fingerprints
348}
349
350pub fn pk_validate(
352 content: &[u8],
353 signature: &[u8],
354 public_keys_for_validation: &[SignedPublicKey],
355) -> Result<HashSet<Fingerprint>> {
356 let mut ret: HashSet<Fingerprint> = Default::default();
357
358 let detached_signature = DetachedSignature::from_armor_single(Cursor::new(signature))?.0;
359
360 for pkey in public_keys_for_validation {
361 if detached_signature.verify(pkey, content).is_ok() {
362 let fp = pkey.dc_fingerprint();
363 ret.insert(fp);
364 }
365 }
366 Ok(ret)
367}
368
369pub async fn symm_encrypt_autocrypt_setup(passphrase: &str, plain: Vec<u8>) -> Result<String> {
371 let passphrase = Password::from(passphrase.to_string());
372
373 tokio::task::spawn_blocking(move || {
374 let mut rng = thread_rng();
375 let s2k = StringToKey::new_default(&mut rng);
376 let builder = MessageBuilder::from_bytes("", plain);
377 let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
378 builder.encrypt_with_password(s2k, &passphrase)?;
379
380 let encoded_msg = builder.to_armored_string(&mut rng, Default::default())?;
381
382 Ok(encoded_msg)
383 })
384 .await?
385}
386
387pub async fn symm_encrypt_message(
391 plain: Vec<u8>,
392 private_key_for_signing: SignedSecretKey,
393 shared_secret: &str,
394 compress: bool,
395) -> Result<String> {
396 let shared_secret = Password::from(shared_secret.to_string());
397
398 tokio::task::spawn_blocking(move || {
399 let msg = MessageBuilder::from_bytes("", plain);
400 let mut rng = thread_rng();
401 let mut salt = [0u8; 8];
402 rng.fill(&mut salt[..]);
403 let s2k = StringToKey::Salted {
404 hash_alg: HashAlgorithm::default(),
405 salt,
406 };
407 let mut msg = msg.seipd_v2(
408 &mut rng,
409 SymmetricKeyAlgorithm::AES128,
410 AeadAlgorithm::Ocb,
411 ChunkSize::C8KiB,
412 );
413 msg.encrypt_with_password(&mut rng, s2k, &shared_secret)?;
414
415 msg.sign(&*private_key_for_signing, Password::empty(), HASH_ALGORITHM);
416 if compress {
417 msg.compression(CompressionAlgorithm::ZLIB);
418 }
419
420 let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
421
422 Ok(encoded_msg)
423 })
424 .await?
425}
426
427pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
429 passphrase: &str,
430 ctext: T,
431) -> Result<Vec<u8>> {
432 let passphrase = passphrase.to_string();
433 tokio::task::spawn_blocking(move || {
434 let (enc_msg, _) = Message::from_armor(ctext)?;
435 let password = Password::from(passphrase);
436
437 let msg = enc_msg.decrypt_with_password(&password)?;
438 let res = msg.decompress()?.as_data_vec()?;
439 Ok(res)
440 })
441 .await?
442}
443
444#[cfg(test)]
445mod tests {
446 use std::sync::LazyLock;
447 use tokio::sync::OnceCell;
448
449 use super::*;
450 use crate::{
451 key::{load_self_public_key, load_self_secret_key},
452 test_utils::{TestContextManager, alice_keypair, bob_keypair},
453 };
454 use pgp::composed::Esk;
455 use pgp::packet::PublicKeyEncryptedSessionKey;
456
457 fn pk_decrypt_and_validate<'a>(
458 ctext: &'a [u8],
459 private_keys_for_decryption: &'a [SignedSecretKey],
460 public_keys_for_validation: &[SignedPublicKey],
461 ) -> Result<(
462 pgp::composed::Message<'static>,
463 HashSet<Fingerprint>,
464 Vec<u8>,
465 )> {
466 let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption, &[])?;
467 let content = msg.as_data_vec()?;
468 let ret_signature_fingerprints =
469 valid_signature_fingerprints(&msg, public_keys_for_validation);
470
471 Ok((msg, ret_signature_fingerprints, content))
472 }
473
474 #[test]
475 fn test_split_armored_data_1() {
476 let (typ, _headers, base64) = split_armored_data(
477 b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE-----",
478 )
479 .unwrap();
480
481 assert_eq!(typ, BlockType::Message);
482 assert!(!base64.is_empty());
483 assert_eq!(
484 std::string::String::from_utf8(base64).unwrap(),
485 "hello world"
486 );
487 }
488
489 #[test]
490 fn test_split_armored_data_2() {
491 let (typ, headers, base64) = split_armored_data(
492 b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
493 )
494 .unwrap();
495
496 assert_eq!(typ, BlockType::PrivateKey);
497 assert!(!base64.is_empty());
498 assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
499 }
500
501 #[test]
502 fn test_create_keypair() {
503 let keypair0 = create_keypair(EmailAddress::new("foo@bar.de").unwrap()).unwrap();
504 let keypair1 = create_keypair(EmailAddress::new("two@zwo.de").unwrap()).unwrap();
505 assert_ne!(keypair0.public, keypair1.public);
506 }
507
508 struct TestKeys {
511 alice_secret: SignedSecretKey,
512 alice_public: SignedPublicKey,
513 bob_secret: SignedSecretKey,
514 bob_public: SignedPublicKey,
515 }
516
517 impl TestKeys {
518 fn new() -> TestKeys {
519 let alice = alice_keypair();
520 let bob = bob_keypair();
521 TestKeys {
522 alice_secret: alice.secret.clone(),
523 alice_public: alice.public,
524 bob_secret: bob.secret.clone(),
525 bob_public: bob.public,
526 }
527 }
528 }
529
530 static CLEARTEXT: &[u8] = b"This is a test";
532
533 static KEYS: LazyLock<TestKeys> = LazyLock::new(TestKeys::new);
535
536 static CTEXT_SIGNED: OnceCell<String> = OnceCell::const_new();
537 static CTEXT_UNSIGNED: OnceCell<String> = OnceCell::const_new();
538
539 async fn ctext_signed() -> &'static String {
541 let anonymous_recipients = true;
542 CTEXT_SIGNED
543 .get_or_init(|| async {
544 let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
545 let compress = true;
546
547 pk_encrypt(
548 CLEARTEXT.to_vec(),
549 keyring,
550 Some(KEYS.alice_secret.clone()),
551 compress,
552 anonymous_recipients,
553 )
554 .await
555 .unwrap()
556 })
557 .await
558 }
559
560 async fn ctext_unsigned() -> &'static String {
562 let anonymous_recipients = true;
563 CTEXT_UNSIGNED
564 .get_or_init(|| async {
565 let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
566 let compress = true;
567
568 pk_encrypt(
569 CLEARTEXT.to_vec(),
570 keyring,
571 None,
572 compress,
573 anonymous_recipients,
574 )
575 .await
576 .unwrap()
577 })
578 .await
579 }
580
581 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
582 async fn test_encrypt_signed() {
583 assert!(!ctext_signed().await.is_empty());
584 assert!(
585 ctext_signed()
586 .await
587 .starts_with("-----BEGIN PGP MESSAGE-----")
588 );
589 }
590
591 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
592 async fn test_encrypt_unsigned() {
593 assert!(!ctext_unsigned().await.is_empty());
594 assert!(
595 ctext_unsigned()
596 .await
597 .starts_with("-----BEGIN PGP MESSAGE-----")
598 );
599 }
600
601 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
602 async fn test_decrypt_singed() {
603 let decrypt_keyring = vec![KEYS.alice_secret.clone()];
605 let sig_check_keyring = vec![KEYS.alice_public.clone()];
606 let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
607 ctext_signed().await.as_bytes(),
608 &decrypt_keyring,
609 &sig_check_keyring,
610 )
611 .unwrap();
612 assert_eq!(content, CLEARTEXT);
613 assert_eq!(valid_signatures.len(), 1);
614
615 let decrypt_keyring = vec![KEYS.bob_secret.clone()];
617 let sig_check_keyring = vec![KEYS.alice_public.clone()];
618 let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
619 ctext_signed().await.as_bytes(),
620 &decrypt_keyring,
621 &sig_check_keyring,
622 )
623 .unwrap();
624 assert_eq!(content, CLEARTEXT);
625 assert_eq!(valid_signatures.len(), 1);
626 }
627
628 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
629 async fn test_decrypt_no_sig_check() {
630 let keyring = vec![KEYS.alice_secret.clone()];
631 let (_msg, valid_signatures, content) =
632 pk_decrypt_and_validate(ctext_signed().await.as_bytes(), &keyring, &[]).unwrap();
633 assert_eq!(content, CLEARTEXT);
634 assert_eq!(valid_signatures.len(), 0);
635 }
636
637 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
638 async fn test_decrypt_signed_no_key() {
639 let decrypt_keyring = vec![KEYS.bob_secret.clone()];
641 let sig_check_keyring = vec![KEYS.bob_public.clone()];
642 let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
643 ctext_signed().await.as_bytes(),
644 &decrypt_keyring,
645 &sig_check_keyring,
646 )
647 .unwrap();
648 assert_eq!(content, CLEARTEXT);
649 assert_eq!(valid_signatures.len(), 0);
650 }
651
652 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
653 async fn test_decrypt_unsigned() {
654 let decrypt_keyring = vec![KEYS.bob_secret.clone()];
655 let (_msg, valid_signatures, content) =
656 pk_decrypt_and_validate(ctext_unsigned().await.as_bytes(), &decrypt_keyring, &[])
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_encrypt_decrypt_broadcast() -> Result<()> {
664 let mut tcm = TestContextManager::new();
665 let alice = &tcm.alice().await;
666 let bob = &tcm.bob().await;
667
668 let plain = Vec::from(b"this is the secret message");
669 let shared_secret = "shared secret";
670 let ctext = symm_encrypt_message(
671 plain.clone(),
672 load_self_secret_key(alice).await?,
673 shared_secret,
674 true,
675 )
676 .await?;
677
678 let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
679 let mut decrypted = decrypt(
680 ctext.into(),
681 &bob_private_keyring,
682 &[shared_secret.to_string()],
683 )?;
684
685 assert_eq!(decrypted.as_data_vec()?, plain);
686
687 Ok(())
688 }
689
690 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
694 async fn test_dont_decrypt_expensive_message() -> Result<()> {
695 let mut tcm = TestContextManager::new();
696 let bob = &tcm.bob().await;
697
698 let plain = Vec::from(b"this is the secret message");
699 let shared_secret = "shared secret";
700
701 let shared_secret_pw = Password::from(shared_secret.to_string());
705 let msg = MessageBuilder::from_bytes("", plain);
706 let mut rng = thread_rng();
707 let s2k = StringToKey::new_default(&mut rng); let mut msg = msg.seipd_v2(
710 &mut rng,
711 SymmetricKeyAlgorithm::AES128,
712 AeadAlgorithm::Ocb,
713 ChunkSize::C8KiB,
714 );
715 msg.encrypt_with_password(&mut rng, s2k, &shared_secret_pw)?;
716
717 let ctext = msg.to_armored_string(&mut rng, Default::default())?;
718
719 let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
722 let error = decrypt(
723 ctext.into(),
724 &bob_private_keyring,
725 &[shared_secret.to_string()],
726 )
727 .unwrap_err();
728
729 assert_eq!(
730 error.to_string(),
731 "missing key (Note: symmetric decryption was not tried: unsupported string2key algorithm)"
732 );
733
734 Ok(())
735 }
736
737 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
738 async fn test_decryption_error_msg() -> Result<()> {
739 let mut tcm = TestContextManager::new();
740 let alice = &tcm.alice().await;
741 let bob = &tcm.bob().await;
742
743 let plain = Vec::from(b"this is the secret message");
744 let pk_for_encryption = load_self_public_key(alice).await?;
745
746 let ctext = pk_encrypt(plain, vec![pk_for_encryption], None, true, true).await?;
748
749 let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
751 let error = decrypt(ctext.into(), &bob_private_keyring, &[]).unwrap_err();
752
753 assert_eq!(
754 error.to_string(),
755 "missing key (Note: symmetric decryption was not tried: not symmetrically encrypted)"
756 );
757
758 Ok(())
759 }
760
761 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
764 async fn test_anonymous_recipients() -> Result<()> {
765 let ctext = ctext_signed().await.as_bytes();
766 let cursor = Cursor::new(ctext);
767 let (msg, _headers) = Message::from_armor(cursor)?;
768
769 let Message::Encrypted { esk, .. } = msg else {
770 unreachable!();
771 };
772
773 for encrypted_session_key in esk {
774 let Esk::PublicKeyEncryptedSessionKey(pkesk) = encrypted_session_key else {
775 unreachable!()
776 };
777
778 match pkesk {
779 PublicKeyEncryptedSessionKey::V3 { id, .. } => {
780 assert!(id.is_wildcard());
781 }
782 PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
783 assert!(fingerprint.is_none());
784 }
785 PublicKeyEncryptedSessionKey::Other { .. } => unreachable!(),
786 }
787 }
788 Ok(())
789 }
790}