#![cfg_attr(docsrs, doc(cfg(feature = "sequoia")))]
use sequoia_openpgp::{
Cert, Fingerprint, KeyHandle, Result as SequoiaResult,
cert::CertParser,
packet::Signature,
parse::{
Parse,
stream::{MessageLayer, MessageStructure, VerificationHelper, VerifierBuilder},
},
policy::StandardPolicy,
};
use std::{
collections::HashMap,
io::{Cursor, Read},
path::{Path, PathBuf},
};
#[derive(Clone, Default)]
pub struct OpenPgpValidatorBuilder {
keyrings: Vec<PathBuf>,
insecure_skip_verify: bool,
}
#[derive(Clone)]
pub struct OpenPgpValidator {
keys: HashMap<Fingerprint, Cert>,
insecure_skip_verify: bool,
}
#[derive(Debug)]
pub enum OpenPgpValidatorError {
NoConfiguredKeyring,
NoValidSignatures,
Io(std::io::Error),
Sequoia(anyhow::Error),
}
crate::errors::error_enum!(OpenPgpValidatorError);
pub type Signatures = Vec<(Cert, Signature)>;
impl OpenPgpValidator {
pub fn build() -> OpenPgpValidatorBuilder {
Default::default()
}
pub fn validate_reader<ReadT>(
&self,
mut message: ReadT,
) -> Result<(Signatures, Cursor<Vec<u8>>), OpenPgpValidatorError>
where
ReadT: Read,
{
let mut bytes = vec![];
message
.read_to_end(&mut bytes)
.map_err(OpenPgpValidatorError::Io)?;
self.validate(&bytes)
}
pub fn validate(
&self,
message: &[u8],
) -> Result<(Signatures, Cursor<Vec<u8>>), OpenPgpValidatorError> {
let p = &StandardPolicy::new();
struct Helper<'a> {
validator: &'a OpenPgpValidator,
results: Signatures,
}
impl VerificationHelper for &mut Helper<'_> {
fn get_certs(&mut self, _ids: &[KeyHandle]) -> SequoiaResult<Vec<Cert>> {
Ok(self.validator.keys.values().cloned().collect())
}
fn check(&mut self, structure: MessageStructure) -> SequoiaResult<()> {
for (i, layer) in structure.into_iter().enumerate() {
match layer {
MessageLayer::Encryption { .. } if i == 0 => (),
MessageLayer::Compression { .. } if i == 1 => (),
MessageLayer::SignatureGroup { results } => {
for result in results {
let Ok(result) = result else {
continue;
};
let signature = result.sig.clone();
let fingerprints = signature.issuer_fingerprints();
for fingerprint in fingerprints {
let Some(signer) = self.validator.keys.get(fingerprint) else {
continue;
};
self.results.push((signer.clone(), signature.clone()));
}
}
}
_ => return Err(anyhow::anyhow!("Unexpected message structure")),
}
}
Ok(())
}
}
let mut helper = Helper {
validator: self,
results: vec![],
};
let mut v = VerifierBuilder::from_bytes(message)
.map_err(OpenPgpValidatorError::Sequoia)?
.with_policy(p, None, &mut helper)
.map_err(OpenPgpValidatorError::Sequoia)?;
let mut content = vec![];
v.read_to_end(&mut content)
.map_err(OpenPgpValidatorError::Io)?;
let Helper { results, .. } = helper;
if results.is_empty() && !self.insecure_skip_verify {
return Err(OpenPgpValidatorError::NoValidSignatures);
}
Ok((results, Cursor::new(content)))
}
}
#[cfg(feature = "tokio")]
mod _tokio {
#![cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
use super::*;
use tokio::io::{AsyncRead, AsyncReadExt};
impl OpenPgpValidator {
pub async fn validate_reader_async<ReadT>(
&self,
mut message: ReadT,
) -> Result<(Vec<(Cert, Signature)>, Cursor<Vec<u8>>), OpenPgpValidatorError>
where
ReadT: AsyncRead,
ReadT: Unpin,
{
let mut bytes = vec![];
message
.read_to_end(&mut bytes)
.await
.map_err(OpenPgpValidatorError::Io)?;
self.validate(&bytes)
}
}
}
impl OpenPgpValidatorBuilder {
pub fn with_keyring(mut self, path: &Path) -> Self {
self.keyrings.push(path.to_owned());
self
}
pub fn with_insecure_skip_verify_this_is_a_bad_idea(mut self) -> Self {
self.insecure_skip_verify = true;
self
}
pub fn build(self) -> Result<OpenPgpValidator, OpenPgpValidatorError> {
let keys = {
let mut keys = HashMap::new();
for keyring in self.keyrings {
for cert in
CertParser::from_file(keyring).map_err(OpenPgpValidatorError::Sequoia)?
{
let cert = cert.map_err(OpenPgpValidatorError::Sequoia)?;
keys.insert(cert.fingerprint(), cert.clone());
for key in cert.keys() {
keys.insert(key.key().fingerprint(), cert.clone());
}
}
}
keys
};
Ok(OpenPgpValidator {
keys,
insecure_skip_verify: self.insecure_skip_verify,
})
}
}
#[cfg_attr(not(feature = "serde"), allow(dead_code))]
pub(crate) fn verify(
keyring: &Path,
input: &str,
) -> Result<(Vec<(Cert, Signature)>, impl Read), OpenPgpValidatorError> {
let verifier = OpenPgpValidator::build().with_keyring(keyring).build()?;
verifier.validate(input.as_bytes())
}