use std::{collections::HashSet, path::PathBuf};
pub use pgp::native::{SignedPublicKey, SignedSecretKey};
use secret::Secret;
use shellexpand_utils::shellexpand_path;
use tracing::debug;
use crate::{Error, Result};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub enum NativePgpSecretKey {
#[default]
None,
#[cfg_attr(feature = "derive", serde(skip))]
Raw(SignedSecretKey),
Path(PathBuf),
#[cfg(feature = "keyring")]
Keyring(secret::keyring::KeyringEntry),
}
impl NativePgpSecretKey {
pub async fn get(&self, recipient: impl ToString) -> Result<SignedSecretKey> {
let recipient = recipient.to_string();
match self {
Self::None => Ok(Err(Error::GetNativePgpSecretKeyNoneError(
recipient.clone(),
))?),
Self::Raw(skey) => Ok(skey.clone()),
Self::Path(path) => {
let path = shellexpand_path(path);
let skey = pgp::read_skey_from_file(path)
.await
.map_err(Error::ReadNativePgpSecretKeyError)?;
Ok(skey)
}
#[cfg(feature = "keyring")]
Self::Keyring(entry) => {
let data = entry
.get_secret()
.await
.map_err(Error::GetPgpSecretKeyFromKeyringError)?;
let skey = pgp::read_skey_from_string(data)
.await
.map_err(Error::ReadNativePgpSecretKeyError)?;
Ok(skey)
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub enum NativePgpPublicKeysResolver {
#[cfg_attr(feature = "derive", serde(skip))]
Raw(String, SignedPublicKey),
Wkd,
KeyServers(Vec<String>),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub struct PgpNative {
pub secret_key: NativePgpSecretKey,
pub secret_key_passphrase: Secret,
pub public_keys_resolvers: Vec<NativePgpPublicKeysResolver>,
}
impl PgpNative {
pub async fn encrypt(
&self,
emails: impl IntoIterator<Item = String>,
data: Vec<u8>,
) -> Result<Vec<u8>> {
let mut pkeys = Vec::new();
let mut recipients: HashSet<String> = HashSet::from_iter(emails.into_iter());
for resolver in &self.public_keys_resolvers {
match resolver {
NativePgpPublicKeysResolver::Raw(recipient, pkey) => {
if recipients.remove(recipient) {
debug!("found pgp public key for {recipient} using raw pair");
pkeys.push(pkey.clone())
}
}
NativePgpPublicKeysResolver::Wkd => {
let recipients_clone = recipients.clone().into_iter().collect();
let wkd_pkeys = pgp::http::wkd::get_all(recipients_clone).await;
pkeys.extend(wkd_pkeys.into_iter().fold(
Vec::new(),
|mut pkeys, (ref recipient, res)| {
match res {
Ok(pkey) => {
if recipients.remove(recipient) {
debug!("found pgp public key for {recipient} using wkd");
pkeys.push(pkey);
}
}
Err(err) => {
let msg = format!("cannot find pgp public key for {recipient}");
debug!("{msg} using wkd: {err}");
debug!("{err:?}");
}
}
pkeys
},
));
}
NativePgpPublicKeysResolver::KeyServers(key_servers) => {
let recipients_clone = recipients.clone().into_iter().collect();
let http_pkeys =
pgp::http::get_all(recipients_clone, key_servers.to_owned()).await;
pkeys.extend(http_pkeys.into_iter().fold(
Vec::default(),
|mut pkeys, (ref recipient, res)| {
match res {
Ok(pkey) => {
if recipients.remove(recipient) {
let msg = format!("found pgp public key for {recipient}");
debug!("{msg} using key servers");
pkeys.push(pkey);
}
}
Err(err) => {
let msg = format!("cannot find pgp public key for {recipient}");
debug!("{msg} using key servers: {err}");
debug!("{err:?}");
}
}
pkeys
},
));
}
}
if recipients.is_empty() {
break;
}
}
let data = pgp::encrypt(pkeys, data)
.await
.map_err(Error::EncryptNativePgpError)?;
Ok(data)
}
pub async fn decrypt(&self, email: impl ToString, data: Vec<u8>) -> Result<Vec<u8>> {
let skey = self.secret_key.get(email).await?;
let passphrase = self
.secret_key_passphrase
.get()
.await
.map_err(Error::GetSecretKeyPassphraseFromKeyringError)?;
let data = pgp::decrypt(skey, passphrase, data)
.await
.map_err(Error::DecryptNativePgpError)?;
Ok(data)
}
pub async fn sign(&self, email: impl ToString, data: Vec<u8>) -> Result<Vec<u8>> {
let skey = self.secret_key.get(email).await?;
let passphrase = self
.secret_key_passphrase
.get()
.await
.map_err(Error::GetSecretKeyPassphraseFromKeyringError)?;
let data = pgp::sign(skey, passphrase, data)
.await
.map_err(Error::SignNativePgpError)?;
Ok(data)
}
pub async fn verify(&self, email: impl AsRef<str>, sig: Vec<u8>, data: Vec<u8>) -> Result<()> {
let email = email.as_ref();
let mut pkey_found = None;
for resolver in &self.public_keys_resolvers {
match resolver {
NativePgpPublicKeysResolver::Raw(recipient, pkey) => {
if recipient == email {
debug!("found pgp public key for {recipient} using raw pair");
pkey_found = Some(pkey.clone());
break;
} else {
continue;
}
}
NativePgpPublicKeysResolver::Wkd => {
match pgp::http::wkd::get_one(email.to_owned()).await {
Ok(pkey) => {
debug!("found pgp public key for {email} using wkd");
pkey_found = Some(pkey);
break;
}
Err(err) => {
debug!(?err, "cannot find pgp public key for {email} using wkd");
continue;
}
}
}
NativePgpPublicKeysResolver::KeyServers(key_servers) => {
let pkey = pgp::http::get_one(email.to_owned(), key_servers.clone()).await;
match pkey {
Ok(pkey) => {
debug!("found pgp public key for {email} using key servers");
pkey_found = Some(pkey);
break;
}
Err(err) => {
let msg = format!("cannot find pgp public key for {email}");
debug!(?err, "{msg} using key servers");
continue;
}
}
}
}
}
let pkey = pkey_found.ok_or(Error::FindPgpPublicKeyError(email.to_owned()))?;
let sig = pgp::read_sig_from_bytes(sig)
.await
.map_err(Error::ReadNativePgpSignatureError)?;
pgp::verify(pkey, sig, data)
.await
.map_err(Error::VerifyNativePgpSignatureError)?;
Ok(())
}
}