sequoia_keystore_gpg_agent/
lib.rs1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::ops::Deref;
4use std::path::Path;
5use std::sync::Arc;
6use std::sync::Weak;
7
8use futures::lock::Mutex;
9
10use anyhow::Context;
11
12use sequoia_openpgp as openpgp;
13use openpgp::Cert;
14use openpgp::Fingerprint;
15use openpgp::Result;
16use openpgp::crypto::Password;
17use openpgp::crypto::SessionKey;
18use openpgp::crypto::mpi;
19use openpgp::packet;
20use openpgp::types::HashAlgorithm;
21use openpgp::types::PublicKeyAlgorithm;
22
23pub use sequoia_gpg_agent;
24use sequoia_gpg_agent as gpg_agent;
25
26use gpg_agent::sequoia_ipc as ipc;
27use ipc::Keygrip;
28
29use sequoia_keystore_backend as backend;
30use backend::DeviceHandle;
31use backend::Error;
32use backend::ImportStatus;
33use backend::PasswordSource;
34use backend::Protection;
35use backend::utils::Directory;
36
37mod certd;
38
39#[derive(Clone)]
40pub struct Backend {
41 inner: Arc<Mutex<BackendInternal>>,
42}
43
44struct BackendInternal {
45 home: Directory,
46
47 certd: Arc<certd::CertD>,
57
58 devices: Vec<Device>,
60}
61
62#[derive(Clone)]
68pub struct Device {
69 id: String,
71 inner: Arc<Mutex<DeviceInternal>>
72}
73
74impl Device {
75 fn downgrade(&self) -> WeakDevice {
77 WeakDevice {
78 id: self.id.clone(),
79 inner: Arc::downgrade(&self.inner),
80 }
81 }
82}
83
84#[derive(Clone)]
88pub struct WeakDevice {
89 id: String,
90 inner: Weak<Mutex<DeviceInternal>>,
91}
92
93impl WeakDevice {
94 fn upgrade(&self) -> Option<Device> {
96 Some(Device {
97 id: self.id.clone(),
98 inner: self.inner.upgrade()?,
99 })
100 }
101}
102
103struct DeviceInternal {
104 id: String,
106
107 agent: Arc<Mutex<gpg_agent::Agent>>,
108
109 certd: Arc<certd::CertD>,
111
112 keys: HashMap<Fingerprint, Key>,
114}
115
116#[derive(Clone)]
117pub struct Key {
118 fpr: Fingerprint,
120 inner: Arc<Mutex<KeyInternal>>,
121}
122
123struct KeyInternal {
125 device: WeakDevice,
126
127 keygrip: Keygrip,
128 fingerprint: Fingerprint,
129 public_key: packet::Key<packet::key::PublicParts,
130 packet::key::UnspecifiedRole>,
131 agent: Arc<Mutex<gpg_agent::Agent>>,
132 password: Option<Password>,
134}
135
136impl Backend {
137 pub async fn init<P: AsRef<Path>>(home: P, default: bool)
145 -> Result<Self>
146 {
147 log::trace!("Backend::init");
148
149 let home = Directory::from(home.as_ref());
150
151 let certd = Arc::new(certd::CertD::new()?);
152
153 let agents: &[(&str, Option<&Path>)] = if default {
154 &[
155 ("default", None),
156 ][..]
157 } else {
158 &[][..]
159 };
160
161 Ok(Self::init_internal(home, &agents[..], certd).await?)
162 }
163
164 pub async fn init_ephemeral() -> Result<Self> {
168 log::trace!("Backend::init_ephemeral");
169
170 let home = Directory::ephemeral()?;
171
172 let agents: &[(String, Option<&Path>)] = &[
173 (home.display().to_string(), Some(home.deref())),
174 ][..];
175
176 let certd_directory = home.to_path_buf().join("pgp.cert.d");
177 std::fs::create_dir_all(&certd_directory)
178 .with_context(|| {
179 format!("Creating {}", certd_directory.display())
180 })?;
181
182 let certd = Arc::new(certd::CertD::open(certd_directory)?);
183
184 Ok(Self::init_internal(home.clone(), &agents[..], certd).await?)
185 }
186
187 async fn init_internal<S>(home: Directory,
188 agents: &[ (S, Option<&Path>) ],
189 certd: Arc<certd::CertD>)
190 -> Result<Self>
191 where S: AsRef<str>
192 {
193 log::trace!("Backend::init_internal");
194 log::info!("sequoia-keystore-gpg-agent's home directory is: {}",
195 home.display());
196
197 let mut devices = Vec::with_capacity(agents.len());
198 for (id, path) in agents {
199 let id = id.as_ref();
200 let agent = if let Some(path) = path {
201 gpg_agent::Agent::connect_to(path).await?
202 } else {
203 gpg_agent::Agent::connect_to_default().await?
204 };
205
206 let mut device = Device {
207 id: id.to_string(),
208 inner: Arc::new(Mutex::new(DeviceInternal {
209 id: id.to_string(),
210 agent: Arc::new(Mutex::new(agent)),
211 certd: Arc::clone(&certd),
212 keys: HashMap::new(),
213 }))
214 };
215
216 if let Err(err) = device.scan().await {
217 log::info!("Failed to connect to gpg-agent for {}: {}",
218 id.to_string(), err);
219 }
220 devices.push(device);
221 }
222
223 Ok(Backend {
224 inner: Arc::new(Mutex::new(BackendInternal {
225 home,
226 certd,
227 devices,
228 }))
229 })
230 }
231}
232
233#[async_trait::async_trait]
234impl backend::Backend for Backend {
235 fn id(&self) -> String {
236 "gpg-agent".into()
237 }
238
239 async fn scan(&mut self) -> Result<()> {
240 log::trace!("Backend::scan");
241
242 let mut backend = self.inner.lock().await;
243 for device in backend.devices.iter_mut() {
244 if let Err(err) = device.scan().await {
245 log::info!("While scanning {}: {}",
246 device.id(), err);
247 }
248 }
249
250 Ok(())
251 }
252
253 async fn list<'a>(&'a self)
254 -> Box<dyn Iterator<Item=Box<dyn backend::DeviceHandle + Send + Sync + 'a>>
255 + Send + Sync + 'a>
256 {
257 log::trace!("Backend::list");
258
259 let backend = self.inner.lock().await;
260
261 Box::new(
262 backend.devices.iter()
263 .map(|device| {
264 Box::new(device.clone())
265 as Box<dyn backend::DeviceHandle + Send + Sync>
266 })
267 .collect::<Vec<_>>()
268 .into_iter())
269 }
270
271 async fn find_device<'a>(&self, id: &str)
272 -> Result<Box<dyn backend::DeviceHandle + Send + Sync + 'a>>
273 {
274 log::trace!("Backend::find_device");
275
276 let backend = self.inner.lock().await;
277
278 for device in backend.devices.iter() {
279 if device.id == id {
280 return Ok(Box::new(device.clone())
281 as Box<dyn backend::DeviceHandle + Send + Sync>);
282 }
283 }
284
285 Err(Error::NotFound(id.into()).into())
286 }
287
288 async fn find_key<'a>(&self, id: &str)
289 -> Result<Box<dyn backend::KeyHandle + Send + Sync + 'a>>
290 {
291 log::trace!("Backend::find_key");
292
293 let backend = self.inner.lock().await;
294
295 for scan in [false, true] {
298 for device in backend.devices.iter() {
299 let device_ref = device;
300 let mut device = device.inner.lock().await;
301
302 if scan {
303 log::trace!("Rescanning {}", device.id);
304 if let Err(err) = device.scan(device_ref).await {
305 log::info!("Failed to connect to gpg-agent for {}: {}",
306 device.id, err);
307 }
308 }
309
310 for (key_id, key) in device.keys.iter() {
311 if &key_id.to_string() == id {
312 return Ok(Box::new(key.clone())
313 as Box<dyn backend::KeyHandle + Send + Sync>);
314 }
315 }
316 }
317 }
318
319 Err(Error::NotFound(id.into()).into())
320 }
321
322 async fn import<'a>(&self, _cert: Cert)
323 -> Result<Vec<(ImportStatus,
324 Box<dyn backend::KeyHandle + Send + Sync + 'a>)>>
325 {
326 log::trace!("Backend::import");
327
328 Err(Error::ExternalImportRequired(Some(
329 "To import a key into a gpg-agent, use something like: \
330 gpg --import key.pgp".into())).into())
331 }
332}
333
334impl Device {
335 async fn scan(&mut self) -> Result<()> {
336 let mut device = self.inner.lock().await;
337 device.scan(&self).await
338 }
339}
340
341impl DeviceInternal {
342 async fn scan(&mut self, device_ref: &Device)
347 -> Result<()>
348 {
349 log::trace!("DeviceInternal::scan");
350
351 let mut agent = self.agent.lock().await;
352
353 let keyinfos = match agent.list_keys().await {
355 Ok(keyinfos) => keyinfos,
356 Err(err) => {
357 log::debug!("Listing keys on the agent for {}: {}",
358 self.id, err);
359 return Ok(());
360 }
361 };
362
363 log::trace!("Agent has {} keys: {}",
364 keyinfos.len(),
365 keyinfos
366 .iter()
367 .map(|keyinfo| {
368 keyinfo.keygrip().to_string()
369 })
370 .collect::<Vec<String>>()
371 .join(", "));
372
373 let agent_keygrips: HashSet<&Keygrip>
374 = keyinfos.iter().map(|i| i.keygrip()).collect();
375
376 let mut our_keys: Vec<(Keygrip, Fingerprint)>
378 = Vec::with_capacity(self.keys.len());
379 for key in self.keys.values() {
380 let key = key.inner.lock().await;
381 our_keys.push((key.keygrip.clone(), key.fingerprint.clone()));
382 }
383 let our_keygrips: HashSet<&Keygrip>
384 = HashSet::from_iter(our_keys.iter().map(|(keygrip, _)| keygrip));
385
386 let new_keygrips = agent_keygrips.difference(&our_keygrips);
388 match self.certd.find(HashSet::from_iter(new_keygrips.cloned())).await {
389 Ok(certs) => {
390 for (keygrip, public_key) in certs.into_iter() {
391 let fingerprint = public_key.fingerprint();
392 self.keys.insert(
393 fingerprint.clone(),
394 Key {
395 fpr: fingerprint.clone(),
396 inner: Arc::new(Mutex::new(KeyInternal {
397 device: device_ref.downgrade(),
398 keygrip,
399 fingerprint,
400 public_key,
401 agent: Arc::clone(&self.agent),
402 password: None,
403 })),
404 });
405 }
406 }
407 Err(err) => {
408 log::debug!("Listing cert-d: {}", err);
409 }
410 }
411
412 let removed_keygrips = our_keygrips.difference(&agent_keygrips);
415 let mut our_keys_map: Option<HashMap<&Keygrip, &Fingerprint>> = None;
416 for removed_keygrip in removed_keygrips {
417 let our_keys_map = our_keys_map.get_or_insert_with(|| {
418 HashMap::from_iter(
419 our_keys.iter().map(|(keygrip, fpr)| (keygrip, fpr)))
420 });
421
422 let fpr = our_keys_map.get(removed_keygrip).expect("have it");
423 self.keys.remove(*fpr);
424 }
425
426 Ok(())
427 }
428}
429
430#[async_trait::async_trait]
431impl backend::DeviceHandle for Device {
432 fn id(&self) -> String {
433 log::trace!("Device::id");
434
435 self.id.clone()
436 }
437
438 async fn available(&self) -> bool {
439 log::trace!("Device::available");
440
441 true
442 }
443
444 async fn configured(&self) -> bool {
445 log::trace!("Device::configured");
446
447 true
448 }
449
450 async fn registered(&self) -> bool {
451 log::trace!("Device::registered");
452
453 true
454 }
455
456 async fn lock(&mut self) -> Result<()> {
457 log::trace!("Device::lock");
458
459 Ok(())
461 }
462
463 async fn list<'a>(&'a self)
464 -> Box<dyn Iterator<Item=Box<dyn backend::KeyHandle + Send + Sync + 'a>>
465 + Send + Sync + 'a>
466 {
467 log::trace!("Device::list");
468
469 let device_ref = self;
470 let mut device = self.inner.lock().await;
471 if let Err(err) = device.scan(device_ref).await {
472 log::info!("Failed to connect to gpg-agent for {}: {}",
473 device.id, err);
474 }
475
476 let keys = device.keys.values()
477 .map(|key| {
478 Box::new(key.clone())
479 as Box<dyn backend::KeyHandle + Send + Sync>
480 })
481 .collect::<Vec<_>>();
482
483 Box::new(keys.into_iter())
484 }
485}
486
487#[async_trait::async_trait]
488impl backend::KeyHandle for Key {
489 fn id(&self) -> String {
490 log::trace!("Key::id");
491
492 self.fpr.to_string()
493 }
494
495 fn fingerprint(&self) -> Fingerprint {
496 log::trace!("Key::fingerprint");
497
498 self.fpr.clone()
499 }
500
501 async fn device<'a>(&self)
502 -> Box<dyn backend::DeviceHandle + Send + Sync + 'a>
503 {
504 let key = self.inner.lock().await;
505 let device = key.device.upgrade().expect("device lives longer than key");
506 drop(key);
507
508 Box::new(device)
509 }
510
511 async fn available(&self) -> bool {
520 log::trace!("Key::available");
521
522 let key = self.inner.lock().await;
523 let mut agent = key.agent.lock().await;
524 let Ok(keyinfo) = agent.key_info(&key.keygrip).await else {
525 return false;
528 };
529
530 if keyinfo.key_disabled()
532 || keyinfo.keytype() == gpg_agent::keyinfo::KeyType::Missing
533 {
534 return false;
535 }
536
537 if keyinfo.keytype() == gpg_agent::keyinfo::KeyType::Smartcard {
538 match agent.card_info().await {
540 Ok(cardinfo) => {
541 return cardinfo.keys().any(|fpr| fpr == key.fingerprint);
542 }
543 Err(err) => {
544 log::debug!("Getting smartcard info from gpg-agent: {}", err);
545 }
546 }
547 false
548 } else {
549 true
551 }
552 }
553
554 async fn locked(&self) -> Protection {
555 log::trace!("Key::locked");
556
557 let key = self.inner.lock().await;
558 let mut agent = key.agent.lock().await;
559 let Ok(keyinfo) = agent.key_info(&key.keygrip).await else {
560 return Protection::Unlocked;
564 };
565 drop(agent);
566 drop(key);
567
568 if keyinfo.protection()
578 == &gpg_agent::keyinfo::KeyProtection::NotProtected
579 {
580 Protection::Unlocked
581 } else {
582 if keyinfo.passphrase_cached() {
586 Protection::Unlocked
587 } else {
588 Protection::ExternalPassword(Some("\
589 This key is protected by GnuPG's agent. To continue, \
590 you may need to provide your password to GnuPG's \
591 pinentry".to_string()))
592 }
593 }
594 }
595
596 async fn password_source(&self) -> PasswordSource {
597 PasswordSource::ExternalSideEffect
598 }
599
600 async fn decryption_capable(&self) -> bool {
601 log::trace!("Key::decryption_capable");
602
603 let key = self.inner.lock().await;
604 key.public_key.pk_algo().for_encryption()
605 }
606
607 async fn signing_capable(&self) -> bool {
608 log::trace!("Key::signing_capable");
609
610 let key = self.inner.lock().await;
611 key.public_key.pk_algo().for_signing()
612 }
613
614 async fn unlock(&mut self, password: Option<&Password>) -> Result<()> {
615 log::trace!("Key::unlock");
616
617 let password = if let Some(password) = password {
618 password
621 } else {
622 return Err(Error::NoExternalPassword(
624 Some("Cannot prompt user for password".into())).into());
625 };
626 let mut key = self.inner.lock().await;
627 key.password = Some(password.clone());
628 Ok(())
629 }
630
631 async fn lock(&mut self) -> Result<()> {
632 log::trace!("Key::lock");
633
634 let mut key = self.inner.lock().await;
635
636 key.password = None;
638
639 let mut agent = key.agent.lock().await;
641 agent.forget_passphrase(&key.keygrip.to_string(), |_| ()).await?;
642
643 Ok(())
644 }
645
646 async fn public_key(&self)
647 -> packet::Key<packet::key::PublicParts,
648 packet::key::UnspecifiedRole>
649 {
650 log::trace!("Key::public_key");
651
652 let key = self.inner.lock().await;
653 key.public_key.clone()
654 }
655
656 async fn decrypt_ciphertext(&mut self,
657 ciphertext: &mpi::Ciphertext,
658 plaintext_len: Option<usize>)
659 -> Result<SessionKey>
660 {
661 log::trace!("Key::decrypt_ciphertext");
662
663 let key = self.inner.lock().await;
664 let agent = key.agent.lock().await;
665 let public_key = key.public_key.clone();
666 let mut keypair = agent.keypair(&public_key)?;
667 drop(agent);
669
670 if let Some(password) = key.password.as_ref() {
671 keypair = keypair.with_password(password.clone());
672 }
673 drop(key);
674
675 keypair.decrypt_async(ciphertext, plaintext_len).await
676 }
677
678 async fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
679 -> Result<(PublicKeyAlgorithm, mpi::Signature)>
680 {
681 log::trace!("Key::sign");
682
683 let key = self.inner.lock().await;
684 let agent = key.agent.lock().await;
685 let public_key = key.public_key.clone();
686 let mut keypair = agent.keypair(&public_key)?;
687 drop(agent);
689
690 if let Some(password) = key.password.as_ref() {
691 keypair = keypair.with_password(password.clone());
692 }
693 drop(key);
694
695 keypair.sign_async(hash_algo, digest).await
696 .map(|mpis| {
697 (public_key.pk_algo(), mpis)
698 })
699 }
700
701 async fn export(&mut self)
702 -> Result<openpgp::packet::Key<
703 openpgp::packet::key::SecretParts,
704 openpgp::packet::key::UnspecifiedRole>>
705 {
706 let key = self.inner.lock().await;
707 let mut agent = key.agent.lock().await;
708
709 Ok(agent.export(key.public_key.clone()).await?)
710 }
711
712 async fn change_password(&mut self, password: Option<&Password>)
713 -> Result<()>
714 {
715 log::trace!("KeyHandle::change_password({}, {})",
716 self.fingerprint(),
717 if let Some(password) = password {
718 if password.map(|p| p.is_empty()) {
719 "clear password"
720 } else {
721 "set password"
722 }
723 } else {
724 "ask for password"
725 });
726
727 if password.is_some() {
728 return Err(Error::NoInlinePassword(None).into());
729 };
730
731 let key = self.inner.lock().await;
732
733 let agent = key.agent.lock().await;
734 let mut keypair = agent.keypair(&key.public_key)?;
735
736 Ok(keypair.password(true).await?)
737 }
738
739 async fn delete_secret_key_material(&mut self)
740 -> Result<()>
741 {
742 log::trace!("KeyHandle::delete_secret_key_material");
743
744 let key = self.inner.lock().await;
745
746 let agent = key.agent.lock().await;
747 let mut keypair = agent.keypair(&key.public_key)?;
748
749 Ok(keypair.delete_key(false).await?)
750 }
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756
757 use std::fs::File;
758 use std::io::Write;
759
760 use anyhow::Context;
761
762 use openpgp::KeyHandle;
763 use openpgp::parse::Parse;
764 use openpgp::policy::StandardPolicy;
765 use openpgp::serialize::Serialize;
766
767 const P: &StandardPolicy = &StandardPolicy::new();
768
769 use backend::test_framework;
770
771 use backend::Backend as _;
772
773 fn preinit() -> bool {
774 true
776 }
777
778 async fn init_backend() -> Backend {
779 gpg_agent::trace(true);
780 let backend = Backend::init_ephemeral().await
781 .expect("creating an ephemeral backend");
782
783 {
785 let mut backend = backend.inner.lock().await;
786
787 let home = backend.home.to_path_buf();
788 let gpg_agent_conf = home.join("gpg-agent.conf");
789
790 let mut f = File::options()
791 .create(true).append(true)
792 .open(&gpg_agent_conf)
793 .with_context(|| {
794 format!("Opening {}", gpg_agent_conf.display())
795 }).expect("can open gpg-agent.conf");
796 writeln!(&mut f, "allow-loopback-pinentry").expect("can write");
797 drop(f);
798
799 let device = backend.devices.get_mut(0).unwrap();
800 let device = device.inner.lock().await;
801 let mut agent = device.agent.lock().await;
802 agent.reload().await.expect("can reload");
803 }
804
805 {
807 let mut backend = backend.inner.lock().await;
808 let device = backend.devices.get_mut(0).unwrap();
809 let device = device.inner.lock().await;
810 let mut agent = device.agent.lock().await;
811 agent.set_pinentry_mode(gpg_agent::PinentryMode::Cancel);
812 }
813
814 backend
815 }
816
817 async fn import_cert(backend: &mut Backend, cert: &Cert) {
818 let mut backend = backend.inner.lock().await;
819
820 {
822 let device = backend.devices.get_mut(0).unwrap();
823 let device = device.inner.lock().await;
824 let mut agent = device.agent.lock().await;
825
826 for k in cert.keys().secret() {
827 agent.import(P,
828 &cert, k.key().parts_as_secret().expect("have secret"),
829 true, true).await.expect("can import");
830 }
831 }
832
833 backend.certd.certd().insert(
836 &cert.fingerprint().to_string(),
837 (), false,
838 |(), disk| {
839 let cert_;
840 let cert = if let Some(disk) = disk {
841 let disk = Cert::from_bytes(disk).expect("valid cert");
843 cert_ = cert.clone().merge_public(disk).expect("can merge");
844 &cert_
845 } else {
846 cert
848 };
849
850 let mut bytes = Vec::new();
851 cert.serialize(&mut bytes).expect("can serialize to a vec");
852 Ok(bytes.into())
853 })
854 .expect("inserted");
855 }
856
857 sequoia_keystore_backend::generate_tests!(
858 preinit, false, Backend, init_backend,
860 import_cert,
861 true, None, None, true, true, true );
868}