sequoia_keystore_gpg_agent/
lib.rs

1use 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    // gpg-agent doesn't store the OpenPGP key data structures nor
48    // does it have all the information required to reconstruct them
49    // (in particular, it's missing the key's creation time).
50    // Instead, we the so-called keygrip which is more or less a hash
51    // over the public key material.  But, to use a key, we need the
52    // OpenPGP public key.  We look for it opportunistically in the
53    // user's default cert-d.  (For ephemeral backends, we use a
54    // cert-d in the ephemeral directory.)  Since looking up a key by
55    // keygrip means doing a full scan, we cache the results.
56    certd: Arc<certd::CertD>,
57
58    // One device per agent.
59    devices: Vec<Device>,
60}
61
62/// A Device exposes the (usable) keys managed by a particular
63/// gpg-agent.
64///
65/// A key is usable if we have the OpenPGP data structure.  If we
66/// don't have the OpenPGP key, we ignore the key.
67#[derive(Clone)]
68pub struct Device {
69    // $GNUPGHOME (escaped).
70    id: String,
71    inner: Arc<Mutex<DeviceInternal>>
72}
73
74impl Device {
75    /// Creates a new `WeakDevice` from this `Device`.
76    fn downgrade(&self) -> WeakDevice {
77        WeakDevice {
78            id: self.id.clone(),
79            inner: Arc::downgrade(&self.inner),
80        }
81    }
82}
83
84/// A `Device`, but with a weak reference to the data.
85///
86/// Before you use this, you need to upgrade this to a `Device`.
87#[derive(Clone)]
88pub struct WeakDevice {
89    id: String,
90    inner: Weak<Mutex<DeviceInternal>>,
91}
92
93impl WeakDevice {
94    /// Upgrades the `WeakDevice` to a `Device`, if possible.
95    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    // $GNUPGHOME (escaped).
105    id: String,
106
107    agent: Arc<Mutex<gpg_agent::Agent>>,
108
109    // See the documentation for BackendInternal.certd.
110    certd: Arc<certd::CertD>,
111
112    // A map from id (fingerprint) to key.
113    keys: HashMap<Fingerprint, Key>,
114}
115
116#[derive(Clone)]
117pub struct Key {
118    // The fingerprint is also the id.
119    fpr: Fingerprint,
120    inner: Arc<Mutex<KeyInternal>>,
121}
122
123/// A secret key.
124struct 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    /// The cached password.
133    password: Option<Password>,
134}
135
136impl Backend {
137    /// Initializes a gpg-agent backend.
138    ///
139    /// `home` is the directory where the backend will look for its
140    /// configuration, e.g., `$HOME/.sq/keystore/gpg-agent`.
141    ///
142    /// If `default` is true, this backend uses the default GnuPG home
143    /// directory, i.e., `$HOME/.gnupg`, by default.
144    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    /// Initializes an ephemeral gpg-agent backend.
165    ///
166    /// This is primarily useful for testing.
167    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        // The first time through we look for the key without
296        // scanning.  If we don't find it, then we rescan.
297        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    /// Scans.
343    ///
344    /// This interface is a bit awkward so that it can be called while
345    /// the device lock is held.
346    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        // List the keys available on the agent.
354        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        // List our keys.
377        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        // Add any new keys.
387        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        // If some keys were removed on the agent, then remove them
413        // locally.
414        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        // This is a noop: an agent can't be locked.
460        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    /// Returns whether the key is available.
512    ///
513    /// A key managed by gpg-agent is considered to *not* be available
514    /// if:
515    ///
516    /// - It is disabled
517    /// - It is missing
518    /// - It is on a smartcard, and the smartcard is not inserted.
519    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            // If this fails, the key was deleted.  In that case,
526            // return false as we clearly can't address the key.
527            return false;
528        };
529
530        // Keys that are disabled or missing are not available.
531        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            // On a smart card.  See if the smart card is inserted.
539            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            // Soft key.
550            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            // If this fails, the key was deleted.  In that case, just
561            // return Unlocked: it doesn't matter; we can't address
562            // the key.
563            return Protection::Unlocked;
564        };
565        drop(agent);
566        drop(key);
567
568        // As a first approximation, a key is locked if it has
569        // protection, and the passphrase is not cached.
570        //
571        // A signing key may be locked and the passphrase may be
572        // cached, but if `ignore-cache-for-signing` is set, then
573        // `gpg` will prompt for a passphrase anyways.  Unfortunately,
574        // AFAICS there is no way to determine if
575        // `ignore-cache-for-signing` is set using gpg-agent's IPC
576        // interface.
577        if keyinfo.protection()
578            == &gpg_agent::keyinfo::KeyProtection::NotProtected
579        {
580            Protection::Unlocked
581        } else {
582            // It's protected...  probably.  It could be the key is on
583            // a smartcard, and the slot is unlocked.  Unfortunately,
584            // we have no reliably way to find out.
585            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            // We can use the password, but only if loopback mode is
619            // enabled.
620            password
621        } else {
622            // We can only prompt as a side effect.
623            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        // Clear our local cache.
637        key.password = None;
638
639        // Clear the password on the agent.
640        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 the lock while we do the actual operation.
668        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 the lock while we do the actual operation.
688        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        // XXX: Return false if gpg-agent is not installed.
775        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        // Enable loopback mode.
784        {
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        // If we didn't supply a password, suppress pinentry.
806        {
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        // Import the keys into the agent.
821        {
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        // And insert the certificate into our local certd so that we
834        // can find the OpenPGP keys.
835        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                    // Merge.
842                    let disk = Cert::from_bytes(disk).expect("valid cert");
843                    cert_ = cert.clone().merge_public(disk).expect("can merge");
844                    &cert_
845                } else {
846                    // New.
847                    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, // Tests don't need to be serialized.
859        Backend, init_backend,
860        import_cert,
861        true, // Can import encrypted secret key material.
862        None, // unlimited key sets
863        None, // No default password
864        true, // Can export.
865        true, // Can change password.
866        true // Can delete secret key material.
867    );
868}