Deduplicate and rework the signature notation argument handling.

This commit is contained in:
Justus Winter 2024-12-06 16:04:29 +01:00
parent 830c49def0
commit 3f81e65ecb
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
19 changed files with 90 additions and 201 deletions

View File

@ -124,20 +124,8 @@ pub struct Command {
SignerDoc>,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the signature.",
long_help = "Add a notation to the signature. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a `!`, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable.",
)]
pub signature_notations: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
long = "encrypt-for",

View File

@ -105,22 +105,8 @@ future.`",
)]
pub message: String,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the revocation",
long_help = "\
Add a notation to the revocation.
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts with a \
`!`, then the notation is marked as being critical. If a consumer of \
a signature doesn't understand a critical notation, then it will \
ignore the signature. The notation is marked as being human \
readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
long,

View File

@ -132,22 +132,8 @@ created a new subkey, please refresh the certificate.\"",
)]
pub message: String,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the revocation.",
long_help = "\
Add a notation to the revocation.
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts with a \
`!`, then the notation is marked as being critical. If a consumer of \
a signature doesn't understand a critical notation, then it will \
ignore the signature. The notation is marked as being human \
readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
long,

View File

@ -252,22 +252,8 @@ has left the organization, it might say who to contact instead.",
)]
pub message: String,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the certification.",
long_help = "\
Add a notation to the certification.
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts with a \
`!`, then the notation is marked as being critical. If a consumer of \
a signature doesn't understand a critical notation, then it will \
ignore the signature. The notation is marked as being human \
readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
long,

View File

@ -222,20 +222,8 @@ to force the signature to be re-created anyway.",
)]
pub recreate: bool,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the certification.",
long_help = "Add a notation to the certification. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a `!`, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
}
const ADD_EXAMPLES: Actions = Actions {
@ -436,20 +424,8 @@ to force the signature to be recreated anyway.",
)]
pub recreate: bool,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the certification.",
long_help = "Add a notation to the certification. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a `!`, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
}
const AUTHORIZE_EXAMPLES: Actions = Actions {
@ -516,20 +492,8 @@ particular time instead of the current time.
after_help = RETRACT_EXAMPLES,
)]
pub struct RetractCommand {
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the certification.",
long_help = "Add a notation to the certification. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a !, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
long = "recreate",

View File

@ -137,20 +137,8 @@ pub struct Command {
)]
pub non_revocable: bool,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the certification.",
long_help = "Add a notation to the certification. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a !, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
help = FileOrStdout::HELP_OPTIONAL,

View File

@ -202,20 +202,8 @@ of power, you have to opt in to this behavior explicitly.",
#[clap(skip)]
pub expiration_source: Option<clap::parser::ValueSource>,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the certification.",
long_help = "Add a notation to the certification. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a !, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable."
)]
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
#[clap(
help = FileOrStdout::HELP_OPTIONAL,

View File

@ -165,24 +165,8 @@ may change line endings. In doubt, create binary signatures.",
OptionalValue,
SignerDoc>,
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the signature.",
conflicts_with = "merge",
long_help = "Add a notation to the signature. \
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a `!`, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable."
)]
// TODO: Is there a better way to express that one notation consists of two arguments, and
// there may be multiple notations? Like something like Vec<(String, String)>.
// TODO: Also, no need for the Option
pub notation: Vec<String>,
#[command(flatten)]
pub signature_notations: crate::cli::types::SignatureNotationsArg,
}
/// Documentation for signer arguments.

View File

@ -41,6 +41,8 @@ pub use expiration::Expiration;
pub use expiration::ExpirationArg;
pub mod profile;
pub use profile::Profile;
pub mod signature_notations;
pub use signature_notations::SignatureNotationsArg;
pub mod special_names;
pub use special_names::SpecialName;
pub mod time;

View File

@ -0,0 +1,58 @@
//! Common argument for signature notations.
use anyhow::Result;
use sequoia_openpgp::packet::signature::subpacket::{
NotationData,
NotationDataFlags,
};
#[derive(Debug, clap::Args)]
pub struct SignatureNotationsArg {
#[clap(
long = "signature-notation",
value_names = &["NAME", "VALUE"],
number_of_values = 2,
help = "Add a notation to the signature",
long_help = "Add a notation to the signature
A user-defined notation's name must be of the form \
`name@a.domain.you.control.org`. If the notation's name starts \
with a `!`, then the notation is marked as being critical. If a \
consumer of a signature doesn't understand a critical notation, \
then it will ignore the signature. The notation is marked as \
being human readable.",
)]
pub signature_notations: Vec<String>,
}
impl SignatureNotationsArg {
/// Parses the notations.
pub fn parse(&self) -> Result<Vec<(bool, NotationData)>> {
let n = &self.signature_notations;
assert_eq!(n.len() % 2, 0, "notations must be pairs of key and value");
// Each --notation takes two values. Iterate over them in chunks of 2.
let notations: Vec<(bool, NotationData)> = n
.chunks(2)
.map(|arg_pair| {
let name = &arg_pair[0];
let value = &arg_pair[1];
let (critical, name) = match name.strip_prefix('!') {
Some(name) => (true, name),
None => (false, name.as_str()),
};
let notation_data = NotationData::new(
name,
value,
NotationDataFlags::empty().set_human_readable(),
);
(critical, notation_data)
})
.collect();
Ok(notations)
}
}

View File

@ -59,8 +59,7 @@ pub fn dispatch(sq: Sq, command: cli::encrypt::Command) -> Result<()> {
sequoia_wot::FULLY_TRUSTED)?;
let signers = sq.get_signing_keys(&signers, None)?;
let notations =
crate::parse_notations(command.signature_notations)?;
let notations = command.signature_notations.parse()?;
if signers.is_empty() && ! notations.is_empty() {
return Err(anyhow::anyhow!("--signature-notation requires signers, \

View File

@ -10,7 +10,6 @@ use crate::Sq;
use crate::cli::key::revoke::Command;
use crate::common::get_secret_signer;
use crate::common::RevocationOutput;
use crate::parse_notations;
/// Handle the revocation of a certificate
struct CertificateRevocation {
@ -95,7 +94,7 @@ pub fn certificate_revoke(
Some(sq.resolve_cert(&command.revoker, sequoia_wot::FULLY_TRUSTED)?.0)
};
let notations = parse_notations(command.notation)?;
let notations = command.signature_notations.parse()?;
let revocation = CertificateRevocation::new(
&sq,

View File

@ -16,7 +16,6 @@ use crate::Sq;
use crate::common::NULL_POLICY;
use crate::common::RevocationOutput;
use crate::common::get_secret_signer;
use crate::parse_notations;
/// Handle the revocation of a subkey
struct SubkeyRevocation {
@ -131,7 +130,7 @@ pub fn dispatch(sq: Sq, command: crate::cli::key::subkey::revoke::Command)
Some(sq.resolve_cert(&command.revoker, sequoia_wot::FULLY_TRUSTED)?.0)
};
let notations = parse_notations(command.notation)?;
let notations = command.signature_notations.parse()?;
let revocation = SubkeyRevocation::new(
&sq,

View File

@ -33,7 +33,6 @@ use crate::common::userid::{
lint_names,
lint_userids,
};
use crate::parse_notations;
/// Handle the revocation of a User ID
struct UserIDRevocation {
@ -324,7 +323,7 @@ pub fn userid_revoke(
Some(sq.resolve_cert(&command.revoker, sequoia_wot::FULLY_TRUSTED)?.0)
};
let notations = parse_notations(command.notation)?;
let notations = command.signature_notations.parse()?;
let revocation = UserIDRevocation::new(
&sq,

View File

@ -16,7 +16,6 @@ use cert_store::{LazyCert, Store};
use crate::Sq;
use crate::commands::active_certification;
use crate::common::NULL_POLICY;
use crate::parse_notations;
use crate::cli::pki::link;
use crate::cli::types::Expiration;
@ -46,7 +45,7 @@ pub fn add(sq: Sq, c: link::AddCommand)
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(c.notation)?;
let notations = c.signature_notations.parse()?;
let templates: Vec<(TrustAmount<_>, Expiration)> = if c.temporary {
// Make the partially trusted link one second younger. When
@ -97,7 +96,7 @@ pub fn authorize(sq: Sq, c: link::AuthorizeCommand)
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(c.notation)?;
let notations = c.signature_notations.parse()?;
crate::common::pki::certify::certify(
&mut std::io::stdout(),
@ -130,7 +129,7 @@ pub fn retract(sq: Sq, c: link::RetractCommand)
let vc = cert.with_policy(NULL_POLICY, Some(sq.time))?;
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(c.notation)?;
let notations = c.signature_notations.parse()?;
crate::common::pki::certify::certify(
&mut std::io::stdout(),

View File

@ -4,7 +4,6 @@ use openpgp::Result;
use crate::Sq;
use crate::cli::pki::vouch::add;
use crate::commands::FileOrStdout;
use crate::parse_notations;
pub fn add(sq: Sq, mut c: add::Command)
-> Result<()>
@ -24,7 +23,7 @@ pub fn add(sq: Sq, mut c: add::Command)
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(&c.notation)?;
let notations = c.signature_notations.parse()?;
let expiration =
sq.config.pki_vouch_expiration(&c.expiration, c.expiration_source);

View File

@ -4,7 +4,6 @@ use openpgp::Result;
use crate::Sq;
use crate::cli::pki::vouch::authorize;
use crate::commands::FileOrStdout;
use crate::parse_notations;
pub fn authorize(sq: Sq, mut c: authorize::Command)
-> Result<()>
@ -24,7 +23,7 @@ pub fn authorize(sq: Sq, mut c: authorize::Command)
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(&c.notation)?;
let notations = c.signature_notations.parse()?;
let expiration =
sq.config.pki_vouch_expiration(&c.expiration, c.expiration_source);

View File

@ -25,7 +25,6 @@ use openpgp::types::SignatureType;
use crate::Sq;
use crate::parse_notations;
use crate::cli;
use crate::cli::sign::Mode;
@ -55,7 +54,7 @@ pub fn dispatch(sq: Sq, command: cli::sign::Command) -> Result<()> {
return Err(anyhow::anyhow!("No signing keys found"));
}
let notations = parse_notations(command.notation)?;
let notations = command.signature_notations.parse()?;
if let Some(merge) = command.merge {
let output = output.create_pgp_safe(

View File

@ -20,8 +20,6 @@ use sequoia_openpgp as openpgp;
use openpgp::Result;
use openpgp::Cert;
use openpgp::parse::Parse;
use openpgp::packet::signature::subpacket::NotationData;
use openpgp::packet::signature::subpacket::NotationDataFlags;
use openpgp::cert::prelude::*;
use clap::FromArgMatches;
@ -421,37 +419,6 @@ fn real_main() -> Result<()> {
}
}
fn parse_notations<N>(n: N) -> Result<Vec<(bool, NotationData)>>
where
N: AsRef<[String]>,
{
let n = n.as_ref();
assert_eq!(n.len() % 2, 0, "notations must be pairs of key and value");
// Each --notation takes two values. Iterate over them in chunks of 2.
let notations: Vec<(bool, NotationData)> = n
.chunks(2)
.map(|arg_pair| {
let name = &arg_pair[0];
let value = &arg_pair[1];
let (critical, name) = match name.strip_prefix('!') {
Some(name) => (true, name),
None => (false, name.as_str()),
};
let notation_data = NotationData::new(
name,
value,
NotationDataFlags::empty().set_human_readable(),
);
(critical, notation_data)
})
.collect();
Ok(notations)
}
// Sometimes the same error cascades, e.g.:
//
// ```