Move all global PKI options to subcommands where they are needed.
This commit is contained in:
parent
2c7a22558a
commit
b000993817
157
src/cli/pki.rs
157
src/cli/pki.rs
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(())
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user