Move all global PKI options to subcommands where they are needed.

This commit is contained in:
Justus Winter 2024-01-16 17:57:37 +01:00
parent 2c7a22558a
commit b000993817
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
4 changed files with 235 additions and 147 deletions

View File

@ -36,47 +36,6 @@ necessarily institutions act as trusted introducers.
arg_required_else_help = true,
)]
pub struct Command {
/// Treats all certificates as unreliable trust roots.
///
/// This option is useful for figuring out what others think about
/// a certificate (i.e., gossip or hearsay). In other words, this
/// finds arbitrary paths to a particular certificate.
///
/// Gossip is useful in helping to identify alternative ways to
/// authenticate a certificate. For instance, imagine Ed wants to
/// authenticate Laura's certificate, but asking her directly is
/// inconvenient. Ed discovers that Micah has certified Laura's
/// certificate, but Ed hasn't yet authenticated Micah's
/// certificate. If Ed is willing to rely on Micah as a trusted
/// introducer, and authenticating Micah's certificate is easier
/// than authenticating Laura's certificate, then Ed has learned
/// about an easier way to authenticate Laura's certificate.
#[arg(global=true, display_order=850, long)]
pub gossip: bool,
/// Treats the network as a certification network.
///
/// Normally, `sq pki` treats the Web of Trust network as an
/// authentication network where a certification only means that
/// the binding is correct, not that the target should be treated
/// as a trusted introducer. In a certification network, the
/// targets of certifications are treated as trusted introducers
/// with infinite depth, and any regular expressions are ignored.
/// Note: The trust amount remains unchanged. This is how most
/// so-called pgp path-finding algorithms work.
#[arg(global=true, display_order=860, long)]
pub certification_network: bool,
/// The required amount of trust.
///
/// 120 indicates full authentication; values less than 120
/// indicate partial authentication. When
/// `--certification-network` is passed, this defaults to 1200,
/// i.e., `sq pki` tries to find 10 paths.
#[arg(global=true, display_order=800, short='a', long="amount",
value_name = "AMOUNT")]
pub trust_amount: Option<TrustAmount<usize>>,
#[command(subcommand)]
pub subcommand: Subcommands,
}
@ -126,6 +85,15 @@ pub struct AuthenticateCommand {
#[command(flatten)]
pub email: EmailArg,
#[command(flatten)]
pub gossip: GossipArg,
#[command(flatten)]
pub certification_network: CertificationNetworkArg,
#[command(flatten)]
pub trust_amount: RequiredTrustAmountArg,
#[command(flatten)]
pub cert: CertArg,
@ -161,6 +129,15 @@ pub struct LookupCommand {
#[command(flatten)]
pub email: EmailArg,
#[command(flatten)]
pub gossip: GossipArg,
#[command(flatten)]
pub certification_network: CertificationNetworkArg,
#[command(flatten)]
pub trust_amount: RequiredTrustAmountArg,
#[command(flatten)]
pub userid: UserIDArg,
}
@ -192,6 +169,15 @@ $ sq pki identify --gossip \\
3217C509292FC67076ECD75C7614269BDDF73B36
")]
pub struct IdentifyCommand {
#[command(flatten)]
pub gossip: GossipArg,
#[command(flatten)]
pub certification_network: CertificationNetworkArg,
#[command(flatten)]
pub trust_amount: RequiredTrustAmountArg,
#[command(flatten)]
pub cert: CertArg,
}
@ -224,6 +210,15 @@ pub struct ListCommand {
#[command(flatten)]
pub email: EmailArg,
#[command(flatten)]
pub gossip: GossipArg,
#[command(flatten)]
pub certification_network: CertificationNetworkArg,
#[command(flatten)]
pub trust_amount: RequiredTrustAmountArg,
/// A pattern to select the bindings to authenticate.
///
/// The pattern is treated as a UTF-8 encoded string and a
@ -259,7 +254,13 @@ $ sq pki path \\
")]
pub struct PathCommand {
#[command(flatten)]
pub email: EmailArg,
pub gossip: GossipArg,
#[command(flatten)]
pub certification_network: CertificationNetworkArg,
#[command(flatten)]
pub trust_amount: RequiredTrustAmountArg,
// This should actually be a repeatable positional argument
// (Vec<Cert>) followed by a manadatory positional argument (a
@ -400,3 +401,75 @@ impl Deref for EmailArg {
}
}
#[derive(clap::Args, Debug)]
pub struct GossipArg {
/// Treats all certificates as unreliable trust roots.
///
/// This option is useful for figuring out what others think about
/// a certificate (i.e., gossip or hearsay). In other words, this
/// finds arbitrary paths to a particular certificate.
///
/// Gossip is useful in helping to identify alternative ways to
/// authenticate a certificate. For instance, imagine Ed wants to
/// authenticate Laura's certificate, but asking her directly is
/// inconvenient. Ed discovers that Micah has certified Laura's
/// certificate, but Ed hasn't yet authenticated Micah's
/// certificate. If Ed is willing to rely on Micah as a trusted
/// introducer, and authenticating Micah's certificate is easier
/// than authenticating Laura's certificate, then Ed has learned
/// about an easier way to authenticate Laura's certificate.
#[arg(long)]
pub gossip: bool,
}
impl Deref for GossipArg {
type Target = bool;
fn deref(&self) -> &Self::Target {
&self.gossip
}
}
#[derive(clap::Args, Debug)]
pub struct CertificationNetworkArg {
/// Treats the network as a certification network.
///
/// Normally, `sq pki` treats the Web of Trust network as an
/// authentication network where a certification only means that
/// the binding is correct, not that the target should be treated
/// as a trusted introducer. In a certification network, the
/// targets of certifications are treated as trusted introducers
/// with infinite depth, and any regular expressions are ignored.
/// Note: The trust amount remains unchanged. This is how most
/// so-called pgp path-finding algorithms work.
#[arg(long)]
pub certification_network: bool,
}
impl Deref for CertificationNetworkArg {
type Target = bool;
fn deref(&self) -> &Self::Target {
&self.certification_network
}
}
#[derive(clap::Args, Debug)]
pub struct RequiredTrustAmountArg {
/// The required amount of trust.
///
/// 120 indicates full authentication; values less than 120
/// indicate partial authentication. When
/// `--certification-network` is passed, this defaults to 1200,
/// i.e., `sq pki` tries to find 10 paths.
#[arg(short='a', long="amount", value_name = "AMOUNT")]
pub trust_amount: Option<TrustAmount<usize>>,
}
impl Deref for RequiredTrustAmountArg {
type Target = Option<TrustAmount<usize>>;
fn deref(&self) -> &Self::Target {
&self.trust_amount
}
}

View File

@ -760,7 +760,7 @@ impl From<EncryptPurpose> for KeyFlags {
}
/// Describes the purpose of the encryption.
#[derive(Clone, Debug)]
#[derive(Copy, Clone, Debug)]
pub enum TrustAmount<T> {
/// Partial trust.
Partial,

View File

@ -6,7 +6,6 @@ use openpgp::Fingerprint;
use openpgp::KeyHandle;
use openpgp::Result;
use openpgp::packet::UserID;
use openpgp::policy::Policy;
use sequoia_cert_store as cert_store;
use cert_store::store::StatusListener;
@ -16,11 +15,13 @@ use cert_store::store::StoreError;
use sequoia_wot as wot;
use wot::store::CertStore;
use wot::store::Backend;
use wot::store::Store;
pub mod output;
use crate::cli;
use cli::output::OutputFormat;
use cli::types::TrustAmount;
use crate::commands::pki as pki_cmd;
use pki_cmd::output::print_path;
@ -31,13 +32,14 @@ use pki_cmd::output::OutputType as _;
use crate::Config;
fn trust_amount(cli: &cli::pki::Command)
fn required_trust_amount(trust_amount: Option<TrustAmount<usize>>,
certification_network: bool)
-> Result<usize>
{
let amount = if let Some(v) = &cli.trust_amount {
let amount = if let Some(v) = &trust_amount {
v.amount()
} else {
if cli.certification_network {
if certification_network {
// Look for multiple paths. Specifically, try to find 10
// paths.
10 * wot::FULLY_TRUSTED
@ -73,18 +75,48 @@ fn have_self_signed_userid(cert: &wot::CertSynopsis,
}
/// Authenticate bindings defined by a Query on a Network
fn authenticate<S>(
fn authenticate(
config: &Config,
cli: &cli::pki::Command,
q: &wot::Query<'_, S>,
gossip: bool,
precompute: bool,
list_pattern: Option<String>,
email: bool,
gossip: bool,
certification_network: bool,
trust_amount: Option<TrustAmount<usize>>,
userid: Option<&UserID>,
certificate: Option<&KeyHandle>,
) -> Result<()>
where S: wot::store::Store
{
let required_amount = trust_amount(cli)?;
// Build the network.
let cert_store = match config.cert_store() {
Ok(Some(cert_store)) => cert_store,
Ok(None) => {
return Err(anyhow::anyhow!("Certificate store has been disabled"));
}
Err(err) => {
return Err(err).context("Opening certificate store");
}
};
let mut cert_store = CertStore::from_store(
cert_store, &config.policy, config.time);
if precompute {
cert_store.precompute();
}
let n = wot::Network::new(cert_store)?;
let mut q = wot::QueryBuilder::new(&n);
if ! gossip {
q.roots(wot::Roots::new(config.trust_roots()));
}
if certification_network {
q.certification_network();
}
let q = q.build();
let required_amount =
required_trust_amount(trust_amount, certification_network)?;
let fingerprint: Option<Fingerprint> = if let Some(kh) = certificate {
Some(match kh {
@ -186,8 +218,7 @@ fn authenticate<S>(
bindings = q.network().certified_userids();
use cli::pki::*;
if let Subcommands::List(ListCommand { pattern: Some(pattern), .. }) = &cli.subcommand {
if let Some(pattern) = list_pattern {
// Or rather, just User IDs that match the pattern.
let pattern = pattern.to_lowercase();
@ -239,7 +270,7 @@ fn authenticate<S>(
required_amount,
q.roots(),
gossip,
cli.certification_network,
certification_network,
))
as Box<dyn output::OutputType>
}
@ -413,27 +444,47 @@ fn authenticate<S>(
}
// For `sq-wot path`.
fn check_path<'a: 'b, 'b, S>(config: &Config,
cli: &cli::pki::Command,
q: &wot::Query<'b, S>,
policy: &dyn Policy)
fn check_path(config: &Config,
gossip: bool,
certification_network: bool,
trust_amount: Option<TrustAmount<usize>>,
path: cli::pki::PathArg)
-> Result<()>
where S: wot::store::Store + wot::store::Backend<'a>
{
tracer!(TRACE, "check_path");
let required_amount = trust_amount(cli)?;
use cli::pki::*;
let (khs, userid) = if let Subcommands::Path(PathCommand { path, .. }) = &cli.subcommand {
(path.certs()?, path.userid()?)
} else {
unreachable!("checked");
// Build the network.
let cert_store = match config.cert_store() {
Ok(Some(cert_store)) => cert_store,
Ok(None) => {
return Err(anyhow::anyhow!("Certificate store has been disabled"));
}
Err(err) => {
return Err(err).context("Opening certificate store");
}
};
let cert_store = CertStore::from_store(
cert_store, &config.policy, config.time);
let n = wot::Network::new(cert_store)?;
let mut q = wot::QueryBuilder::new(&n);
if ! gossip {
q.roots(wot::Roots::new(config.trust_roots()));
}
if certification_network {
q.certification_network();
}
let q = q.build();
let required_amount =
required_trust_amount(trust_amount, certification_network)?;
let (khs, userid) = (path.certs()?, path.userid()?);
assert!(khs.len() > 0, "guaranteed by clap");
let r = q.lint_path(&khs, &userid, required_amount, policy);
let r = q.lint_path(&khs, &userid, required_amount, &config.policy);
let target_kh = khs.last().expect("have one");
@ -497,89 +548,53 @@ impl StatusListener for KeyServerUpdate {
pub fn dispatch(config: Config, cli: cli::pki::Command) -> Result<()> {
tracer!(TRACE, "pki::dispatch");
// Build the network.
let cert_store = match config.cert_store() {
Ok(Some(cert_store)) => cert_store,
Ok(None) => {
return Err(anyhow::anyhow!("Certificate store has been disabled"));
}
Err(err) => {
return Err(err).context("Opening certificate store");
}
};
let mut cert_store = CertStore::from_store(
cert_store, &config.policy, config.time);
use cli::pki::*;
if let Subcommands::List(ListCommand { pattern: None, .. }) = cli.subcommand {
cert_store.precompute();
}
let n = wot::Network::new(cert_store)?;
let mut q = wot::QueryBuilder::new(&n);
if ! cli.gossip {
q.roots(wot::Roots::new(config.trust_roots()));
}
if cli.certification_network {
q.certification_network();
}
let q = q.build();
match &cli.subcommand {
match cli.subcommand {
// Authenticate a given binding.
Subcommands::Authenticate(AuthenticateCommand {
email,
cert,
userid,
..
}) => {
// Authenticate a given binding.
authenticate(
&config, &cli, &q,
cli.gossip,
**email,
Some(userid), Some(cert))?;
}
email, gossip, certification_network, trust_amount,
cert, userid,
}) => authenticate(
&config, false, None,
*email, *gossip, *certification_network, *trust_amount,
Some(&userid), Some(&cert))?,
// Find all authenticated bindings for a given User ID, list
// the certificates.
Subcommands::Lookup(LookupCommand {
email,
email, gossip, certification_network, trust_amount,
userid,
..
}) => {
// Find all authenticated bindings for a given
// User ID, list the certificates.
authenticate(
&config, &cli, &q,
cli.gossip,
**email,
Some(userid), None)?;
}
}) => authenticate(
&config, false, None,
*email, *gossip, *certification_network, *trust_amount,
Some(&userid), None)?,
// Find and list all authenticated bindings for a given
// certificate.
Subcommands::Identify(IdentifyCommand {
gossip, certification_network, trust_amount,
cert,
..
}) => {
// Find and list all authenticated bindings for a given
// certificate.
authenticate(
&config, &cli, &q,
cli.gossip,
false,
None, Some(cert))?;
}
}) => authenticate(
&config, false, None,
false, *gossip, *certification_network, *trust_amount,
None, Some(&cert))?,
// List all authenticated bindings.
Subcommands::List(ListCommand {
email,
..
}) => {
// List all authenticated bindings.
authenticate(
&config, &cli, &q,
cli.gossip,
**email,
None, None)?;
}
Subcommands::Path(PathCommand { .. }) => {
check_path(
&config, &cli, &q, &config.policy)?;
}
email, gossip, certification_network, trust_amount,
pattern,
}) => authenticate(
&config, pattern.is_none(), pattern,
*email, *gossip, *certification_network, *trust_amount,
None, None)?,
// Authenticates a given path.
Subcommands::Path(PathCommand {
gossip, certification_network, trust_amount,
path,
}) => check_path(
&config, *gossip, *certification_network, *trust_amount,
path)?,
}
Ok(())

View File

@ -108,8 +108,8 @@ mod integration {
}
cmd
.arg("pki")
.args(sqwot_args)
.arg(command);
.arg(command)
.args(sqwot_args);
if let Some(target) = target {
cmd.arg(&target.to_string());
}