1use crate::config::data_dir;
25use crate::securitykey::{self, CardAccess, CardDecryptor, KnownCard};
26use openpgp::cert::prelude::*;
27use openpgp::crypto::{self, Password, SessionKey, mpi};
28use openpgp::packet::{PKESK, SKESK, key};
29use openpgp::parse::Parse;
30use openpgp::parse::stream::{DecryptionHelper, DecryptorBuilder, DetachedVerifierBuilder, MessageLayer, MessageStructure, VerificationHelper};
31use openpgp::policy::StandardPolicy;
32use openpgp::serialize::stream::{Armorer, Encryptor, LiteralWriter, Message, Signer};
33use openpgp::serialize::{Serialize, SerializeInto};
34use openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
35use openpgp::{Cert, KeyHandle};
36pub use openpgp::Fingerprint;
38use sequoia_openpgp as openpgp;
39use serde::Serialize as Serde;
40use std::cell::RefCell;
41use std::collections::BTreeMap;
42use std::io::Write;
43use std::path::PathBuf;
44use std::sync::Mutex;
45
46fn policy() -> StandardPolicy<'static> {
47 StandardPolicy::new()
48}
49
50fn pgp_dir() -> PathBuf {
51 data_dir().join("pgp")
52}
53
54fn own_dir() -> PathBuf {
55 pgp_dir().join("own")
56}
57
58#[derive(Default, Clone)]
61pub struct Keys {
62 pub own: Vec<Cert>,
63 pub others: Vec<Cert>,
64 pub cards: Vec<KnownCard>,
67 pub shared: Vec<KnownCard>,
72 pub addresses: Vec<String>,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum Place {
82 Here,
84 Card {
86 ident: String,
88 },
89}
90
91#[derive(Debug, Clone)]
93pub struct Own<'a> {
94 pub cert: &'a Cert,
95 pub place: Place,
96}
97
98fn read_certs(dir: &std::path::Path) -> Vec<Cert> {
99 let Ok(entries) = std::fs::read_dir(dir) else { return Vec::new() };
100 entries
101 .filter_map(Result::ok)
102 .map(|e| e.path())
103 .filter(|p| p.extension().is_some_and(|x| x == "asc" || x == "pgp"))
104 .filter_map(|p| std::fs::read(&p).ok())
105 .flat_map(|bytes| CertParser::from_bytes(&bytes).map(|parser| parser.filter_map(Result::ok).collect::<Vec<_>>()).unwrap_or_default())
106 .collect()
107}
108
109fn bound(cert: &Cert, address: &str) -> bool {
115 let policy = policy();
116 let Ok(valid) = cert.with_policy(&policy, None) else { return false };
117 valid.userids().revoked(false).any(|u| email_is(u.userid(), address))
118}
119
120fn usable(cert: &Cert) -> bool {
127 let policy = policy();
128 cert.with_policy(&policy, None).is_ok_and(|valid| !matches!(valid.revocation_status(), openpgp::types::RevocationStatus::Revoked(_)))
129}
130
131fn names(cert: &Cert, address: &str) -> bool {
135 usable(cert) && bound(cert, address)
136}
137
138pub fn names_address(cert: &Cert, address: &str) -> bool {
141 bound(cert, address)
142}
143
144pub fn revoked_whole(cert: &Cert) -> bool {
146 !usable(cert) && cert.with_policy(&policy(), None).is_ok()
147}
148
149fn email_is(userid: &openpgp::packet::UserID, address: &str) -> bool {
150 userid.email().ok().flatten().is_some_and(|e| e.eq_ignore_ascii_case(address.trim()))
151}
152
153fn for_its_address(cert: Cert, address: &str) -> Option<Cert> {
159 let kept = cert.retain_userids(|u| email_is(u.userid(), address));
160 bound(&kept, address).then_some(kept)
162}
163
164fn bound_addresses(cert: &Cert) -> Vec<String> {
166 let policy = policy();
167 let Ok(valid) = cert.with_policy(&policy, None) else { return Vec::new() };
168 let mut out: Vec<String> = Vec::new();
169 for address in valid.userids().revoked(false).filter_map(|u| u.userid().email().ok().flatten().map(|e| e.trim().to_string())) {
170 if !out.iter().any(|kept| kept.eq_ignore_ascii_case(&address)) {
171 out.push(address);
172 }
173 }
174 out
175}
176
177#[derive(serde::Serialize, serde::Deserialize)]
182struct Served {
183 format: u32,
184 #[serde(default)]
185 addresses: Vec<String>,
186 #[serde(default, skip_serializing_if = "Vec::is_empty")]
190 judged: Vec<String>,
191}
192
193const OTHERS_FORMAT: u32 = 2;
197
198fn served_path(dir: &std::path::Path, fingerprint: &str) -> PathBuf {
199 dir.join(format!("{fingerprint}.toml"))
200}
201
202fn record_of(dir: &std::path::Path, cert: &Cert) -> Option<Served> {
205 std::fs::read_to_string(served_path(dir, &cert.fingerprint().to_hex())).ok().and_then(|text| toml::from_str::<Served>(&text).ok())
206}
207
208fn judged(cert: &Cert) -> Vec<String> {
219 let mut named: Vec<String> = Vec::new();
220 for address in cert.userids().filter_map(|u| u.userid().email().ok().flatten().map(|e| e.trim().to_string())) {
221 if !named.iter().any(|kept| kept.eq_ignore_ascii_case(&address)) {
222 named.push(address);
223 }
224 }
225 match named.as_slice() {
226 [one] if bound(cert, one) => named,
227 _ => Vec::new(),
228 }
229}
230
231fn served_by(dir: &std::path::Path, cert: &Cert) -> Vec<String> {
235 record_of(dir, cert).map_or_else(|| judged(cert), |served| served.addresses)
236}
237
238fn only_for(cert: Cert, addresses: &[String]) -> Cert {
240 cert.retain_userids(|u| u.userid().email().ok().flatten().is_some_and(|e| addresses.iter().any(|a| a.trim().eq_ignore_ascii_case(e.trim()))))
241}
242
243fn read_others(dir: &std::path::Path) -> Vec<Cert> {
248 read_certs(dir)
249 .into_iter()
250 .filter_map(|cert| {
251 let served = served_by(dir, &cert);
252 let cut = only_for(cert, &served);
253 cut.userids().next().is_some().then_some(cut)
254 })
255 .collect()
256}
257
258fn write_served(dir: &std::path::Path, fingerprint: &str, served: &Served) -> Result<(), String> {
260 let text = format!(
261 "# Sioul: the addresses this key was learned for, the only ones it serves (docs/client.md, \"What others' keys serve\").\n{}",
262 toml::to_string(served).map_err(|e| e.to_string())?
263 );
264 write_key(&served_path(dir, fingerprint), text.as_bytes())
265}
266
267pub fn keep_others_for_their_addresses() -> Result<usize, String> {
276 keep_others_for_their_addresses_at(&pgp_dir(), &crate::config::state_dir().join("pgp-others.toml"))
277}
278
279fn keep_others_for_their_addresses_at(root: &std::path::Path, mark: &std::path::Path) -> Result<usize, String> {
281 #[derive(Default, serde::Serialize, serde::Deserialize)]
282 struct Mark {
283 #[serde(default)]
284 format: u32,
285 }
286 let done = std::fs::read_to_string(mark).ok().and_then(|text| toml::from_str::<Mark>(&text).ok()).unwrap_or_default();
287 if done.format >= OTHERS_FORMAT {
288 return Ok(0);
289 }
290 let dir = root.join("others");
291 let mut written = 0;
292 for cert in read_certs(&dir) {
293 let fingerprint = cert.fingerprint().to_hex();
294 if served_path(&dir, &fingerprint).exists() {
295 continue;
296 }
297 let addresses = judged(&cert);
298 write_served(&dir, &fingerprint, &Served { format: OTHERS_FORMAT, judged: addresses.clone(), addresses })?;
299 written += 1;
300 }
301 if let Some(parent) = mark.parent() {
302 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
303 }
304 let text = format!(
305 "# Sioul: others' keys say beside them what they serve (pgp/others/<fingerprint>.toml) since this format; never shared.\n{}",
306 toml::to_string(&Mark { format: OTHERS_FORMAT }).map_err(|e| e.to_string())?
307 );
308 crate::filelock::replace(mark, text.as_bytes()).map_err(|e| format!("{}: {e}", mark.display()))?;
309 Ok(written)
310}
311
312impl Keys {
313 pub fn load() -> Keys {
314 let addresses = crate::config::Config::load(&crate::config::default_path()).map(|c| c.accounts.iter().filter_map(|a| a.address.clone()).collect()).unwrap_or_default();
315 Keys { addresses, ..Keys::load_at(&pgp_dir()) }
316 }
317
318 pub fn load_at(root: &std::path::Path) -> Keys {
326 let mut keys = Keys { own: read_certs(&root.join("own")), others: read_others(&root.join("others")), cards: securitykey::known_cards_in(&root.join("cards.toml")), ..Keys::default() };
327 let stopped = securitykey::stopped_in(root);
328 for (card, cert) in securitykey::shared_cards_in(&root.join(securitykey::SHARED_CARDS)) {
329 let read_here = keys.cards.iter().any(|known| known.ident.eq_ignore_ascii_case(&card.ident));
330 if !read_here && stopped.iter().any(|ident| ident.eq_ignore_ascii_case(&card.ident)) {
332 continue;
333 }
334 if !read_here {
335 keys.shared.push(card);
336 }
337 match keys.own.iter_mut().find(|own| own.fingerprint() == cert.fingerprint()) {
338 Some(own) => {
339 if let Ok(merged) = own.clone().merge_public(cert) {
340 *own = merged;
341 }
342 }
343 None => keys.own.push(cert),
344 }
345 }
346 keys
347 }
348
349 pub fn own_for(&self, address: &str) -> Option<Own<'_>> {
356 if let Some((card, cert)) = self.card_for(address) {
357 return (can_sign_with(cert, &card.sign) && self.usable(cert)).then(|| Own { cert, place: Place::Card { ident: card.ident.clone() } });
358 }
359 let policy = policy();
360 self.own
361 .iter()
362 .find(|c| self.serves(c, address) && c.keys().secret().with_policy(&policy, None).alive().revoked(false).for_signing().next().is_some())
363 .map(|cert| Own { cert, place: Place::Here })
364 }
365
366 pub fn card_for(&self, address: &str) -> Option<(&KnownCard, &Cert)> {
369 self.cards.iter().filter(|card| card.serves(address)).find_map(|card| Some((card, self.cert(&card.cert)?)))
370 }
371
372 pub fn serving_card(&self, address: &str) -> Option<(&KnownCard, &Cert)> {
376 self.all_cards().filter(|card| card.serves(address)).find_map(|card| Some((card, self.cert(&card.cert)?)))
377 }
378
379 pub fn all_cards(&self) -> impl Iterator<Item = &KnownCard> {
382 self.cards.iter().chain(&self.shared)
383 }
384
385 pub fn cert(&self, fingerprint: &str) -> Option<&Cert> {
387 self.own.iter().find(|c| c.fingerprint().to_hex().eq_ignore_ascii_case(fingerprint))
388 }
389
390 pub fn for_address(&self, address: &str) -> Option<&Cert> {
402 if self.is_own_address(address) {
403 return self.self_cert_for(address);
404 }
405 let policy = policy();
406 let newest = |c: &Cert| {
407 let encryption = c.keys().with_policy(&policy, None).alive().revoked(false).for_transport_encryption().map(|k| k.key().creation_time()).max();
408 (encryption, c.primary_key().key().creation_time(), c.fingerprint().to_hex())
409 };
410 let best = |certs: &'_ [Cert]| -> Option<usize> {
411 certs.iter().enumerate().filter(|(_, c)| self.serves(c, address)).filter_map(|(i, c)| { let n = newest(c); n.0.map(|_| (n, i)) }).max_by(|a, b| a.0.cmp(&b.0)).map(|(_, i)| i)
412 };
413 best(&self.others).map(|i| &self.others[i]).or_else(|| best(&self.own).map(|i| &self.own[i]))
414 }
415
416 pub fn self_cert_for(&self, address: &str) -> Option<&Cert> {
421 let policy = policy();
422 let encrypts = |c: &&Cert| self.usable(c) && c.keys().with_policy(&policy, None).alive().revoked(false).for_transport_encryption().next().is_some();
423 if let Some((_, cert)) = self.serving_card(address) {
424 return Some(cert).filter(encrypts);
425 }
426 self.own.iter().filter(|c| self.serves(c, address)).find(encrypts)
427 }
428
429 pub fn own_certs(&self) -> Vec<&Cert> {
435 let policy = policy();
436 let mut out: Vec<&Cert> = Vec::new();
437 for cert in &self.own {
438 if self.usable(cert) && cert.keys().with_policy(&policy, None).supported().revoked(false).for_transport_encryption().next().is_some() && !out.iter().any(|c| c.fingerprint() == cert.fingerprint()) {
439 out.push(cert);
440 }
441 }
442 out
443 }
444
445 pub fn is_own_address(&self, address: &str) -> bool {
449 let address = address.trim();
450 self.addresses.iter().any(|a| a.trim().eq_ignore_ascii_case(address)) || self.all_cards().any(|card| card.serves(address))
451 }
452
453 pub fn usable(&self, cert: &Cert) -> bool {
461 let policy = policy();
462 let Ok(valid) = cert.with_policy(&policy, None) else { return false };
463 match valid.revocation_status() {
464 openpgp::types::RevocationStatus::Revoked(_) => false,
465 openpgp::types::RevocationStatus::NotAsFarAsWeKnow => true,
466 openpgp::types::RevocationStatus::CouldBe(revocations) => {
467 let revokers: Vec<&Fingerprint> = cert.revocation_keys(&policy).map(|key| key.revoker().1).collect();
468 let kept = self.own.iter().chain(&self.others).filter(|kept| revokers.contains(&&kept.fingerprint()));
469 !kept.into_iter().any(|revoker| revocations.iter().any(|revocation| revocation.verify_primary_key_revocation(revoker.primary_key().key(), cert.primary_key().key()).is_ok()))
470 }
471 }
472 }
473
474 fn serves(&self, cert: &Cert, address: &str) -> bool {
477 names(cert, address) && self.usable(cert)
478 }
479
480 pub fn is_yours(&self, fingerprint: &Fingerprint) -> bool {
483 let hex = fingerprint.to_hex();
484 self.own.iter().any(|c| c.fingerprint() == *fingerprint) || self.all_cards().any(|card| card.cert.eq_ignore_ascii_case(&hex))
485 }
486
487 fn all(&self) -> impl Iterator<Item = &Cert> {
488 self.own.iter().chain(&self.others)
489 }
490
491 fn by_handle(&self, handle: &KeyHandle) -> Option<&Cert> {
492 self.all().find(|c| c.keys().any(|k| k.key().key_handle().aliases(handle)))
493 }
494}
495
496fn can_sign_with(cert: &Cert, fingerprint: &str) -> bool {
499 let policy = policy();
500 usable(cert) && cert.keys().with_policy(&policy, None).alive().revoked(false).for_signing().any(|k| k.key().fingerprint().to_hex().eq_ignore_ascii_case(fingerprint))
501}
502
503pub fn key_of(cert: &Cert, fingerprint: &str) -> Option<securitykey::PublicKey> {
505 cert.keys().find(|k| k.key().fingerprint().to_hex().eq_ignore_ascii_case(fingerprint)).map(|k| k.key().clone())
506}
507
508#[derive(Debug, Clone, Serde, PartialEq, Eq)]
510pub struct KeySummary {
511 pub fingerprint: String,
512 pub names: Vec<String>,
514 pub own: bool,
515 pub created: String,
517 pub expires: String,
519 pub can_encrypt: bool,
520 pub card: String,
522 pub revocation: bool,
524}
525
526fn summary(cert: &Cert, own: bool) -> KeySummary {
527 let policy = policy();
528 let valid = cert.with_policy(&policy, None).ok();
529 let date = |t: std::time::SystemTime| jiff::Timestamp::try_from(t).map(|t| t.strftime("%Y-%m-%d").to_string()).unwrap_or_default();
530 KeySummary {
531 fingerprint: cert.fingerprint().to_hex(),
532 names: valid.as_ref().map(|v| v.userids().revoked(false).map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).collect()).unwrap_or_default(),
534 own,
535 created: date(cert.primary_key().key().creation_time()),
536 expires: valid.as_ref().and_then(|v| v.primary_key().key_expiration_time()).map(date).unwrap_or_default(),
537 can_encrypt: usable(cert) && cert.keys().with_policy(&policy, None).alive().revoked(false).for_transport_encryption().next().is_some(),
538 card: String::new(),
539 revocation: false,
540 }
541}
542
543pub fn summaries() -> Vec<KeySummary> {
545 let keys = Keys::load();
546 let summarised = |c: &Cert, own: bool| KeySummary { can_encrypt: summary(c, own).can_encrypt && keys.usable(c), ..summary(c, own) };
548 let mut all: Vec<KeySummary> = keys.own.iter().map(|c| summarised(c, true)).chain(keys.others.iter().map(|c| summarised(c, false))).collect();
549 for key in all.iter_mut().filter(|k| k.own) {
550 if let Some(card) = keys.all_cards().find(|c| c.cert.eq_ignore_ascii_case(&key.fingerprint)) {
551 key.card = card.ident.clone();
552 }
553 key.revocation = revocation_path(&key.fingerprint).is_file();
554 }
555 all
556}
557
558fn store_from_elsewhere_at(root: &std::path::Path, cert: Cert, address: &str) -> Option<KeySummary> {
565 let cert = for_its_address(cert.strip_secret_key_material(), address)?;
566 let fingerprint = cert.fingerprint().to_hex();
567 let yours = root.join("own").join(format!("{fingerprint}.asc"));
568 if let Some(known) = std::fs::read(&yours).ok().and_then(|bytes| Cert::from_bytes(&bytes).ok()) {
569 let names: Vec<Vec<u8>> = known.userids().map(|u| u.userid().value().to_vec()).collect();
570 let merged = known.merge_public(cert).ok()?.retain_userids(|u| names.iter().any(|n| n.as_slice() == u.userid().value()));
571 return store_in(root, only_bound_subkeys(merged), true).ok();
572 }
573 if yours_at(root, &fingerprint) {
574 return None;
575 }
576 store_in(root, cert, false).ok()
577}
578
579fn only_bound_subkeys(cert: Cert) -> Cert {
585 let policy = policy();
586 let bound: Vec<Fingerprint> = cert.keys().subkeys().with_policy(&policy, None).map(|k| k.key().fingerprint()).collect();
587 cert.retain_subkeys(|k| bound.contains(&k.key().fingerprint()))
588}
589
590fn yours_at(root: &std::path::Path, fingerprint: &str) -> bool {
596 root.join("own").join(format!("{fingerprint}.asc")).exists()
597 || securitykey::known_cards_in(&root.join("cards.toml")).iter().any(|card| card.cert.eq_ignore_ascii_case(fingerprint))
598 || securitykey::shared_cards_in(&root.join(securitykey::SHARED_CARDS)).iter().any(|(card, _)| card.cert.eq_ignore_ascii_case(fingerprint))
599}
600
601fn store_at(root: &std::path::Path, cert: Cert) -> Result<KeySummary, String> {
605 let known_as_own = root.join("own").join(format!("{}.asc", cert.fingerprint().to_hex())).exists();
606 let own = cert.is_tsk() || known_as_own;
607 store_in(root, cert, own)
608}
609
610fn store_in(root: &std::path::Path, cert: Cert, own: bool) -> Result<KeySummary, String> {
611 if !own {
612 let learned = bound_addresses(&cert);
613 return store_others(root, cert, &learned);
614 }
615 let dir = root.join(if own { "own" } else { "others" });
616 std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
617 let path = dir.join(format!("{}.asc", cert.fingerprint().to_hex()));
618 let merged = match std::fs::read(&path).ok().and_then(|bytes| Cert::from_bytes(&bytes).ok()) {
619 Some(known) => known.merge_public_and_secret(cert).map_err(|e| e.to_string())?,
620 None => cert,
621 };
622 let armored = if merged.is_tsk() { merged.as_tsk().armored().to_vec() } else { merged.armored().to_vec() }.map_err(|e| e.to_string())?;
623 write_key(&path, &armored)?;
624 let fingerprint = merged.fingerprint().to_hex();
626 if own && securitykey::known_cards_in(&root.join("cards.toml")).iter().any(|card| card.cert.eq_ignore_ascii_case(&fingerprint)) {
627 securitykey::share_cards_in(&root.join("cards.toml"), &dir, &root.join(securitykey::SHARED_CARDS), false)?;
628 }
629 Ok(summary(&merged, own))
630}
631
632fn store_others(root: &std::path::Path, cert: Cert, learned: &[String]) -> Result<KeySummary, String> {
640 let dir = root.join("others");
641 std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
642 let fingerprint = cert.fingerprint().to_hex();
643 let path = dir.join(format!("{fingerprint}.asc"));
644 let kept = std::fs::read(&path).ok().and_then(|bytes| Cert::from_bytes(&bytes).ok());
645 let mut served = match &kept {
646 Some(kept) => record_of(&dir, kept).unwrap_or_else(|| {
647 let addresses = judged(kept);
648 Served { format: OTHERS_FORMAT, judged: addresses.clone(), addresses }
649 }),
650 None => Served { format: OTHERS_FORMAT, addresses: Vec::new(), judged: Vec::new() },
651 };
652 served.format = OTHERS_FORMAT;
653 for address in learned {
654 let address = address.trim();
655 served.judged.retain(|judged| !judged.eq_ignore_ascii_case(address));
656 if !served.addresses.iter().any(|a| a.trim().eq_ignore_ascii_case(address)) {
657 served.addresses.push(address.to_string());
658 }
659 }
660 let merged = match kept {
661 Some(kept) => kept.merge_public(cert.strip_secret_key_material()).map_err(|e| e.to_string())?,
662 None => cert.strip_secret_key_material(),
663 };
664 let merged = only_for(merged, &served.addresses);
665 write_served(&dir, &fingerprint, &served)?;
666 write_key(&path, &merged.armored().to_vec().map_err(|e| e.to_string())?)?;
667 Ok(summary(&merged, false))
668}
669
670pub fn keep_card_certificate(cert: &Cert) -> Result<KeySummary, String> {
672 store_in(&pgp_dir(), cert.clone().strip_secret_key_material(), true)
673}
674
675fn write_key(path: &std::path::Path, armored: &[u8]) -> Result<(), String> {
678 let fail = |e: std::io::Error| format!("{}: {e}", path.display());
679 let temporary = path.with_file_name(format!(".{}.tmp", path.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default()));
682 let _ = std::fs::remove_file(&temporary);
684 let mut options = std::fs::OpenOptions::new();
685 options.write(true).create_new(true);
686 #[cfg(unix)]
687 {
688 use std::os::unix::fs::OpenOptionsExt;
689 options.mode(0o600);
690 }
691 let mut file = options.open(&temporary).map_err(fail)?;
692 file.write_all(armored).and_then(|()| file.sync_all()).map_err(fail)?;
693 drop(file);
694 std::fs::rename(&temporary, path).map_err(fail)
695}
696
697pub fn certificates_in(bytes: &[u8]) -> Vec<Cert> {
699 certificates_of(bytes).unwrap_or_default()
700}
701
702pub fn certificates_of(bytes: &[u8]) -> Result<Vec<Cert>, String> {
709 let read = |bytes: &[u8]| CertParser::from_bytes(bytes).map(|parser| parser.filter_map(Result::ok).collect::<Vec<Cert>>()).map_err(|e| e.to_string());
710 let as_they_came = read(bytes);
711 let shortest = match minimal_mpis(bytes) {
712 std::borrow::Cow::Borrowed(_) => return as_they_came,
713 std::borrow::Cow::Owned(repaired) => read(&repaired),
714 };
715 let (mut certs, shortest) = match (as_they_came, shortest) {
716 (Ok(certs), Ok(shortest)) => (certs, shortest),
717 (Ok(certs), Err(_)) => return Ok(certs),
718 (Err(_), shortest) => return shortest,
719 };
720 for cert in shortest {
721 match certs.iter().position(|kept| kept.fingerprint() == cert.fingerprint()) {
722 Some(index) => {
723 if let Ok(merged) = certs[index].clone().merge_public_and_secret(cert) {
725 certs[index] = merged;
726 }
727 }
728 None => certs.push(cert),
729 }
730 }
731 Ok(certs)
732}
733
734pub fn minimal_mpis(bytes: &[u8]) -> std::borrow::Cow<'_, [u8]> {
747 use std::borrow::Cow;
748 if bytes.first().is_some_and(|b| b & 0x80 != 0) {
750 return shortened(bytes).map_or(Cow::Borrowed(bytes), Cow::Owned);
751 }
752 let mut out = Vec::new();
755 let mut changed = false;
756 for block in armored_keys(bytes) {
757 let mut reader = openpgp::armor::Reader::from_bytes(block, openpgp::armor::ReaderMode::Tolerant(None));
758 let mut binary = Vec::new();
759 if std::io::Read::read_to_end(&mut reader, &mut binary).is_err() || binary.is_empty() {
760 return Cow::Borrowed(bytes);
761 }
762 match shortened(&binary) {
763 Some(shorter) => {
764 out.extend_from_slice(&shorter);
765 changed = true;
766 }
767 None => out.extend_from_slice(&binary),
768 }
769 }
770 if changed { Cow::Owned(out) } else { Cow::Borrowed(bytes) }
771}
772
773fn armored_keys(text: &[u8]) -> Vec<&[u8]> {
778 const BEGIN: &[u8] = b"-----BEGIN PGP ";
779 const END: &[u8] = b"-----END PGP ";
780 let find = |from: usize, needle: &[u8]| text.get(from..).and_then(|rest| rest.windows(needle.len()).position(|window| window == needle)).map(|i| from + i);
781 let mut blocks = Vec::new();
782 let mut at = 0;
783 while let Some(begin) = find(at, BEGIN) {
784 let Some(end) = find(begin + BEGIN.len(), END) else { break };
785 let line_end = text[end..].iter().position(|b| *b == b'\n').map_or(text.len(), |i| end + i + 1);
786 let kind = &text[begin + BEGIN.len()..];
787 if kind.starts_with(b"PUBLIC KEY BLOCK-----") || kind.starts_with(b"PRIVATE KEY BLOCK-----") {
788 blocks.push(&text[begin..line_end]);
789 }
790 at = line_end;
791 }
792 blocks
793}
794
795fn shortened(binary: &[u8]) -> Option<Vec<u8>> {
799 let mut out = Vec::with_capacity(binary.len());
800 let mut changed = false;
801 let mut at = 0;
802 while at < binary.len() {
803 let (tag, body) = packet_at(binary, at)?;
804 let shorter = if tag == 2 && binary[body.clone()].first() == Some(&4) { signature_shortened(&binary[body.clone()]) } else { None };
805 match shorter {
806 Some(shorter) => {
808 put_packet(&mut out, tag, &shorter);
809 changed = true;
810 }
811 None => out.extend_from_slice(&binary[at..body.end]),
812 }
813 at = body.end;
814 }
815 changed.then_some(out)
816}
817
818fn packet_at(bytes: &[u8], at: usize) -> Option<(u8, std::ops::Range<usize>)> {
821 let ctb = *bytes.get(at)?;
822 let number = |from: usize, octets: usize| bytes.get(from..from + octets).map(|b| b.iter().fold(0usize, |n, b| (n << 8) | usize::from(*b)));
823 let (tag, start, length) = match ctb {
824 0xC0..=0xFF => match *bytes.get(at + 1)? {
826 first @ 0..=191 => (ctb & 0x3F, at + 2, usize::from(first)),
827 first @ 192..=223 => (ctb & 0x3F, at + 3, ((usize::from(first) - 192) << 8) + usize::from(*bytes.get(at + 2)?) + 192),
828 255 => (ctb & 0x3F, at + 6, number(at + 2, 4)?),
829 _ => return None,
830 },
831 0x80..=0xBF => match ctb & 3 {
833 0 => ((ctb >> 2) & 0x0F, at + 2, number(at + 1, 1)?),
834 1 => ((ctb >> 2) & 0x0F, at + 3, number(at + 1, 2)?),
835 2 => ((ctb >> 2) & 0x0F, at + 5, number(at + 1, 4)?),
836 _ => ((ctb >> 2) & 0x0F, at + 1, bytes.len().checked_sub(at + 1)?),
838 },
839 _ => return None,
840 };
841 let end = start.checked_add(length)?;
842 (end <= bytes.len()).then_some((tag, start..end))
843}
844
845fn signature_shortened(body: &[u8]) -> Option<Vec<u8>> {
853 shortened_at(body, true)
854}
855
856fn shortened_at(body: &[u8], descend: bool) -> Option<Vec<u8>> {
863 let numbers = match *body.get(2)? {
864 1 | 3 => 1,
866 16 | 17 | 19 | 20 | 22 => 2,
868 _ => return None,
869 };
870 let length_at = |at: usize| body.get(at..at + 2).map(|two| usize::from(u16::from_be_bytes([two[0], two[1]])));
871 let unhashed = 4usize.checked_add(2 + length_at(4)?)?;
874 let mut out = body.get(..unhashed)?.to_vec();
875 let area = body.get(unhashed + 2..unhashed + 2 + length_at(unhashed)?)?;
877 let (area_out, mut changed) = match descend.then(|| embedded_shortened(area)).flatten() {
878 Some(shorter) => (shorter, true),
879 None => (area.to_vec(), false),
880 };
881 out.extend_from_slice(&u16::try_from(area_out.len()).ok()?.to_be_bytes());
882 out.extend_from_slice(&area_out);
883 let mut at = unhashed + 2 + area.len();
885 out.extend_from_slice(body.get(at..at + 2)?);
886 at += 2;
887 for _ in 0..numbers {
888 let bits = length_at(at)?;
889 let value = body.get(at + 2..at + 2 + bits.div_ceil(8))?;
890 at += 2 + value.len();
891 let value = &value[value.iter().position(|b| *b != 0).unwrap_or(value.len())..];
892 let shortest = value.first().map_or(0, |first| value.len() * 8 - first.leading_zeros() as usize);
893 if shortest > bits {
894 return None;
895 }
896 changed |= shortest < bits;
897 out.extend_from_slice(&u16::try_from(shortest).ok()?.to_be_bytes());
898 out.extend_from_slice(value);
899 }
900 (changed && at == body.len()).then_some(out)
901}
902
903fn embedded_shortened(area: &[u8]) -> Option<Vec<u8>> {
908 let mut out = Vec::with_capacity(area.len());
909 let mut changed = false;
910 let mut at = 0;
911 while at < area.len() {
912 let (start, length) = match *area.get(at)? {
913 first @ 0..=191 => (at + 1, usize::from(first)),
914 first @ 192..=254 => (at + 2, ((usize::from(first) - 192) << 8) + usize::from(*area.get(at + 1)?) + 192),
915 255 => (at + 5, usize::try_from(u32::from_be_bytes(area.get(at + 1..at + 5)?.try_into().ok()?)).ok()?),
916 };
917 let subpacket = area.get(start..start.checked_add(length)?)?;
919 let (&kind, inner) = subpacket.split_first()?;
920 match (kind & 0x7F == 32 && inner.first() == Some(&4)).then(|| shortened_at(inner, false)).flatten() {
921 Some(shorter) => {
922 put_subpacket_length(&mut out, 1 + shorter.len());
923 out.push(kind);
924 out.extend_from_slice(&shorter);
925 changed = true;
926 }
927 None => out.extend_from_slice(&area[at..start + length]),
928 }
929 at = start + length;
930 }
931 changed.then_some(out)
932}
933
934fn put_subpacket_length(out: &mut Vec<u8>, length: usize) {
936 match length {
937 0..=191 => out.push(length as u8),
938 192..=16_319 => out.extend_from_slice(&[((length - 192) >> 8) as u8 + 192, (length - 192) as u8]),
939 _ => {
940 out.push(255);
941 out.extend_from_slice(&(length as u32).to_be_bytes());
942 }
943 }
944}
945
946fn put_packet(out: &mut Vec<u8>, tag: u8, body: &[u8]) {
948 out.push(0xC0 | tag);
949 match body.len() {
950 length @ 0..=191 => out.push(length as u8),
951 length @ 192..=8383 => out.extend_from_slice(&[((length - 192) >> 8) as u8 + 192, (length - 192) as u8]),
952 length => {
953 out.push(255);
954 out.extend_from_slice(&(length as u32).to_be_bytes());
955 }
956 }
957 out.extend_from_slice(body);
958}
959
960pub fn import(bytes: &[u8]) -> Result<Vec<KeySummary>, String> {
964 import_at(&pgp_dir(), bytes)
965}
966
967fn import_at(root: &std::path::Path, bytes: &[u8]) -> Result<Vec<KeySummary>, String> {
969 let certs = certificates_of(bytes)?;
970 if certs.is_empty() {
971 return Err("no OpenPGP key".into());
972 }
973 certs.into_iter().map(|cert| store_at(root, cert)).collect()
974}
975
976pub fn opens(fingerprint: &str, passphrase: &str) -> bool {
978 let keys = Keys::load();
979 let Some(cert) = keys.own.iter().find(|c| c.fingerprint().to_hex() == fingerprint) else { return false };
980 let policy = policy();
981 let password = Password::from(passphrase);
982 cert.keys().secret().with_policy(&policy, None).for_signing().next().is_some_and(|k| k.key().clone().decrypt_secret(&password).is_ok())
983}
984
985pub fn new_passphrase() -> Result<String, String> {
987 let mut bytes = [0u8; 24];
988 openpgp::crypto::random(&mut bytes).map_err(|e| e.to_string())?;
989 Ok(crate::lines::base64_encode(&bytes))
990}
991
992pub fn generate(name: &str, address: &str, passphrase: &str) -> Result<KeySummary, String> {
997 generate_at(&pgp_dir(), name, address, passphrase)
998}
999
1000fn generate_at(root: &std::path::Path, name: &str, address: &str, passphrase: &str) -> Result<KeySummary, String> {
1002 let userid = if name.trim().is_empty() { format!("<{}>", address.trim()) } else { format!("{} <{}>", name.trim(), address.trim()) };
1003 let (cert, revocation) = CertBuilder::general_purpose(Some(userid))
1004 .set_cipher_suite(CipherSuite::Cv25519)
1005 .set_validity_period(std::time::Duration::from_secs(3 * 365 * 24 * 3600))
1006 .set_password(Some(Password::from(passphrase)))
1007 .generate()
1008 .map_err(|e| e.to_string())?;
1009 let own = root.join("own");
1011 std::fs::create_dir_all(&own).map_err(|e| e.to_string())?;
1012 let kept = own.join(format!("{}.rev", cert.fingerprint().to_hex()));
1013 write_key(&kept, revocation_text(&cert, revocation)?.as_bytes())?;
1014 match store_at(root, cert) {
1015 Ok(summary) => Ok(KeySummary { revocation: true, ..summary }),
1016 Err(e) => {
1017 let _ = std::fs::remove_file(&kept);
1018 Err(e)
1019 }
1020 }
1021}
1022
1023pub fn revocation_path(fingerprint: &str) -> PathBuf {
1027 own_dir().join(format!("{fingerprint}.rev"))
1028}
1029
1030fn revocation_text(cert: &Cert, revocation: openpgp::packet::Signature) -> Result<String, String> {
1034 let mut armored = Vec::new();
1035 {
1036 let mut writer = openpgp::armor::Writer::with_headers(&mut armored, openpgp::armor::Kind::PublicKey, [("Comment", "This is a revocation certificate")]).map_err(|e| e.to_string())?;
1037 openpgp::Packet::from(revocation).serialize(&mut writer).map_err(|e| e.to_string())?;
1038 writer.finalize().map_err(|e| e.to_string())?;
1039 }
1040 let names: Vec<String> = cert.userids().map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).collect();
1041 Ok(format!(
1042 "This is the revocation certificate of the OpenPGP key\n{}\n{}\n\n\
1043 Published, it tells everyone that this key must no longer be used, for\n\
1044 good: it cannot be taken back. Keep a copy where only you can reach it,\n\
1045 apart from the key, for the day the key is lost or stolen.\n\n\
1046 A colon stands before the five dashes below, so that the certificate\n\
1047 cannot be imported by mistake: take it away with a text editor before\n\
1048 you import the certificate and publish it.\n\n:{}",
1049 cert.fingerprint().to_hex(),
1050 names.join("\n"),
1051 String::from_utf8_lossy(&armored)
1052 ))
1053}
1054
1055pub fn save_revocation(fingerprint: &str, folder: &std::path::Path) -> Result<PathBuf, String> {
1059 save_revocation_at(&pgp_dir(), fingerprint, folder)
1060}
1061
1062fn save_revocation_at(root: &std::path::Path, fingerprint: &str, folder: &std::path::Path) -> Result<PathBuf, String> {
1064 let text = std::fs::read(root.join("own").join(format!("{fingerprint}.rev"))).map_err(|e| e.to_string())?;
1065 std::fs::create_dir_all(folder).map_err(|e| e.to_string())?;
1066 let path = (1..)
1067 .map(|n| folder.join(if n == 1 { format!("{fingerprint}.rev") } else { format!("{fingerprint}-{n}.rev") }))
1068 .find(|p| !p.exists())
1069 .unwrap_or_else(|| folder.join(format!("{fingerprint}.rev")));
1070 write_key(&path, &text)?;
1071 Ok(path)
1072}
1073
1074pub fn export_public(fingerprint: &str) -> Option<String> {
1076 let keys = Keys::load();
1077 let cert = keys.all().find(|c| c.fingerprint().to_hex() == fingerprint)?;
1078 cert.armored().to_vec().ok().map(|b| String::from_utf8_lossy(&b).to_string())
1079}
1080
1081pub fn remove(fingerprint: &str) -> Result<(), String> {
1084 remove_in(&pgp_dir(), fingerprint)
1085}
1086
1087pub(crate) fn remove_in(root: &std::path::Path, fingerprint: &str) -> Result<(), String> {
1089 for (dir, own) in [(root.join("own"), true), (root.join("others"), false)] {
1090 let path = dir.join(format!("{fingerprint}.asc"));
1091 if !path.exists() {
1092 continue;
1093 }
1094 if own {
1095 let aside = root.join("removed");
1096 std::fs::create_dir_all(&aside).map_err(|e| e.to_string())?;
1097 std::fs::rename(&path, aside.join(format!("{fingerprint}.asc"))).map_err(|e| e.to_string())?;
1098 let revocation = dir.join(format!("{fingerprint}.rev"));
1099 if revocation.exists() {
1100 std::fs::rename(&revocation, aside.join(format!("{fingerprint}.rev"))).map_err(|e| e.to_string())?;
1101 }
1102 } else {
1103 std::fs::remove_file(&path).map_err(|e| e.to_string())?;
1104 let _ = std::fs::remove_file(served_path(&dir, fingerprint));
1105 }
1106 }
1107 Ok(())
1108}
1109
1110pub type Signing<'a> = dyn crypto::Signer + Send + Sync + 'a;
1113
1114pub fn here_signer(cert: &Cert, passphrase: &str) -> Result<crypto::KeyPair, String> {
1117 let policy = policy();
1118 let key = cert.keys().secret().with_policy(&policy, None).alive().revoked(false).for_signing().next().ok_or("no signing key")?.key().clone();
1119 let key = if key.has_unencrypted_secret() { key } else { key.decrypt_secret(&Password::from(passphrase)).map_err(|_| "wrong passphrase".to_string())? };
1120 key.into_keypair().map_err(|e| e.to_string())
1121}
1122
1123struct Lent<'r, 'o>(&'r mut Signing<'o>);
1126
1127impl crypto::Signer for Lent<'_, '_> {
1128 fn public(&self) -> &openpgp::packet::Key<key::PublicParts, key::UnspecifiedRole> {
1129 self.0.public()
1130 }
1131
1132 fn acceptable_hashes(&self) -> &[HashAlgorithm] {
1133 self.0.acceptable_hashes()
1134 }
1135
1136 fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> openpgp::Result<mpi::Signature> {
1137 self.0.sign(hash_algo, digest)
1138 }
1139}
1140
1141pub fn sign_detached(data: &[u8], signer: &mut Signing<'_>) -> Result<(String, String), String> {
1145 let mut out = Vec::new();
1146 {
1147 let message = Message::new(&mut out);
1148 let message = Armorer::new(message).kind(openpgp::armor::Kind::Signature).build().map_err(|e| e.to_string())?;
1149 let mut signing = Signer::new(message, Lent(signer)).map_err(|e| e.to_string())?.detached().build().map_err(|e| e.to_string())?;
1150 signing.write_all(data).map_err(|e| e.to_string())?;
1151 signing.finalize().map_err(|e| e.to_string())?;
1152 }
1153 let micalg = openpgp::Packet::from_bytes(&out)
1154 .ok()
1155 .and_then(|p| match p {
1156 openpgp::Packet::Signature(s) => Some(format!("pgp-{}", s.hash_algo().to_string().to_ascii_lowercase())),
1157 _ => None,
1158 })
1159 .unwrap_or_else(|| "pgp-sha512".into());
1160 Ok((String::from_utf8_lossy(&out).to_string(), micalg))
1161}
1162
1163pub fn encrypt(data: &[u8], recipients: &[&Cert], signer: Option<&mut Signing<'_>>) -> Result<String, String> {
1165 let policy = policy();
1166 let keys: Vec<_> = recipients.iter().filter(|c| usable(c)).flat_map(|c| c.keys().with_policy(&policy, None).supported().alive().revoked(false).for_transport_encryption()).collect();
1168 encrypt_to(data, keys, signer)
1169}
1170
1171fn encrypt_for_yourself(data: &[u8], yours: &[&Cert]) -> Result<String, String> {
1175 let policy = policy();
1176 let keys: Vec<_> = yours.iter().filter(|c| usable(c)).flat_map(|c| c.keys().with_policy(&policy, None).supported().revoked(false).for_transport_encryption()).collect();
1177 encrypt_to(data, keys, None)
1178}
1179
1180fn encrypt_to<'a>(data: &[u8], keys: Vec<ValidErasedKeyAmalgamation<'a, openpgp::packet::key::PublicParts>>, signer: Option<&mut Signing<'_>>) -> Result<String, String> {
1181 if keys.is_empty() {
1182 return Err("no encryption key".into());
1183 }
1184 let mut out = Vec::new();
1185 {
1186 let message = Message::new(&mut out);
1187 let message = Armorer::new(message).build().map_err(|e| e.to_string())?;
1188 let message = Encryptor::for_recipients(message, keys).build().map_err(|e| e.to_string())?;
1189 let message = match signer {
1190 Some(signer) => Signer::new(message, Lent(signer)).map_err(|e| e.to_string())?.build().map_err(|e| e.to_string())?,
1191 None => message,
1192 };
1193 let mut literal = LiteralWriter::new(message).build().map_err(|e| e.to_string())?;
1194 literal.write_all(data).map_err(|e| e.to_string())?;
1195 literal.finalize().map_err(|e| e.to_string())?;
1196 }
1197 Ok(String::from_utf8_lossy(&out).to_string())
1198}
1199
1200#[derive(Debug, Clone, Serde, PartialEq, Eq)]
1202pub struct Checked {
1203 pub good: bool,
1204 pub signer: String,
1206 pub fingerprint: String,
1207 pub problem: String,
1209}
1210
1211#[derive(Debug, Clone, Default)]
1213pub struct Opened {
1214 pub data: Vec<u8>,
1215 pub signatures: Vec<Checked>,
1216}
1217
1218#[derive(Default)]
1222pub struct SessionKeys(Mutex<BTreeMap<[u8; 32], (Option<SymmetricAlgorithm>, SessionKey)>>);
1223
1224impl SessionKeys {
1225 pub const fn new() -> SessionKeys {
1227 SessionKeys(Mutex::new(BTreeMap::new()))
1228 }
1229
1230 pub fn global() -> &'static SessionKeys {
1232 static GLOBAL: SessionKeys = SessionKeys::new();
1233 &GLOBAL
1234 }
1235
1236 fn get(&self, id: &[u8; 32]) -> Option<(Option<SymmetricAlgorithm>, SessionKey)> {
1237 self.0.lock().ok()?.get(id).cloned()
1238 }
1239
1240 fn keep(&self, id: [u8; 32], algo: Option<SymmetricAlgorithm>, key: SessionKey) {
1241 if let Ok(mut kept) = self.0.lock() {
1242 kept.insert(id, (algo, key));
1243 }
1244 }
1245
1246 pub fn forget(&self) {
1248 if let Ok(mut kept) = self.0.lock() {
1249 kept.clear();
1250 }
1251 }
1252}
1253
1254fn message_id(message: &[u8]) -> Option<[u8; 32]> {
1256 let mut context = HashAlgorithm::SHA256.context().ok()?.for_digest();
1257 context.update(message);
1258 context.into_digest().ok()?.try_into().ok()
1259}
1260
1261pub struct Unlock<'a> {
1263 pub passphrase: &'a dyn Fn(&Fingerprint) -> Option<String>,
1265 pub sessions: &'a SessionKeys,
1267 pub card: Option<&'a CardAccess<'a>>,
1270 wanted: RefCell<Option<String>>,
1272}
1273
1274impl<'a> Unlock<'a> {
1275 pub fn new(passphrase: &'a dyn Fn(&Fingerprint) -> Option<String>, sessions: &'a SessionKeys) -> Unlock<'a> {
1277 Unlock { passphrase, sessions, card: None, wanted: RefCell::new(None) }
1278 }
1279
1280 pub fn with_card(mut self, card: &'a CardAccess<'a>) -> Unlock<'a> {
1282 self.card = Some(card);
1283 self
1284 }
1285
1286 pub fn wanted(&self) -> Option<String> {
1289 self.wanted.borrow().clone()
1290 }
1291}
1292
1293struct Helper<'a> {
1294 keys: &'a Keys,
1295 unlock: &'a Unlock<'a>,
1296 id: Option<[u8; 32]>,
1298 checked: Vec<Checked>,
1299}
1300
1301impl VerificationHelper for Helper<'_> {
1302 fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
1303 Ok(ids.iter().filter_map(|id| self.keys.by_handle(id).cloned()).collect())
1304 }
1305
1306 fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
1307 for layer in structure {
1308 let MessageLayer::SignatureGroup { results } = layer else { continue };
1309 for result in results {
1310 self.checked.push(match result {
1311 Ok(good) if matches!(good.ka.cert().revocation_status(&policy(), None), openpgp::types::RevocationStatus::CouldBe(_)) && !self.keys.usable(good.ka.cert()) => Checked { good: false, signer: String::new(), fingerprint: good.ka.cert().fingerprint().to_hex(), problem: "revoked key".into() },
1317 Ok(good) => Checked {
1318 good: true,
1319 signer: good.ka.cert().userids().next().map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).unwrap_or_default(),
1320 fingerprint: good.ka.cert().fingerprint().to_hex(),
1321 problem: String::new(),
1322 },
1323 Err(error) => {
1324 use openpgp::parse::stream::VerificationError as E;
1325 let (fingerprint, problem) = match &error {
1326 E::MissingKey { sig } => (sig.issuer_fingerprints().next().map(|f| f.to_hex()).unwrap_or_default(), "unknown key"),
1327 E::BadSignature { .. } => (String::new(), "bad signature"),
1328 E::BadKey { .. } => (String::new(), "bad key"),
1329 E::MalformedSignature { .. } => (String::new(), "malformed signature"),
1330 E::UnboundKey { .. } => (String::new(), "unbound key"),
1331 _ => (String::new(), "unknown"),
1332 };
1333 Checked { good: false, signer: String::new(), fingerprint, problem: problem.to_string() }
1334 }
1335 });
1336 }
1337 }
1338 Ok(())
1340 }
1341}
1342
1343impl DecryptionHelper for Helper<'_> {
1344 fn decrypt(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> openpgp::Result<Option<Cert>> {
1345 if let Some(id) = &self.id
1347 && let Some((algo, session)) = self.unlock.sessions.get(id)
1348 && decrypt(algo, &session)
1349 {
1350 return Ok(None);
1351 }
1352 let policy = policy();
1353 for pkesk in pkesks {
1354 for cert in &self.keys.own {
1355 let passphrase = (self.unlock.passphrase)(&cert.fingerprint()).unwrap_or_default();
1356 for key in cert.keys().secret().with_policy(&policy, None).for_transport_encryption().chain(cert.keys().secret().with_policy(&policy, None).for_storage_encryption()) {
1357 let recipient_matches = pkesk.recipient().is_none_or(|r| key.key().key_handle().aliases(&r));
1358 if !recipient_matches {
1359 continue;
1360 }
1361 let secret = key.key().clone();
1362 let secret = if secret.has_unencrypted_secret() { secret } else if let Ok(s) = secret.decrypt_secret(&Password::from(passphrase.as_str())) { s } else { continue };
1363 let Ok(mut pair) = secret.into_keypair() else { continue };
1364 if let Some((algo, session)) = pkesk.decrypt(&mut pair, sym_algo)
1365 && decrypt(algo, &session)
1366 {
1367 return Ok(Some(cert.clone()));
1368 }
1369 }
1370 }
1371 }
1372 for pkesk in pkesks {
1375 let Some(recipient) = pkesk.recipient() else { continue };
1376 for card in self.keys.all_cards() {
1377 let Ok(fingerprint) = card.decrypt.parse::<Fingerprint>() else { continue };
1378 if !KeyHandle::from(fingerprint).aliases(&recipient) {
1379 continue;
1380 }
1381 let Some((cert, public)) = self.keys.cert(&card.cert).and_then(|cert| Some((cert, key_of(cert, &card.decrypt)?))) else { continue };
1382 *self.unlock.wanted.borrow_mut() = Some(card.ident.clone());
1383 let Some(access) = self.unlock.card else { continue };
1384 let mut decryptor = CardDecryptor::new(access, &card.ident, public);
1385 if let Some((algo, session)) = pkesk.decrypt(&mut decryptor, sym_algo)
1386 && decrypt(algo, &session)
1387 {
1388 *self.unlock.wanted.borrow_mut() = None;
1389 if let Some(id) = self.id {
1390 self.unlock.sessions.keep(id, algo, session);
1391 }
1392 return Ok(Some(cert.clone()));
1393 }
1394 }
1395 }
1396 Err(openpgp::Error::InvalidOperation("no key of yours opens this message".into()).into())
1397 }
1398}
1399
1400const LARGEST_DECRYPTED: u64 = 256 * 1024 * 1024;
1403
1404pub fn decrypt(message: &[u8], keys: &Keys, unlock: &Unlock) -> Result<Opened, String> {
1408 use std::io::Read as _;
1409 let policy = policy();
1410 let helper = Helper { keys, unlock, id: message_id(message), checked: Vec::new() };
1411 let mut decryptor = DecryptorBuilder::from_bytes(message).map_err(|e| e.to_string())?.with_policy(&policy, None, helper).map_err(|e| e.to_string())?;
1412 let mut data = Vec::new();
1413 (&mut decryptor).take(LARGEST_DECRYPTED + 1).read_to_end(&mut data).map_err(|e| e.to_string())?;
1414 if data.len() as u64 > LARGEST_DECRYPTED {
1415 return Err(format!("larger than {LARGEST_DECRYPTED} bytes once decrypted"));
1416 }
1417 let helper = decryptor.into_helper();
1418 Ok(Opened { data, signatures: helper.checked })
1419}
1420
1421pub fn verify_detached(data: &[u8], signature: &[u8], keys: &Keys) -> Vec<Checked> {
1423 let policy = policy();
1424 let none = |_: &Fingerprint| None;
1425 let sessions = SessionKeys::new();
1426 let unlock = Unlock::new(&none, &sessions);
1427 let helper = Helper { keys, unlock: &unlock, id: None, checked: Vec::new() };
1428 let Ok(builder) = DetachedVerifierBuilder::from_bytes(signature) else { return Vec::new() };
1429 let Ok(mut verifier) = builder.with_policy(&policy, None, helper) else { return Vec::new() };
1430 let _ = verifier.verify_bytes(data);
1431 verifier.into_helper().checked
1432}
1433
1434pub fn wkd_urls(address: &str) -> Option<[String; 2]> {
1437 let (local, domain) = address.trim().rsplit_once('@')?;
1438 let domain = domain.to_ascii_lowercase();
1439 let labels: Vec<&str> = domain.split('.').collect();
1443 let named = labels.len() >= 2
1444 && labels.iter().all(|l| !l.is_empty() && !l.starts_with('-') && !l.ends_with('-') && l.chars().all(|c| c.is_ascii_alphanumeric() || c == '-'))
1445 && labels.last().is_some_and(|tld| tld.chars().any(|c| c.is_ascii_alphabetic()) && *tld != "localhost");
1446 if !named {
1447 return None;
1448 }
1449 let mut context = openpgp::types::HashAlgorithm::SHA1.context().ok()?.for_digest();
1450 context.update(local.to_lowercase().as_bytes());
1451 let digest = context.into_digest().ok()?;
1452 let hash = zbase32(&digest);
1453 let query = local.bytes().map(|b| if b.is_ascii_alphanumeric() || b"-._~".contains(&b) { (b as char).to_string() } else { format!("%{b:02X}") }).collect::<String>();
1454 Some([
1455 format!("https://openpgpkey.{domain}/.well-known/openpgpkey/{domain}/hu/{hash}?l={query}"),
1456 format!("https://{domain}/.well-known/openpgpkey/hu/{hash}?l={query}"),
1457 ])
1458}
1459
1460fn zbase32(bytes: &[u8]) -> String {
1462 const ALPHABET: &[u8; 32] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
1463 let mut out = String::new();
1464 let (mut buffer, mut bits) = (0u32, 0u32);
1465 for &byte in bytes {
1466 buffer = (buffer << 8) | u32::from(byte);
1467 bits += 8;
1468 while bits >= 5 {
1469 bits -= 5;
1470 out.push(ALPHABET[((buffer >> bits) & 31) as usize] as char);
1471 }
1472 }
1473 if bits > 0 {
1474 out.push(ALPHABET[((buffer << (5 - bits)) & 31) as usize] as char);
1475 }
1476 out
1477}
1478
1479pub fn learn(bytes: &[u8], address: &str) -> Vec<KeySummary> {
1481 learn_at(&pgp_dir(), bytes, address)
1482}
1483
1484fn learn_at(root: &std::path::Path, bytes: &[u8], address: &str) -> Vec<KeySummary> {
1486 certificates_of(bytes).unwrap_or_default().into_iter().filter_map(|c| store_from_elsewhere_at(root, c, address)).collect()
1487}
1488
1489#[derive(Debug, Clone, Default, Serde, PartialEq, Eq)]
1491pub struct PgpView {
1492 pub encrypted: bool,
1493 pub opened: bool,
1495 pub signatures: Vec<Checked>,
1496 pub security_key: Option<String>,
1499 pub key_elsewhere: bool,
1502}
1503
1504fn canonical(bytes: &[u8]) -> Vec<u8> {
1506 let mut out = Vec::with_capacity(bytes.len() + bytes.len() / 32);
1507 for (i, &b) in bytes.iter().enumerate() {
1508 if b == b'\n' && (i == 0 || bytes[i - 1] != b'\r') {
1509 out.push(b'\r');
1510 }
1511 out.push(b);
1512 }
1513 out
1514}
1515
1516fn kind_of(part: &mail_parser::MessagePart) -> (String, String) {
1517 use mail_parser::MimeHeaders;
1518 part.content_type().map_or_else(Default::default, |t| {
1519 (format!("{}/{}", t.ctype(), t.subtype().unwrap_or("")).to_ascii_lowercase(), t.attribute("protocol").unwrap_or("").to_ascii_lowercase())
1520 })
1521}
1522
1523fn outer_headers(raw: &[u8], body_offset: usize) -> Vec<u8> {
1525 let head = String::from_utf8_lossy(&raw[..body_offset.min(raw.len())]).to_string();
1526 let mut out = String::new();
1527 let mut keep = false;
1528 for line in head.split_inclusive('\n') {
1529 if !line.starts_with([' ', '\t']) {
1530 let name = line.split(':').next().unwrap_or("").trim().to_ascii_lowercase();
1531 keep = !name.is_empty() && !name.starts_with("content-") && name != "mime-version";
1532 }
1533 if keep {
1534 out.push_str(line);
1535 }
1536 }
1537 canonical(out.trim_end().as_bytes()).into_iter().chain(b"\r\nMIME-Version: 1.0\r\n".iter().copied()).collect()
1538}
1539
1540pub fn open(raw: &[u8], keys: &Keys, unlock: &Unlock) -> Option<(Option<Vec<u8>>, PgpView)> {
1547 let message = mail_parser::MessageParser::default().parse(raw)?;
1548 let root = message.parts.first()?;
1549 let (kind, protocol) = kind_of(root);
1550 let children: Vec<&mail_parser::MessagePart> = root.sub_parts().map(|ids| ids.iter().filter_map(|&id| message.parts.get(id as usize)).collect()).unwrap_or_default();
1551 let closed = || PgpView { encrypted: true, opened: false, signatures: Vec::new(), security_key: unlock.wanted(), key_elsewhere: false };
1552 if kind == "multipart/encrypted" && protocol == "application/pgp-encrypted" {
1553 let payload = children.iter().find(|p| kind_of(p).0 == "application/octet-stream").map(|p| p.contents().to_vec())?;
1554 return Some(match decrypt(&payload, keys, unlock) {
1555 Ok(opened) => {
1556 let mut virtual_message = outer_headers(raw, root.raw_body_offset() as usize);
1557 virtual_message.extend(canonical(&opened.data));
1558 let mut signatures = opened.signatures;
1559 if let Some((_, inner)) = open(&virtual_message, keys, unlock) {
1561 signatures.extend(inner.signatures);
1562 }
1563 (Some(virtual_message), PgpView { encrypted: true, opened: true, signatures, security_key: None, key_elsewhere: false })
1564 }
1565 Err(_) => (None, closed()),
1566 });
1567 }
1568 if kind == "multipart/signed" && protocol == "application/pgp-signature" && children.len() >= 2 {
1569 let signed = children[0];
1570 let (start, end) = (signed.raw_header_offset() as usize, signed.raw_end_offset() as usize);
1571 let data = canonical(raw.get(start..end)?);
1572 let signature = children.iter().find(|p| kind_of(p).0 == "application/pgp-signature")?.contents().to_vec();
1573 let mut checked = verify_detached(&data, &signature, keys);
1576 if !checked.iter().any(|c| c.good) && data.ends_with(b"\r\n") {
1577 let trimmed = verify_detached(&data[..data.len() - 2], &signature, keys);
1578 if trimmed.iter().any(|c| c.good) {
1579 checked = trimmed;
1580 }
1581 }
1582 return Some((None, PgpView { encrypted: false, opened: true, signatures: checked, security_key: None, key_elsewhere: false }));
1583 }
1584 let text = message.body_text(0)?.to_string();
1586 let start = text.find("-----BEGIN PGP MESSAGE-----")?;
1587 let end = text[start..].find("-----END PGP MESSAGE-----").map(|e| start + e + "-----END PGP MESSAGE-----".len())?;
1588 Some(match decrypt(text[start..end].as_bytes(), keys, unlock) {
1589 Ok(opened) => {
1590 let mut virtual_message = outer_headers(raw, root.raw_body_offset() as usize);
1591 virtual_message.extend_from_slice(b"Content-Type: text/plain; charset=utf-8\r\n\r\n");
1592 virtual_message.extend(canonical(&opened.data));
1593 (Some(virtual_message), PgpView { encrypted: true, opened: true, signatures: opened.signatures, security_key: None, key_elsewhere: false })
1594 }
1595 Err(_) => (None, closed()),
1596 })
1597}
1598
1599pub fn is_encrypted(raw: &[u8]) -> bool {
1605 let Some(message) = mail_parser::MessageParser::default().parse(raw) else { return false };
1606 let Some(root) = message.parts.first() else { return false };
1607 let (kind, protocol) = kind_of(root);
1608 if kind == "multipart/encrypted" && protocol == "application/pgp-encrypted" {
1609 return true;
1610 }
1611 message.body_text(0).is_some_and(|text| {
1612 let text = text.trim();
1613 text.starts_with("-----BEGIN PGP MESSAGE-----") && text.ends_with("-----END PGP MESSAGE-----") && text.matches("-----BEGIN PGP MESSAGE-----").count() == 1
1614 })
1615}
1616
1617fn opened_split(raw: &[u8]) -> Option<usize> {
1624 mime_split(raw, true)
1625}
1626
1627fn mime_split(raw: &[u8], content: bool) -> Option<usize> {
1633 let head = &raw[..header_end(raw)];
1634 let mut at = 0;
1635 let mut seen_mime = None;
1636 let mut content_after = false;
1637 for line in head.split_inclusive(|b| *b == b'\n') {
1638 let start = at;
1639 at += line.len();
1640 if line.starts_with(b" ") || line.starts_with(b"\t") || line.trim_ascii().is_empty() {
1641 continue;
1642 }
1643 let name = String::from_utf8_lossy(line.split(|b| *b == b':').next().unwrap_or_default()).trim().to_ascii_lowercase();
1644 match seen_mime {
1645 None if line == b"MIME-Version: 1.0\r\n" => seen_mime = Some(start),
1646 None if name.starts_with("content-") || name == "mime-version" => return None,
1647 None => {}
1648 Some(_) => content_after |= name.starts_with("content-"),
1649 }
1650 }
1651 seen_mime.filter(|_| content_after || !content)
1652}
1653
1654pub fn looks_opened(raw: &[u8]) -> bool {
1657 opened_split(raw).is_some()
1658}
1659
1660fn header_end(raw: &[u8]) -> usize {
1662 let crlf = raw.windows(4).position(|w| w == b"\r\n\r\n").map(|p| p + 4);
1663 let lf = raw.windows(2).position(|w| w == b"\n\n").map(|p| p + 2);
1664 match (crlf, lf) {
1665 (Some(a), Some(b)) => a.min(b),
1666 (a, b) => a.or(b).unwrap_or(raw.len()),
1667 }
1668}
1669
1670pub fn seal_message(clear: &[u8], to: &[&Cert]) -> Result<Vec<u8>, String> {
1677 let end = header_end(clear);
1678 let (head, body) = clear.split_at(end);
1679 let (mut outer, mut inner): (Vec<u8>, Vec<u8>) = (Vec::new(), Vec::new());
1680 let payload = if let Some(split) = mime_split(clear, false) {
1688 let mime = b"MIME-Version: 1.0\r\n".len();
1689 outer.extend_from_slice(&head[..split]);
1690 clear[split + mime..].to_vec()
1691 } else {
1692 let mut into_outer = true;
1693 let mut skip = false;
1694 for line in head.split_inclusive(|b| *b == b'\n') {
1695 if line == b"\r\n" || line == b"\n" {
1696 continue;
1697 }
1698 if !line.starts_with(b" ") && !line.starts_with(b"\t") {
1699 let name = String::from_utf8_lossy(line.split(|b| *b == b':').next().unwrap_or_default()).trim().to_ascii_lowercase();
1700 skip = name == "mime-version";
1701 into_outer = !name.starts_with("content-");
1702 }
1703 if skip {
1704 continue;
1705 }
1706 if into_outer { outer.extend_from_slice(line) } else { inner.extend_from_slice(line) }
1707 }
1708 if inner.is_empty() {
1709 inner.extend_from_slice(b"Content-Type: text/plain; charset=utf-8\r\n");
1710 }
1711 let mut payload = canonical(&inner);
1712 payload.extend_from_slice(b"\r\n");
1713 payload.extend_from_slice(body);
1714 payload
1715 };
1716 let armored = encrypt_for_yourself(&payload, to)?;
1717 let boundary = "sioul-sealed-copy";
1718 let mut out = canonical(&outer);
1719 out.extend_from_slice(
1720 format!(
1721 "MIME-Version: 1.0\r\nContent-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"{boundary}\"\r\n\r\n--{boundary}\r\nContent-Type: application/pgp-encrypted\r\nContent-Description: PGP/MIME version identification\r\n\r\nVersion: 1\r\n\r\n--{boundary}\r\nContent-Type: application/octet-stream; name=\"encrypted.asc\"\r\nContent-Disposition: inline; filename=\"encrypted.asc\"\r\n\r\n{}\r\n--{boundary}--\r\n",
1722 canonical(armored.trim_end().as_bytes()).iter().map(|&b| b as char).collect::<String>()
1723 )
1724 .as_bytes(),
1725 );
1726 Ok(out)
1727}
1728
1729pub fn stand_in_key(userid: &str) -> Result<Vec<u8>, String> {
1734 let (cert, _) = CertBuilder::new().set_cipher_suite(CipherSuite::Cv25519).add_userid(userid).add_transport_encryption_subkey().generate().map_err(|e| e.to_string())?;
1735 cert.as_tsk().armored().to_vec().map_err(|e| e.to_string())
1736}
1737
1738pub fn recipients_of(raw: &[u8]) -> Vec<String> {
1741 let Some(message) = mail_parser::MessageParser::default().parse(raw) else { return Vec::new() };
1742 let payload = message.parts.iter().find(|p| kind_of(p).0 == "application/octet-stream").map(|p| p.contents().to_vec()).unwrap_or_else(|| raw.to_vec());
1743 let mut out = Vec::new();
1744 let Ok(mut parsing) = openpgp::parse::PacketParser::from_bytes(&payload) else { return out };
1745 while let openpgp::parse::PacketParserResult::Some(packet) = parsing {
1746 if let openpgp::Packet::PKESK(pkesk) = &packet.packet {
1747 out.push(pkesk.recipient().map(|r| r.to_hex()).unwrap_or_default());
1748 }
1749 match packet.next() {
1750 Ok((_, next)) => parsing = next,
1751 Err(_) => break,
1752 }
1753 }
1754 out
1755}
1756
1757pub fn autocrypt_key(header: &str, from: &str) -> Option<Cert> {
1759 let mut addr = None;
1760 let mut keydata = None;
1761 for part in header.split(';') {
1762 let (name, value) = part.split_once('=')?;
1763 match name.trim() {
1764 "addr" => addr = Some(value.trim().to_string()),
1765 "keydata" => keydata = Some(value.split_whitespace().collect::<String>()),
1766 _ => {}
1767 }
1768 }
1769 if !addr?.eq_ignore_ascii_case(from.trim()) {
1770 return None;
1771 }
1772 let bytes = crate::lines::base64_decode(&keydata?)?;
1773 let [cert] = <[Cert; 1]>::try_from(certificates_of(&bytes).ok()?).ok()?;
1777 for_its_address(cert, from)
1778}
1779
1780pub fn learn_autocrypt(header: &str, from: &str) -> Option<KeySummary> {
1784 learn_autocrypt_at(&pgp_dir(), header, from)
1785}
1786
1787fn learn_autocrypt_at(root: &std::path::Path, header: &str, from: &str) -> Option<KeySummary> {
1789 let cert = autocrypt_key(header, from)?.strip_secret_key_material();
1790 if yours_at(root, &cert.fingerprint().to_hex()) {
1791 return None;
1792 }
1793 store_in(root, cert, false).ok()
1794}
1795
1796pub fn autocrypt_header(cert: &Cert, address: &str) -> Option<String> {
1799 let policy = policy();
1800 let valid = cert.with_policy(&policy, None).ok()?;
1801 let userid = valid.userids().find(|u| u.userid().email().ok().flatten().is_some_and(|e| e.eq_ignore_ascii_case(address)))?;
1802 let encryption = valid.keys().subkeys().alive().revoked(false).for_transport_encryption().next()?;
1803 let mut packets: Vec<openpgp::Packet> = vec![cert.primary_key().key().clone().into()];
1804 packets.push(userid.userid().clone().into());
1805 packets.extend(userid.self_signatures().take(1).cloned().map(Into::into));
1806 packets.push(encryption.key().clone().into());
1807 packets.extend(encryption.self_signatures().take(1).cloned().map(Into::into));
1808 let minimal = Cert::from_packets(packets.into_iter()).ok()?;
1809 let mut bytes = Vec::new();
1810 minimal.serialize(&mut bytes).ok()?;
1811 let encoded: Vec<char> = crate::lines::base64_encode(&bytes).chars().collect();
1815 let keydata = encoded.chunks(76).map(|chunk| chunk.iter().collect::<String>()).collect::<Vec<_>>().join(" ");
1816 Some(format!("addr={address}; keydata={keydata}"))
1817}
1818
1819#[cfg(test)]
1820mod tests {
1821 use super::*;
1822
1823 fn temporary() -> PathBuf {
1824 let dir = std::env::temp_dir().join(format!("sioul-pgp-{}-{}", std::process::id(), std::thread::current().name().unwrap_or("t").replace("::", "-")));
1825 std::fs::create_dir_all(&dir).unwrap();
1826 dir
1827 }
1828
1829 fn key(address: &str) -> Cert {
1830 CertBuilder::general_purpose(Some(format!("Someone <{address}>")))
1831 .set_cipher_suite(CipherSuite::Cv25519)
1832 .set_password(Some(Password::from("secret words")))
1833 .generate()
1834 .unwrap()
1835 .0
1836 }
1837
1838 #[test]
1839 fn signs_encrypts_decrypts_and_verifies() {
1840 let _ = temporary();
1841 let alice = key("alice@example.org");
1842 let bob = key("bob@example.org");
1843 let keys_of_bob = Keys { own: vec![bob.clone()], others: vec![alice.clone().strip_secret_key_material()], ..Keys::default() };
1844 let mut signer = here_signer(&alice, "secret words").unwrap();
1845 let sealed = encrypt(b"Content-Type: text/plain\r\n\r\nHello Bob.\r\n", &[&bob, &alice], Some(&mut signer)).unwrap();
1846 assert!(sealed.starts_with("-----BEGIN PGP MESSAGE-----"));
1847 let passphrase = |_: &Fingerprint| Some("secret words".to_string());
1848 let sessions = SessionKeys::new();
1849 let opened = decrypt(sealed.as_bytes(), &keys_of_bob, &Unlock::new(&passphrase, &sessions)).unwrap();
1850 assert_eq!(opened.data, b"Content-Type: text/plain\r\n\r\nHello Bob.\r\n");
1851 assert_eq!(opened.signatures.len(), 1);
1852 assert!(opened.signatures[0].good && opened.signatures[0].signer.contains("alice@example.org"));
1853 let wrong = |_: &Fingerprint| Some("other words".to_string());
1854 assert!(decrypt(sealed.as_bytes(), &keys_of_bob, &Unlock::new(&wrong, &sessions)).is_err());
1855 assert!(here_signer(&alice, "other words").is_err());
1856 let (signature, micalg) = sign_detached(b"signed part", &mut signer).unwrap();
1857 assert!(micalg.starts_with("pgp-sha"), "{micalg}");
1858 let checked = verify_detached(b"signed part", signature.as_bytes(), &keys_of_bob);
1859 assert!(checked[0].good);
1860 let tampered = verify_detached(b"signed part!", signature.as_bytes(), &keys_of_bob);
1861 assert!(!tampered[0].good);
1862 let stranger = verify_detached(b"signed part", signature.as_bytes(), &Keys::default());
1863 assert_eq!(stranger[0].problem, "unknown key");
1864 }
1865
1866 #[test]
1867 fn key_files_are_yours_alone() {
1868 let path = temporary().join("key.asc");
1869 write_key(&path, b"-----BEGIN PGP PRIVATE KEY BLOCK-----").unwrap();
1870 write_key(&path, b"-----BEGIN PGP PRIVATE KEY BLOCK----- again").unwrap();
1871 assert_eq!(std::fs::read(&path).unwrap(), b"-----BEGIN PGP PRIVATE KEY BLOCK----- again");
1872 #[cfg(unix)]
1873 {
1874 use std::os::unix::fs::PermissionsExt;
1875 assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600);
1876 }
1877 let _ = std::fs::remove_file(&path);
1878 }
1879
1880 #[test]
1883 fn a_key_made_here_keeps_its_revocation_certificate() {
1884 let root = temporary().join("pgp-made");
1885 let _ = std::fs::remove_dir_all(&root);
1886 let made = generate_at(&root, "Jane Exemple", "jane@example.org", "secret words").unwrap();
1887 assert!(made.own && made.revocation);
1888 let kept = root.join("own").join(format!("{}.rev", made.fingerprint));
1889 let text = std::fs::read_to_string(&kept).unwrap();
1890 #[cfg(unix)]
1891 {
1892 use std::os::unix::fs::PermissionsExt;
1893 assert_eq!(std::fs::metadata(&kept).unwrap().permissions().mode() & 0o777, 0o600, "protected as the key is");
1894 }
1895 assert!(text.contains(&made.fingerprint) && text.contains("Jane Exemple <jane@example.org>"), "{text}");
1896 assert!(text.contains("\n:-----BEGIN PGP PUBLIC KEY BLOCK-----") && certificates_in(text.as_bytes()).is_empty());
1898 let armored = &text[text.find(":-----BEGIN").unwrap() + 1..];
1900 let pile = openpgp::PacketPile::from_bytes(armored.as_bytes()).unwrap();
1901 let revocation = pile.descendants().find_map(|p| if let openpgp::Packet::Signature(s) = p { Some(s.clone()) } else { None }).unwrap();
1902 let key = Cert::from_bytes(&std::fs::read(root.join("own").join(format!("{}.asc", made.fingerprint))).unwrap()).unwrap();
1903 assert!(matches!(key.revocation_status(&policy(), None), openpgp::types::RevocationStatus::NotAsFarAsWeKnow));
1904 let (revoked, _) = key.insert_packets(revocation).unwrap();
1905 assert!(matches!(revoked.revocation_status(&policy(), None), openpgp::types::RevocationStatus::Revoked(_)));
1906 let elsewhere = root.join("saved");
1908 let copy = save_revocation_at(&root, &made.fingerprint, &elsewhere).unwrap();
1909 assert_eq!((copy.file_name().unwrap().to_string_lossy().to_string(), std::fs::read_to_string(©).unwrap()), (format!("{}.rev", made.fingerprint), text));
1910 assert!(save_revocation_at(&root, &made.fingerprint, &elsewhere).unwrap().ends_with(format!("{}-2.rev", made.fingerprint)));
1911 assert!(save_revocation_at(&root, "0000", &elsewhere).is_err(), "none kept for a key not made here");
1912 let _ = std::fs::remove_dir_all(&root);
1913 }
1914
1915 #[test]
1916 fn web_key_directory_names() {
1917 let [advanced, direct] = wkd_urls("Joe.Doe@Example.ORG").unwrap();
1919 assert_eq!(direct, "https://example.org/.well-known/openpgpkey/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=Joe.Doe");
1920 assert!(advanced.starts_with("https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q"));
1921 for elsewhere in ["joe@192.168.1.1", "joe@[::1]", "joe@localhost", "joe@host.example:8080/x?", "joe@1.2.3"] {
1923 assert!(wkd_urls(elsewhere).is_none(), "{elsewhere}");
1924 }
1925 }
1926
1927 fn card_key(address: &str, made: Option<std::time::SystemTime>) -> Cert {
1929 let mut builder = CertBuilder::new()
1930 .add_userid(format!("Card Holder <{address}>"))
1931 .set_cipher_suite(CipherSuite::Cv25519)
1932 .set_primary_key_flags(openpgp::types::KeyFlags::empty().set_certification().set_signing())
1933 .add_transport_encryption_subkey();
1934 if let Some(made) = made {
1935 builder = builder.set_creation_time(made).set_validity_period(std::time::Duration::from_secs(365 * 24 * 3600));
1936 }
1937 builder.generate().unwrap().0
1938 }
1939
1940 fn record(card: &securitykey::SoftCard, cert: &Cert, address: &str) -> KnownCard {
1941 KnownCard {
1942 ident: card.info.ident.clone(),
1943 sign: card.info.sign.as_ref().map(|s| s.fingerprint.clone()).unwrap_or_default(),
1944 decrypt: card.info.decrypt.as_ref().map(|s| s.fingerprint.clone()).unwrap_or_default(),
1945 cert: cert.fingerprint().to_hex(),
1946 addresses: vec![address.to_string()],
1947 ..KnownCard::default()
1948 }
1949 }
1950
1951 #[test]
1952 fn a_security_key_signs_alone_for_its_addresses() {
1953 let on_card = card_key("me@example.org", None);
1954 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
1955 let here = key("me@example.org");
1956 let mut keys = Keys { own: vec![on_card.clone().strip_secret_key_material(), here.clone()], ..Keys::default() };
1957 assert_eq!(keys.own_for("me@example.org").unwrap().place, Place::Here);
1958 keys.cards.push(record(&card, &on_card, "me@example.org"));
1959 let own = keys.own_for("ME@example.org").unwrap();
1960 assert_eq!(own.place, Place::Card { ident: "0006:12345678".into() });
1961 assert_eq!(own.cert.fingerprint(), on_card.fingerprint());
1962 assert!(autocrypt_header(own.cert, "me@example.org").is_some());
1964 assert!(keys.own_for("other@example.org").is_none());
1965 let late = card_key("late@example.org", Some(std::time::SystemTime::now() - std::time::Duration::from_secs(3 * 365 * 24 * 3600)));
1967 let late_card = securitykey::SoftCard::new("0006:00000002", &late, "123456");
1968 let keys = Keys { own: vec![late.clone().strip_secret_key_material(), key("late@example.org")], cards: vec![record(&late_card, &late, "late@example.org")], ..Keys::default() };
1969 assert!(keys.own_for("late@example.org").is_none());
1970 assert!(keys.card_for("late@example.org").is_some());
1971 }
1972
1973 fn read_here(root: &std::path::Path) {
1975 securitykey::share_cards_in(&root.join("cards.toml"), &root.join("own"), &root.join(securitykey::SHARED_CARDS), false).unwrap();
1976 }
1977
1978 fn carry(from: &std::path::Path, to: &std::path::Path) {
1980 let (from, to) = (from.join(securitykey::SHARED_CARDS), to.join(securitykey::SHARED_CARDS));
1981 let _ = std::fs::remove_dir_all(&to);
1982 std::fs::create_dir_all(&to).unwrap();
1983 for entry in std::fs::read_dir(&from).unwrap().filter_map(Result::ok) {
1984 std::fs::copy(entry.path(), to.join(entry.file_name())).unwrap();
1985 }
1986 }
1987
1988 #[test]
1995 fn your_security_keys_certificate_travels_and_only_it_is_yours() {
1996 let root = temporary().join("cards-travel");
1997 let _ = std::fs::remove_dir_all(&root);
1998 let (computer, phone) = (root.join("computer"), root.join("phone"));
1999 let on_card = card_key("me@example.org", None);
2000 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
2001 store_in(&computer, on_card.clone().strip_secret_key_material(), true).unwrap();
2002 securitykey::change_known_at(&computer.join("cards.toml"), |cards| cards.push(record(&card, &on_card, "me@example.org"))).unwrap();
2003 read_here(&computer);
2004 let shared = std::fs::read_to_string(computer.join("cards").join("0006-12345678.toml")).unwrap();
2005 assert!(shared.contains("BEGIN PGP PUBLIC KEY BLOCK") && !shared.contains("PRIVATE KEY"), "public parts only");
2006 carry(&computer, &phone);
2008 let keys = Keys::load_at(&phone);
2009 assert_eq!((keys.cards.len(), keys.shared.len()), (0, 1), "known from the computer, not read here");
2010 assert!(keys.own_for("me@example.org").is_none(), "signing only where it was read (F18)");
2011 assert_eq!(keys.serving_card("me@example.org").map(|(card, _)| card.ident.as_str()), Some("0006:12345678"));
2012 let yours = keys.self_cert_for("me@example.org").expect("yours to encrypt to");
2013 assert_eq!(yours.fingerprint(), on_card.fingerprint());
2014 assert!(autocrypt_header(yours, "me@example.org").is_some(), "Autocrypt from the phone too");
2015 let none = |_: &Fingerprint| None;
2017 let sessions = SessionKeys::new();
2018 let unlock = Unlock::new(&none, &sessions);
2019 let sealed = encrypt(b"Hello me.", &[yours], None).unwrap();
2020 assert!(decrypt(sealed.as_bytes(), &keys, &unlock).is_err());
2021 assert_eq!(unlock.wanted().as_deref(), Some("0006:12345678"));
2022 let readers = securitykey::SoftReaders::with(vec![securitykey::SoftCard::new("0006:12345678", &on_card, "123456").with_x25519(securitykey::x25519::x25519)]);
2024 let pin = Password::from("123456");
2025 let access = CardAccess::new(&readers, &pin, &|| {});
2026 assert_eq!(decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions).with_card(&access)).unwrap().data, b"Hello me.");
2027 assert!(learn_autocrypt_at(&phone, &autocrypt_header(yours, "me@example.org").unwrap(), "me@example.org").is_none());
2031 assert!(learn_at(&phone, &on_card.clone().strip_secret_key_material().armored().to_vec().unwrap(), "me@example.org").is_empty());
2032 assert!(!phone.join("others").join(format!("{}.asc", on_card.fingerprint().to_hex())).exists());
2033 let stranger = key("me@example.org");
2035 store_in(&phone, stranger.clone().strip_secret_key_material(), false).unwrap();
2036 let keys = Keys::load_at(&phone);
2037 assert_eq!(keys.for_address("me@example.org").map(Cert::fingerprint), Some(on_card.fingerprint()));
2038 assert!(keys.own.iter().all(|c| c.fingerprint() != stranger.fingerprint()));
2039 let mut replaced: securitykey::SharedCard = toml::from_str(&shared).unwrap();
2041 replaced.certificate = String::from_utf8(stranger.clone().strip_secret_key_material().armored().to_vec().unwrap()).unwrap();
2042 std::fs::write(phone.join("cards").join("0006-12345678.toml"), toml::to_string(&replaced).unwrap()).unwrap();
2043 let keys = Keys::load_at(&phone);
2044 assert!(keys.shared.is_empty() && keys.serving_card("me@example.org").is_none() && keys.own.iter().all(|c| c.fingerprint() != stranger.fingerprint()), "replaced: refused");
2045 let _ = std::fs::remove_dir_all(&root);
2046 }
2047
2048 #[test]
2052 fn a_renewal_reaches_every_device_and_is_never_undone() {
2053 let root = temporary().join("cards-renewal");
2054 let _ = std::fs::remove_dir_all(&root);
2055 let (one, two, phone) = (root.join("one"), root.join("two"), root.join("phone"));
2056 let made = std::time::SystemTime::now() - std::time::Duration::from_secs(30 * 24 * 3600);
2057 let on_card = card_key("me@example.org", Some(made));
2058 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
2059 for computer in [&one, &two] {
2060 store_in(computer, on_card.clone().strip_secret_key_material(), true).unwrap();
2061 securitykey::change_known_at(&computer.join("cards.toml"), |cards| cards.push(record(&card, &on_card, "me@example.org"))).unwrap();
2062 read_here(computer);
2063 }
2064 let expires = |keys: &Keys| keys.serving_card("me@example.org").and_then(|(_, cert)| cert.with_policy(&policy(), None).ok()?.primary_key().key_expiration_time());
2065 carry(&one, &phone);
2066 let before = expires(&Keys::load_at(&phone)).unwrap();
2067 let mut signer = on_card.primary_key().key().clone().parts_into_secret().unwrap().into_keypair().unwrap();
2069 let later = std::time::SystemTime::now() + std::time::Duration::from_secs(2 * 365 * 24 * 3600);
2070 let signatures = on_card.set_expiration_time(&policy(), None, &mut signer, Some(later)).unwrap();
2071 let renewed = on_card.clone().insert_packets(signatures).unwrap().0;
2072 store_in(&one, renewed.strip_secret_key_material(), true).unwrap();
2073 carry(&one, &phone);
2074 let after = expires(&Keys::load_at(&phone)).unwrap();
2075 assert!(after > before, "renewed on the phone too");
2076 carry(&one, &two);
2078 securitykey::change_known_at(&two.join("cards.toml"), |cards| cards[0].holder = "Card Holder".into()).unwrap();
2079 read_here(&two);
2080 carry(&two, &phone);
2081 assert_eq!(expires(&Keys::load_at(&phone)), Some(after), "never undone");
2082 let old = card_key("old@example.org", Some(std::time::SystemTime::now() - std::time::Duration::from_secs(400 * 24 * 3600)));
2084 let old_card = securitykey::SoftCard::new("0006:00000001", &old, "123456");
2085 let keys = Keys { own: vec![old.clone().strip_secret_key_material()], cards: vec![record(&old_card, &old, "old@example.org")], ..Keys::default() };
2086 assert!(keys.is_own_address("old@example.org") && keys.self_cert_for("old@example.org").is_none() && keys.for_address("old@example.org").is_none());
2087 let _ = std::fs::remove_dir_all(&root);
2088 }
2089
2090 fn marks(root: &std::path::Path) -> std::path::PathBuf {
2092 root.join("marks.toml")
2093 }
2094
2095 fn exchange_pass(root: &std::path::Path) {
2097 securitykey::share_cards_marked_in(&root.join("cards.toml"), &root.join("own"), &root.join(securitykey::SHARED_CARDS), &marks(root)).unwrap();
2098 }
2099
2100 #[test]
2107 fn a_card_stopped_on_one_computer_never_comes_back_to_it() {
2108 let root = temporary().join("cards-stopped");
2109 let _ = std::fs::remove_dir_all(&root);
2110 let (one, two, phone) = (root.join("one"), root.join("two"), root.join("phone"));
2111 let on_card = card_key("me@example.org", None);
2112 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
2113 for computer in [&one, &two] {
2114 store_in(computer, on_card.clone().strip_secret_key_material(), true).unwrap();
2115 securitykey::change_known_at(&computer.join("cards.toml"), |cards| cards.push(record(&card, &on_card, "me@example.org"))).unwrap();
2116 exchange_pass(computer);
2117 }
2118 carry(&one, &phone);
2119 let file = |root: &std::path::Path| root.join(securitykey::SHARED_CARDS).join("0006-12345678.toml");
2120 assert!(file(&two).exists() && file(&phone).exists());
2121 assert!(securitykey::forget_in(&one, "0006:12345678").unwrap());
2123 assert!(Keys::load_at(&one).own_for("me@example.org").is_none());
2124 carry(&one, &two);
2125 carry(&one, &phone);
2126 exchange_pass(&two);
2128 assert!(!file(&two).exists(), "never made again by the exchange's pass");
2129 securitykey::change_known_at(&two.join("cards.toml"), |cards| cards[0].holder = "Ferrand<<Noa".into()).unwrap();
2132 read_here(&two);
2133 carry(&two, &one);
2134 let first = Keys::load_at(&one);
2135 assert!(first.own_for("me@example.org").is_none() && first.shared.is_empty() && first.serving_card("me@example.org").is_none(), "stopped there");
2136 assert_eq!(Keys::load_at(&two).own_for("me@example.org").map(|o| o.place), Some(Place::Card { ident: "0006:12345678".into() }));
2138 carry(&two, &phone);
2139 let on_phone = Keys::load_at(&phone);
2140 assert!(on_phone.self_cert_for("me@example.org").is_some() && on_phone.own_for("me@example.org").is_none());
2141 let _ = std::fs::remove_dir_all(&root);
2142 }
2143
2144 #[test]
2149 fn a_card_read_on_another_computer_signs_only_there() {
2150 let root = temporary().join("cards-f18");
2151 let _ = std::fs::remove_dir_all(&root);
2152 let (one, three) = (root.join("one"), root.join("three"));
2153 let on_card = card_key("me@example.org", None);
2154 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
2155 store_in(&one, on_card.clone().strip_secret_key_material(), true).unwrap();
2156 securitykey::change_known_at(&one.join("cards.toml"), |cards| cards.push(record(&card, &on_card, "me@example.org"))).unwrap();
2157 exchange_pass(&one);
2158 let here = key("me@example.org");
2159 store_at(&three, here.clone()).unwrap();
2160 carry(&one, &three);
2161 let keys = Keys::load_at(&three);
2162 let signer = keys.own_for("me@example.org").expect("the key kept here signs");
2163 assert_eq!((signer.place, signer.cert.fingerprint()), (Place::Here, here.fingerprint()));
2164 assert_eq!(keys.self_cert_for("me@example.org").map(Cert::fingerprint), Some(on_card.fingerprint()));
2165 assert!(autocrypt_header(keys.self_cert_for("me@example.org").unwrap(), "me@example.org").is_some());
2166 let _ = std::fs::remove_dir_all(&root);
2167 }
2168
2169 #[test]
2173 fn a_shared_certificate_counts_only_with_the_cards_keys_bound() {
2174 let on_card = card_key("me@example.org", None);
2175 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
2176 let known = record(&card, &on_card, "me@example.org");
2177 let armored = |c: &Cert| String::from_utf8(c.clone().strip_secret_key_material().armored().to_vec().unwrap()).unwrap();
2178 assert!(securitykey::vouched(&known, &armored(&on_card)).is_some());
2179 let unbound = Cert::from_packets(on_card.clone().into_packets().filter(|p| !matches!(p, openpgp::Packet::Signature(sig) if sig.typ() == openpgp::types::SignatureType::SubkeyBinding))).unwrap();
2180 assert!(on_card.keys().subkeys().count() == 1 && unbound.keys().subkeys().count() == 1);
2181 assert!(securitykey::vouched(&known, &armored(&unbound)).is_none(), "no binding");
2182 let mut signer = on_card.primary_key().key().clone().parts_into_secret().unwrap().into_keypair().unwrap();
2183 let subkey = on_card.keys().subkeys().next().unwrap().key().clone();
2184 let revocation = SubkeyRevocationBuilder::new().set_reason_for_revocation(openpgp::types::ReasonForRevocation::KeyCompromised, b"lost").unwrap().build(&mut signer, &on_card, &subkey, None).unwrap();
2185 let revoked = on_card.clone().insert_packets(vec![openpgp::Packet::from(revocation)]).unwrap().0;
2186 assert!(securitykey::vouched(&known, &armored(&revoked)).is_none(), "revoked");
2187 assert_eq!(securitykey::revoked_encryption(&revoked), Some(subkey.fingerprint().to_hex()));
2188 assert_eq!(securitykey::revoked_encryption(&on_card), None);
2189 }
2190
2191 #[test]
2196 fn a_renewal_lost_to_an_older_write_comes_back_at_the_next_exchange() {
2197 let root = temporary().join("cards-renewal-lost");
2198 let _ = std::fs::remove_dir_all(&root);
2199 let (one, two, phone) = (root.join("one"), root.join("two"), root.join("phone"));
2200 let made = std::time::SystemTime::now() - std::time::Duration::from_secs(400 * 24 * 3600);
2201 let on_card = card_key("me@example.org", Some(made));
2202 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
2203 for computer in [&one, &two] {
2204 store_in(computer, on_card.clone().strip_secret_key_material(), true).unwrap();
2205 securitykey::change_known_at(&computer.join("cards.toml"), |cards| cards.push(record(&card, &on_card, "me@example.org"))).unwrap();
2206 exchange_pass(computer);
2207 }
2208 let mut signer = on_card.primary_key().key().clone().parts_into_secret().unwrap().into_keypair().unwrap();
2209 let later = std::time::SystemTime::now() + std::time::Duration::from_secs(2 * 365 * 24 * 3600);
2210 let mut packets = on_card.set_expiration_time(&policy(), None, &mut signer, Some(later)).unwrap();
2211 let subkey = on_card.keys().subkeys().next().unwrap().key().clone().parts_into_secret().unwrap();
2212 let primary = on_card.primary_key().key().clone();
2213 let binding = openpgp::packet::signature::SignatureBuilder::new(openpgp::types::SignatureType::SubkeyBinding)
2214 .set_key_flags(openpgp::types::KeyFlags::empty().set_transport_encryption().set_storage_encryption())
2215 .unwrap()
2216 .set_key_validity_period(later.duration_since(subkey.creation_time()).unwrap())
2217 .unwrap()
2218 .sign_subkey_binding(&mut signer, &primary, &subkey)
2219 .unwrap();
2220 packets.push(binding.into());
2221 let renewed = on_card.clone().insert_packets(packets).unwrap().0;
2222 store_in(&one, renewed.strip_secret_key_material(), true).unwrap();
2223 assert!(Keys::load_at(&one).self_cert_for("me@example.org").is_some(), "renewed on the first");
2224 securitykey::change_known_at(&two.join("cards.toml"), |cards| cards[0].holder = "Ferrand<<Noa".into()).unwrap();
2226 read_here(&two);
2227 carry(&two, &one);
2228 carry(&two, &phone);
2229 assert!(Keys::load_at(&phone).self_cert_for("me@example.org").is_none(), "the race: expired on the phone");
2230 exchange_pass(&one);
2232 carry(&one, &phone);
2233 assert!(Keys::load_at(&phone).self_cert_for("me@example.org").is_some(), "renewed on the phone");
2234 let written = std::fs::read(one.join(securitykey::SHARED_CARDS).join("0006-12345678.toml")).unwrap();
2236 exchange_pass(&one);
2237 assert_eq!(std::fs::read(one.join(securitykey::SHARED_CARDS).join("0006-12345678.toml")).unwrap(), written);
2238 let _ = std::fs::remove_dir_all(&root);
2239 }
2240
2241 #[test]
2242 fn a_message_to_the_security_key_waits_for_it_then_opens_from_memory() {
2243 let on_card = card_key("me@example.org", None);
2244 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456").with_x25519(securitykey::x25519::x25519);
2245 let keys = Keys { own: vec![on_card.clone().strip_secret_key_material()], cards: vec![record(&card, &on_card, "me@example.org")], ..Keys::default() };
2246 let readers = securitykey::SoftReaders::with(vec![card]);
2247 let sealed = encrypt(b"Hello me.", &[&on_card], None).unwrap();
2248 let none = |_: &Fingerprint| None;
2249 let sessions = SessionKeys::new();
2250 let unlock = Unlock::new(&none, &sessions);
2252 assert!(decrypt(sealed.as_bytes(), &keys, &unlock).is_err());
2253 assert_eq!(unlock.wanted().as_deref(), Some("0006:12345678"));
2254 assert_eq!(readers.card("0006:12345678", |c| c.operations), Some(0));
2255 let wrong = Password::from("654321");
2257 let access = CardAccess::new(&readers, &wrong, &|| {});
2258 let unlock = Unlock::new(&none, &sessions).with_card(&access);
2259 assert!(decrypt(sealed.as_bytes(), &keys, &unlock).is_err());
2260 assert_eq!(access.failure(), Some(securitykey::CardError::WrongPin { left: 2 }));
2261 let pin = Password::from("123456");
2263 let access = CardAccess::new(&readers, &pin, &|| {});
2264 let opened = decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions).with_card(&access)).unwrap();
2265 assert_eq!(opened.data, b"Hello me.");
2266 assert_eq!(readers.card("0006:12345678", |c| c.operations), Some(1));
2267 let again = decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions)).unwrap();
2269 assert_eq!(again.data, b"Hello me.");
2270 assert_eq!(readers.card("0006:12345678", |c| c.operations), Some(1));
2271 let other = encrypt(b"Another.", &[&on_card], None).unwrap();
2273 assert!(decrypt(other.as_bytes(), &keys, &Unlock::new(&none, &sessions)).is_err());
2274 sessions.forget();
2275 assert!(decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions)).is_err());
2276 }
2277
2278 #[test]
2279 fn an_expired_security_key_still_opens_older_mail() {
2280 let then = std::time::SystemTime::now() - std::time::Duration::from_secs(3 * 365 * 24 * 3600);
2281 let late = card_key("late@example.org", Some(then));
2282 let policy = policy();
2284 let sent = then + std::time::Duration::from_secs(24 * 3600);
2285 let recipients: Vec<_> = late.keys().with_policy(&policy, sent).alive().for_transport_encryption().collect();
2286 assert!(!recipients.is_empty());
2287 let mut sealed = Vec::new();
2288 {
2289 let message = Message::new(&mut sealed);
2290 let message = Encryptor::for_recipients(message, recipients).build().unwrap();
2291 let mut literal = LiteralWriter::new(message).build().unwrap();
2292 literal.write_all(b"Older mail.").unwrap();
2293 literal.finalize().unwrap();
2294 }
2295 let card = securitykey::SoftCard::new("0006:00000002", &late, "123456").with_x25519(securitykey::x25519::x25519);
2296 let keys = Keys { own: vec![late.clone().strip_secret_key_material()], cards: vec![record(&card, &late, "late@example.org")], ..Keys::default() };
2297 let readers = securitykey::SoftReaders::with(vec![card]);
2298 let pin = Password::from("123456");
2299 let access = CardAccess::new(&readers, &pin, &|| {});
2300 let none = |_: &Fingerprint| None;
2301 let sessions = SessionKeys::new();
2302 let opened = decrypt(&sealed, &keys, &Unlock::new(&none, &sessions).with_card(&access)).unwrap();
2303 assert_eq!(opened.data, b"Older mail.");
2304 assert!(encrypt(b"New mail.", &[&late], None).is_err());
2306 }
2307
2308 #[test]
2309 fn a_security_key_certificate_comes_back_to_your_keys() {
2310 let root = temporary().join("pgp-card");
2311 let _ = std::fs::remove_dir_all(&root);
2312 let on_card = card_key("me@example.org", None);
2313 let name = format!("{}.asc", on_card.fingerprint().to_hex());
2314 let kept = store_in(&root, on_card.clone().strip_secret_key_material(), true).unwrap();
2315 assert!(kept.own);
2316 let file = std::fs::read_to_string(root.join("own").join(&name)).unwrap();
2317 assert!(file.starts_with("-----BEGIN PGP PUBLIC KEY BLOCK-----"), "public only: {file}");
2318 let header = autocrypt_header(&on_card, "me@example.org").unwrap();
2322 assert!(learn_autocrypt_at(&root, &header, "me@example.org").is_none());
2323 assert!(learn_at(&root, &on_card.armored().to_vec().unwrap(), "me@example.org").iter().all(|k| k.own));
2324 assert!(!root.join("others").join(&name).exists());
2325 let jane = key("jane@example.org").strip_secret_key_material();
2327 assert!(!store_at(&root, jane).unwrap().own);
2328 let _ = std::fs::remove_dir_all(&root);
2329 }
2330
2331 #[test]
2336 fn a_sealed_copy_keeps_the_protected_headers_inside() {
2337 let me = key("me@example.org");
2338 let inner = "Content-Type: multipart/mixed; boundary=\"b\"; protected-headers=\"v1\"\r\nSubject: Lease renewal for flat 4B\r\nFrom: Jane <jane@example.org>\r\nTo: Me <me@example.org>\r\nMessage-ID: <ph@example.org>\r\n\r\n--b\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThe renewal.\r\n--b--\r\n";
2339 let outer = "From: Jane <jane@example.org>\r\nTo: Me <me@example.org>\r\nSubject: ...\r\nMessage-ID: <ph@example.org>\r\n";
2340 let received = format!(
2341 "{outer}MIME-Version: 1.0\r\nContent-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"o\"\r\n\r\n--o\r\nContent-Type: application/pgp-encrypted\r\n\r\nVersion: 1\r\n\r\n--o\r\nContent-Type: application/octet-stream\r\n\r\n{}\r\n--o--\r\n",
2342 encrypt(inner.as_bytes(), &[&me], None).unwrap()
2343 );
2344 let keys = Keys { own: vec![me.clone()], ..Keys::default() };
2345 let sessions = SessionKeys::new();
2346 let passphrase = |_: &Fingerprint| Some("secret words".to_string());
2347 let copy = open(received.as_bytes(), &keys, &Unlock::new(&passphrase, &sessions)).unwrap().0.unwrap();
2348 assert!(looks_opened(©), "as Sioul wrote it before");
2349 let sealed = seal_message(©, &[&me]).unwrap();
2350 let head = String::from_utf8_lossy(&sealed[..header_end(&sealed)]).to_string();
2351 assert!(!head.contains("Lease renewal") && head.contains("Subject: ..."), "{head}");
2352 let again = open(&sealed, &keys, &Unlock::new(&passphrase, &sessions)).unwrap().0.unwrap();
2353 assert!(String::from_utf8_lossy(&again).contains("Subject: Lease renewal for flat 4B"));
2354 let late = CertBuilder::general_purpose(Some("Me <late@example.org>")).set_cipher_suite(CipherSuite::Cv25519).set_creation_time(std::time::SystemTime::now() - std::time::Duration::from_secs(400 * 24 * 3600)).set_validity_period(std::time::Duration::from_secs(365 * 24 * 3600)).generate().unwrap().0;
2356 assert!(encrypt(b"x", &[&late], None).is_err(), "nobody encrypts to it");
2357 assert!(seal_message(©, &[&late]).is_ok());
2358 assert_eq!(Keys { own: vec![late], ..Keys::default() }.own_certs().len(), 1);
2359 }
2360
2361 fn header_of(cert: &Cert, addr: &str) -> String {
2363 let mut bytes = Vec::new();
2364 cert.serialize(&mut bytes).unwrap();
2365 format!("addr={addr}; keydata={}", crate::lines::base64_encode(&bytes))
2366 }
2367
2368 #[test]
2376 fn an_autocrypt_header_never_makes_another_address_yours() {
2377 let root = temporary().join("pgp-p1");
2378 let _ = std::fs::remove_dir_all(&root);
2379 let me = card_key("me@example.org", None);
2380 store_in(&root, me.clone().strip_secret_key_material(), true).unwrap();
2381 let jane = key("jane@example.org");
2382 assert!(learn_autocrypt_at(&root, &autocrypt_header(&jane, "jane@example.org").unwrap(), "jane@example.org").is_some());
2383 let load = || Keys { addresses: vec!["me@example.org".into()], ..Keys::load_at(&root) };
2384 assert_eq!(load().for_address("jane@example.org").map(Cert::fingerprint), Some(jane.fingerprint()));
2385 let forged = Cert::from_packets(vec![openpgp::Packet::from(me.primary_key().key().clone()), openpgp::packet::UserID::from("Mallory <mallory@example.net>").into(), openpgp::packet::UserID::from("Jane <jane@example.org>").into()].into_iter()).unwrap();
2386 assert!(!names(&forged, "jane@example.org") && !names(&forged, "mallory@example.net"), "unbound user IDs name nothing");
2387 assert!(learn_autocrypt_at(&root, &header_of(&forged, "mallory@example.net"), "mallory@example.net").is_none());
2388 let keys = load();
2389 assert!(!keys.is_own_address("jane@example.org"));
2390 assert!(keys.is_own_address("me@example.org"));
2391 assert_eq!(keys.for_address("jane@example.org").map(Cert::fingerprint), Some(jane.fingerprint()));
2392 assert_eq!(keys.own.iter().find(|c| c.fingerprint() == me.fingerprint()).unwrap().userids().count(), 1, "nothing added to yours");
2393 let sealed = encrypt(b"Hello Jane.", &[keys.for_address("jane@example.org").unwrap()], None).unwrap();
2395 let sessions = SessionKeys::new();
2396 let passphrase = |_: &Fingerprint| Some("secret words".to_string());
2397 assert!(decrypt(sealed.as_bytes(), &Keys { own: vec![jane.clone()], ..Keys::default() }, &Unlock::new(&passphrase, &sessions)).is_ok());
2398 let _ = std::fs::remove_dir_all(&root);
2399 }
2400
2401 #[test]
2407 fn a_senders_key_naming_janes_address_never_becomes_janes() {
2408 let root = temporary().join("pgp-p2");
2409 let _ = std::fs::remove_dir_all(&root);
2410 let mallory = CertBuilder::new().add_userid("Mallory <mallory@example.net>").add_userid("Jane <jane@example.org>").set_cipher_suite(CipherSuite::Cv25519).add_transport_encryption_subkey().generate().unwrap().0;
2411 assert!(names(&mallory, "jane@example.org"), "bound by Mallory himself");
2412 let kept = learn_autocrypt_at(&root, &header_of(&mallory, "mallory@example.net"), "mallory@example.net").unwrap();
2413 assert_eq!(kept.names, vec!["Mallory <mallory@example.net>".to_string()]);
2414 let keys = Keys::load_at(&root);
2415 assert!(keys.for_address("jane@example.org").is_none(), "Jane's key unknown: none, never Mallory's");
2416 assert_eq!(keys.for_address("mallory@example.net").map(Cert::fingerprint), Some(mallory.fingerprint()));
2417 let year = std::time::Duration::from_secs(365 * 24 * 3600);
2419 let old = CertBuilder::general_purpose(Some("Jane <jane@example.org>")).set_cipher_suite(CipherSuite::Cv25519).set_creation_time(std::time::SystemTime::now() - year).generate().unwrap().0;
2420 let new = key("jane@example.org");
2421 for jane in [&new, &old] {
2422 assert!(learn_autocrypt_at(&root, &autocrypt_header(jane, "jane@example.org").unwrap(), "jane@example.org").is_some());
2423 }
2424 assert_eq!(Keys::load_at(&root).for_address("jane@example.org").map(Cert::fingerprint), Some(new.fingerprint()));
2425 let bob = CertBuilder::new().add_userid("Bob <bob@example.org>").add_userid("Robert <robert@example.net>").set_cipher_suite(CipherSuite::Cv25519).add_transport_encryption_subkey().generate().unwrap().0;
2427 let found = learn_at(&root, &bob.armored().to_vec().unwrap(), "bob@example.org");
2428 assert_eq!(found.iter().map(|k| k.names.clone()).collect::<Vec<_>>(), vec![vec!["Bob <bob@example.org>".to_string()]]);
2429 assert!(Keys::load_at(&root).for_address("robert@example.net").is_none());
2430 let me = key("me@example.org");
2433 store_at(&root, me.clone()).unwrap();
2434 let mut signer = me.primary_key().key().clone().parts_into_secret().unwrap().decrypt_secret(&Password::from("secret words")).unwrap().into_keypair().unwrap();
2435 let other = openpgp::packet::UserID::from("Me <other@example.net>");
2436 let binding = other.bind(&mut signer, &me, openpgp::packet::signature::SignatureBuilder::new(openpgp::types::SignatureType::PositiveCertification)).unwrap();
2437 let more = me.clone().insert_packets(vec![openpgp::Packet::from(other), binding.into()]).unwrap().0.strip_secret_key_material();
2438 assert!(learn_at(&root, &more.armored().to_vec().unwrap(), "other@example.net").iter().all(|k| k.own));
2439 let mine = Keys::load_at(&root).own.into_iter().find(|c| c.fingerprint() == me.fingerprint()).unwrap();
2440 assert_eq!(mine.userids().map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).collect::<Vec<_>>(), vec!["Someone <me@example.org>".to_string()]);
2441 let _ = std::fs::remove_dir_all(&root);
2442 }
2443
2444 fn open_key(userids: &[&str], ago: std::time::Duration) -> Cert {
2447 let mut builder = CertBuilder::new().set_cipher_suite(CipherSuite::Cv25519).add_transport_encryption_subkey().set_creation_time(std::time::SystemTime::now() - ago);
2448 for userid in userids {
2449 builder = builder.add_userid(*userid);
2450 }
2451 builder.generate().unwrap().0
2452 }
2453
2454 fn signer_of(cert: &Cert) -> crypto::KeyPair {
2455 cert.primary_key().key().clone().parts_into_secret().unwrap().into_keypair().unwrap()
2456 }
2457
2458 fn public(cert: &Cert) -> Vec<u8> {
2459 cert.clone().strip_secret_key_material().armored().to_vec().unwrap()
2460 }
2461
2462 fn revoked(cert: &Cert) -> Cert {
2463 let revocation = CertRevocationBuilder::new().set_reason_for_revocation(openpgp::types::ReasonForRevocation::KeyCompromised, b"stolen").unwrap().build(&mut signer_of(cert), cert, None).unwrap();
2464 cert.clone().insert_packets(vec![openpgp::Packet::from(revocation)]).unwrap().0
2465 }
2466
2467 #[test]
2475 fn a_key_kept_before_serves_no_address_it_was_not_learned_for() {
2476 let root = temporary().join("pgp-n2");
2477 let _ = std::fs::remove_dir_all(&root);
2478 let jane = open_key(&["Jane <jane@example.org>"], std::time::Duration::from_secs(365 * 24 * 3600));
2479 learn_autocrypt_at(&root, &autocrypt_header(&jane, "jane@example.org").unwrap(), "jane@example.org").unwrap();
2480 let mallory = open_key(&["Mallory <mallory@example.net>", "Jane <jane@example.org>"], std::time::Duration::ZERO);
2481 let legacy = root.join("others").join(format!("{}.asc", mallory.fingerprint().to_hex()));
2482 std::fs::write(&legacy, public(&mallory)).unwrap();
2483 let keys = Keys::load_at(&root);
2484 assert_eq!(keys.for_address("jane@example.org").map(Cert::fingerprint), Some(jane.fingerprint()), "Jane's own key, never the newer one naming her");
2485 assert!(keys.for_address("mallory@example.net").is_none(), "which address it came for is not known: none until it comes again");
2486 assert!(keys.others.iter().all(|c| c.fingerprint() == jane.fingerprint() || !bound(c, "jane@example.org")), "no other key names her");
2487 let kept = learn_autocrypt_at(&root, &header_of(&mallory.clone().strip_secret_key_material(), "mallory@example.net"), "mallory@example.net").unwrap();
2488 assert_eq!(kept.names, vec!["Mallory <mallory@example.net>".to_string()]);
2489 assert_eq!(Cert::from_bytes(&std::fs::read(&legacy).unwrap()).unwrap().userids().count(), 1, "Jane's user ID gone from his file");
2490 let keys = Keys::load_at(&root);
2491 assert_eq!(keys.for_address("mallory@example.net").map(Cert::fingerprint), Some(mallory.fingerprint()));
2492 assert_eq!(keys.for_address("jane@example.org").map(Cert::fingerprint), Some(jane.fingerprint()));
2493 remove_in(&root, &jane.fingerprint().to_hex()).unwrap();
2495 assert!(!root.join("others").join(format!("{}.toml", jane.fingerprint().to_hex())).exists(), "what it served goes with it");
2496 assert!(Keys::load_at(&root).for_address("jane@example.org").is_none());
2497 let _ = std::fs::remove_dir_all(&root);
2498 }
2499
2500 #[test]
2510 fn a_key_kept_before_bound_to_one_address_it_did_not_come_for_serves_none() {
2511 let root = temporary().join("pgp-r1");
2512 let _ = std::fs::remove_dir_all(&root);
2513 let mark = root.join("state").join("pgp-others.toml");
2514 let jane = open_key(&["Jane <jane@example.org>"], std::time::Duration::from_secs(365 * 24 * 3600));
2515 learn_autocrypt_at(&root, &autocrypt_header(&jane, "jane@example.org").unwrap(), "jane@example.org").unwrap();
2516 let bare = open_key(&["Jane <jane@example.org>"], std::time::Duration::ZERO);
2517 let mallory = bare.clone().insert_packets(vec![openpgp::Packet::from(openpgp::packet::UserID::from("Mallory <mallory@example.net>"))]).unwrap().0;
2518 assert!(bound(&mallory, "jane@example.org") && !bound(&mallory, "mallory@example.net"));
2519 assert!(autocrypt_key(&header_of(&mallory, "mallory@example.net"), "mallory@example.net").is_none(), "refused today");
2520 std::fs::write(root.join("others").join(format!("{}.asc", mallory.fingerprint().to_hex())), public(&mallory)).unwrap();
2521 let (jane_fp, mallory_fp) = (Some(jane.fingerprint()), Some(mallory.fingerprint()));
2522 assert_eq!(Keys::load_at(&root).for_address("jane@example.org").map(Cert::fingerprint), jane_fp, "never the newer key of his");
2523 assert_eq!(keep_others_for_their_addresses_at(&root, &mark).unwrap(), 1, "Jane's was learned: its file is there");
2524 let said = record_of(&root.join("others"), &mallory).unwrap();
2525 assert!(said.addresses.is_empty() && said.judged.is_empty());
2526 assert_eq!(Keys::load_at(&root).for_address("jane@example.org").map(Cert::fingerprint), jane_fp);
2527 remove_in(&root, &jane.fingerprint().to_hex()).unwrap();
2528 assert!(Keys::load_at(&root).for_address("jane@example.org").is_none(), "without her key, still never his");
2529 let own = openpgp::packet::UserID::from("Mallory <mallory@example.net>");
2531 let binding = own.bind(&mut signer_of(&bare), &bare, openpgp::packet::signature::SignatureBuilder::new(openpgp::types::SignatureType::PositiveCertification)).unwrap();
2532 let bound_too = bare.clone().insert_packets(vec![openpgp::Packet::from(own), binding.into()]).unwrap().0;
2533 assert!(learn_autocrypt_at(&root, &header_of(&bound_too, "mallory@example.net"), "mallory@example.net").is_some());
2534 let keys = Keys::load_at(&root);
2535 assert_eq!(keys.for_address("mallory@example.net").map(Cert::fingerprint), mallory_fp);
2536 assert!(keys.for_address("jane@example.org").is_none());
2537 let said = record_of(&root.join("others"), &mallory).unwrap();
2538 assert_eq!((said.addresses, said.judged), (vec!["mallory@example.net".to_string()], Vec::<String>::new()));
2539 let bob = open_key(&["Bob <bob@example.org>", "Robert <bob@example.org>"], std::time::Duration::ZERO);
2541 assert_eq!(judged(&bob), vec!["bob@example.org".to_string()]);
2542 let _ = std::fs::remove_dir_all(&root);
2543 }
2544
2545 #[test]
2554 fn others_keys_say_what_they_serve_and_an_older_write_adds_none() {
2555 let root = temporary().join("pgp-n2-migration");
2556 let _ = std::fs::remove_dir_all(&root);
2557 let mark = root.join("state").join("pgp-others.toml");
2558 let others = root.join("others");
2559 std::fs::create_dir_all(&others).unwrap();
2560 let file = |cert: &Cert, end: &str| others.join(format!("{}.{end}", cert.fingerprint().to_hex()));
2561 let bob = open_key(&["Bob <bob@example.org>"], std::time::Duration::ZERO);
2562 let carol = open_key(&["Carol <carol@work.example>", "Carol <carol@home.example>"], std::time::Duration::ZERO);
2563 for cert in [&bob, &carol] {
2564 std::fs::write(file(cert, "asc"), public(cert)).unwrap();
2565 }
2566 assert_eq!(keep_others_for_their_addresses_at(&root, &mark).unwrap(), 2);
2567 assert_eq!(keep_others_for_their_addresses_at(&root, &mark).unwrap(), 0, "once");
2568 let said = |cert: &Cert| toml::from_str::<Served>(&std::fs::read_to_string(file(cert, "toml")).unwrap()).unwrap();
2569 assert_eq!((said(&bob).format, said(&bob).addresses), (2, vec!["bob@example.org".to_string()]));
2570 assert!(said(&carol).addresses.is_empty());
2571 let jane_id = openpgp::packet::UserID::from("Bob <jane@example.org>");
2573 let binding = jane_id.bind(&mut signer_of(&bob), &bob, openpgp::packet::signature::SignatureBuilder::new(openpgp::types::SignatureType::PositiveCertification)).unwrap();
2574 let more = bob.clone().insert_packets(vec![openpgp::Packet::from(jane_id), binding.into()]).unwrap().0;
2575 assert!(bound(&more, "jane@example.org"));
2576 std::fs::write(file(&bob, "asc"), public(&more)).unwrap();
2577 let dave = open_key(&["Dave <dave@example.org>", "Dave <jane@example.org>"], std::time::Duration::ZERO);
2579 std::fs::write(file(&dave, "asc"), public(&dave)).unwrap();
2580 let keys = Keys::load_at(&root);
2581 assert_eq!(keys.for_address("bob@example.org").map(Cert::fingerprint), Some(bob.fingerprint()));
2582 assert!(keys.for_address("jane@example.org").is_none(), "neither Bob's nor Dave's");
2583 assert!(keys.for_address("dave@example.org").is_none());
2584 assert!(keys.for_address("carol@work.example").is_none() && keys.for_address("carol@home.example").is_none());
2585 assert!(learn_autocrypt_at(&root, &header_of(&carol.clone().strip_secret_key_material(), "carol@work.example"), "carol@work.example").is_some());
2587 let keys = Keys::load_at(&root);
2588 assert_eq!(keys.for_address("carol@work.example").map(Cert::fingerprint), Some(carol.fingerprint()));
2589 assert!(keys.for_address("carol@home.example").is_none());
2590 assert!(learn_autocrypt_at(&root, &header_of(&carol.clone().strip_secret_key_material(), "carol@home.example"), "carol@home.example").is_some());
2591 let keys = Keys::load_at(&root);
2592 assert!(keys.for_address("carol@work.example").is_some() && keys.for_address("carol@home.example").is_some(), "learned for both: both");
2593 let erin = open_key(&["Erin <erin@work.example>", "Erin <erin@home.example>"], std::time::Duration::ZERO);
2595 import_at(&root, &public(&erin)).unwrap();
2596 let keys = Keys::load_at(&root);
2597 assert!(keys.for_address("erin@work.example").is_some() && keys.for_address("erin@home.example").is_some());
2598 let _ = std::fs::remove_dir_all(&root);
2599 }
2600
2601 #[test]
2610 fn a_revoked_certificate_never_serves() {
2611 let jane = open_key(&["Jane <jane@example.org>"], std::time::Duration::ZERO);
2612 let root = temporary().join("pgp-n3");
2613 let _ = std::fs::remove_dir_all(&root);
2614 assert!(learn_autocrypt_at(&root, &header_of(&revoked(&jane).strip_secret_key_material(), "jane@example.org"), "jane@example.org").is_some());
2615 assert!(Keys::load_at(&root).for_address("jane@example.org").is_none());
2616 assert!(encrypt(b"x", &[&revoked(&jane)], None).is_err(), "nothing is encrypted to it");
2617 let _ = std::fs::remove_dir_all(&root);
2619 learn_autocrypt_at(&root, &autocrypt_header(&jane, "jane@example.org").unwrap(), "jane@example.org").unwrap();
2620 assert!(Keys::load_at(&root).for_address("jane@example.org").is_some());
2621 assert!(!learn_at(&root, &public(&revoked(&jane)), "jane@example.org").is_empty(), "the revocation kept");
2622 assert!(Keys::load_at(&root).for_address("jane@example.org").is_none());
2623 let _ = std::fs::remove_dir_all(&root);
2625 let old = open_key(&["Jane <jane@example.org>"], std::time::Duration::from_secs(365 * 24 * 3600));
2626 learn_autocrypt_at(&root, &autocrypt_header(&old, "jane@example.org").unwrap(), "jane@example.org").unwrap();
2627 learn_at(&root, &public(&revoked(&jane)), "jane@example.org");
2628 assert_eq!(Keys::load_at(&root).for_address("jane@example.org").map(Cert::fingerprint), Some(old.fingerprint()));
2629 let me = open_key(&["Me <me@example.org>"], std::time::Duration::ZERO);
2631 let yours = Keys { own: vec![revoked(&me)], addresses: vec!["me@example.org".into()], ..Keys::default() };
2632 assert!(yours.own_certs().is_empty() && yours.self_cert_for("me@example.org").is_none() && yours.own_for("me@example.org").is_none());
2633 assert!(revoked_whole(&yours.own[0]) && names_address(&yours.own[0], "me@example.org"), "the window can say it");
2634 let sealed = encrypt(b"Before.", &[&me], None).unwrap();
2636 let none = |_: &Fingerprint| None;
2637 let sessions = SessionKeys::new();
2638 assert_eq!(decrypt(sealed.as_bytes(), &yours, &Unlock::new(&none, &sessions)).unwrap().data, b"Before.");
2639 let _ = std::fs::remove_dir_all(&root);
2640 }
2641
2642 #[test]
2650 fn a_designated_revokers_revocation_is_honoured_once_it_checks() {
2651 use openpgp::types::{ReasonForRevocation, RevocationKey};
2652 let root = temporary().join("pgp-r2");
2653 let _ = std::fs::remove_dir_all(&root);
2654 let naming = |userid: &str, revoker: &Cert| {
2655 CertBuilder::new()
2656 .set_cipher_suite(CipherSuite::Cv25519)
2657 .add_userid(userid)
2658 .add_transport_encryption_subkey()
2659 .add_signing_subkey()
2660 .set_creation_time(std::time::SystemTime::now() - std::time::Duration::from_secs(3600))
2661 .set_revocation_keys(vec![RevocationKey::new(revoker.primary_key().key().pk_algo(), revoker.fingerprint(), false)])
2662 .generate()
2663 .unwrap()
2664 .0
2665 };
2666 let revoke = |cert: &Cert, by: &Cert| {
2667 let revocation = CertRevocationBuilder::new().set_reason_for_revocation(ReasonForRevocation::KeyCompromised, b"stolen").unwrap().build(&mut signer_of(by), cert, None).unwrap();
2668 cert.clone().insert_packets(vec![openpgp::Packet::from(revocation)]).unwrap().0
2669 };
2670 let backup = open_key(&["Jane's backup <jane-backup@example.org>"], std::time::Duration::ZERO);
2671 let jane = naming("Jane <jane@example.org>", &backup);
2672 let revoked = revoke(&jane, &backup);
2673 assert!(usable(&revoked), "Sequoia alone: it could be");
2674 learn_autocrypt_at(&root, &header_of(&revoked, "jane@example.org"), "jane@example.org").unwrap();
2675 assert!(Keys::load_at(&root).for_address("jane@example.org").is_some(), "its revoker not kept here: nothing checks it");
2676 learn_autocrypt_at(&root, &header_of(&backup, "jane-backup@example.org"), "jane-backup@example.org").unwrap();
2677 let keys = Keys::load_at(&root);
2678 assert!(keys.for_address("jane@example.org").is_none(), "her revoker's revocation checks");
2679 let stored = keys.others.iter().find(|c| c.fingerprint() == jane.fingerprint()).unwrap();
2680 assert!(!keys.usable(stored));
2681 let mallory = open_key(&["Mallory <mallory@example.net>"], std::time::Duration::ZERO);
2683 let forged = revoke(&jane, &mallory);
2684 assert!(Keys { others: vec![forged.clone(), mallory.clone()], ..Keys::default() }.usable(&forged));
2685 let mine = open_key(&["Me backup <me-backup@example.org>"], std::time::Duration::ZERO);
2687 let me = revoke(&naming("Me <me@example.org>", &mine), &mine);
2688 let yours = Keys { own: vec![me.clone(), mine.clone()], addresses: vec!["me@example.org".into()], ..Keys::default() };
2689 assert!(yours.own_certs().iter().all(|c| c.fingerprint() != me.fingerprint()));
2690 assert!(yours.self_cert_for("me@example.org").is_none() && yours.own_for("me@example.org").is_none());
2691 let alone = Keys { own: vec![me.clone()], addresses: vec!["me@example.org".into()], ..Keys::default() };
2692 assert!(alone.self_cert_for("me@example.org").is_some() && alone.own_for("me@example.org").is_some(), "its revoker not kept: nothing checks it");
2693 let noa = open_key(&["Noa <noa@example.org>"], std::time::Duration::ZERO);
2695 let mut jane_signs = here_signer(&jane, "").unwrap();
2696 let signed = encrypt(b"The lease.", &[&noa], Some(&mut jane_signs)).unwrap();
2697 let none = |_: &Fingerprint| None;
2698 let sessions = SessionKeys::new();
2699 let reading = Keys { own: vec![noa.clone()], others: vec![revoked.clone(), backup.clone()], ..Keys::default() };
2700 let checked = decrypt(signed.as_bytes(), &reading, &Unlock::new(&none, &sessions)).unwrap().signatures;
2701 assert_eq!((checked[0].good, checked[0].problem.as_str()), (false, "revoked key"));
2702 let without = Keys { own: vec![noa.clone()], others: vec![revoked.clone()], ..Keys::default() };
2703 assert!(decrypt(signed.as_bytes(), &without, &Unlock::new(&none, &sessions)).unwrap().signatures[0].good, "its revoker unknown here");
2704 let _ = std::fs::remove_dir_all(&root);
2705 }
2706
2707 #[test]
2711 fn a_lookup_bringing_yours_adds_only_what_your_key_signed() {
2712 let root = temporary().join("pgp-lookup-yours");
2713 let _ = std::fs::remove_dir_all(&root);
2714 let me = open_key(&["Me <me@example.org>"], std::time::Duration::ZERO);
2715 store_in(&root, me.clone(), true).unwrap();
2716 let mallory = open_key(&["Mallory <mallory@example.net>"], std::time::Duration::ZERO);
2717 let stray = open_key(&[], std::time::Duration::ZERO).keys().subkeys().next().unwrap().key().clone();
2718 let binding = openpgp::packet::signature::SignatureBuilder::new(openpgp::types::SignatureType::SubkeyBinding)
2719 .set_key_flags(openpgp::types::KeyFlags::empty().set_transport_encryption())
2720 .unwrap()
2721 .sign_subkey_binding(&mut signer_of(&mallory), me.primary_key().key(), &stray)
2722 .unwrap();
2723 let mut packets: Vec<openpgp::Packet> = me.clone().strip_secret_key_material().into_packets().collect();
2724 packets.push(openpgp::Packet::from(stray.clone()));
2725 packets.push(binding.into());
2726 let polluted = Cert::from_packets(packets.into_iter()).unwrap();
2727 assert!(!learn_at(&root, &polluted.armored().to_vec().unwrap(), "me@example.org").is_empty());
2728 let stored = Cert::from_bytes(&std::fs::read(root.join("own").join(format!("{}.asc", me.fingerprint().to_hex()))).unwrap()).unwrap();
2729 assert_eq!(stored.keys().subkeys().count(), me.keys().subkeys().count(), "the stray subkey left out");
2730 assert!(stored.is_tsk(), "still your secret key");
2731 let _ = std::fs::remove_dir_all(&root);
2732 }
2733
2734 #[test]
2735 fn autocrypt_carries_the_smallest_key() {
2736 let alice = key("alice@example.org");
2737 let header = autocrypt_header(&alice, "alice@example.org").unwrap();
2738 assert!(header.starts_with("addr=alice@example.org; keydata="));
2739 assert!(header.split_whitespace().all(|word| word.len() <= 100), "foldable: {header}");
2740 let found = autocrypt_key(&header, "alice@example.org").unwrap();
2741 assert_eq!(found.fingerprint(), alice.fingerprint());
2742 assert!(!found.is_tsk());
2743 assert!(autocrypt_key(&header, "mallory@example.org").is_none());
2744 }
2745
2746 const GNUPG_2_4_4: &[u8] = include_bytes!("../tests/fixtures/pgp/gnupg-2.4.4-non-minimal-mpi.asc");
2751
2752 #[test]
2757 fn a_signature_gnupg_wrote_long_is_read_as_its_value() {
2758 let strict = Cert::from_bytes(GNUPG_2_4_4).unwrap();
2759 assert_eq!(securitykey::parts(&strict).len(), 2, "Sequoia alone: the primary key and one subkey, the other's binding dropped");
2760 let read = certificates_in(GNUPG_2_4_4);
2761 assert_eq!(read.len(), 1);
2762 let parts = securitykey::parts(&read[0]);
2763 assert_eq!(parts.len(), 3, "{parts:?}");
2764 assert!(parts[1..].iter().all(|part| part.role == securitykey::Role::Authentication), "{parts:?}");
2765 assert_eq!(securitykey::parts_in(GNUPG_2_4_4), parts);
2766 let policy = policy();
2768 let valid = read[0].with_policy(&policy, None).unwrap();
2769 assert_eq!(valid.keys().subkeys().count(), 2);
2770 let written = read[0].to_vec().unwrap();
2772 assert!(matches!(minimal_mpis(&written), std::borrow::Cow::Borrowed(_)));
2773 assert!(matches!(minimal_mpis(GNUPG_2_4_4), std::borrow::Cow::Owned(_)));
2774 }
2775
2776 #[test]
2781 fn numbers_written_long_come_back_shortest() {
2782 let alice = key("alice@example.org").strip_secret_key_material();
2783 let binary = alice.to_vec().unwrap();
2784 let armored = String::from_utf8(alice.armored().to_vec().unwrap()).unwrap();
2785 assert!(matches!(minimal_mpis(&binary), std::borrow::Cow::Borrowed(_)));
2786 assert!(matches!(minimal_mpis(armored.as_bytes()), std::borrow::Cow::Borrowed(_)));
2787 let long = written_long(&binary);
2788 assert!(Cert::from_bytes(&long).ok().is_none_or(|strict| securitykey::parts(&strict).is_empty()), "Sequoia alone refuses what is written long");
2789 assert_eq!(minimal_mpis(&long).as_ref(), binary.as_slice());
2790 assert_eq!(certificates_in(&long), [alice]);
2791 assert!(matches!(minimal_mpis(b"no key here"), std::borrow::Cow::Borrowed(_)));
2793 assert_eq!(minimal_mpis(b"no key here").as_ref(), b"no key here");
2794 let cut = &long[..long.len() - 5];
2795 assert_eq!(minimal_mpis(cut).as_ref(), cut);
2796 assert!(matches!(minimal_mpis(&[]), std::borrow::Cow::Borrowed(_)));
2797 }
2798
2799 fn numbers_at(body: &[u8]) -> usize {
2802 let mut at = 4;
2803 for _ in 0..2 {
2804 at += 2 + usize::from(u16::from_be_bytes([body[at], body[at + 1]]));
2805 }
2806 at + 2
2807 }
2808
2809 fn last_number_as(body: &[u8], new: impl Fn(usize, &[u8]) -> (usize, Vec<u8>)) -> Vec<u8> {
2813 let mut at = numbers_at(body);
2814 let mut last = at;
2815 while at < body.len() {
2816 last = at;
2817 at += 2 + usize::from(u16::from_be_bytes([body[at], body[at + 1]])).div_ceil(8);
2818 }
2819 let bits = usize::from(u16::from_be_bytes([body[last], body[last + 1]]));
2820 let (said, octets) = new(bits, &body[last + 2..]);
2821 let mut out = body[..last].to_vec();
2822 out.extend_from_slice(&u16::try_from(said).unwrap().to_be_bytes());
2823 out.extend_from_slice(&octets);
2824 out
2825 }
2826
2827 fn one_octet_long(bits: usize, value: &[u8]) -> (usize, Vec<u8>) {
2830 let octets = bits.div_ceil(8);
2831 (octets * 8 + 8, std::iter::once(0).chain(value[..octets].iter().copied()).collect())
2832 }
2833
2834 fn signatures_as(binary: &[u8], each: impl Fn(&[u8]) -> Vec<u8>) -> Vec<u8> {
2836 let mut out = Vec::new();
2837 let mut at = 0;
2838 while at < binary.len() {
2839 let (tag, body) = packet_at(binary, at).unwrap();
2840 let packet = &binary[body.clone()];
2841 put_packet(&mut out, tag, &if tag == 2 { each(packet) } else { packet.to_vec() });
2842 at = body.end;
2843 }
2844 out
2845 }
2846
2847 fn written_long(binary: &[u8]) -> Vec<u8> {
2849 signatures_as(binary, |body| last_number_as(body, one_octet_long))
2850 }
2851
2852 fn armored_block(binary: &[u8]) -> Vec<u8> {
2854 let mut out = Vec::new();
2855 let mut writer = openpgp::armor::Writer::new(&mut out, openpgp::armor::Kind::PublicKey).unwrap();
2856 writer.write_all(binary).unwrap();
2857 writer.finalize().unwrap();
2858 out
2859 }
2860
2861 #[test]
2867 fn concatenated_armored_keys_are_each_read_and_repaired() {
2868 let gnupg = certificates_in(GNUPG_2_4_4).remove(0);
2869 let bob = key("bob@example.org").strip_secret_key_material();
2870 let carol = key("carol@example.org").strip_secret_key_material();
2871 let blocks: [(&Cert, Vec<u8>); 3] = [(&gnupg, GNUPG_2_4_4.to_vec()), (&bob, bob.armored().to_vec().unwrap()), (&carol, armored_block(&written_long(&carol.to_vec().unwrap())))];
2872 let orders: [&[usize]; 12] = [&[0, 1], &[1, 0], &[1, 2], &[2, 1], &[0, 2], &[2, 0], &[0, 1, 2], &[0, 2, 1], &[1, 0, 2], &[1, 2, 0], &[2, 0, 1], &[2, 1, 0]];
2873 for order in orders {
2874 let mut file = Vec::new();
2875 for &i in order {
2876 file.extend_from_slice(&blocks[i].1);
2877 file.extend_from_slice(b"\nSomething said between the keys.\n");
2878 }
2879 let read = certificates_of(&file).unwrap();
2880 let fingerprints: Vec<Fingerprint> = read.iter().map(Cert::fingerprint).collect();
2881 let wanted: Vec<Fingerprint> = order.iter().map(|&i| blocks[i].0.fingerprint()).collect();
2882 assert_eq!(fingerprints, wanted, "{order:?}");
2883 for (cert, &i) in read.iter().zip(order) {
2884 assert_eq!(securitykey::parts(cert), securitykey::parts(blocks[i].0), "{order:?}: key {i} whole");
2885 }
2886 let alone: Vec<Fingerprint> = CertParser::from_bytes(&file).unwrap().filter_map(Result::ok).map(|c| c.fingerprint()).collect();
2887 assert!(alone.iter().all(|f| fingerprints.contains(f)), "{order:?}: never fewer than Sequoia alone");
2888 assert_eq!(matches!(minimal_mpis(&file), std::borrow::Cow::Owned(_)), order.contains(&0) || order.contains(&2), "{order:?}");
2889 }
2890 assert!(matches!(minimal_mpis(&blocks[1].1), std::borrow::Cow::Borrowed(_)));
2892 }
2893
2894 const SIGNING_SUBKEY: &[u8] = include_bytes!("../tests/fixtures/pgp/gnupg-2.4.9-signing-subkey.asc");
2897
2898 fn embedded_as(body: &[u8], each: impl Fn(&[u8]) -> Vec<u8>, to_hashed: bool) -> Vec<u8> {
2901 let hashed_end = 6 + usize::from(u16::from_be_bytes([body[4], body[5]]));
2902 let area_len = usize::from(u16::from_be_bytes([body[hashed_end], body[hashed_end + 1]]));
2903 let area = &body[hashed_end + 2..hashed_end + 2 + area_len];
2904 let (mut kept, mut embedded) = (Vec::new(), Vec::new());
2905 let mut at = 0;
2906 while at < area.len() {
2907 let length = usize::from(area[at]);
2909 assert!(length < 192);
2910 let (kind, inner) = (area[at + 1], &area[at + 2..at + 1 + length]);
2911 if kind & 0x7F == 32 {
2912 let written = each(inner);
2913 put_subpacket_length(&mut embedded, 1 + written.len());
2914 embedded.push(kind);
2915 embedded.extend_from_slice(&written);
2916 } else {
2917 kept.extend_from_slice(&area[at..at + 1 + length]);
2918 }
2919 at += 1 + length;
2920 }
2921 assert!(!embedded.is_empty(), "a back-signature there");
2922 let (hashed, unhashed) = if to_hashed { ([&body[6..hashed_end], &embedded[..]].concat(), kept) } else { (body[6..hashed_end].to_vec(), [&kept[..], &embedded[..]].concat()) };
2923 let mut out = body[..4].to_vec();
2924 out.extend_from_slice(&u16::try_from(hashed.len()).unwrap().to_be_bytes());
2925 out.extend_from_slice(&hashed);
2926 out.extend_from_slice(&u16::try_from(unhashed.len()).unwrap().to_be_bytes());
2927 out.extend_from_slice(&unhashed);
2928 out.extend_from_slice(&body[hashed_end + 2 + area_len..]);
2929 out
2930 }
2931
2932 #[test]
2938 fn a_back_signature_written_long_is_read_shortest_in_the_unhashed_area_only() {
2939 let canonical = Cert::from_bytes(SIGNING_SUBKEY).unwrap();
2940 let signs = |cert: &Cert| securitykey::parts(cert).iter().any(|part| part.role == securitykey::Role::Signing);
2941 assert!(signs(&canonical));
2942 let mut binary = Vec::new();
2943 std::io::Read::read_to_end(&mut openpgp::armor::Reader::from_bytes(SIGNING_SUBKEY, openpgp::armor::ReaderMode::Tolerant(None)), &mut binary).unwrap();
2944 let binding = |to_hashed: bool| move |body: &[u8]| if body[1] == 0x18 { embedded_as(body, |inner| last_number_as(inner, one_octet_long), to_hashed) } else { body.to_vec() };
2945 let long = signatures_as(&binary, binding(false));
2946 assert!(Cert::from_bytes(&long).ok().is_none_or(|strict| !signs(&strict)), "Sequoia alone drops the signing subkey");
2947 assert!(matches!(minimal_mpis(&long), std::borrow::Cow::Owned(_)));
2948 assert_eq!(Cert::from_bytes(minimal_mpis(&long).as_ref()).unwrap(), canonical);
2949 let read = certificates_in(&long);
2950 assert_eq!(read.len(), 1);
2951 assert!(signs(&read[0]));
2952 let hashed = signatures_as(&binary, binding(true));
2954 assert!(matches!(minimal_mpis(&hashed), std::borrow::Cow::Borrowed(_)), "the signed area as it came");
2955 }
2956
2957 #[test]
2962 fn a_length_said_too_short_is_left_as_it_came() {
2963 let dave = key("dave@example.org").strip_secret_key_material().to_vec().unwrap();
2964 let short_first = |body: &[u8]| -> Option<Vec<u8>> {
2966 let first = numbers_at(body);
2967 let bits = usize::from(u16::from_be_bytes([body[first], body[first + 1]]));
2968 if bits < 2 || (bits - 1).div_ceil(8) != bits.div_ceil(8) {
2969 return None;
2970 }
2971 let mut out = body.to_vec();
2972 out[first..first + 2].copy_from_slice(&u16::try_from(bits - 1).unwrap().to_be_bytes());
2973 Some(out)
2974 };
2975 let mut checked = 0;
2976 let mut at = 0;
2977 while at < dave.len() {
2978 let (tag, body) = packet_at(&dave, at).unwrap();
2979 if let Some(short) = (tag == 2).then(|| short_first(&dave[body.clone()])).flatten() {
2980 checked += 1;
2981 assert_eq!(signature_shortened(&short), None, "said short: as it came");
2982 assert_eq!(signature_shortened(&last_number_as(&short, one_octet_long)), None, "beside a number written long: as it came");
2983 }
2984 at = body.end;
2985 }
2986 assert!(checked > 0);
2987 let short_key = signatures_as(&dave, |body| short_first(body).unwrap_or_else(|| body.to_vec()));
2988 assert!(matches!(minimal_mpis(&short_key), std::borrow::Cow::Borrowed(_)));
2989 }
2990
2991 fn nesting(inner: Option<&[u8]>, numbers: &[u8]) -> Vec<u8> {
2994 let mut area = Vec::new();
2995 if let Some(inner) = inner {
2996 put_subpacket_length(&mut area, 1 + inner.len());
2997 area.push(32);
2998 area.extend_from_slice(inner);
2999 }
3000 let mut body = vec![4, 0x19, 22, 10, 0, 0];
3001 body.extend_from_slice(&u16::try_from(area.len()).unwrap().to_be_bytes());
3002 body.extend_from_slice(&area);
3003 body.extend_from_slice(&[0xAB, 0xCD]);
3004 body.extend_from_slice(numbers);
3005 body
3006 }
3007
3008 #[test]
3016 fn signatures_nested_deep_are_looked_into_one_level_only() {
3017 let shortest: &[u8] = &[0, 1, 1, 0, 1, 1];
3019 let long: &[u8] = &[0, 9, 0, 1, 0, 1, 1];
3020 let packet = |body: &[u8]| {
3021 let mut out = Vec::new();
3022 put_packet(&mut out, 2, body);
3023 out
3024 };
3025 let mut deep = nesting(None, long);
3026 let mut levels = 0;
3027 while deep.len() < 60_000 {
3028 deep = nesting(Some(&deep), shortest);
3029 levels += 1;
3030 }
3031 assert!(levels > 2_000, "{levels} levels");
3032 let file = packet(&nesting(Some(&deep), shortest));
3033 let read = std::thread::Builder::new().stack_size(256 * 1024).spawn(move || matches!(minimal_mpis(&file), std::borrow::Cow::Borrowed(_))).unwrap();
3034 assert!(read.join().unwrap(), "as it came, at once");
3035 let one = packet(&nesting(Some(&nesting(None, long)), shortest));
3037 assert!(matches!(minimal_mpis(&one), std::borrow::Cow::Owned(_)));
3038 let two = packet(&nesting(Some(&nesting(Some(&nesting(None, long)), shortest)), shortest));
3039 assert!(matches!(minimal_mpis(&two), std::borrow::Cow::Borrowed(_)));
3040 }
3041}