Identify common user errors when verifying detached signatures.

- And try to give helpful advice.

  - Fixes #162.
This commit is contained in:
Justus Winter 2024-10-17 14:30:20 +02:00
parent 7c1296da56
commit d1a10b9346
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
2 changed files with 203 additions and 12 deletions

View File

@ -1,11 +1,12 @@
use std::convert::TryFrom;
use std::fmt;
use std::io::{self, Read};
use std::path::Path;
use std::time::Duration;
use anyhow::Context;
use buffered_reader::BufferedReader;
use buffered_reader::{BufferedReader, Dup};
use sequoia_openpgp as openpgp;
use openpgp::{Fingerprint, KeyHandle, Packet, Result};
@ -15,7 +16,13 @@ use openpgp::packet::{
Signature,
key::PublicParts,
};
use openpgp::parse::{Dearmor, Parse, PacketParserBuilder, PacketParserResult};
use openpgp::parse::{
Cookie,
Dearmor,
Parse,
PacketParserBuilder,
PacketParserResult,
};
use openpgp::policy::{Policy, HashAlgoSecurity};
use openpgp::packet::key::SecretKeyMaterial;
use openpgp::types::{
@ -106,7 +113,8 @@ pub fn inspect<'a, R>(sq: &mut Sq,
print_certifications: bool,
dump_bad_signatures: bool)
-> Result<Kind>
where R: BufferedReader<sequoia_openpgp::parse::Cookie> + 'a,
where
R: BufferedReader<sequoia_openpgp::parse::Cookie> + 'a,
{
let mut ppr = openpgp::parse::PacketParser::from_buffered_reader(input)?;
let mut type_called = None; // Did we print the type yet?
@ -849,3 +857,176 @@ impl fmt::Display for Kind {
}
}
}
impl Kind {
/// Identifies OpenPGP data.
///
/// Returns the kind and the original reader without any data
/// consumed.
pub fn identify<'a, T>(sq: &mut Sq, input: T)
-> Result<(Kind, Box<dyn BufferedReader<Cookie> + 'a>)>
where
T: BufferedReader<Cookie> + 'a,
{
let mut sink: Box<dyn io::Write + Send + Sync> =
Box::new(io::Sink::default());
let mut dup = Dup::with_cookie(input, Default::default());
let kind = inspect(sq, &mut dup, None, &mut sink, false, false)?;
Ok((kind, dup.into_boxed().into_inner().unwrap()))
}
/// Checks that `self` matches `expected`, or prints hints on what
/// to do instead and returns an error.
pub fn expect_or_else(&self,
sq: &Sq,
command: &str,
expected: Kind,
input_arg: &str,
input_path: Option<&Path>)
-> Result<()>
{
if self != &expected {
let input_path_text =
input_path.as_ref().map(|p| p.display().to_string())
.unwrap_or_else(|| "stdin".into());
let input_path_arg =
input_path.map(|p| p.display().to_string())
.unwrap_or_else(|| "-".into());
let msg = format!(
"Expected {} for {}, but {} is {}.",
expected, input_arg, input_path_text, self);
let mut hint = sq.hint(format_args!("{}", msg));
match self {
Kind::Cert => {
if command == "verify" {
hint = hint.hint(format_args!(
"To verify a message or signature using {}:",
input_path_text))
.sq().arg("verify")
.arg_value("--signer-file", &input_path_arg)
.done();
}
if command == "decrypt" {
hint = hint.hint(format_args!(
"To verify a message using {}:",
input_path_text))
.sq().arg("decrypt")
.arg_value("--signer-file", &input_path_arg)
.done();
}
hint.hint(format_args!(
"To import the cert {}:", input_path_text))
.sq().arg("cert").arg("import")
.arg(&input_path_arg)
.done();
},
Kind::Key => {
if command == "verify" {
hint = hint.hint(format_args!(
"To verify a message or signature using {}:",
input_path_text))
.sq().arg("verify")
.arg_value("--signer-file", &input_path_arg)
.done();
}
if command == "decrypt" {
hint = hint.hint(format_args!(
"To verify the signature on an encrypted message \
using {}:",
input_path_text))
.sq().arg("decrypt")
.arg_value("--signer-file", &input_path_arg)
.done();
hint = hint.hint(format_args!(
"To decrypt an encrypted message using {}:",
input_path_text))
.sq().arg("decrypt")
.arg_value("--recipient-file", &input_path_arg)
.done();
}
hint.hint(format_args!(
"To import the key {}:", input_path_text))
.sq().arg("key").arg("import")
.arg(&input_path_arg)
.done();
},
Kind::Keyring => {
hint.hint(format_args!(
"To import the certificates in {}:", input_path_text))
.sq().arg("cert").arg("import")
.arg(&input_path_arg)
.done();
},
Kind::SignedMessage => {
hint.hint(format_args!(
"To verify a signed message:"))
.sq().arg("verify")
.arg(&input_path_arg)
.done();
},
Kind::EncryptedMessage => {
hint.hint(format_args!(
"To decrypt an encrypted message:"))
.sq().arg("decrypt")
.arg(input_path_arg)
.done();
},
Kind::DetachedSig => {
hint.hint(format_args!(
"To verify the detached signature {}:",
input_path_text))
.sq().arg("verify")
.arg("--signature-file").arg(&input_path_arg)
.arg("the-data-file")
.done();
},
Kind::RevocationCert => {
hint.hint(format_args!(
"To import the revocation certificate {}:",
input_path_text))
.sq().arg("cert").arg("import")
.arg(&input_path_arg)
.done();
},
Kind::Unknown => {
hint.hint(format_args!(
"To inspect the packet sequence in {}:",
input_path_text))
.sq().arg("toolbox").arg("packet").arg("dump")
.arg(&input_path_arg)
.done();
},
Kind::NotOpenPGP => {
if command == "verify" {
hint.hint(format_args!(
"To verify the detached signature \
over the data in {}:", input_path_text))
.sq().arg("verify")
.arg("--signature-file").arg("the-signature-file")
.arg(&input_path_arg)
.done();
}
},
}
Err(anyhow::anyhow!("{}", msg))
} else {
Ok(())
}
}
}

View File

@ -1,8 +1,10 @@
use std::io;
use std::fs::File;
use std::path::PathBuf;
use anyhow::Context;
use buffered_reader::File;
use sequoia_openpgp as openpgp;
use openpgp::Cert;
use openpgp::types::KeyFlags;
@ -14,6 +16,7 @@ use crate::Sq;
use crate::Result;
use crate::cli;
use crate::commands::VHelper;
use crate::commands::inspect::Kind;
use crate::load_certs;
pub fn dispatch(sq: Sq, command: cli::verify::Command)
@ -23,11 +26,6 @@ pub fn dispatch(sq: Sq, command: cli::verify::Command)
let mut input = command.input.open()?;
let mut output = command.output.create_safe(&sq)?;
let mut detached = if let Some(f) = command.detached {
Some(File::open(f)?)
} else {
None
};
let signatures = command.signatures;
// TODO ugly adaptation to load_certs' signature, fix later
let mut certs = load_certs(
@ -39,18 +37,30 @@ pub fn dispatch(sq: Sq, command: cli::verify::Command)
false)
.context("--sender-cert")?);
verify(sq, &mut input,
detached.as_mut().map(|r| r as &mut (dyn io::Read + Sync + Send)),
command.detached,
&mut output, signatures, certs)?;
Ok(())
}
pub fn verify(sq: Sq,
pub fn verify(mut sq: Sq,
input: &mut (dyn io::Read + Sync + Send),
detached: Option<&mut (dyn io::Read + Sync + Send)>,
detached: Option<PathBuf>,
output: &mut dyn io::Write,
signatures: usize, certs: Vec<Cert>)
-> Result<()> {
let detached = if let Some(sig_path) = detached {
let sig = File::with_cookie(&sig_path, Default::default())?;
let (kind, sig) = Kind::identify(&mut sq, sig)?;
kind.expect_or_else(&sq, "verify", Kind::DetachedSig,
"--signature-file", Some(&sig_path))?;
Some(sig)
} else {
None
};
let helper = VHelper::new(&sq, signatures, certs);
let helper = if let Some(dsig) = detached {
let mut v = DetachedVerifierBuilder::from_reader(dsig)?