sequoia-keystore-openpgp-card-0.1.0/.cargo_vcs_info.json0000644000000001520000000000100167160ustar { "git": { "sha1": "483ed320307be2423e4b7685129001e6d4329a3b" }, "path_in_vcs": "openpgp-card" }sequoia-keystore-openpgp-card-0.1.0/Cargo.toml0000644000000044160000000000100147230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.77" name = "sequoia-keystore-openpgp-card" version = "0.1.0" authors = ["Neal H. Walfield "] description = "An OpenPGP card backend for Sequoia's private key store." homepage = "https://sequoia-pgp.org/" readme = "README.md" keywords = [ "cryptography", "openpgp", ] categories = ["cryptography"] license = "LGPL-2.0-or-later" repository = "https://gitlab.com/sequoia-pgp/sequoia-keystore" [package.metadata.docs.rs] features = ["sequoia-openpgp/default"] [dependencies.anyhow] version = "1.0.18" [dependencies.async-trait] version = "0.1" [dependencies.card-backend-pcsc] version = "0.5" [dependencies.futures] version = "0.3" [dependencies.log] version = "0.4.17" [dependencies.openpgp-card] version = "0.5" [dependencies.openpgp-cert-d] version = "0.3.1" [dependencies.rsa] version = "0.9" [dependencies.sequoia-keystore-backend] version = "0.6" [dependencies.sequoia-openpgp] version = "1.17" default-features = false [dependencies.tokio] version = "1.19" features = [ "rt-multi-thread", "io-util", "net", ] [dev-dependencies.env_logger] version = ">=0.10, <0.12" [dev-dependencies.sequoia-openpgp] version = "1" features = ["compression"] default-features = false [dev-dependencies.tracing] version = "0.1" default-features = false [dev-dependencies.tracing-subscriber] version = "0.3" features = ["env-filter"] default-features = false [target."cfg(not(windows))".dev-dependencies.sequoia-openpgp] version = "1" features = [ "crypto-nettle", "__implicit-crypto-backend-for-tests", ] default-features = false [target."cfg(windows)".dev-dependencies.sequoia-openpgp] version = "1" features = [ "crypto-cng", "__implicit-crypto-backend-for-tests", ] default-features = false [badges.maintenance] status = "actively-developed" sequoia-keystore-openpgp-card-0.1.0/Cargo.toml.orig000064400000000000000000000035051046102023000204020ustar 00000000000000[package] name = "sequoia-keystore-openpgp-card" description = "An OpenPGP card backend for Sequoia's private key store." version = "0.1.0" authors = ["Neal H. Walfield "] homepage = "https://sequoia-pgp.org/" repository = "https://gitlab.com/sequoia-pgp/sequoia-keystore" readme = "README.md" keywords = ["cryptography", "openpgp"] categories = ["cryptography"] license = "LGPL-2.0-or-later" edition = "2021" rust-version = "1.77" [badges] maintenance = { status = "actively-developed" } [dependencies] anyhow = "1.0.18" async-trait = "0.1" card-backend-pcsc = "0.5" futures = "0.3" log = "0.4.17" openpgp-card = "0.5" # XXX: openpgp-card-sequoia is deprecated. We've copied what we # need, but we should fork it. # openpgp-card-sequoia = "0.2" openpgp-cert-d = "0.3.1" sequoia-keystore-backend = { path = "../backend", version = "0.6" } sequoia-openpgp = { version = "1.17", default-features = false } rsa = "0.9" tokio = { version = "1.19", features = [ "rt-multi-thread", "io-util", "net" ] } [dev-dependencies] env_logger = ">=0.10, <0.12" sequoia-openpgp = { version = "1", default-features = false, features = ["compression"] } tracing = {version = "0.1", default-features = false} tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter"]} # Enables a crypto backend for the tests: [target.'cfg(not(windows))'.dev-dependencies] sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-nettle", "__implicit-crypto-backend-for-tests"] } # Enables a crypto backend for the tests: [target.'cfg(windows)'.dev-dependencies] sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-cng", "__implicit-crypto-backend-for-tests"] } # Enables a crypto backend for the docs.rs generation: [package.metadata.docs.rs] features = ["sequoia-openpgp/default"] sequoia-keystore-openpgp-card-0.1.0/README.md000064400000000000000000000011621046102023000167670ustar 00000000000000An openpgp card backend for Sequoia's private key store. The `sequoia-keystore` crate implements a server that manages secret keys. Secret key material can be stored in files, on hardware devices like smartcards, or accessed via the network. `sequoia-keystore` doesn't implement these access methods. This is taken care of by various backends. This crate includes a backend that exposes the secret keys managed by OpenPGP cards. It uses PCSC in order to access the cards. On Linux distributions, this means that you'll need to install a PCSC service like PC/CS Lite. On Debian this is provided by the `pcscd` package. sequoia-keystore-openpgp-card-0.1.0/src/certd.rs000064400000000000000000000077321046102023000177570ustar 00000000000000use std::collections::HashMap; use std::path::Path; use futures::lock::Mutex; use futures::lock::MutexGuard; use sequoia_openpgp as openpgp; use openpgp::cert::raw::RawCert; use openpgp::Fingerprint; use openpgp::packet::Key; use openpgp::packet::key::PublicParts; use openpgp::packet::key::UnspecifiedRole; use openpgp::parse::Parse; use openpgp_cert_d as cert_d; use crate::Result; struct CertDCache { certd_tag: Option, // A cache of fingerprints to keys. keys: HashMap>, } pub struct CertD { certd: cert_d::CertD, cache: Mutex, } impl CertD { /// Opens the default cert-d. pub fn new() -> Result { Ok(CertD { certd: cert_d::CertD::new()?, cache: Mutex::new(CertDCache { certd_tag: None, keys: HashMap::default(), }), }) } /// Opens the specified cert-d. pub fn open

(path: P) -> Result where P: AsRef { Ok(CertD { certd: cert_d::CertD::with_base_dir(path.as_ref())?, cache: Mutex::new(CertDCache { certd_tag: None, keys: HashMap::default(), }), }) } /// Returns a reference to the underlying cert-d. pub fn certd(&self) -> &cert_d::CertD { &self.certd } // Check that the in-memory cache is up to date, and rebuild it if // necessary. fn rescan<'a>(&self, mut cache: MutexGuard<'a, CertDCache>) -> MutexGuard<'a, CertDCache> { let certd_tag = self.certd.tag(); if let Some(tag) = cache.certd_tag.as_ref() { if tag == &certd_tag { // Up to date. return cache; } } // Rebuild the cache. // // We should probably parallelize this, but we are only // dealing with raw certificates, so even for 1000s of // certificates this is pretty fast. let mut items = Vec::with_capacity(128); for item in self.certd.iter_files() { let (fingerprint, f) = match item { Ok(r) => r, Err(err) => { log::debug!("Reading certd: {}", err); continue; } }; let cert = match RawCert::from_reader(f) { Ok(cert) => cert, Err(err) => { log::debug!("Parsing certificate for {}: {}", fingerprint, err); continue; } }; for key in cert.keys() { items.push((key.fingerprint(), key.clone())); } } cache.certd_tag = Some(certd_tag); cache.keys = HashMap::from_iter(items.into_iter()); cache } /// Look up certificates by their fingerprint. pub async fn find_one(&self, fingerprint: &Fingerprint) -> Option> { let cache = self.cache.lock().await; // To avoid blocking the current thread, we run the rescan // function on a separate thread. It signals that it is done // by sending the result via a one-shot channel, which we can // asynchronously wait on. This avoids blocking the main // thread. let (sender, receiver) = futures::channel::oneshot::channel::<_>(); std::thread::scope(|s| { s.spawn(move || { sender.send(self.rescan(cache)) }); }); let cache = receiver.await.ok()?; if let Some(key) = cache.keys.get(fingerprint) { log::trace!("Found {} in cert-d", fingerprint); Some(key.clone()) } else { log::debug!("{} unusable: can't find the public key \ (try searching for it: sq network fetch {})", fingerprint, fingerprint); None } } } sequoia-keystore-openpgp-card-0.1.0/src/lib.rs000064400000000000000000001263671046102023000174320ustar 00000000000000use std::collections::HashMap; use std::collections::hash_map::Entry; use std::path::Path; use std::sync::Arc; use std::sync::Weak; use std::time::Duration; use std::time::SystemTime; use futures::lock::Mutex; use card_backend_pcsc; use openpgp_card::ocard::crypto::Cryptogram; use openpgp_card::ocard::data::KeyStatus; use openpgp_card::ocard::KeyType; use openpgp_card::ocard::Transaction; use sequoia_openpgp as openpgp; use openpgp::Cert; use openpgp::Fingerprint; use openpgp::Result; use openpgp::crypto::mem::Protected; use openpgp::crypto::ecdh; use openpgp::crypto::mpi; use openpgp::crypto::Password; use openpgp::crypto::SessionKey; use openpgp::packet; use openpgp::types::Curve; use openpgp::types::HashAlgorithm; use openpgp::types::PublicKeyAlgorithm; use sequoia_keystore_backend as backend; use backend::Error; use backend::ImportStatus; use backend::PasswordSource; use backend::Protection; use backend::utils::Directory; mod certd; // XXX: openpgp-card-sequoia is deprecated. We've copied what we // need, but we should fork it. #[allow(unused)] mod privkey; #[derive(Clone)] pub struct Backend { inner: Arc>, } struct BackendInternal { home: Directory, // False if the backend is disabled. enabled: bool, // OpenPGP cards don't store the OpenPGP key data structures nor // do they (always) have all the information required to // reconstruct them (in particular, it's missing the ECC algorithm // parameters). Since looking up a key by subkey means doing a // full scan, we cache the results. certd: Arc, // One device per OpenPGP card. // XXX: Make this a HashMap keyed on the card id. devices: Vec, last_scan: Option, } /// A Device exposes the (usable) keys managed by a particular /// OpenPGP Card. /// /// A key is usable if we have the OpenPGP data structure. If we /// don't have the OpenPGP key, we ignore the key. #[derive(Clone)] pub struct Device { // "application identifier". id: String, inner: Arc> } impl Device { /// Creates a new `WeakDevice` from this `Device`. fn downgrade(&self) -> WeakDevice { WeakDevice { id: self.id.clone(), inner: Arc::downgrade(&self.inner), } } } /// A `Device`, but with a weak reference to the data. /// /// Before you use this, you need to upgrade this to a `Device`. #[derive(Clone)] pub struct WeakDevice { id: String, inner: Weak>, } impl WeakDevice { /// Upgrades the `WeakDevice` to a `Device`, if possible. fn upgrade(&self) -> Option { Some(Device { id: self.id.clone(), inner: self.inner.upgrade()?, }) } } struct DeviceInternal { /// The "application identifier". /// /// This is of the form `FFFE:43233446`. It identifies both a /// card and the OpenPGP application. id: String, /// The opened card. /// /// The card is open in shared mode so that other applications can /// use it. card: openpgp_card::Card, /// Whether the card has a pinpad. pinpad: bool, /// A map from id (fingerprint) to key. keys: HashMap, } #[derive(Clone)] pub struct Key { // The fingerprint is also the id. fpr: Fingerprint, inner: Arc>, } /// A secret key. struct KeyInternal { device: WeakDevice, fingerprint: Fingerprint, public_key: packet::Key, /// The cached password. /// /// OpenPGP cards can have up to three passwords: an admin /// password, a user password, and an optional signing password. /// The user password unlocks the decryption and authentication /// slots. If configured, the signing password unlocks the /// signing slot. Otherwise, the user password unlocks the /// signing slot. /// /// We simplify things by having a per-slot password. This means /// that occasionally we'll ask for a password even though we know /// it (e.g., we've cached the password for the decryption key, /// and we're trying to use the authentication key), but that's /// not a big deal. password: Option, /// The key's slot. slot: openpgp_card::ocard::KeyType, } impl Backend { /// Initializes an openpgp card backend. /// /// `home` is the directory where the backend will look for its /// configuration, e.g., `$HOME/.sq/keystore/openpgp-card`. /// /// If `default` is true, this backend uses the cards managed by /// PCSC. Otherwise, the backend is disabled. pub async fn init>(home: P, default: bool) -> Result { log::trace!("Backend::init"); let home = Directory::from(home.as_ref()); let certd = Arc::new(certd::CertD::new()?); Ok(Backend { inner: Arc::new(Mutex::new(BackendInternal { home, enabled: default, certd, devices: vec![], last_scan: None, })) }) } /// Initializes an ephemeral OpenPGP card backend. /// /// This is primarily useful for testing. pub async fn init_ephemeral() -> Result { log::trace!("Backend::init_ephemeral"); let home = Directory::ephemeral()?; // XXX: default should be false, not true Self::init(home, true).await } } impl BackendInternal { async fn scan_internal(&mut self, force: bool) -> Result<()> { log::trace!("Backend::scan"); if ! self.enabled { log::debug!("OpenPGP Card backend is disabled"); return Ok(()); } if ! force { // Don't scan too often. Rate limit it to once every few seconds. if let Some(last_scan) = self.last_scan { if let Ok(duration) = SystemTime::now().duration_since(last_scan) { if duration < Duration::new(3, 0) { return Ok(()) } } } } let certd = Arc::clone(&self.certd); let card_backends = match card_backend_pcsc::PcscBackend::cards(None) { Ok(card_backends) => card_backends.collect(), Err(err) => { log::debug!("Listing openpgp cards using PC/SC: {}", err); Vec::new() } }; // XXX: Remove keys and devices that are not longer available. for card_backend in card_backends.into_iter() { let card_backend = match card_backend { Ok(card_backend) => card_backend, Err(err) => { log::debug!("Opening openpgp card backend: {}", err); continue; } }; let mut card = match openpgp_card::Card::new(card_backend) { Ok(card) => card, Err(err) => { log::debug!("Opening openpgp card: {}", err); continue; } }; let mut tx = match card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); continue; } }; // The device's identifier. let id = match tx.application_identifier() { Ok(id) => id, Err(err) => { log::debug!("Getting application id from openpgp card: {}", err); continue; } }; let id = id.ident(); // Gather information about the keys. let keyinfo = match tx.key_information() { Ok(keyinfo) => keyinfo, Err(err) => { log::debug!("Getting key information from openpgp card: {}", err); continue; } }; let present = if let Some(keyinfo) = keyinfo { [ keyinfo.sig_status() != KeyStatus::NotPresent, keyinfo.dec_status() != KeyStatus::NotPresent, keyinfo.aut_status() != KeyStatus::NotPresent ] } else { [false, false, false] }; let fprs = match tx.fingerprints() { Ok(tx) => tx, Err(err) => { log::debug!("Getting fingerprints from openpgp card: {}", err); continue; } }; let pinpad = tx.feature_pinpad_verify(); drop(tx); // See if we already know about this device. let mut device = None; for d in self.devices.iter_mut() { if d.id == id { device = Some(d); } } let device = if let Some(device) = device { // Already known. device } else { // Create a new device. let device = Device { id: id.clone(), inner: Arc::new(Mutex::new(DeviceInternal { id: id.clone(), keys: HashMap::new(), card: card, pinpad, })), }; self.devices.push(device); self.devices.last_mut().unwrap() }; let mut device_internal = device.inner.lock().await; let fprs = &[ (fprs.signature(), KeyType::Signing), (fprs.decryption(), KeyType::Decryption), (fprs.authentication(), KeyType::Authentication), ][..]; for ((fpr, slot), present) in fprs.into_iter().zip(present.into_iter()) { let Some(fpr) = fpr else { continue }; // Convert from an // openpgp_card::ocard::data::Fingerprint to a // sequoia_openpgp::Fingerprint. let fpr = Fingerprint::from_bytes(fpr.as_bytes()); log::trace!("{}: {}present", fpr, if present { "" } else { "NOT " }); match device_internal.keys.entry(fpr.clone()) { Entry::Occupied(_oe) => { // Already have it. } Entry::Vacant(ve) => { // Need to add it. log::debug!("Found key {}", fpr); if let Some(pk) = certd.find_one(&fpr).await { let key = Key { fpr: fpr.clone(), inner: Arc::new(Mutex::new(KeyInternal { device: device.downgrade(), fingerprint: fpr, // XXX public_key: pk, password: None, slot: slot.clone(), })), }; ve.insert(key); } else { log::debug!("Ignoring {}: no public key available", fpr); } } } } } self.last_scan = Some(SystemTime::now()); Ok(()) } } #[async_trait::async_trait] impl backend::Backend for Backend { fn id(&self) -> String { "openpgp-card".into() } async fn scan(&mut self) -> Result<()> { let mut backend = self.inner.lock().await; backend.scan_internal(false).await } async fn list<'a>(&'a self) -> Box> + Send + Sync + 'a> { log::trace!("Backend::list"); let mut backend = self.inner.lock().await; if let Err(err) = backend.scan_internal(false).await { log::debug!("Scanning OpenPGP Cards: {}", err); } Box::new( backend.devices.iter() .map(|device| { Box::new(device.clone()) as Box }) .collect::>() .into_iter()) } async fn find_device<'a>(&self, id: &str) -> Result> { log::trace!("Backend::find_device"); let mut backend = self.inner.lock().await; // The first time through we look for the key without // scanning. If we don't find it, then we rescan. for scan in [false, true] { if scan { log::trace!("Rescanning"); if let Err(err) = backend.scan_internal(true).await { log::debug!("Scanning OpenPGP Cards: {}", err); } } for device in backend.devices.iter() { if device.id == id { return Ok(Box::new(device.clone()) as Box); } } } Err(Error::NotFound(id.into()).into()) } async fn find_key<'a>(&self, id: &str) -> Result> { log::trace!("Backend::find_key"); let mut backend = self.inner.lock().await; // The first time through we look for the key without // scanning. If we don't find it, then we rescan. for scan in [false, true] { if scan { log::trace!("Rescanning"); if let Err(err) = backend.scan_internal(true).await { log::debug!("Scanning OpenPGP Cards: {}", err); } } for device in backend.devices.iter() { let device = device.inner.lock().await; for (key_id, key) in device.keys.iter() { if &key_id.to_string() == id { return Ok(Box::new(key.clone()) as Box); } } } } Err(Error::NotFound(id.into()).into()) } async fn import<'a>(&self, _cert: Cert) -> Result)>> { log::trace!("Backend::import"); Err(Error::ExternalImportRequired(Some( "To import a key into an OpenPGP Card, use something like: \ oct admin --card ABCD:01234567 import key.priv".into())).into()) } } #[async_trait::async_trait] impl backend::DeviceHandle for Device { fn id(&self) -> String { log::trace!("Device::id"); self.id.clone() } async fn available(&self) -> bool { log::trace!("Device::available"); // XXX: Right now, we only support plugged-in cards. Change // this when we support registering OpenPGP cards. true } async fn configured(&self) -> bool { log::trace!("Device::configured"); // XXX: Right now, we only support plugged-in cards. Change // this when we support registering OpenPGP cards. true } async fn registered(&self) -> bool { log::trace!("Device::registered"); // XXX: Right now, we only support plugged-in cards. Change // this when we support registering OpenPGP cards. true } async fn lock(&mut self) -> Result<()> { log::trace!("Device::lock"); // We manage passwords at the slot level. let device = self.inner.lock().await; for key in device.keys.values() { let mut key_internal = key.inner.lock().await; key_internal.password = None; } Ok(()) } async fn list<'a>(&'a self) -> Box> + Send + Sync + 'a> { log::trace!("Device::list"); let device = self.inner.lock().await; let keys = device.keys.values() .map(|key| { Box::new(key.clone()) as Box }) .collect::>(); Box::new(keys.into_iter()) } } #[async_trait::async_trait] impl backend::KeyHandle for Key { fn id(&self) -> String { log::trace!("Key::id"); self.fpr.to_string() } fn fingerprint(&self) -> Fingerprint { log::trace!("Key::fingerprint"); self.fpr.clone() } async fn device<'a>(&self) -> Box { log::trace!("Key::device"); let key_internal = self.inner.lock().await; // Get the device that the key is on. let device = match key_internal.device.upgrade() { Some(device) => device, None => panic!("Device disappeared"), }; Box::new(device) } /// Returns whether the key is available. /// /// A key managed by an OpenPGP card is considered to *not* be /// available if: /// /// - It is not present /// - The smartcard is not inserted. async fn available(&self) -> bool { log::trace!("Key::available"); // XXX: Right now, we only support plugged-in cards. Change // this when we support registering OpenPGP cards. true } async fn locked(&self) -> Protection { log::trace!("Key::locked"); // We pessimistically consider a slot to be locked if we // haven't cached a password for the slot. On some cards, we // could use [`Card::check_user_verified`], but on other cards // that increase the pin's error count, which is a bit of a // disaster. // // [`Card::check_user_verified`]: https://docs.rs/openpgp-card/latest/openpgp_card/struct.Card.html#method.check_user_verified // Due to the locking order requirements, in order to get the // device, we have to drop the lock on the key. We copy what // we need from the key, and then get the device. let key_internal = self.inner.lock().await; let slot = key_internal.slot; let slot_str = || { match slot { KeyType::Signing => "signing".to_string(), KeyType::Decryption => "decryption".to_string(), KeyType::Authentication => "authentication".to_string(), s => format!("{:?}", s), } }; if key_internal.password.is_some() { return Protection::Unlocked; } // Get the device that the key is on. let device = match key_internal.device.upgrade() { Some(device) => device, None => { return Protection::ExternalOther( Some("Card disappeared".into())); } }; // Now we drop the lock on key and take the lock on device. drop(key_internal); let mut device_internal = device.inner.lock().await; if device_internal.pinpad { return Protection::ExternalPassword(None); } let mut tx = match device_internal.card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); return Protection::ExternalOther(Some( "Communication with card failed".into())); } }; match tx.user_interaction_flag(slot) { Ok(uif) => { if let Some(uif) = uif { if uif.touch_policy().touch_required() { return Protection::ExternalTouch(Some( format!("Touch the OpenPGP card to unlock the {} key", slot_str()))); } } } Err(err) => { log::debug!("Getting user interaction flags: {}", err); } } Protection::Password(Some( format!("Enter PIN to unlock the {} key", slot_str()))) } async fn password_source(&self) -> PasswordSource { log::trace!("Key::password_source"); // Due to the locking order requirements, in order to get the // device, we have to drop the lock on the key. let key_internal = self.inner.lock().await; // Get the device that the key is on. let device = match key_internal.device.upgrade() { Some(device) => device, None => { // We don't have a way to return an error. return PasswordSource::ExternalSideEffect; } }; // Now we drop the lock on key and take the lock on device. drop(key_internal); let mut device_internal = device.inner.lock().await; // Retake the key lock. let key_internal = self.inner.lock().await; if device_internal.pinpad { // XXX: Is ExternalOnDemand more accurate? return PasswordSource::ExternalSideEffect; } let mut tx = match device_internal.card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); // We don't have a way to return an error. return PasswordSource::ExternalSideEffect; } }; match tx.user_interaction_flag(key_internal.slot) { Ok(uif) => { if let Some(uif) = uif { if uif.touch_policy().touch_required() { return PasswordSource::InlineWithConfirmation; } } } Err(err) => { log::debug!("Getting user interaction flags: {}", err); } } PasswordSource::Inline } async fn decryption_capable(&self) -> bool { log::trace!("Key::decryption_capable"); let key_internal = self.inner.lock().await; match key_internal.slot { KeyType::Signing => false, KeyType::Decryption => true, KeyType::Authentication => false, _ => false, } } async fn signing_capable(&self) -> bool { log::trace!("Key::signing_capable"); let key_internal = self.inner.lock().await; match key_internal.slot { KeyType::Signing => true, KeyType::Decryption => false, KeyType::Authentication => true, _ => false, } } async fn unlock(&mut self, password: Option<&Password>) -> Result<()> { log::trace!("Key::unlock"); // Due to the locking order requirements, in order to get the // device, we have to drop the lock on the key. let key_internal = self.inner.lock().await; if key_internal.password.is_some() { return Err(Error::AlreadyUnlocked( "Key is already unlocked".into()).into()); } // Get the device that the key is on. let device = match key_internal.device.upgrade() { Some(device) => device, None => { return Err(anyhow::anyhow!("Card disappeared")); } }; // Now we drop the lock on key and take the lock on device. drop(key_internal); let mut device_internal = device.inner.lock().await; // Retake the key lock. let mut key_internal = self.inner.lock().await; // Prompt the user for the password. let pinpad = device_internal.pinpad; let mut tx = match device_internal.card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); // XXX: Turn it into a real error. return Err(err.into()); } }; if let Some(password) = password { // The user supplied a password. if pinpad { // We can't use the password: we need to use the pin // pad. return Err(Error::NoInlinePassword(None).into()); } let p = password.map(|p| String::from_utf8(p.to_vec()))?; if key_internal.slot == KeyType::Signing { tx.verify_user_signing_pin(p.into())?; } else { tx.verify_user_pin(p.into())?; } key_internal.password = Some(password.clone()); } else { // The user didn't supply a password. This means we are // supposed to unlock the slot using the pin pad. if ! pinpad { return Err(Error::NoExternalPassword( Some("Cannot prompt user for password".into())).into()); } fn cb() {} if key_internal.slot == KeyType::Signing { tx.verify_user_signing_pinpad(&cb)?; } else { tx.verify_user_pinpad(&cb)?; } } Ok(()) } async fn lock(&mut self) -> Result<()> { log::trace!("Key::lock"); let mut key_internal = self.inner.lock().await; key_internal.password = None; // XXX: We should also lock the slot. Currently the // openpgp-card library doesn't (appear to) support that. Ok(()) } async fn public_key(&self) -> packet::Key { log::trace!("Key::public_key"); let key = self.inner.lock().await; key.public_key.clone() } // XXX: Use plaintext_len. async fn decrypt_ciphertext(&mut self, ciphertext: &mpi::Ciphertext, _plaintext_len: Option) -> Result { log::trace!("Key::decrypt_ciphertext"); // Due to the locking order requirements, in order to get the // device, we have to drop the lock on the key. We copy what // we need from the key, and then get the device. let key_internal = self.inner.lock().await; // Copy what we need. let public_key = key_internal.public_key.clone(); let password = key_internal.password.clone(); // Get the device that the key is on. let device = match key_internal.device.upgrade() { Some(device) => device, None => { return Err(anyhow::anyhow!("Card disappeared")); } }; // Now we drop the lock on key and take the lock on device. drop(key_internal); let mut device_internal = device.inner.lock().await; let mut tx = match device_internal.card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); // XXX: Turn it into a real error. return Err(err.into()); } }; // The rest of this function is derived from // openpgp-card-sequoia's [CardDecryptor::decrypt]. // // [CardDecryptor::decrypt]: https://gitlab.com/openpgp-card/openpgp-card/-/blob/fa3e2e5c/openpgp-card-sequoia/src/decryptor.rs // // The file has the following copyright notice: // // SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 let unlock = || -> Result<()> { if let Some(password) = password { let password = password.map(|p| String::from_utf8(p.to_vec()))?; tx.verify_user_pin(password.into())?; } Ok(()) }; // Delegate a decryption operation to the OpenPGP card. // // This fn prepares the data structures that openpgp-card needs to // perform the decryption operation. // // (7.2.11 PSO: DECIPHER) match (ciphertext, public_key.mpis()) { (mpi::Ciphertext::RSA { c: ct }, mpi::PublicKey::RSA { .. }) => { let dm = Cryptogram::RSA(ct.value()); unlock()?; let dec = tx.card().decipher(dm)?; let sk = SessionKey::from(&dec[..]); Ok(sk) } (mpi::Ciphertext::ECDH { ref e, .. }, mpi::PublicKey::ECDH { ref curve, .. }) => { let dm = if curve == &Curve::Cv25519 { assert_eq!( e.value()[0], 0x40, "Unexpected shape of decrypted Cv25519 data" ); // Ephemeral key without header byte 0x40 Cryptogram::ECDH(&e.value()[1..]) } else { // NIST curves: ephemeral key with header byte Cryptogram::ECDH(e.value()) }; // Decryption operation on the card unlock()?; let mut dec = tx.card().decipher(dm)?; // Specifically handle return value format like Gnuk's // (Gnuk returns a leading '0x04' byte and // an additional 32 trailing bytes) if curve == &Curve::NistP256 && dec.len() == 65 { assert_eq!(dec[0], 0x04, "Unexpected shape of decrypted NistP256 data"); // see Gnuk src/call-ec.c:82 dec = dec[1..33].to_vec(); } #[allow(non_snake_case)] let S: Protected = dec.into(); #[allow(deprecated)] Ok(ecdh::decrypt_unwrap(&public_key, &S, ciphertext)?) } (ciphertext, public) => Err(anyhow::anyhow!( "Unsupported combination of ciphertext {:?} \ and public key {:?} ", ciphertext, public )), } } async fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<(PublicKeyAlgorithm, mpi::Signature)> { log::trace!("Key::sign"); // Due to the locking order requirements, in order to get the // device, we have to drop the lock on the key. We copy what // we need from the key, and then get the device. let key_internal = self.inner.lock().await; // Copy what we need. let public_key = key_internal.public_key.clone(); let password = key_internal.password.clone(); let slot = key_internal.slot; // Get the device that the key is on. let device = match key_internal.device.upgrade() { Some(device) => device, None => { return Err(anyhow::anyhow!("Card disappeared")); } }; // Now we drop the lock on key and take the lock on device. drop(key_internal); let mut device_internal = device.inner.lock().await; let mut tx = match device_internal.card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); // XXX: Turn it into a real error. return Err(err.into()); } }; let unlock = || -> Result<()> { if let Some(password) = password { log::trace!("Unlocking key with password"); let password = password.map(|p| String::from_utf8(p.to_vec()))?; tx.verify_user_signing_pin(password.into())?; log::trace!("Successfully unlocked slot"); } else { log::debug!("Not unlocking slot; no password cached"); } Ok(()) }; // The rest of this function is derived from // openpgp-card-sequoia's [CardSigner::sign]. // // [CardDecryptor::decrypt]: https://gitlab.com/openpgp-card/openpgp-card/-/blob/fa3e2e5c/openpgp-card-sequoia/src/signer.rs // // The file has the following copyright notice: // // SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 let sig_fn = if slot == KeyType::Signing { Transaction::signature_for_hash } else { Transaction::authenticate_for_hash }; // Delegate a signing (or auth) operation to the OpenPGP card. // // This fn prepares the data structures that openpgp-card needs to // perform the signing operation. // // (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) // or (7.2.13 INTERNAL AUTHENTICATE) use openpgp_card::ocard::crypto::Hash; let sig = match (public_key.pk_algo(), public_key.mpis()) { #[allow(deprecated)] (PublicKeyAlgorithm::RSASign, mpi::PublicKey::RSA { .. }) | (PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { .. }) => { let hash = match hash_algo { sequoia_openpgp::types::HashAlgorithm::SHA256 => Hash::SHA256( digest .try_into() .map_err(|_| anyhow::anyhow!("invalid slice length"))?, ), sequoia_openpgp::types::HashAlgorithm::SHA384 => Hash::SHA384( digest .try_into() .map_err(|_| anyhow::anyhow!("invalid slice length"))?, ), sequoia_openpgp::types::HashAlgorithm::SHA512 => Hash::SHA512( digest .try_into() .map_err(|_| anyhow::anyhow!("invalid slice length"))?, ), _ => { return Err(anyhow::anyhow!( "Unsupported hash algorithm for RSA {:?}", hash_algo )); } }; unlock()?; let sig = sig_fn(tx.card(), hash)?; let mpi = mpi::MPI::new(&sig[..]); mpi::Signature::RSA { s: mpi } } (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => { let hash = Hash::EdDSA(digest); unlock()?; let sig = sig_fn(tx.card(), hash)?; let r = mpi::MPI::new(&sig[..32]); let s = mpi::MPI::new(&sig[32..]); mpi::Signature::EdDSA { r, s } } (PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, .. }) => { let hash = match curve { Curve::NistP256 => Hash::ECDSA(&digest[..32]), Curve::NistP384 => Hash::ECDSA(&digest[..48]), Curve::NistP521 => Hash::ECDSA(&digest[..64]), _ => Hash::ECDSA(digest), }; unlock()?; let sig = sig_fn(tx.card(), hash)?; let len_2 = sig.len() / 2; let r = mpi::MPI::new(&sig[..len_2]); let s = mpi::MPI::new(&sig[len_2..]); mpi::Signature::ECDSA { r, s } } // FIXME: implement NIST etc (pk_algo, _) => return Err(anyhow::anyhow!( "Unsupported combination of algorithm {:?} and pubkey {:?}", pk_algo, public_key )), }; log::trace!("Returned a {} signature", public_key.pk_algo()); Ok((public_key.pk_algo(), sig)) } async fn export(&mut self) -> Result> { Err(Error::OperationNotSupported( "Keys cannot be exported from OpenPGP cards.".into()).into()) } async fn change_password(&mut self, password: Option<&Password>) -> Result<()> { log::trace!("KeyHandle::change_password({}, {})", self.fingerprint(), if let Some(password) = password { if password.map(|p| p.is_empty()) { "clear password" } else { "set password" } } else { "ask for password" }); Err(Error::OperationNotSupported( "Use an external tool to manage OpenPGP cards.".into()).into()) } async fn delete_secret_key_material(&mut self) -> Result<()> { log::trace!("KeyHandle::delete_secret_key_material"); Err(Error::OperationNotSupported( "Use an external tool to manage OpenPGP cards.".into()).into()) } } #[cfg(test)] mod tests { use super::*; use openpgp::cert::amalgamation::key::ValidKeyAmalgamation; use openpgp::parse::Parse; use openpgp::policy::StandardPolicy; use openpgp::serialize::Serialize; const P: &StandardPolicy = &StandardPolicy::new(); use backend::test_framework; use backend::Backend as _; const TEST_CARD_ID: &str = "0005:0000AB6D"; const TEST_CARD_ADMIN_PIN: &str = "12345678"; const TEST_CARD_USER_PIN: &str = "123456"; fn get_test_card() -> Option> { let card_backends = match card_backend_pcsc::PcscBackend::cards(None) { Ok(card_backends) => card_backends.collect(), Err(err) => { log::debug!("Listing openpgp cards using PC/SC: {}", err); Vec::new() } }; let mut test_card = None; for card_backend in card_backends.into_iter() { let card_backend = match card_backend { Ok(card_backend) => card_backend, Err(err) => { log::debug!("Opening openpgp card backend: {}", err); continue; } }; let mut card = match openpgp_card::Card::new(card_backend) { Ok(card) => card, Err(err) => { log::debug!("Opening openpgp card: {}", err); continue; } }; let tx = match card.transaction() { Ok(tx) => tx, Err(err) => { log::debug!("Starting transaction on openpgp card: {}", err); continue; } }; let id = match tx.application_identifier() { Ok(id) => id, Err(err) => { log::debug!("Getting application id from openpgp card: {}", err); continue; } }; let id = id.ident(); if id == TEST_CARD_ID { drop(tx); test_card = Some(card); break; } else { eprintln!("Found card {}", id); } } test_card } fn preinit() -> bool { // Don't run the tests if the test card is not available. get_test_card().is_some() } async fn init_backend() -> Backend { let backend = Backend::init_ephemeral().await.expect("can init backend"); let mut card = if let Some(card) = get_test_card() { card } else { panic!("Test card ({}) not available", TEST_CARD_ID); }; // Reset the card so that it is empty. let mut tx = card.transaction().expect("can start a transaction"); tx.factory_reset().expect("can factory reset"); backend } async fn import_cert(backend: &mut Backend, cert: &Cert) { let vc = cert.with_policy(P, None).expect("valid cert"); eprintln!("Importing cert {}'s subkeys", cert.fingerprint()); for ka in vc.keys().subkeys() { eprintln!(" - {}, secret: {}, key flags: {:?}", ka.fingerprint(), ka.has_secret(), ka.key_flags()); } let signing_key = vc.keys().subkeys().for_signing().secret().next(); let auth_key = vc.keys().subkeys().for_authentication().secret().next(); let encryption_key = vc.keys().subkeys().for_transport_encryption().secret().next(); assert!(signing_key.is_some() || auth_key.is_some() || encryption_key.is_some(), "Expect at least one subkey with secret key material"); let mut card = if let Some(card) = get_test_card() { card } else { panic!("Test card ({}) not available", TEST_CARD_ID); }; let mut tx = card.transaction().expect("can start a transaction"); tx.verify_admin_pin(TEST_CARD_ADMIN_PIN.to_string().into()) .expect("can use admin pin"); let mut admin = tx.to_admin_card(None) .expect("can access admin functionality"); // Import the keys. let mut import_key = |key: Option>, slot: KeyType| { if let Some(key) = key { eprintln!("Importing key {} to {:?} slot", key.fingerprint(), slot); assert!(key.has_unencrypted_secret()); let key = Box::new(privkey::SequoiaKey::new(key.into(), None)); //let key = openpgp_card_sequoia::util::vka_as_uploadable_key( // key.into(), None); admin.import_key(key, slot) .expect("can import key"); } else { eprintln!("No {:?} key to import", slot); } }; import_key(signing_key, KeyType::Signing); import_key(auth_key, KeyType::Authentication); import_key(encryption_key, KeyType::Decryption); // And insert the certificate into our local certd so that we // can find the OpenPGP keys. backend.inner.lock().await.certd.certd().insert( &cert.fingerprint().to_string(), (), false, |(), disk| { let cert_; let cert = if let Some(disk) = disk { // Merge. let disk = Cert::from_bytes(disk).expect("valid cert"); cert_ = cert.clone().merge_public(disk).expect("can merge"); &cert_ } else { // New. cert }; let mut bytes = Vec::new(); cert.serialize(&mut bytes).expect("can serialize to a vec"); Ok(bytes.into()) }) .expect("inserted"); } sequoia_keystore_backend::generate_tests!( preinit, true, // Serialize tests. Backend, init_backend, import_cert, false, // Can import encrypted secret key material. Some(1), // Supported key sets. Some(TEST_CARD_USER_PIN), // Default password. false, // Can export. false, // Can change password. false // Can delete secret key material. ); } sequoia-keystore-openpgp-card-0.1.0/src/privkey.rs000064400000000000000000000273731046102023000203520ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 // Copied from // https://gitlab.com/openpgp-card/openpgp-card/-/blob/fa3e2e5c/openpgp-card-sequoia/src/privkey.rs use std::convert::TryFrom; use std::convert::TryInto; use openpgp_card::ocard::crypto::{CardUploadableKey, EccKey, EccType, PrivateKeyMaterial, RSAKey}; use openpgp_card::ocard::data::{Fingerprint, KeyGenerationTime}; use openpgp_card::Error; use sequoia_openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; use sequoia_openpgp::crypto::{mpi, mpi::ProtectedMPI, mpi::MPI}; use sequoia_openpgp::packet::{ key, key::{SecretParts, UnspecifiedRole}, Key, }; use sequoia_openpgp::types::{Curve, Timestamp}; use rsa::traits::PrivateKeyParts; /// A SequoiaKey represents the private cryptographic key material of an /// OpenPGP (sub)key to be uploaded to an OpenPGP card. pub(crate) struct SequoiaKey { key: Key, public: mpi::PublicKey, password: Option, } impl SequoiaKey { /// A `SequoiaKey` wraps a Sequoia PGP private (sub)key data /// (i.e. a ValidErasedKeyAmalgamation) in a form that can be uploaded /// by the openpgp-card crate. pub(crate) fn new( vka: ValidErasedKeyAmalgamation, password: Option, ) -> Self { let public = vka.parts_as_public().mpis().clone(); Self { key: vka.key().clone(), public, password, } } } /// Implement the `CardUploadableKey` trait that openpgp-card uses to /// upload (sub)keys to a card. impl CardUploadableKey for SequoiaKey { fn private_key(&self) -> Result { // Decrypt key with password, if set let key = match &self.password { None => self.key.clone(), Some(pw) => self .key .clone() .decrypt_secret(&sequoia_openpgp::crypto::Password::from(pw.as_str())) .map_err(|e| Error::InternalError(format!("sequoia decrypt failed {e:?}")))?, }; // Get private cryptographic material let unenc = if let Some(key::SecretKeyMaterial::Unencrypted(ref u)) = key.optional_secret() { u } else { panic!("can't get private key material"); }; let secret_key_material = unenc.map(|mpis| mpis.clone()); match (self.public.clone(), secret_key_material) { (mpi::PublicKey::RSA { e, n }, mpi::SecretKeyMaterial::RSA { d, p, q, u: _ }) => { let sq_rsa = SqRSA::new(e, d, n, p, q)?; Ok(PrivateKeyMaterial::R(Box::new(sq_rsa))) } (mpi::PublicKey::ECDH { curve, q, .. }, mpi::SecretKeyMaterial::ECDH { scalar }) => { let sq_ecc = SqEccKey::new(curve, scalar, q, EccType::ECDH); Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) } (mpi::PublicKey::ECDSA { curve, q, .. }, mpi::SecretKeyMaterial::ECDSA { scalar }) => { let sq_ecc = SqEccKey::new(curve, scalar, q, EccType::ECDSA); Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) } (mpi::PublicKey::EdDSA { curve, q, .. }, mpi::SecretKeyMaterial::EdDSA { scalar }) => { let sq_ecc = SqEccKey::new(curve, scalar, q, EccType::EdDSA); Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) } (p, s) => { unimplemented!("Unexpected algorithms: public {:?}, secret {:?}", p, s); } } } /// Number of non-leap seconds since January 1, 1970 0:00:00 UTC /// (aka "UNIX timestamp") fn timestamp(&self) -> KeyGenerationTime { let ts: Timestamp = Timestamp::try_from(self.key.creation_time()) .expect("Creation time cannot be converted into u32 timestamp"); let ts: u32 = ts.into(); ts.into() } fn fingerprint(&self) -> Result { let fp = self.key.fingerprint(); fp.as_bytes().try_into() } } fn mpi_to_biguint(mpi: &MPI) -> rsa::BigUint { slice_to_biguint(mpi.value()) } fn slice_to_biguint(bytes: &[u8]) -> rsa::BigUint { rsa::BigUint::from_bytes_be(bytes) } /// RSA-specific data-structure to hold private (sub)key material for upload /// with the `openpgp-card` crate. struct SqRSA { e: MPI, n: MPI, p: ProtectedMPI, q: ProtectedMPI, pq: ProtectedMPI, dp1: ProtectedMPI, dq1: ProtectedMPI, } impl SqRSA { #[allow(clippy::many_single_char_names)] fn new( e: MPI, d: ProtectedMPI, n: MPI, p: ProtectedMPI, q: ProtectedMPI, ) -> Result { let key = rsa::RsaPrivateKey::from_components( mpi_to_biguint(&n), mpi_to_biguint(&e), slice_to_biguint(d.value()), vec![slice_to_biguint(p.value()), slice_to_biguint(q.value())], ) .map_err(|e| Error::InternalError(format!("rsa error {e:?}")))?; let pq = key .qinv() .ok_or_else(|| Error::InternalError("pq value missing".into()))? .to_biguint() .ok_or_else(|| Error::InternalError("conversion to bigunit failed".into()))? .to_bytes_be() .into(); let dp1 = key .dp() .ok_or_else(|| Error::InternalError("dp1 value missing".into()))? .to_bytes_be() .into(); let dq1 = key .dq() .ok_or_else(|| Error::InternalError("dq1 value missing".into()))? .to_bytes_be() .into(); Ok(Self { e, n, p, q, pq, dp1, dq1, }) } } impl RSAKey for SqRSA { fn e(&self) -> &[u8] { self.e.value() } fn p(&self) -> &[u8] { self.p.value() } fn q(&self) -> &[u8] { self.q.value() } fn pq(&self) -> Box<[u8]> { self.pq.value().into() } fn dp1(&self) -> Box<[u8]> { self.dp1.value().into() } fn dq1(&self) -> Box<[u8]> { self.dq1.value().into() } fn n(&self) -> &[u8] { self.n.value() } } /// ECC-specific data-structure to hold private (sub)key material for upload /// with the `openpgp-card` crate. struct SqEccKey { curve: Curve, private: ProtectedMPI, public: MPI, ecc_type: EccType, } impl SqEccKey { fn new(curve: Curve, private: ProtectedMPI, public: MPI, ecc_type: EccType) -> Self { SqEccKey { curve, private, public, ecc_type, } } } impl EccKey for SqEccKey { fn oid(&self) -> &[u8] { self.curve.oid() } fn private(&self) -> Vec { match self.curve { Curve::NistP256 => self.private.value_padded(0x20).to_vec(), Curve::NistP384 => self.private.value_padded(0x30).to_vec(), Curve::NistP521 => self.private.value_padded(0x42).to_vec(), Curve::Cv25519 | Curve::Ed25519 => self.private.value_padded(0x20).to_vec(), _ => self.private.value().to_vec(), } } fn public(&self) -> Vec { // FIXME: padding? self.public.value().to_vec() } fn ecc_type(&self) -> EccType { self.ecc_type } } // #[cfg(test)] // mod tests { // use openpgp::cert::Cert; // use openpgp::crypto::mpi::PublicKey; // use openpgp::packet::key::SecretKeyMaterial; // use openpgp::parse::Parse; // use sequoia_openpgp as openpgp; // use testresult::TestResult; // // use super::*; // // #[test] // fn parsing_rsa_key() -> TestResult { // let cert = Cert::from_bytes( // r#"-----BEGIN PGP PRIVATE KEY BLOCK----- // // lQHYBGPmItUBBADp/S0sPqOQF6oBEQf558E5HeVtRP0qyWaVT0/fl7gj2jMSu6kF // de1jbr7AdeQxa7RiOo7m/ob8ZzKIzFNMLVfKsfo4mn5QjYulnadl+dyl87Jj1TlN // iEmeVvKbJUzXf7p4B4zFBFwIoCWtGZMTuUOgvi11Gbt00QwNUZdB10VjNwARAQAB // AAP/dH22pR3kSWL2oMRNX8XZJSn0pENh9RDCsRgE0HDU3IiPv8ZMviq5TjT+44tt // 2YrhCbxUk7zpEDUCbCepWrYCS7Q7pMCJul2AdymJBDkNwzrPjNdzPwx1mOIudDFp // uosokjzx/bDNb9c8rdQpB5Oz9f9qZ9WhmfittQvBFPmBjyUCAPHWyhSVt86Wc3Dd // /1nQRLwMHVJK6VszIMO0EYgGvaFN9WXh6VUue9DXnAkHejUDNpsOlJfiAHMDU0fS // PnBX4D0CAPewtqGyIyluZ+S/+MJQBOUqLPzqHHr6smGmbOYFG52RFv17LhQH/02h // sLkd6qXXNUFSOF02XiYV9RywhnSadIMCALP4oM2YGCQL+B5bj3bT1uwoF8O0gwuW // FAc6Sz3ESpaI11ABLOv2wPNS3OcUyyIUe/DPVbekaKswvO57Ddzw5iait7QFQUJD // REWIzgQTAQgAOBYhBBCVR7AQd8pmtyaMetgkYA0A8AOABQJj5iLVAhsBBQsJCAcC // BhUKCQgLAgQWAgMBAh4BAheAAAoJENgkYA0A8AOA5W4EAMGuqrRLFjonYYS97Ypx // zo7HUpOALrLVgfwKoxX2/DdC4FWOQ61cog63KKOiM/DjF/TimLD7R4wls6pbELyD // T038FOlGoWtmtQuf3iUsBKdAYPPiqInaDU9XCy/hm1f7xOz70kpUXVG8K6c6my+b // /fGkli/zcEWR55dOMPeoZ6zF // =QZJ9 // -----END PGP PRIVATE KEY BLOCK-----"#, // )?; // if let Key::V4(key) = cert.primary_key().key().clone().parts_into_secret()? { // let (e, n) = if let PublicKey::RSA { e, n } = key.mpis() { // (e, n) // } else { // unreachable!(); // }; // if let Some(SecretKeyMaterial::Unencrypted(secret)) = key.optional_secret() { // assert!(secret.map(|secret| { // if let openpgp::crypto::mpi::SecretKeyMaterial::RSA { d, p, q, .. } = secret { // let rsa = SqRSA::new(e.clone(), d.clone(), n.clone(), p.clone(), q.clone()) // .unwrap(); // assert_eq!( // rsa.pq(), // vec![ // 66, 30, 140, 169, 99, 220, 224, 43, 7, 176, 133, 35, 251, 25, 162, // 178, 14, 200, 188, 60, 82, 126, 134, 117, 184, 10, 186, 28, 162, // 177, 225, 3, 147, 218, 96, 195, 182, 159, 32, 48, 87, 141, 182, 73, // 232, 37, 154, 152, 123, 11, 1, 86, 188, 224, 157, 35, 125, 4, 210, // 229, 233, 121, 207, 14 // ] // .into() // ); // assert_eq!( // rsa.dp1(), // vec![ // 19, 67, 44, 109, 95, 79, 120, 160, 251, 40, 238, 69, 188, 125, 158, // 59, 236, 43, 25, 182, 229, 199, 97, 215, 38, 63, 93, 118, 28, 51, // 86, 121, 195, 38, 14, 76, 107, 128, 124, 84, 50, 24, 55, 143, 228, // 231, 252, 13, 137, 100, 43, 233, 189, 18, 148, 22, 155, 183, 136, // 195, 120, 103, 71, 113 // ] // .into() // ); // assert_eq!( // rsa.dq1(), // vec![ // 29, 192, 92, 47, 143, 246, 41, 67, 217, 182, 224, 88, 64, 254, 219, // 151, 171, 57, 60, 39, 226, 195, 226, 217, 10, 97, 179, 50, 237, // 234, 35, 67, 10, 63, 232, 75, 224, 156, 21, 78, 125, 221, 124, 94, // 219, 144, 144, 9, 21, 143, 138, 181, 167, 146, 39, 128, 251, 176, // 54, 131, 239, 253, 157, 129 // ] // .into() // ); // true // } else { // false // } // })) // } else { // unreachable!(); // } // } else { // unreachable!(); // } // // Ok(()) // } // }