#![allow(clippy::expect_fun_call)] use std::sync::{Arc, Mutex}; use keyfork_bug::{bug, POISONED_MUTEX}; use keyfork_prompt::{Error as PromptError, PromptHandler}; use super::openpgp::{ self, cert::Cert, packet::{PKESK, SKESK}, parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper}, policy::NullPolicy, KeyHandle, KeyID, }; use anyhow::Context; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Secret key was not found")] SecretKeyNotFound, #[error("Prompt failed: {0}")] Prompt(#[from] PromptError), } pub type Result = std::result::Result; pub struct Keyring { full_certs: Vec, root: Option, pm: Arc>, } impl Keyring

{ pub fn new(certs: impl AsRef<[Cert]>, p: Arc>) -> Result { Ok(Self { full_certs: certs.as_ref().to_vec(), root: Default::default(), pm: p, }) } pub fn is_empty(&self) -> bool { self.full_certs.is_empty() } // Sets the root cert, returning the old cert pub fn set_root_cert(&mut self, cert: impl Into>) -> Option { let mut cert = cert.into(); std::mem::swap(&mut self.root, &mut cert); cert } pub fn root_cert(&self) -> Option<&Cert> { self.root.as_ref() } pub fn get_cert_for_primary_keyid<'a>(&'a self, keyid: &KeyID) -> Option<&'a Cert> { self.full_certs.iter().find(|cert| &cert.keyid() == keyid) } } impl VerificationHelper for &mut Keyring

{ fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result> { Ok(ids .iter() .flat_map(|kh| { self.root .iter() .filter(move |cert| &cert.key_handle() == kh) }) .cloned() .collect()) } fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> { for layer in structure { #[allow(unused_variables)] match layer { MessageLayer::Compression { algo } => {} MessageLayer::Encryption { sym_algo, aead_algo, } => {} MessageLayer::SignatureGroup { results } => { match &results[..] { [Ok(_)] => { return Ok(()); } _ => { // FIXME: anyhow leak: VerificationError impl std::error::Error // return Err(e.context("Invalid signature")); return Err(anyhow::anyhow!("Error validating signature; either multiple signatures were passed or the single signature was not valid")); } } /* for result in results { if let Err(e) = result { return Err(anyhow::anyhow!("Invalid signature: {e}")); } } */ } } } Ok(()) } } impl DecryptionHelper for &mut Keyring

{ fn decrypt( &mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option, mut decrypt: D, ) -> openpgp::Result> where D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool, { let null = NullPolicy::new(); // unoptimized route: use all locally stored certs for pkesk in pkesks { for cert in self.full_certs.iter().filter(|cert| { pkesk.recipient().is_wildcard() || cert.keys().any(|k| &k.keyid() == pkesk.recipient()) }) { let name = cert .userids() .next() .and_then(|userid| userid.userid().name2().transpose()) .transpose() .ok() .flatten(); for key in cert .keys() .with_policy(&null, None) .for_storage_encryption() .secret() { let secret_key = key.key().clone(); let mut keypair = if secret_key.has_unencrypted_secret() { secret_key .into_keypair() .context("Has unencrypted secret")? } else { let message = if let Some(name) = name.as_ref() { format!("Decryption key for {} ({name}): ", secret_key.keyid()) } else { format!("Decryption key for {}: ", secret_key.keyid()) }; let passphrase = self .pm .lock() .expect(bug!(POISONED_MUTEX)) .prompt_passphrase(&message) .context("Decryption passphrase")?; secret_key .decrypt_secret(&passphrase.as_str().into()) .context("has_unencrypted_secret is false, could not decrypt secret")? .into_keypair() .context("just-decrypted key")? }; if pkesk .decrypt(&mut keypair, sym_algo) .is_some_and(|(algo, sk)| decrypt(algo, &sk)) { return Ok(Some(key.fingerprint())); } } } } Err(Error::SecretKeyNotFound.into()) } }