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
58fn others_dir() -> PathBuf {
59 pgp_dir().join("others")
60}
61
62#[derive(Default, Clone)]
65pub struct Keys {
66 pub own: Vec<Cert>,
67 pub others: Vec<Cert>,
68 pub cards: Vec<KnownCard>,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum Place {
75 Here,
77 Card {
79 ident: String,
81 },
82}
83
84#[derive(Debug, Clone)]
86pub struct Own<'a> {
87 pub cert: &'a Cert,
88 pub place: Place,
89}
90
91fn read_certs(dir: &std::path::Path) -> Vec<Cert> {
92 let Ok(entries) = std::fs::read_dir(dir) else { return Vec::new() };
93 entries
94 .filter_map(Result::ok)
95 .map(|e| e.path())
96 .filter(|p| p.extension().is_some_and(|x| x == "asc" || x == "pgp"))
97 .filter_map(|p| std::fs::read(&p).ok())
98 .flat_map(|bytes| CertParser::from_bytes(&bytes).map(|parser| parser.filter_map(Result::ok).collect::<Vec<_>>()).unwrap_or_default())
99 .collect()
100}
101
102fn names(cert: &Cert, address: &str) -> bool {
104 cert.userids().any(|u| u.userid().email().ok().flatten().is_some_and(|e| e.eq_ignore_ascii_case(address.trim())))
105}
106
107impl Keys {
108 pub fn load() -> Keys {
109 Keys { own: read_certs(&own_dir()), others: read_certs(&others_dir()), cards: securitykey::known_cards() }
110 }
111
112 pub fn own_for(&self, address: &str) -> Option<Own<'_>> {
117 if let Some((card, cert)) = self.card_for(address) {
118 return can_sign_with(cert, &card.sign).then(|| Own { cert, place: Place::Card { ident: card.ident.clone() } });
119 }
120 let policy = policy();
121 self.own
122 .iter()
123 .find(|c| names(c, address) && c.keys().secret().with_policy(&policy, None).alive().revoked(false).for_signing().next().is_some())
124 .map(|cert| Own { cert, place: Place::Here })
125 }
126
127 pub fn card_for(&self, address: &str) -> Option<(&KnownCard, &Cert)> {
130 self.cards.iter().filter(|card| card.serves(address)).find_map(|card| Some((card, self.cert(&card.cert)?)))
131 }
132
133 pub fn cert(&self, fingerprint: &str) -> Option<&Cert> {
135 self.own.iter().find(|c| c.fingerprint().to_hex().eq_ignore_ascii_case(fingerprint))
136 }
137
138 pub fn for_address(&self, address: &str) -> Option<&Cert> {
140 let policy = policy();
141 self.others.iter().chain(&self.own).filter(|c| names(c, address)).find(|c| c.keys().with_policy(&policy, None).alive().revoked(false).for_transport_encryption().next().is_some())
142 }
143
144 fn all(&self) -> impl Iterator<Item = &Cert> {
145 self.own.iter().chain(&self.others)
146 }
147
148 fn by_handle(&self, handle: &KeyHandle) -> Option<&Cert> {
149 self.all().find(|c| c.keys().any(|k| k.key().key_handle().aliases(handle)))
150 }
151}
152
153fn can_sign_with(cert: &Cert, fingerprint: &str) -> bool {
156 let policy = policy();
157 cert.keys().with_policy(&policy, None).alive().revoked(false).for_signing().any(|k| k.key().fingerprint().to_hex().eq_ignore_ascii_case(fingerprint))
158}
159
160pub fn key_of(cert: &Cert, fingerprint: &str) -> Option<securitykey::PublicKey> {
162 cert.keys().find(|k| k.key().fingerprint().to_hex().eq_ignore_ascii_case(fingerprint)).map(|k| k.key().clone())
163}
164
165#[derive(Debug, Clone, Serde, PartialEq, Eq)]
167pub struct KeySummary {
168 pub fingerprint: String,
169 pub names: Vec<String>,
171 pub own: bool,
172 pub created: String,
174 pub expires: String,
176 pub can_encrypt: bool,
177 pub card: String,
179 pub revocation: bool,
181}
182
183fn summary(cert: &Cert, own: bool) -> KeySummary {
184 let policy = policy();
185 let valid = cert.with_policy(&policy, None).ok();
186 let date = |t: std::time::SystemTime| jiff::Timestamp::try_from(t).map(|t| t.strftime("%Y-%m-%d").to_string()).unwrap_or_default();
187 KeySummary {
188 fingerprint: cert.fingerprint().to_hex(),
189 names: cert.userids().map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).collect(),
190 own,
191 created: date(cert.primary_key().key().creation_time()),
192 expires: valid.as_ref().and_then(|v| v.primary_key().key_expiration_time()).map(date).unwrap_or_default(),
193 can_encrypt: cert.keys().with_policy(&policy, None).alive().revoked(false).for_transport_encryption().next().is_some(),
194 card: String::new(),
195 revocation: false,
196 }
197}
198
199pub fn summaries() -> Vec<KeySummary> {
201 let keys = Keys::load();
202 let mut all: Vec<KeySummary> = keys.own.iter().map(|c| summary(c, true)).chain(keys.others.iter().map(|c| summary(c, false))).collect();
203 for key in all.iter_mut().filter(|k| k.own) {
204 if let Some(card) = keys.cards.iter().find(|c| c.cert.eq_ignore_ascii_case(&key.fingerprint)) {
205 key.card = card.ident.clone();
206 }
207 key.revocation = revocation_path(&key.fingerprint).is_file();
208 }
209 all
210}
211
212fn store(cert: Cert) -> Result<KeySummary, String> {
216 store_at(&pgp_dir(), cert)
217}
218
219fn store_at(root: &std::path::Path, cert: Cert) -> Result<KeySummary, String> {
221 let known_as_own = root.join("own").join(format!("{}.asc", cert.fingerprint().to_hex())).exists();
222 let own = cert.is_tsk() || known_as_own;
223 store_in(root, cert, own)
224}
225
226fn store_in(root: &std::path::Path, cert: Cert, own: bool) -> Result<KeySummary, String> {
227 let dir = root.join(if own { "own" } else { "others" });
228 std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
229 let path = dir.join(format!("{}.asc", cert.fingerprint().to_hex()));
230 let merged = match std::fs::read(&path).ok().and_then(|bytes| Cert::from_bytes(&bytes).ok()) {
231 Some(known) => known.merge_public_and_secret(cert).map_err(|e| e.to_string())?,
232 None => cert,
233 };
234 let armored = if merged.is_tsk() { merged.as_tsk().armored().to_vec() } else { merged.armored().to_vec() }.map_err(|e| e.to_string())?;
235 write_key(&path, &armored)?;
236 Ok(summary(&merged, own))
237}
238
239pub fn keep_card_certificate(cert: &Cert) -> Result<KeySummary, String> {
241 store_in(&pgp_dir(), cert.clone().strip_secret_key_material(), true)
242}
243
244fn write_key(path: &std::path::Path, armored: &[u8]) -> Result<(), String> {
247 let fail = |e: std::io::Error| format!("{}: {e}", path.display());
248 let temporary = path.with_extension("tmp");
249 let _ = std::fs::remove_file(&temporary);
251 let mut options = std::fs::OpenOptions::new();
252 options.write(true).create_new(true);
253 #[cfg(unix)]
254 {
255 use std::os::unix::fs::OpenOptionsExt;
256 options.mode(0o600);
257 }
258 let mut file = options.open(&temporary).map_err(fail)?;
259 file.write_all(armored).and_then(|()| file.sync_all()).map_err(fail)?;
260 drop(file);
261 std::fs::rename(&temporary, path).map_err(fail)
262}
263
264pub fn certificates_in(bytes: &[u8]) -> Vec<Cert> {
266 CertParser::from_bytes(bytes).map(|parser| parser.filter_map(Result::ok).collect()).unwrap_or_default()
267}
268
269pub fn import(bytes: &[u8]) -> Result<Vec<KeySummary>, String> {
271 let certs: Vec<Cert> = CertParser::from_bytes(bytes).map_err(|e| e.to_string())?.filter_map(Result::ok).collect();
272 if certs.is_empty() {
273 return Err("no OpenPGP key".into());
274 }
275 certs.into_iter().map(store).collect()
276}
277
278pub fn opens(fingerprint: &str, passphrase: &str) -> bool {
280 let keys = Keys::load();
281 let Some(cert) = keys.own.iter().find(|c| c.fingerprint().to_hex() == fingerprint) else { return false };
282 let policy = policy();
283 let password = Password::from(passphrase);
284 cert.keys().secret().with_policy(&policy, None).for_signing().next().is_some_and(|k| k.key().clone().decrypt_secret(&password).is_ok())
285}
286
287pub fn new_passphrase() -> Result<String, String> {
289 let mut bytes = [0u8; 24];
290 openpgp::crypto::random(&mut bytes).map_err(|e| e.to_string())?;
291 Ok(crate::lines::base64_encode(&bytes))
292}
293
294pub fn generate(name: &str, address: &str, passphrase: &str) -> Result<KeySummary, String> {
299 generate_at(&pgp_dir(), name, address, passphrase)
300}
301
302fn generate_at(root: &std::path::Path, name: &str, address: &str, passphrase: &str) -> Result<KeySummary, String> {
304 let userid = if name.trim().is_empty() { format!("<{}>", address.trim()) } else { format!("{} <{}>", name.trim(), address.trim()) };
305 let (cert, revocation) = CertBuilder::general_purpose(Some(userid))
306 .set_cipher_suite(CipherSuite::Cv25519)
307 .set_validity_period(std::time::Duration::from_secs(3 * 365 * 24 * 3600))
308 .set_password(Some(Password::from(passphrase)))
309 .generate()
310 .map_err(|e| e.to_string())?;
311 let own = root.join("own");
313 std::fs::create_dir_all(&own).map_err(|e| e.to_string())?;
314 let kept = own.join(format!("{}.rev", cert.fingerprint().to_hex()));
315 write_key(&kept, revocation_text(&cert, revocation)?.as_bytes())?;
316 match store_at(root, cert) {
317 Ok(summary) => Ok(KeySummary { revocation: true, ..summary }),
318 Err(e) => {
319 let _ = std::fs::remove_file(&kept);
320 Err(e)
321 }
322 }
323}
324
325pub fn revocation_path(fingerprint: &str) -> PathBuf {
329 own_dir().join(format!("{fingerprint}.rev"))
330}
331
332fn revocation_text(cert: &Cert, revocation: openpgp::packet::Signature) -> Result<String, String> {
336 let mut armored = Vec::new();
337 {
338 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())?;
339 openpgp::Packet::from(revocation).serialize(&mut writer).map_err(|e| e.to_string())?;
340 writer.finalize().map_err(|e| e.to_string())?;
341 }
342 let names: Vec<String> = cert.userids().map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).collect();
343 Ok(format!(
344 "This is the revocation certificate of the OpenPGP key\n{}\n{}\n\n\
345 Published, it tells everyone that this key must no longer be used, for\n\
346 good: it cannot be taken back. Keep a copy where only you can reach it,\n\
347 apart from the key, for the day the key is lost or stolen.\n\n\
348 A colon stands before the five dashes below, so that the certificate\n\
349 cannot be imported by mistake: take it away with a text editor before\n\
350 you import the certificate and publish it.\n\n:{}",
351 cert.fingerprint().to_hex(),
352 names.join("\n"),
353 String::from_utf8_lossy(&armored)
354 ))
355}
356
357pub fn save_revocation(fingerprint: &str, folder: &std::path::Path) -> Result<PathBuf, String> {
361 save_revocation_at(&pgp_dir(), fingerprint, folder)
362}
363
364fn save_revocation_at(root: &std::path::Path, fingerprint: &str, folder: &std::path::Path) -> Result<PathBuf, String> {
366 let text = std::fs::read(root.join("own").join(format!("{fingerprint}.rev"))).map_err(|e| e.to_string())?;
367 std::fs::create_dir_all(folder).map_err(|e| e.to_string())?;
368 let path = (1..)
369 .map(|n| folder.join(if n == 1 { format!("{fingerprint}.rev") } else { format!("{fingerprint}-{n}.rev") }))
370 .find(|p| !p.exists())
371 .unwrap_or_else(|| folder.join(format!("{fingerprint}.rev")));
372 write_key(&path, &text)?;
373 Ok(path)
374}
375
376pub fn export_public(fingerprint: &str) -> Option<String> {
378 let keys = Keys::load();
379 let cert = keys.all().find(|c| c.fingerprint().to_hex() == fingerprint)?;
380 cert.armored().to_vec().ok().map(|b| String::from_utf8_lossy(&b).to_string())
381}
382
383pub fn remove(fingerprint: &str) -> Result<(), String> {
386 for (dir, own) in [(own_dir(), true), (others_dir(), false)] {
387 let path = dir.join(format!("{fingerprint}.asc"));
388 if !path.exists() {
389 continue;
390 }
391 if own {
392 let aside = data_dir().join("pgp").join("removed");
393 std::fs::create_dir_all(&aside).map_err(|e| e.to_string())?;
394 std::fs::rename(&path, aside.join(format!("{fingerprint}.asc"))).map_err(|e| e.to_string())?;
395 let revocation = revocation_path(fingerprint);
396 if revocation.exists() {
397 std::fs::rename(&revocation, aside.join(format!("{fingerprint}.rev"))).map_err(|e| e.to_string())?;
398 }
399 } else {
400 std::fs::remove_file(&path).map_err(|e| e.to_string())?;
401 }
402 }
403 Ok(())
404}
405
406pub type Signing<'a> = dyn crypto::Signer + Send + Sync + 'a;
409
410pub fn here_signer(cert: &Cert, passphrase: &str) -> Result<crypto::KeyPair, String> {
413 let policy = policy();
414 let key = cert.keys().secret().with_policy(&policy, None).alive().revoked(false).for_signing().next().ok_or("no signing key")?.key().clone();
415 let key = if key.has_unencrypted_secret() { key } else { key.decrypt_secret(&Password::from(passphrase)).map_err(|_| "wrong passphrase".to_string())? };
416 key.into_keypair().map_err(|e| e.to_string())
417}
418
419struct Lent<'r, 'o>(&'r mut Signing<'o>);
422
423impl crypto::Signer for Lent<'_, '_> {
424 fn public(&self) -> &openpgp::packet::Key<key::PublicParts, key::UnspecifiedRole> {
425 self.0.public()
426 }
427
428 fn acceptable_hashes(&self) -> &[HashAlgorithm] {
429 self.0.acceptable_hashes()
430 }
431
432 fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> openpgp::Result<mpi::Signature> {
433 self.0.sign(hash_algo, digest)
434 }
435}
436
437pub fn sign_detached(data: &[u8], signer: &mut Signing<'_>) -> Result<(String, String), String> {
441 let mut out = Vec::new();
442 {
443 let message = Message::new(&mut out);
444 let message = Armorer::new(message).kind(openpgp::armor::Kind::Signature).build().map_err(|e| e.to_string())?;
445 let mut signing = Signer::new(message, Lent(signer)).map_err(|e| e.to_string())?.detached().build().map_err(|e| e.to_string())?;
446 signing.write_all(data).map_err(|e| e.to_string())?;
447 signing.finalize().map_err(|e| e.to_string())?;
448 }
449 let micalg = openpgp::Packet::from_bytes(&out)
450 .ok()
451 .and_then(|p| match p {
452 openpgp::Packet::Signature(s) => Some(format!("pgp-{}", s.hash_algo().to_string().to_ascii_lowercase())),
453 _ => None,
454 })
455 .unwrap_or_else(|| "pgp-sha512".into());
456 Ok((String::from_utf8_lossy(&out).to_string(), micalg))
457}
458
459pub fn encrypt(data: &[u8], recipients: &[&Cert], signer: Option<&mut Signing<'_>>) -> Result<String, String> {
461 let policy = policy();
462 let keys: Vec<_> = recipients.iter().flat_map(|c| c.keys().with_policy(&policy, None).supported().alive().revoked(false).for_transport_encryption()).collect();
463 if keys.is_empty() {
464 return Err("no encryption key".into());
465 }
466 let mut out = Vec::new();
467 {
468 let message = Message::new(&mut out);
469 let message = Armorer::new(message).build().map_err(|e| e.to_string())?;
470 let message = Encryptor::for_recipients(message, keys).build().map_err(|e| e.to_string())?;
471 let message = match signer {
472 Some(signer) => Signer::new(message, Lent(signer)).map_err(|e| e.to_string())?.build().map_err(|e| e.to_string())?,
473 None => message,
474 };
475 let mut literal = LiteralWriter::new(message).build().map_err(|e| e.to_string())?;
476 literal.write_all(data).map_err(|e| e.to_string())?;
477 literal.finalize().map_err(|e| e.to_string())?;
478 }
479 Ok(String::from_utf8_lossy(&out).to_string())
480}
481
482#[derive(Debug, Clone, Serde, PartialEq, Eq)]
484pub struct Checked {
485 pub good: bool,
486 pub signer: String,
488 pub fingerprint: String,
489 pub problem: String,
491}
492
493#[derive(Debug, Clone, Default)]
495pub struct Opened {
496 pub data: Vec<u8>,
497 pub signatures: Vec<Checked>,
498}
499
500#[derive(Default)]
504pub struct SessionKeys(Mutex<BTreeMap<[u8; 32], (Option<SymmetricAlgorithm>, SessionKey)>>);
505
506impl SessionKeys {
507 pub const fn new() -> SessionKeys {
509 SessionKeys(Mutex::new(BTreeMap::new()))
510 }
511
512 pub fn global() -> &'static SessionKeys {
514 static GLOBAL: SessionKeys = SessionKeys::new();
515 &GLOBAL
516 }
517
518 fn get(&self, id: &[u8; 32]) -> Option<(Option<SymmetricAlgorithm>, SessionKey)> {
519 self.0.lock().ok()?.get(id).cloned()
520 }
521
522 fn keep(&self, id: [u8; 32], algo: Option<SymmetricAlgorithm>, key: SessionKey) {
523 if let Ok(mut kept) = self.0.lock() {
524 kept.insert(id, (algo, key));
525 }
526 }
527
528 pub fn forget(&self) {
530 if let Ok(mut kept) = self.0.lock() {
531 kept.clear();
532 }
533 }
534}
535
536fn message_id(message: &[u8]) -> Option<[u8; 32]> {
538 let mut context = HashAlgorithm::SHA256.context().ok()?.for_digest();
539 context.update(message);
540 context.into_digest().ok()?.try_into().ok()
541}
542
543pub struct Unlock<'a> {
545 pub passphrase: &'a dyn Fn(&Fingerprint) -> Option<String>,
547 pub sessions: &'a SessionKeys,
549 pub card: Option<&'a CardAccess<'a>>,
552 wanted: RefCell<Option<String>>,
554}
555
556impl<'a> Unlock<'a> {
557 pub fn new(passphrase: &'a dyn Fn(&Fingerprint) -> Option<String>, sessions: &'a SessionKeys) -> Unlock<'a> {
559 Unlock { passphrase, sessions, card: None, wanted: RefCell::new(None) }
560 }
561
562 pub fn with_card(mut self, card: &'a CardAccess<'a>) -> Unlock<'a> {
564 self.card = Some(card);
565 self
566 }
567
568 pub fn wanted(&self) -> Option<String> {
571 self.wanted.borrow().clone()
572 }
573}
574
575struct Helper<'a> {
576 keys: &'a Keys,
577 unlock: &'a Unlock<'a>,
578 id: Option<[u8; 32]>,
580 checked: Vec<Checked>,
581}
582
583impl VerificationHelper for Helper<'_> {
584 fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
585 Ok(ids.iter().filter_map(|id| self.keys.by_handle(id).cloned()).collect())
586 }
587
588 fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
589 for layer in structure {
590 let MessageLayer::SignatureGroup { results } = layer else { continue };
591 for result in results {
592 self.checked.push(match result {
593 Ok(good) => Checked {
594 good: true,
595 signer: good.ka.cert().userids().next().map(|u| String::from_utf8_lossy(u.userid().value()).to_string()).unwrap_or_default(),
596 fingerprint: good.ka.cert().fingerprint().to_hex(),
597 problem: String::new(),
598 },
599 Err(error) => {
600 use openpgp::parse::stream::VerificationError as E;
601 let (fingerprint, problem) = match &error {
602 E::MissingKey { sig } => (sig.issuer_fingerprints().next().map(|f| f.to_hex()).unwrap_or_default(), "unknown key"),
603 E::BadSignature { .. } => (String::new(), "bad signature"),
604 E::BadKey { .. } => (String::new(), "bad key"),
605 E::MalformedSignature { .. } => (String::new(), "malformed signature"),
606 E::UnboundKey { .. } => (String::new(), "unbound key"),
607 _ => (String::new(), "unknown"),
608 };
609 Checked { good: false, signer: String::new(), fingerprint, problem: problem.to_string() }
610 }
611 });
612 }
613 }
614 Ok(())
616 }
617}
618
619impl DecryptionHelper for Helper<'_> {
620 fn decrypt(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> openpgp::Result<Option<Cert>> {
621 if let Some(id) = &self.id
623 && let Some((algo, session)) = self.unlock.sessions.get(id)
624 && decrypt(algo, &session)
625 {
626 return Ok(None);
627 }
628 let policy = policy();
629 for pkesk in pkesks {
630 for cert in &self.keys.own {
631 let passphrase = (self.unlock.passphrase)(&cert.fingerprint()).unwrap_or_default();
632 for key in cert.keys().secret().with_policy(&policy, None).for_transport_encryption().chain(cert.keys().secret().with_policy(&policy, None).for_storage_encryption()) {
633 let recipient_matches = pkesk.recipient().is_none_or(|r| key.key().key_handle().aliases(&r));
634 if !recipient_matches {
635 continue;
636 }
637 let secret = key.key().clone();
638 let secret = if secret.has_unencrypted_secret() { secret } else if let Ok(s) = secret.decrypt_secret(&Password::from(passphrase.as_str())) { s } else { continue };
639 let Ok(mut pair) = secret.into_keypair() else { continue };
640 if let Some((algo, session)) = pkesk.decrypt(&mut pair, sym_algo)
641 && decrypt(algo, &session)
642 {
643 return Ok(Some(cert.clone()));
644 }
645 }
646 }
647 }
648 for pkesk in pkesks {
651 let Some(recipient) = pkesk.recipient() else { continue };
652 for card in &self.keys.cards {
653 let Ok(fingerprint) = card.decrypt.parse::<Fingerprint>() else { continue };
654 if !KeyHandle::from(fingerprint).aliases(&recipient) {
655 continue;
656 }
657 let Some((cert, public)) = self.keys.cert(&card.cert).and_then(|cert| Some((cert, key_of(cert, &card.decrypt)?))) else { continue };
658 *self.unlock.wanted.borrow_mut() = Some(card.ident.clone());
659 let Some(access) = self.unlock.card else { continue };
660 let mut decryptor = CardDecryptor::new(access, &card.ident, public);
661 if let Some((algo, session)) = pkesk.decrypt(&mut decryptor, sym_algo)
662 && decrypt(algo, &session)
663 {
664 *self.unlock.wanted.borrow_mut() = None;
665 if let Some(id) = self.id {
666 self.unlock.sessions.keep(id, algo, session);
667 }
668 return Ok(Some(cert.clone()));
669 }
670 }
671 }
672 Err(openpgp::Error::InvalidOperation("no key of yours opens this message".into()).into())
673 }
674}
675
676const LARGEST_DECRYPTED: u64 = 256 * 1024 * 1024;
679
680pub fn decrypt(message: &[u8], keys: &Keys, unlock: &Unlock) -> Result<Opened, String> {
684 use std::io::Read as _;
685 let policy = policy();
686 let helper = Helper { keys, unlock, id: message_id(message), checked: Vec::new() };
687 let mut decryptor = DecryptorBuilder::from_bytes(message).map_err(|e| e.to_string())?.with_policy(&policy, None, helper).map_err(|e| e.to_string())?;
688 let mut data = Vec::new();
689 (&mut decryptor).take(LARGEST_DECRYPTED + 1).read_to_end(&mut data).map_err(|e| e.to_string())?;
690 if data.len() as u64 > LARGEST_DECRYPTED {
691 return Err(format!("larger than {LARGEST_DECRYPTED} bytes once decrypted"));
692 }
693 let helper = decryptor.into_helper();
694 Ok(Opened { data, signatures: helper.checked })
695}
696
697pub fn verify_detached(data: &[u8], signature: &[u8], keys: &Keys) -> Vec<Checked> {
699 let policy = policy();
700 let none = |_: &Fingerprint| None;
701 let sessions = SessionKeys::new();
702 let unlock = Unlock::new(&none, &sessions);
703 let helper = Helper { keys, unlock: &unlock, id: None, checked: Vec::new() };
704 let Ok(builder) = DetachedVerifierBuilder::from_bytes(signature) else { return Vec::new() };
705 let Ok(mut verifier) = builder.with_policy(&policy, None, helper) else { return Vec::new() };
706 let _ = verifier.verify_bytes(data);
707 verifier.into_helper().checked
708}
709
710pub fn wkd_urls(address: &str) -> Option<[String; 2]> {
713 let (local, domain) = address.trim().rsplit_once('@')?;
714 let domain = domain.to_ascii_lowercase();
715 let labels: Vec<&str> = domain.split('.').collect();
719 let named = labels.len() >= 2
720 && labels.iter().all(|l| !l.is_empty() && !l.starts_with('-') && !l.ends_with('-') && l.chars().all(|c| c.is_ascii_alphanumeric() || c == '-'))
721 && labels.last().is_some_and(|tld| tld.chars().any(|c| c.is_ascii_alphabetic()) && *tld != "localhost");
722 if !named {
723 return None;
724 }
725 let mut context = openpgp::types::HashAlgorithm::SHA1.context().ok()?.for_digest();
726 context.update(local.to_lowercase().as_bytes());
727 let digest = context.into_digest().ok()?;
728 let hash = zbase32(&digest);
729 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>();
730 Some([
731 format!("https://openpgpkey.{domain}/.well-known/openpgpkey/{domain}/hu/{hash}?l={query}"),
732 format!("https://{domain}/.well-known/openpgpkey/hu/{hash}?l={query}"),
733 ])
734}
735
736fn zbase32(bytes: &[u8]) -> String {
738 const ALPHABET: &[u8; 32] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
739 let mut out = String::new();
740 let (mut buffer, mut bits) = (0u32, 0u32);
741 for &byte in bytes {
742 buffer = (buffer << 8) | u32::from(byte);
743 bits += 8;
744 while bits >= 5 {
745 bits -= 5;
746 out.push(ALPHABET[((buffer >> bits) & 31) as usize] as char);
747 }
748 }
749 if bits > 0 {
750 out.push(ALPHABET[((buffer << (5 - bits)) & 31) as usize] as char);
751 }
752 out
753}
754
755pub fn learn(bytes: &[u8], address: &str) -> Vec<KeySummary> {
757 CertParser::from_bytes(bytes)
758 .map(|parser| parser.filter_map(Result::ok).filter(|c| names(c, address)).filter_map(|c| store(c.strip_secret_key_material()).ok()).collect())
759 .unwrap_or_default()
760}
761
762#[derive(Debug, Clone, Default, Serde, PartialEq, Eq)]
764pub struct PgpView {
765 pub encrypted: bool,
766 pub opened: bool,
768 pub signatures: Vec<Checked>,
769 pub security_key: Option<String>,
772}
773
774fn canonical(bytes: &[u8]) -> Vec<u8> {
776 let mut out = Vec::with_capacity(bytes.len() + bytes.len() / 32);
777 for (i, &b) in bytes.iter().enumerate() {
778 if b == b'\n' && (i == 0 || bytes[i - 1] != b'\r') {
779 out.push(b'\r');
780 }
781 out.push(b);
782 }
783 out
784}
785
786fn kind_of(part: &mail_parser::MessagePart) -> (String, String) {
787 use mail_parser::MimeHeaders;
788 part.content_type().map_or_else(Default::default, |t| {
789 (format!("{}/{}", t.ctype(), t.subtype().unwrap_or("")).to_ascii_lowercase(), t.attribute("protocol").unwrap_or("").to_ascii_lowercase())
790 })
791}
792
793fn outer_headers(raw: &[u8], body_offset: usize) -> Vec<u8> {
795 let head = String::from_utf8_lossy(&raw[..body_offset.min(raw.len())]).to_string();
796 let mut out = String::new();
797 let mut keep = false;
798 for line in head.split_inclusive('\n') {
799 if !line.starts_with([' ', '\t']) {
800 let name = line.split(':').next().unwrap_or("").trim().to_ascii_lowercase();
801 keep = !name.is_empty() && !name.starts_with("content-") && name != "mime-version";
802 }
803 if keep {
804 out.push_str(line);
805 }
806 }
807 canonical(out.trim_end().as_bytes()).into_iter().chain(b"\r\nMIME-Version: 1.0\r\n".iter().copied()).collect()
808}
809
810pub fn open(raw: &[u8], keys: &Keys, unlock: &Unlock) -> Option<(Option<Vec<u8>>, PgpView)> {
817 let message = mail_parser::MessageParser::default().parse(raw)?;
818 let root = message.parts.first()?;
819 let (kind, protocol) = kind_of(root);
820 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();
821 let closed = || PgpView { encrypted: true, opened: false, signatures: Vec::new(), security_key: unlock.wanted() };
822 if kind == "multipart/encrypted" && protocol == "application/pgp-encrypted" {
823 let payload = children.iter().find(|p| kind_of(p).0 == "application/octet-stream").map(|p| p.contents().to_vec())?;
824 return Some(match decrypt(&payload, keys, unlock) {
825 Ok(opened) => {
826 let mut virtual_message = outer_headers(raw, root.raw_body_offset() as usize);
827 virtual_message.extend(canonical(&opened.data));
828 let mut signatures = opened.signatures;
829 if let Some((_, inner)) = open(&virtual_message, keys, unlock) {
831 signatures.extend(inner.signatures);
832 }
833 (Some(virtual_message), PgpView { encrypted: true, opened: true, signatures, security_key: None })
834 }
835 Err(_) => (None, closed()),
836 });
837 }
838 if kind == "multipart/signed" && protocol == "application/pgp-signature" && children.len() >= 2 {
839 let signed = children[0];
840 let (start, end) = (signed.raw_header_offset() as usize, signed.raw_end_offset() as usize);
841 let data = canonical(raw.get(start..end)?);
842 let signature = children.iter().find(|p| kind_of(p).0 == "application/pgp-signature")?.contents().to_vec();
843 let mut checked = verify_detached(&data, &signature, keys);
846 if !checked.iter().any(|c| c.good) && data.ends_with(b"\r\n") {
847 let trimmed = verify_detached(&data[..data.len() - 2], &signature, keys);
848 if trimmed.iter().any(|c| c.good) {
849 checked = trimmed;
850 }
851 }
852 return Some((None, PgpView { encrypted: false, opened: true, signatures: checked, security_key: None }));
853 }
854 let text = message.body_text(0)?.to_string();
856 let start = text.find("-----BEGIN PGP MESSAGE-----")?;
857 let end = text[start..].find("-----END PGP MESSAGE-----").map(|e| start + e + "-----END PGP MESSAGE-----".len())?;
858 Some(match decrypt(text[start..end].as_bytes(), keys, unlock) {
859 Ok(opened) => {
860 let mut virtual_message = outer_headers(raw, root.raw_body_offset() as usize);
861 virtual_message.extend_from_slice(b"Content-Type: text/plain; charset=utf-8\r\n\r\n");
862 virtual_message.extend(canonical(&opened.data));
863 (Some(virtual_message), PgpView { encrypted: true, opened: true, signatures: opened.signatures, security_key: None })
864 }
865 Err(_) => (None, closed()),
866 })
867}
868
869pub fn autocrypt_key(header: &str, from: &str) -> Option<Cert> {
871 let mut addr = None;
872 let mut keydata = None;
873 for part in header.split(';') {
874 let (name, value) = part.split_once('=')?;
875 match name.trim() {
876 "addr" => addr = Some(value.trim().to_string()),
877 "keydata" => keydata = Some(value.split_whitespace().collect::<String>()),
878 _ => {}
879 }
880 }
881 if !addr?.eq_ignore_ascii_case(from.trim()) {
882 return None;
883 }
884 let bytes = crate::lines::base64_decode(&keydata?)?;
885 let cert = Cert::from_bytes(&bytes).ok()?;
886 names(&cert, from).then_some(cert)
887}
888
889pub fn learn_autocrypt(header: &str, from: &str) -> Option<KeySummary> {
891 let cert = autocrypt_key(header, from)?;
892 store(cert.strip_secret_key_material()).ok()
893}
894
895pub fn autocrypt_header(cert: &Cert, address: &str) -> Option<String> {
898 let policy = policy();
899 let valid = cert.with_policy(&policy, None).ok()?;
900 let userid = valid.userids().find(|u| u.userid().email().ok().flatten().is_some_and(|e| e.eq_ignore_ascii_case(address)))?;
901 let encryption = valid.keys().subkeys().alive().revoked(false).for_transport_encryption().next()?;
902 let mut packets: Vec<openpgp::Packet> = vec![cert.primary_key().key().clone().into()];
903 packets.push(userid.userid().clone().into());
904 packets.extend(userid.self_signatures().take(1).cloned().map(Into::into));
905 packets.push(encryption.key().clone().into());
906 packets.extend(encryption.self_signatures().take(1).cloned().map(Into::into));
907 let minimal = Cert::from_packets(packets.into_iter()).ok()?;
908 let mut bytes = Vec::new();
909 minimal.serialize(&mut bytes).ok()?;
910 let encoded: Vec<char> = crate::lines::base64_encode(&bytes).chars().collect();
914 let keydata = encoded.chunks(76).map(|chunk| chunk.iter().collect::<String>()).collect::<Vec<_>>().join(" ");
915 Some(format!("addr={address}; keydata={keydata}"))
916}
917
918#[cfg(test)]
919mod tests {
920 use super::*;
921
922 fn temporary() -> PathBuf {
923 let dir = std::env::temp_dir().join(format!("sioul-pgp-{}-{}", std::process::id(), std::thread::current().name().unwrap_or("t").replace("::", "-")));
924 std::fs::create_dir_all(&dir).unwrap();
925 dir
926 }
927
928 fn key(address: &str) -> Cert {
929 CertBuilder::general_purpose(Some(format!("Someone <{address}>")))
930 .set_cipher_suite(CipherSuite::Cv25519)
931 .set_password(Some(Password::from("secret words")))
932 .generate()
933 .unwrap()
934 .0
935 }
936
937 #[test]
938 fn signs_encrypts_decrypts_and_verifies() {
939 let _ = temporary();
940 let alice = key("alice@example.org");
941 let bob = key("bob@example.org");
942 let keys_of_bob = Keys { own: vec![bob.clone()], others: vec![alice.clone().strip_secret_key_material()], ..Keys::default() };
943 let mut signer = here_signer(&alice, "secret words").unwrap();
944 let sealed = encrypt(b"Content-Type: text/plain\r\n\r\nHello Bob.\r\n", &[&bob, &alice], Some(&mut signer)).unwrap();
945 assert!(sealed.starts_with("-----BEGIN PGP MESSAGE-----"));
946 let passphrase = |_: &Fingerprint| Some("secret words".to_string());
947 let sessions = SessionKeys::new();
948 let opened = decrypt(sealed.as_bytes(), &keys_of_bob, &Unlock::new(&passphrase, &sessions)).unwrap();
949 assert_eq!(opened.data, b"Content-Type: text/plain\r\n\r\nHello Bob.\r\n");
950 assert_eq!(opened.signatures.len(), 1);
951 assert!(opened.signatures[0].good && opened.signatures[0].signer.contains("alice@example.org"));
952 let wrong = |_: &Fingerprint| Some("other words".to_string());
953 assert!(decrypt(sealed.as_bytes(), &keys_of_bob, &Unlock::new(&wrong, &sessions)).is_err());
954 assert!(here_signer(&alice, "other words").is_err());
955 let (signature, micalg) = sign_detached(b"signed part", &mut signer).unwrap();
956 assert!(micalg.starts_with("pgp-sha"), "{micalg}");
957 let checked = verify_detached(b"signed part", signature.as_bytes(), &keys_of_bob);
958 assert!(checked[0].good);
959 let tampered = verify_detached(b"signed part!", signature.as_bytes(), &keys_of_bob);
960 assert!(!tampered[0].good);
961 let stranger = verify_detached(b"signed part", signature.as_bytes(), &Keys::default());
962 assert_eq!(stranger[0].problem, "unknown key");
963 }
964
965 #[test]
966 fn key_files_are_yours_alone() {
967 let path = temporary().join("key.asc");
968 write_key(&path, b"-----BEGIN PGP PRIVATE KEY BLOCK-----").unwrap();
969 write_key(&path, b"-----BEGIN PGP PRIVATE KEY BLOCK----- again").unwrap();
970 assert_eq!(std::fs::read(&path).unwrap(), b"-----BEGIN PGP PRIVATE KEY BLOCK----- again");
971 #[cfg(unix)]
972 {
973 use std::os::unix::fs::PermissionsExt;
974 assert_eq!(std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600);
975 }
976 let _ = std::fs::remove_file(&path);
977 }
978
979 #[test]
982 fn a_key_made_here_keeps_its_revocation_certificate() {
983 let root = temporary().join("pgp-made");
984 let _ = std::fs::remove_dir_all(&root);
985 let made = generate_at(&root, "Jane Exemple", "jane@example.org", "secret words").unwrap();
986 assert!(made.own && made.revocation);
987 let kept = root.join("own").join(format!("{}.rev", made.fingerprint));
988 let text = std::fs::read_to_string(&kept).unwrap();
989 #[cfg(unix)]
990 {
991 use std::os::unix::fs::PermissionsExt;
992 assert_eq!(std::fs::metadata(&kept).unwrap().permissions().mode() & 0o777, 0o600, "protected as the key is");
993 }
994 assert!(text.contains(&made.fingerprint) && text.contains("Jane Exemple <jane@example.org>"), "{text}");
995 assert!(text.contains("\n:-----BEGIN PGP PUBLIC KEY BLOCK-----") && certificates_in(text.as_bytes()).is_empty());
997 let armored = &text[text.find(":-----BEGIN").unwrap() + 1..];
999 let pile = openpgp::PacketPile::from_bytes(armored.as_bytes()).unwrap();
1000 let revocation = pile.descendants().find_map(|p| if let openpgp::Packet::Signature(s) = p { Some(s.clone()) } else { None }).unwrap();
1001 let key = Cert::from_bytes(&std::fs::read(root.join("own").join(format!("{}.asc", made.fingerprint))).unwrap()).unwrap();
1002 assert!(matches!(key.revocation_status(&policy(), None), openpgp::types::RevocationStatus::NotAsFarAsWeKnow));
1003 let (revoked, _) = key.insert_packets(revocation).unwrap();
1004 assert!(matches!(revoked.revocation_status(&policy(), None), openpgp::types::RevocationStatus::Revoked(_)));
1005 let elsewhere = root.join("saved");
1007 let copy = save_revocation_at(&root, &made.fingerprint, &elsewhere).unwrap();
1008 assert_eq!((copy.file_name().unwrap().to_string_lossy().to_string(), std::fs::read_to_string(©).unwrap()), (format!("{}.rev", made.fingerprint), text));
1009 assert!(save_revocation_at(&root, &made.fingerprint, &elsewhere).unwrap().ends_with(format!("{}-2.rev", made.fingerprint)));
1010 assert!(save_revocation_at(&root, "0000", &elsewhere).is_err(), "none kept for a key not made here");
1011 let _ = std::fs::remove_dir_all(&root);
1012 }
1013
1014 #[test]
1015 fn web_key_directory_names() {
1016 let [advanced, direct] = wkd_urls("Joe.Doe@Example.ORG").unwrap();
1018 assert_eq!(direct, "https://example.org/.well-known/openpgpkey/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=Joe.Doe");
1019 assert!(advanced.starts_with("https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q"));
1020 for elsewhere in ["joe@192.168.1.1", "joe@[::1]", "joe@localhost", "joe@host.example:8080/x?", "joe@1.2.3"] {
1022 assert!(wkd_urls(elsewhere).is_none(), "{elsewhere}");
1023 }
1024 }
1025
1026 fn card_key(address: &str, made: Option<std::time::SystemTime>) -> Cert {
1028 let mut builder = CertBuilder::new()
1029 .add_userid(format!("Card Holder <{address}>"))
1030 .set_cipher_suite(CipherSuite::Cv25519)
1031 .set_primary_key_flags(openpgp::types::KeyFlags::empty().set_certification().set_signing())
1032 .add_transport_encryption_subkey();
1033 if let Some(made) = made {
1034 builder = builder.set_creation_time(made).set_validity_period(std::time::Duration::from_secs(365 * 24 * 3600));
1035 }
1036 builder.generate().unwrap().0
1037 }
1038
1039 fn record(card: &securitykey::SoftCard, cert: &Cert, address: &str) -> KnownCard {
1040 KnownCard {
1041 ident: card.info.ident.clone(),
1042 sign: card.info.sign.as_ref().map(|s| s.fingerprint.clone()).unwrap_or_default(),
1043 decrypt: card.info.decrypt.as_ref().map(|s| s.fingerprint.clone()).unwrap_or_default(),
1044 cert: cert.fingerprint().to_hex(),
1045 addresses: vec![address.to_string()],
1046 ..KnownCard::default()
1047 }
1048 }
1049
1050 #[test]
1051 fn a_security_key_signs_alone_for_its_addresses() {
1052 let on_card = card_key("me@example.org", None);
1053 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456");
1054 let here = key("me@example.org");
1055 let mut keys = Keys { own: vec![on_card.clone().strip_secret_key_material(), here.clone()], ..Keys::default() };
1056 assert_eq!(keys.own_for("me@example.org").unwrap().place, Place::Here);
1057 keys.cards.push(record(&card, &on_card, "me@example.org"));
1058 let own = keys.own_for("ME@example.org").unwrap();
1059 assert_eq!(own.place, Place::Card { ident: "0006:12345678".into() });
1060 assert_eq!(own.cert.fingerprint(), on_card.fingerprint());
1061 assert!(autocrypt_header(own.cert, "me@example.org").is_some());
1063 assert!(keys.own_for("other@example.org").is_none());
1064 let late = card_key("late@example.org", Some(std::time::SystemTime::now() - std::time::Duration::from_secs(3 * 365 * 24 * 3600)));
1066 let late_card = securitykey::SoftCard::new("0006:00000002", &late, "123456");
1067 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() };
1068 assert!(keys.own_for("late@example.org").is_none());
1069 assert!(keys.card_for("late@example.org").is_some());
1070 }
1071
1072 #[test]
1073 fn a_message_to_the_security_key_waits_for_it_then_opens_from_memory() {
1074 let on_card = card_key("me@example.org", None);
1075 let card = securitykey::SoftCard::new("0006:12345678", &on_card, "123456").with_x25519(securitykey::x25519::x25519);
1076 let keys = Keys { own: vec![on_card.clone().strip_secret_key_material()], cards: vec![record(&card, &on_card, "me@example.org")], ..Keys::default() };
1077 let readers = securitykey::SoftReaders::with(vec![card]);
1078 let sealed = encrypt(b"Hello me.", &[&on_card], None).unwrap();
1079 let none = |_: &Fingerprint| None;
1080 let sessions = SessionKeys::new();
1081 let unlock = Unlock::new(&none, &sessions);
1083 assert!(decrypt(sealed.as_bytes(), &keys, &unlock).is_err());
1084 assert_eq!(unlock.wanted().as_deref(), Some("0006:12345678"));
1085 assert_eq!(readers.card("0006:12345678", |c| c.operations), Some(0));
1086 let wrong = Password::from("654321");
1088 let access = CardAccess::new(&readers, &wrong, &|| {});
1089 let unlock = Unlock::new(&none, &sessions).with_card(&access);
1090 assert!(decrypt(sealed.as_bytes(), &keys, &unlock).is_err());
1091 assert_eq!(access.failure(), Some(securitykey::CardError::WrongPin { left: 2 }));
1092 let pin = Password::from("123456");
1094 let access = CardAccess::new(&readers, &pin, &|| {});
1095 let opened = decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions).with_card(&access)).unwrap();
1096 assert_eq!(opened.data, b"Hello me.");
1097 assert_eq!(readers.card("0006:12345678", |c| c.operations), Some(1));
1098 let again = decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions)).unwrap();
1100 assert_eq!(again.data, b"Hello me.");
1101 assert_eq!(readers.card("0006:12345678", |c| c.operations), Some(1));
1102 let other = encrypt(b"Another.", &[&on_card], None).unwrap();
1104 assert!(decrypt(other.as_bytes(), &keys, &Unlock::new(&none, &sessions)).is_err());
1105 sessions.forget();
1106 assert!(decrypt(sealed.as_bytes(), &keys, &Unlock::new(&none, &sessions)).is_err());
1107 }
1108
1109 #[test]
1110 fn an_expired_security_key_still_opens_older_mail() {
1111 let then = std::time::SystemTime::now() - std::time::Duration::from_secs(3 * 365 * 24 * 3600);
1112 let late = card_key("late@example.org", Some(then));
1113 let policy = policy();
1115 let sent = then + std::time::Duration::from_secs(24 * 3600);
1116 let recipients: Vec<_> = late.keys().with_policy(&policy, sent).alive().for_transport_encryption().collect();
1117 assert!(!recipients.is_empty());
1118 let mut sealed = Vec::new();
1119 {
1120 let message = Message::new(&mut sealed);
1121 let message = Encryptor::for_recipients(message, recipients).build().unwrap();
1122 let mut literal = LiteralWriter::new(message).build().unwrap();
1123 literal.write_all(b"Older mail.").unwrap();
1124 literal.finalize().unwrap();
1125 }
1126 let card = securitykey::SoftCard::new("0006:00000002", &late, "123456").with_x25519(securitykey::x25519::x25519);
1127 let keys = Keys { own: vec![late.clone().strip_secret_key_material()], cards: vec![record(&card, &late, "late@example.org")], ..Keys::default() };
1128 let readers = securitykey::SoftReaders::with(vec![card]);
1129 let pin = Password::from("123456");
1130 let access = CardAccess::new(&readers, &pin, &|| {});
1131 let none = |_: &Fingerprint| None;
1132 let sessions = SessionKeys::new();
1133 let opened = decrypt(&sealed, &keys, &Unlock::new(&none, &sessions).with_card(&access)).unwrap();
1134 assert_eq!(opened.data, b"Older mail.");
1135 assert!(encrypt(b"New mail.", &[&late], None).is_err());
1137 }
1138
1139 #[test]
1140 fn a_security_key_certificate_comes_back_to_your_keys() {
1141 let root = temporary().join("pgp-card");
1142 let _ = std::fs::remove_dir_all(&root);
1143 let on_card = card_key("me@example.org", None);
1144 let name = format!("{}.asc", on_card.fingerprint().to_hex());
1145 let kept = store_in(&root, on_card.clone().strip_secret_key_material(), true).unwrap();
1146 assert!(kept.own);
1147 let file = std::fs::read_to_string(root.join("own").join(&name)).unwrap();
1148 assert!(file.starts_with("-----BEGIN PGP PUBLIC KEY BLOCK-----"), "public only: {file}");
1149 let header = autocrypt_header(&on_card, "me@example.org").unwrap();
1151 let again = autocrypt_key(&header, "me@example.org").unwrap();
1152 assert!(store_at(&root, again.strip_secret_key_material()).unwrap().own);
1153 assert!(!root.join("others").join(&name).exists());
1154 let jane = key("jane@example.org").strip_secret_key_material();
1156 assert!(!store_at(&root, jane).unwrap().own);
1157 let _ = std::fs::remove_dir_all(&root);
1158 }
1159
1160 #[test]
1161 fn autocrypt_carries_the_smallest_key() {
1162 let alice = key("alice@example.org");
1163 let header = autocrypt_header(&alice, "alice@example.org").unwrap();
1164 assert!(header.starts_with("addr=alice@example.org; keydata="));
1165 assert!(header.split_whitespace().all(|word| word.len() <= 100), "foldable: {header}");
1166 let found = autocrypt_key(&header, "alice@example.org").unwrap();
1167 assert_eq!(found.fingerprint(), alice.fingerprint());
1168 assert!(!found.is_tsk());
1169 assert!(autocrypt_key(&header, "mallory@example.org").is_none());
1170 }
1171}