1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
//! 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 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<(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<()>;
}