//! Defines the traits that keystore backends need to implement.
//!
//! Sequoia's keystore is a service, which manages and multiplexes
//! access to secret key material.  Conceptually, keys live on
//! devices, and devices are managed by backends.  A device may be as
//! simple as an on-disk file, it may be a smartcard, or it could be
//! another keystore server that is accessed over the network.
//!
//! A backend implements the traits defined in this crate.  The traits
//! abstract away the details of the various devices.  They are mostly
//! concerned with enumerating keys, and executing the low-level
//! decrypt and sign operations.  The backend interfaces are different
//! from, and more low level than the general-purpose interface
//! exposed to applications.
//!
//! The following figure illustrates the architecture.  The squares
//! represent different address spaces.
//!
//! ```text
//!    +---------------+        +---------------+
//!    |  Application  |        |  Application  |
//!    +---------------+        +---------------+
//!                  \            /
//! +----------------------------------------------+
//! |                   Keystore                   |
//! |                /            \                |
//! |        soft key              openpgp card    |
//! |        backend                 backend       |
//! +----------------------------------------------+
//! ```
//!
//! The keystore does not have to run as a server; it is also possible
//! to co-locate the keystore in an application, as shown here:
//!
//! ```text
//! +----------------------------------------------+
//! |                 Application                  |
//! |                      |                       |
//! |                   Keystore                   |
//! |                /            \                |
//! |        soft key              openpgp card    |
//! +----------------------------------------------+
//! ```
//!
//! Using a daemon instead of a library or a sub-process, which is
//! spawned once per application and is terminated when the
//! application terminates, offers several advantages.
//!
//! The main user-visible advantage is that the daemon is able to hold
//! state.  In the case of soft keys, the daemon can cache an
//! unencrypted key in memory so that the user doesn't have to unlock
//! the key as frequently.  This is particularly helpful when a
//! command-line tool like `sq` is executed multiple times in a row
//! and each time accesses the same password-protected key.  Likewise,
//! a daemon can cache a PIN needed to access an HSM.  It can also
//! keep the HSM open thereby avoiding the initialization overhead.
//! This also applies to remote keys: an ssh tunnel, for instance, can
//! be held open, and reused as required.
//!
//! A separate daemon also simplifies an important non-functional security
//! property: process separation.  Since soft keys aren't managed by the
//! application, but by the daemon, an attacker is not able to use a
//! [heartbleed]-style attack to exfiltrate secret key material.
//!
//!   [heartbleed]: https://heartbleed.com/
//!
//! The traits model backends as collections of devices each of which
//! contains zero or more keys.  The following figure illustrates a
//! possible configuration.  The keystore uses two backends, the
//! softkey backend, and the openpgp card backend, and each backend
//! has two devices.  The softkey backend models certificates as
//! devices; the openpgp card backend has one device for each physical
//! device.  Each device contains between 1 and 3 keys.  The interface
//! does not impose a limit on the number of devices per backend, or
//! the number of keys per device.  As such, a TPM managing thousands
//! of keys is conceivable, and in scope.
//!
//! ```text
//! +----------------------------------------------------+
//! |                   Keystore                         |
//! |                /            \                      |
//! |        soft key              openpgp card          |
//! |       /        \            /            \         |
//! |   0x1234      0xABCE     Gnuk         Nitro Key    |
//! |   /    \        |      #123456         #234567     |
//! | 0x10  0x23     0x34   /   |   \       /   |   \    |
//! |                     0x31 0x49 0x5A  0x64 0x71 0x88 |
//! +----------------------------------------------------+
//! ```
//!
//! The different devices may or may not be connected at any given
//! time.  For instance, the user may remove a smartcard, but if the
//! backend has recorded the configuration, the keystore still knows
//! about the
//!
//! When the keystore starts, it eagerly initializes the various
//! backends that it knows about.  At this time, backends are
//! statically linked to the keystore, and have to be explicitly
//! listed in the keystore initialization function.
//!
//! When a backend is initialized, the initialization function is
//! passed a directory.  The backend should read any required state
//! from a subdirectory, which is named after the backend.  For
//! instance, the soft keys backend uses the "softkeys" subdirectory.
//!
//! A backend must be extremely careful when using state stored
//! somewhere else.  If a user selects a different home directory,
//! then they usually want a different configuration, which is
//! isolated from the main configuration.  This is not entirely
//! possible in the case where a backend uses a physical resource, for
//! example.
//!
//! # Keys and Devices
//!
//! At its simplest, a device contains zero or more OpenPGP keys.  A
//! device may also be locked or unlocked, registered or not
//! registered, and available or unavailable.
//!
//! If a device is locked, it first has to be unlocked before it can
//! be used.  Sometimes a device can be unlocked by supplying a
//! password via the [`DeviceHandle::unlock`] interface.  Other times,
//! the device has to be manually unlocked by the user.  If a device
//! is locked, it may or may not be possible to enumerate the keys
//! stored on the device.
//!
//! If a device is registered, then the device's configuration has
//! been cached locally.  In this case, the keys on the device can be
//! enumerated even if the device is not connected to the host.  For
//! instance, when an OpenPGP card is registered, the OpenPGP card
//! backend records the serial number of device, the list of keys that
//! are stored on the smartcard, and their attributes.  When the user
//! enumerates the keys managed by the key store, these keys are
//! returned, even if the smartcard is not attached.  The user cannot,
//! of course, use the keys.
//!
//! If a device is registered, but is not attached to the system, then
//! it is considered unavailable.  If the user attempts to use a key
//! on an unavailable device, then an error is returned.  In this
//! case, the application could normally prompt the user to make the
//! corresponding device available.
//!
//! These states are documented in more detail in the documentation
//! for [`DeviceHandle`].
//!
//! Whether a key is registered or available is purely a function of
//! the device.  If a device contains multiple keys, and they can be
//! registered, or available independent of the other keys, then the
//! backend must model the keys as separate devices.

use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use openpgp::KeyID;
use openpgp::Result;
use openpgp::Cert;
use openpgp::crypto::Password;
use openpgp::crypto::SessionKey;
use openpgp::crypto::mpi;
use openpgp::packet::PKESK;
use openpgp::types::HashAlgorithm;
use openpgp::types::PublicKeyAlgorithm;
use openpgp::types::SymmetricAlgorithm;

/// A testing framework for backends that implement this interface.
///
/// Define a couple of functions and then use
/// [`sequoia_keystore_backend::generate_tests!`] to generate a number
/// of standard tests.
pub mod test_framework;

pub mod utils;

mod protection;
pub use protection::Protection;

mod password_source;
pub use password_source::PasswordSource;

/// Errors used in this crate.
///
/// Note: This enum cannot be exhaustively matched to allow for future
/// extensions.
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// Invalid argument.
    #[error("Invalid argument: {0}")]
    InvalidArgument(String),

    /// Not found.
    #[error("Not found: {0}")]
    NotFound(String),

    /// Operation not supported.
    #[error("Operation not supported: {0}")]
    OperationNotSupported(String),

    /// The device or key is locked.
    ///
    /// A device is locked when a pin, password, or some other
    /// interaction is needed to unlock the device.
    ///
    /// When this error is received, the caller should prompt the user
    /// for the password, unlock the object, and then retry the
    /// operation.
    #[error("Object is locked: {0}")]
    Locked(String),

    /// The device or key is not available.
    ///
    /// When this error is received, the caller may prompt the user to
    /// insert the device, and then retry the operation.
    #[error("Object is not available: {0}")]
    Unavailable(String),

    /// The device is not registered.
    #[error("Device is not registered: {0}")]
    Unregistered(String),

    /// The device is not configured.
    ///
    /// The device may contain keys, but it first needs to be
    /// configured before it can be used.
    #[error("Device needs to be configured: {0}")]
    Unconfigured(String),

    /// The device or key is already unlocked.
    ///
    /// Returned by [`DeviceHandle::unlock`] and [`KeyHandle::unlock`]
    /// if a device or key is already unlocked or doesn't need to be
    /// unlocked.
    #[error("Device is already unlocked: {0}")]
    AlreadyUnlocked(String),

    /// The backend doesn't support importing keys using this interface.
    ///
    /// This error is returned by [`Backend::import`] if the backend
    /// requires backend-specific information in order to import a
    /// key, which is not provided via this interface.  For instance,
    /// when importing a key to a smartcard, the user needs to specify
    /// the smartcard, and the slot to use on the smartcard.  Rather
    /// than try and model these parameters using this generic
    /// interface, backends should just have their own tools.  The
    /// text should be a hint that is displayed to the user describing
    /// how to find the tool.
    #[error("Can't import keys into the backend: {}",
            .0.as_deref().unwrap_or("use another tool to import keys \
                                     into this backend"))]
    ExternalImportRequired(Option<String>),

    /// The key doesn't support inline passwords.
    ///
    /// Password entry is taken care of by the device managing the
    /// key.  For instance, the password may be obtained using an
    /// external PIN pad.
    #[error("Can't unlock using an inline password{}{}",
            .0.as_ref().map(|_| ": ").unwrap_or(""),
            .0.as_deref().map(|msg| msg).unwrap_or(""))]
    NoInlinePassword(Option<String>),

    /// The key doesn't support getting passwords.
    ///
    /// The password must be provided inline.  The password cannot be
    /// obtained using something like an external PIN pad.
    #[error("External password entry is not supported{}{}",
            .0.as_ref().map(|_| ": ").unwrap_or(""),
            .0.as_deref().map(|msg| msg).unwrap_or(""))]
    NoExternalPassword(Option<String>),
}

/// The result of an import operation.
///
/// This is returned by [`Backend::import`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImportStatus {
    /// The imported object is new.
    New,

    /// The imported object updated an existing object.
    Updated,

    /// The object already existed, and is unchanged.
    Unchanged,
}

/// The backend interface for the sequoia key store.
///
/// This is the the interface that each device driver needs to
/// implement.
#[async_trait::async_trait]
pub trait Backend: Send {
    /// Returns the backend's identifier.
    ///
    /// This should be an identifier that uniquely identifies the
    /// device driver, is human readable, and is stable across
    /// restarts.
    fn id(&self) -> String;

    /// Causes the backend to look for devices.
    ///
    /// This function should perform a lightweight scan.  In
    /// particular, it should:
    ///
    ///   - Read in the configuration of any devices that have been
    ///     registered.
    ///
    ///   - Look for locally connected devices with OpenPGP keys,
    ///     e.g., smartcards, TPMs, etc.
    ///
    /// This function should not search for devices that may take a
    /// long time to find.  For instance, it should not scan the
    /// network, or prompt the user for a password.  Instead, a
    /// separate utility should be used to discover and register those
    /// devices.
    ///
    /// The backend should cache information about any devices that it
    /// finds in memory, but it should not register them.
    ///
    /// This function should not initialize registered devices for
    /// use, e.g., the ssh tunnel needed to access a remote key should
    /// not be brought up.
    ///
    /// In the terminology of [`DeviceHandle`], this should look for
    /// devices that are available or registered.
    async fn scan(&mut self) -> Result<()> {
        Err(Error::OperationNotSupported("Backend::scan".into()).into())
    }

    /// Lists all devices that are available or registered.
    ///
    /// This does not actively perform a scan.  It simply returns the
    /// devices that were available or registered as of the last scan.
    ///
    /// In the terminology of [`DeviceHandle`], it returns devices
    /// that are available or are registered.
    async fn list<'a>(&'a self)
        -> Box<dyn Iterator<Item=Box<dyn DeviceHandle + Send + Sync + 'a>>
               + Send + Sync + 'a>;

    /// Returns a handle for the specified device.
    ///
    /// id is a string as returned by [`DeviceHandle::id`].
    ///
    /// If the device is not available or not registered, then this
    /// should return [`Error::NotFound`].
    async fn find_device<'a>(&self, id: &str)
        -> Result<Box<dyn DeviceHandle + Send + Sync + 'a>>;

    /// Returns a handle for the specified key.
    ///
    /// `id` is a string as returned by [`KeyHandle::id`].
    ///
    /// If the key is not available or not registered, then this
    /// should return [`Error::NotFound`].
    async fn find_key<'a>(&self, id: &str)
        -> Result<Box<dyn KeyHandle + Send + Sync + 'a>>;

    /// Imports the keys in the cert.
    ///
    /// `cert` is a TSK.  Any keys without secret key material are
    /// silently ignored.
    ///
    /// Returns each key's import status, and a capability to the key.
    ///
    /// If the TSK doesn't include any secret keys, then an empty list
    /// is returned.
    ///
    /// Some backends require additional information to import a key.
    /// These backends should [`Error::ExternalImportRequired`], and
    /// indicate how a user might import a key to this backend.
    async fn import<'a>(&self, cert: Cert)
        -> Result<Vec<(ImportStatus, Box<dyn KeyHandle + Send + Sync + 'a>)>>;
}

/// A device that contains zero or more keys.
///
/// # Device Properties
///
/// A device has three properties: it can be available or not,
/// configured or not, and registered or not.
///
/// ## Available
///
/// A device is available if the secret key material can be used.
/// (This is independent of whether the secret key material is
/// locked.)  A smart card, for instance, is available when it is
/// inserted, and it is not available when it is not inserted.
/// Devices that are available are always returned by
/// [`Backend::list`].
///
/// ## Configured
///
/// A device is configured if it can be used without further
/// configuration.  (This is independent of whether the secret key
/// material is locked.)  To use the secret key material on a
/// smartcard or a TPM, the user may first need to load the OpenPGP
/// certificate corresponding to the secret keys.  Most devices,
/// however, are already configured, or are able to configure
/// themselves without the user's intervention.  For instance, OpenPGP
/// smartcards don't normally contain the OpenPGP certificate
/// corresponding to the secret keys, but they do contain the keys'
/// OpenPGP fingerprints, and an OpenPGP card backend could fetch
/// the certificate's without user intervention.
///
/// Devices that are available, but not configured should still be
/// returned by [`Backend::list`].  However, because the keys may not
/// be known, the keys may not be enumerable or not usable.  In this
/// case, [`DeviceHandle::list`] should return
/// [`Error::Unconfigured`].
///
/// If a device contains multiple keys, and some can be configured
/// while others aren't, then each key should be exposed as a separate
/// device; this API assumes that either no key or all keys on a given
/// device are configured.
///
/// ## Registered
///
/// A device that is registered is a device whose meta-data has been
/// saved locally.  Devices that are registered are returned by
/// [`Backend::list`] even if they are not available.  Likewise, the
/// keys on a registered device are returned by [`DeviceHandle::list`]
/// even when the device is not available.  When the key store
/// attempts to decrypt a message, it may prompt the user to insert an
/// unavailable, registered device.
///
/// When a backend scans for devices, it should not automatically
/// register devices.  Once a device has been successfully used,
/// however, a backend may register the device.  For instance, a smart
/// card that is discovered by a scan should not automatically be
/// registered.  However, when the smart card is successfully used to
/// create a signature or decrypt a message, then it may be registered
/// without further intervention from the user.
///
/// ## Alternate View
///
/// Another, less precise view, is that a device may be in one of the
/// following states:
///
///   - Ready: The device is available and configured.
///
///     The device can be used immediately.  For instance, a soft key,
///     or a smartcard that is inserted.
///
///     A device that is ready may still need to be unlocked.
///
///   - Disconnected: A device that was registered, but is not
///     available.
///
///     The device is not available, but the user may be prompted to
///     make it available.  For instance, if a key needed to decrypt a
///     message is on a smartcard that is unavailable, the user may be
///     prompted to insert the smartcard.  Similarly, if a key is
///     accessible via an ssh tunnel, but the ssh tunnel cannot be
///     established, the user may be prompted to connect to the
///     network.
///
///     The backend should still expose all of an unavailable device's
///     keys (insofar as they are known), however, the decrypt and
///     sign operations should fail with [`Error::Unavailable`].
///
///   - Unusable: A device that was discovered, but that cannot be
///     used without some additional configuration.  For instance, a
///     smartcard or a TPM key may need the corresponding certificate
///     before it can be used.
///
///   - Unknown: A device that is not available, and not registered.
///
/// The aforementioned three properties map onto these states as
/// follows:
///
/// ```text
///     Available,     Configured,     Registered: Ready
///     Available,     Configured, Not Registered: Ready
///     Available, Not Configured,     Registered: Unusable
///     Available, Not Configured, Not Registered: Unusable
/// Not Available,     Configured,     Registered: Disconnected
/// Not Available,     Configured, Not Registered: Unknown
/// Not Available, Not Configured,     Registered: Disconnected
/// Not Available, Not Configured, Not Registered: Unknown
/// ```
///
/// A device is only ever instantiated if it is available or registered.
#[async_trait::async_trait]
pub trait DeviceHandle {
    /// Returns the device's id.
    ///
    /// The id is a globally unique, stable, and mostly human readable
    /// identifier.
    fn id(&self) -> String;

    /// Returns the device's description.
    ///
    /// The description is a human readable string, e.g., "GnuK with
    /// certificate FINGERPRINT".
    async fn description(&self) -> String {
        self.id()
    }

    /// Sets the device's human readable description.
    ///
    /// This should fail if the device is not registered.
    async fn set_description(&self, _description: String) -> Result<()> {
        Err(Error::Unregistered(self.id()).into())
    }

    /// Returns whether the device is available.
    ///
    /// A device is available if it is plugged-in.
    async fn available(&self) -> bool;

    /// Returns whether the device is configured.
    ///
    /// A device is configured if the keys can be used without further
    /// configuration.
    async fn configured(&self) -> bool;

    /// Returns whether the device is registered.
    ///
    /// A device is registered if the device is locally memorized.  In
    /// this case, the user may be prompted to insert the device.
    async fn registered(&self) -> bool;

    /// Registers the device.
    ///
    /// This explicitly registers the device.  The backend should
    /// memorize the device and should return it during a scan even if
    /// it is not available.  If the device is already registered,
    /// this should silently succeed.
    async fn register(&mut self) -> Result<()> {
        Err(Error::OperationNotSupported("DeviceHandle::register".into()).into())
    }

    /// Unregisters the device from the backend.
    ///
    /// This should not destroy any secret key material stored on the
    /// device.  It should just remove any cached state about the
    /// device.  For instance, if the device is an ssh tunnel, then
    /// the ssh tunnel's configuration should be forgotten.  If the
    /// device is a smart card, then the smart card should be
    /// forgotten.  Note: if the smart card is inserted, it is still
    /// available and thus still usable.
    ///
    /// Note: devices that are not available can only be registered
    /// using a backend-specific tool.  For instance, a device
    /// accessible via an ssh tunnel is never available.
    async fn unregister(&mut self) -> Result<()> {
        Err(Error::OperationNotSupported("DeviceHandle::unregister".into()).into())
    }

    /// Connects to and unlocks the device.
    ///
    /// Some devices need to be initialized.  For instance, to access
    /// a remote key, it may be necessary to create an ssh tunnel.
    /// Some devices need to be unlocked before the keys can be
    /// enumerated.  For instance, if soft keys are stored in a
    /// database and the database is encrypted, it may be necessary to
    /// supply a password to decrypt the database.
    async fn unlock(&mut self, _password: &Password) -> Result<()> {
        Err(Error::AlreadyUnlocked("DeviceHandle::unlock".into()).into())
    }

    /// Locks the device and any keys in contains.
    ///
    /// Locks the device if it has been previously unlocked as well as
    /// any keys managed by the device.  If the device is locked or
    /// can't be locked, this is a noop.  If the device needs to be
    /// deinitialized, it MAY be deinitialized lazily if doing so
    /// cannot result in a user-visible error.  For instance, if the
    /// device uses an ssh tunnel, the ssh tunnel may be closed later.
    async fn lock(&mut self) -> Result<()> {
        Ok(())
    }

    /// Lists keys on the device.
    ///
    /// Returns the keys on the device.  If the device is not usable,
    /// then this should return [`Error::Unconfigured`].
    async fn list<'a>(&'a self)
        -> Box<dyn Iterator<Item=Box<dyn KeyHandle + Send + Sync + 'a>>
               + Send + Sync + 'a>;
}

/// A Key on a Device.
///
/// A key may or may not be available.  This is a function of the
/// device.
#[async_trait::async_trait]
pub trait KeyHandle {
    /// Returns the key's id.
    ///
    /// The id is a globally unique, stable, and mostly human readable
    /// identifier.  An example of a good id is the concatenation of
    /// the the key's fingerprint, and the device's serial number,
    /// e.g., "Key 8F17777118A33DDA9BA48E62AACB3243630052D9 on Yubikey
    /// 5 #217813388320."
    fn id(&self) -> String;

    /// Returns the key's handle.
    fn key_handle(&self) -> openpgp::KeyHandle {
        self.fingerprint().into()
    }

    /// Returns the key's fingerprint.
    fn fingerprint(&self) -> Fingerprint;

    /// Returns the key's key ID.
    fn keyid(&self) -> KeyID {
        KeyID::from(self.fingerprint())
    }

    /// Returns the key's device.
    async fn device<'a>(&self) -> Box<dyn DeviceHandle + Send + Sync + 'a>;

    /// Returns whether the key is available.
    async fn available(&self) -> bool;

    /// Returns whether the key is locked.
    async fn locked(&self) -> Protection;

    /// Returns how the password is obtained.
    ///
    /// This is similar to, but not identical to
    /// [`KeyHandle::locked`].  This function indicates how the
    /// password must be provided independency of the current
    /// protection.
    async fn password_source(&self) -> PasswordSource;

    /// Returns whether the key is decryption capable.
    async fn decryption_capable(&self) -> bool;

    /// Returns whether the key is signing capable.
    async fn signing_capable(&self) -> bool;

    /// Unlocks a key.
    ///
    /// A key is typically unlocked by providing a password or pin.
    /// Not all keys are locked.  If the key is not available, this
    /// should attempt to connect to the device.  If the device is not
    /// available or cannot be initialized, then this should fail.
    ///
    /// If `password` is `Some` and [`KeyHandle::password_source`]
    /// indicates that the password cannot be provided inline, then
    /// the backend must return [`Error::NoInlinePassword`].  Likewise,
    /// if `password` is `None`, and [`KeyHandle::password_source`]
    /// does not indicate that the user can be prompted for the
    /// password ([`PasswordSource::ExternalOnDemand`]), then the
    /// backend must return [`Error::NoExternalPassword`].
    ///
    /// If the key is already unlocked, this returns
    /// [`Error::AlreadyUnlocked`].
    async fn unlock(&mut self, _password: Option<&Password>) -> Result<()> {
        Err(Error::AlreadyUnlocked("KeyHandle::unlock".into()).into())
    }

    /// Lock a key.
    ///
    /// Relocks the key.  This usually causes the backend to forget the
    /// key's password.
    async fn lock(&mut self) -> Result<()> {
        Ok(())
    }

    /// Returns the corresponding public key.
    ///
    /// The backend SHOULD ensure that the secret key material is
    /// removed.
    async fn public_key(&self)
        -> openpgp::packet::Key<openpgp::packet::key::PublicParts,
                                openpgp::packet::key::UnspecifiedRole>;

    /// Decrypts a PKESK.
    ///
    async fn decrypt_pkesk(&mut self, pkesk: &PKESK)
        -> Option<(Option<SymmetricAlgorithm>, SessionKey)>
    {
        // We want to use `PKESK::decrypt`.  For that, we need
        // something that implements the `Decryptor` interface.  We
        // could implement `Decryptor` for `KeyHandle`, but then that
        // is part of our public API.  Instead, we do a bit of
        // acrobatics here.
        struct Decryptor<'a, T>
            where T: KeyHandle + ?Sized
        {
            slf: &'a mut T,
            pk: openpgp::packet::Key<
                    openpgp::packet::key::PublicParts,
                    openpgp::packet::key::UnspecifiedRole>,
        }

        impl<'a, T> openpgp::crypto::Decryptor for Decryptor<'a, T>
            where T: KeyHandle + ?Sized + Send
        {
            fn public(&self)
                -> &openpgp::packet::Key<
                    openpgp::packet::key::PublicParts,
                    openpgp::packet::key::UnspecifiedRole>
            {
                &self.pk
            }

            fn decrypt(&mut self, ciphertext: &mpi::Ciphertext,
                       plaintext_len: Option<usize>)
                -> Result<SessionKey>
            {
                // We know that pkesk.decrypt is running on a separate
                // thread.  So it is safe to create a new runtime on
                // this thread.
                let rt = tokio::runtime::Runtime::new()?;

                rt.block_on(async {
                    self.slf.decrypt_ciphertext(ciphertext, plaintext_len).await
                })
            }
        }

        let pk = self.public_key();
        let pk = pk.await;

        let mut decryptor = Decryptor {
            slf: self,
            pk: pk,
        };

        // To avoid blocking the current thread, we run the sync
        // function `pkesk.decrypt` on a separate thread.  When it is
        // done, it results the result via a one shot channel, which
        // we can asynchronously wait on.
        let (sender, receiver) = futures::channel::oneshot::channel::<_>();

        std::thread::scope(|s| {
            s.spawn(move || {
                sender.send(pkesk.decrypt(&mut decryptor, None))
            });
        });

        match receiver.await {
            Ok(Some(result)) => Some(result),
            Ok(None) => None,
            Err(_) => None,
        }
    }

    /// Decrypts a ciphertext.
    ///
    /// This method has the same semantics as
    /// [`sequoia_openpgp::crypto::Decryptor::decrypt`].
    ///
    /// Returns the session key.
    async fn decrypt_ciphertext(&mut self,
                                ciphertext: &mpi::Ciphertext,
                                plaintext_len: Option<usize>)
        -> Result<SessionKey>;

    /// Signs a message.
    ///
    /// `text` is the message to sign.
    async fn sign(&mut self, hash_algo: HashAlgorithm, text: &[u8])
        -> Result<(PublicKeyAlgorithm, mpi::Signature)>;

    /// Exports the secret key material.
    async fn export(&mut self)
        -> Result<openpgp::packet::Key<
            openpgp::packet::key::SecretParts,
            openpgp::packet::key::UnspecifiedRole>>;

    /// Changes the key's password.
    ///
    /// Changes the password.  Before calling this function, you
    /// should call [`KeyHandle::password_source`] to determine if you
    /// need to unlock the key, and whether you need to provide the
    /// new password directly, or if that is obtained externally.
    ///
    /// If `new_password` is `Some` and [`KeyHandle::password_source`]
    /// indicates that the password cannot be provided inline, then
    /// the backend must return [`Error::NoInlinePassword`].  Likewise,
    /// if `new_password` is `None`, and
    /// [`KeyHandle::password_source`] does not indicate that the user
    /// can be prompted for the password, then the backend must return
    /// [`Error::NoExternalPassword`].
    async fn change_password(&mut self, new_password: Option<&Password>)
         -> Result<()>;

    /// Deletes the key.
    ///
    /// This destroys the key's secret key material.
    ///
    /// If the key has to be unlocked, and the key is locked, the
    /// backend should return an error.
    ///
    /// If the device managing the key does not support deleting keys,
    /// then it should return [`Error::OperationNotSupported`].
    async fn delete_secret_key_material(&mut self) -> Result<()>;
}
