178 lines
6.0 KiB
Rust
178 lines
6.0 KiB
Rust
#![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<T, E = Error> = std::result::Result<T, E>;
|
|
|
|
pub struct Keyring<P: PromptHandler> {
|
|
full_certs: Vec<Cert>,
|
|
root: Option<Cert>,
|
|
pm: Arc<Mutex<P>>,
|
|
}
|
|
|
|
impl<P: PromptHandler> Keyring<P> {
|
|
pub fn new(certs: impl AsRef<[Cert]>, p: Arc<Mutex<P>>) -> Result<Self> {
|
|
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<Cert>>) -> Option<Cert> {
|
|
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<P: PromptHandler> VerificationHelper for &mut Keyring<P> {
|
|
fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
|
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<P: PromptHandler> DecryptionHelper for &mut Keyring<P> {
|
|
fn decrypt<D>(
|
|
&mut self,
|
|
pkesks: &[PKESK],
|
|
_skesks: &[SKESK],
|
|
sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
|
|
mut decrypt: D,
|
|
) -> openpgp::Result<Option<openpgp::Fingerprint>>
|
|
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())
|
|
}
|
|
}
|