Align user ID designators in sq pki {link,vouch} {add,authorize}.

- Align user ID designators across these four commands.  Previously,
    `--all` was implied for the authorize commands if no user ID
    designator was given.

  - However, this is problematic for the following reasons:

    - First, it is inconsistent across the commands.

    - Second, while CAs can add any name to their cert because they
      are CAs, those certifications are subject to constraints, such
      as domain constraints, or the amount.  But, the link we add
      fully authenticates the current user IDs, which may not be what
      the user wants, so it should require explicit consent.

    - Third, making this implicit again is easier than going from
      implicit to explicit, which breaks existing users.

  - Fixes #442.
This commit is contained in:
Justus Winter 2024-11-27 12:44:13 +01:00
parent 1c6bf5d6fd
commit 3b1bd79195
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
10 changed files with 61 additions and 74 deletions

View File

@ -98,6 +98,7 @@ certificate is considered a trusted introducer for example.org.",
"sq", "pki", "link", "authorize",
"--domain=example.org",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
],
}),
@ -341,8 +342,7 @@ pub struct AuthorizeCommand {
#[command(flatten)]
pub userids: UserIDDesignators<
userid_designator::AllExistingAndAddXUserIDEmailArgs,
userid_designator::OptionalValue>,
userid_designator::AllExistingAndAddXUserIDEmailArgs>,
#[clap(
long = "amount",
@ -450,7 +450,8 @@ Add an unconstrained trusted introducer.",
command: &[
"sq", "pki", "link", "authorize",
"--unconstrained",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0"
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
],
}),
@ -462,6 +463,7 @@ Add a trusted introducer for example.org and example.com.",
"--domain=example.org",
"--domain=example.com",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
],
}),
@ -473,6 +475,7 @@ Add a partially trusted introducer.",
"--unconstrained",
"--amount=60",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
],
}),
],

View File

@ -87,6 +87,7 @@ for example.org.",
"--certifier=E7FC51AD886BBB5C4F44C3D7A9DA14F3E740F63F",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--domain=example.org",
"--all",
],
}),
],

View File

@ -98,7 +98,7 @@ pub struct Command {
#[command(flatten)]
pub userids: UserIDDesignators<
userid_designator::ExistingAndAddXUserIDEmailArgs>,
userid_designator::AllExistingAndAddXUserIDEmailArgs>,
#[clap(
long = "amount",

View File

@ -41,6 +41,7 @@ for example.org and example.com.",
"--cert=E7FC51AD886BBB5C4F44C3D7A9DA14F3E740F63F",
"--domain=example.org",
"--domain=example.com",
"--all",
],
}),
],
@ -108,8 +109,7 @@ pub struct Command {
#[command(flatten)]
pub userids: UserIDDesignators<
userid_designator::ExistingAndAddXUserIDEmailArgs,
userid_designator::OptionalValue>,
userid_designator::AllExistingAndAddXUserIDEmailArgs>,
#[clap(
long = "amount",

View File

@ -798,6 +798,7 @@ impl ResolvedUserID {
/// Return implicitly resolved user IDs for of a certificate's
/// self-signed user IDs.
#[allow(dead_code)]
pub fn implicit_for_valid_cert(vc: &ValidCert) -> Vec<Self> {
vc.userids()
.map(|ua| Self::implicit(ua.userid().clone()))

View File

@ -90,21 +90,7 @@ pub fn authorize(sq: Sq, c: link::AuthorizeCommand)
= sq.resolve_cert(&c.cert, sequoia_wot::FULLY_TRUSTED)?;
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
let mut userids = c.userids.resolve(&vc)?;
let user_supplied_userids = if userids.is_empty() {
// Use all self-signed User IDs.
userids = ResolvedUserID::implicit_for_valid_cert(&vc);
if userids.is_empty() {
return Err(anyhow::anyhow!(
"{} has no self-signed user IDs, and you didn't provide \
an alternate user ID",
vc.fingerprint()));
}
false
} else {
true
};
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(c.notation)?;
@ -114,7 +100,7 @@ pub fn authorize(sq: Sq, c: link::AuthorizeCommand)
&trust_root,
&cert,
&userids[..],
user_supplied_userids,
true, // User supplied user IDs.
&[(c.amount, c.expiration.value())][..],
c.depth,
&c.domain[..],

View File

@ -3,7 +3,6 @@ use openpgp::Result;
use crate::Sq;
use crate::cli::pki::vouch::authorize;
use crate::cli::types::userid_designator::ResolvedUserID;
use crate::commands::FileOrStdout;
use crate::parse_notations;
@ -23,22 +22,7 @@ pub fn authorize(sq: Sq, mut c: authorize::Command)
}
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
let mut userids = c.userids.resolve(&vc)?;
let user_supplied_userids = if userids.is_empty() {
// Use all self-signed User IDs.
userids = ResolvedUserID::implicit_for_valid_cert(&vc);
if userids.is_empty() {
return Err(anyhow::anyhow!(
"{} has no self-signed user IDs, and you didn't provide \
an alternate user ID",
vc.fingerprint()));
}
false
} else {
true
};
let userids = c.userids.resolve(&vc)?;
let notations = parse_notations(&c.notation)?;
@ -48,7 +32,7 @@ pub fn authorize(sq: Sq, mut c: authorize::Command)
&certifier,
&cert,
&userids[..],
user_supplied_userids,
true, // User supplied user IDs.
&[(c.amount, c.expiration.value())],
c.depth,
&c.domain[..],

View File

@ -497,7 +497,7 @@ fn sq_pki_link_update_detection() -> Result<()> {
let bytes = compare(bytes, &alice_cert_pgp, true);
// Make Alice a CA.
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained", "--all"],
alice.key_handle(),
NO_USERIDS);
let bytes = compare(bytes, &alice_cert_pgp, false);

View File

@ -6,12 +6,12 @@ use super::common::UserIDArg;
fn sq_pki_link_authorize_then_authenticate() {
let ca_example_org = "<ca@example.org>";
for userids in &[
&[UserIDArg::UserID(ca_example_org)][..],
for (all, userids) in &[
(false, &[UserIDArg::UserID(ca_example_org)][..]),
// Implicitly use all self-signed user IDs.
NO_USERIDS,
(true, NO_USERIDS),
// Use a non-self signed user ID.
&[UserIDArg::AddUserID("frank")],
(false, &[UserIDArg::AddUserID("frank")]),
] {
let mut sq = Sq::new();
@ -82,6 +82,14 @@ fn sq_pki_link_authorize_then_authenticate() {
}
};
let maybe_all = |args: &[&'static str]| {
let mut args = args.to_vec();
if *all {
args.push("--all");
}
args
};
// No delegation yet.
println!("CA: not authorized");
check(
@ -92,7 +100,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user completely authorizes the CA.
sq.tick(1);
sq.pki_link_authorize(&["--unconstrained"],
sq.pki_link_authorize(&maybe_all(&["--unconstrained"]),
ca.key_handle(),
userids);
@ -105,7 +113,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the regex contraint "example".
sq.tick(1);
sq.pki_link_authorize(&["--regex", "example"],
sq.pki_link_authorize(&maybe_all(&["--regex", "example"]),
ca.key_handle(),
userids);
@ -119,7 +127,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the domain contraint
// "example.org".
sq.tick(1);
sq.pki_link_authorize(&["--domain", "example.org"],
sq.pki_link_authorize(&maybe_all(&["--domain", "example.org"]),
ca.key_handle(),
userids);
@ -132,7 +140,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the regex contraint "other".
sq.tick(1);
sq.pki_link_authorize(&["--regex", "other"],
sq.pki_link_authorize(&maybe_all(&["--regex", "other"]),
ca.key_handle(),
userids);
@ -145,7 +153,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the regex contraint "bob".
sq.tick(1);
sq.pki_link_authorize(&["--regex", "bob"],
sq.pki_link_authorize(&maybe_all(&["--regex", "bob"]),
ca.key_handle(),
userids);
@ -159,8 +167,8 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the regex contraint "bob" or
// "alice".
sq.tick(1);
sq.pki_link_authorize(&["--regex", "bob",
"--regex", "alice"],
sq.pki_link_authorize(&maybe_all(&["--regex", "bob",
"--regex", "alice"]),
ca.key_handle(),
userids);
@ -175,8 +183,8 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA for the domains example.org and
// other.org.
sq.tick(1);
sq.pki_link_authorize(&["--domain", "example.org",
"--domain", "other.org"],
sq.pki_link_authorize(&maybe_all(&["--domain", "example.org",
"--domain", "other.org"]),
ca.key_handle(),
userids);
@ -191,8 +199,8 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA for the domain example.com and the
// regex alice.
sq.tick(1);
sq.pki_link_authorize(&["--domain", "other.org",
"--regex", "alice"],
sq.pki_link_authorize(&maybe_all(&["--domain", "other.org",
"--regex", "alice"]),
ca.key_handle(),
userids);
@ -206,7 +214,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the regex contraint "zoo".
sq.tick(1);
sq.pki_link_authorize(&["--regex", "zoo"],
sq.pki_link_authorize(&maybe_all(&["--regex", "zoo"]),
ca.key_handle(),
userids);
@ -219,7 +227,7 @@ fn sq_pki_link_authorize_then_authenticate() {
// The user authorizes the CA with the domain contraint "example".
sq.tick(1);
sq.pki_link_authorize(&["--domain", "example"],
sq.pki_link_authorize(&maybe_all(&["--domain", "example"]),
ca.key_handle(),
userids);
@ -244,8 +252,8 @@ fn retract_explicit() {
// When authorizing the CA we can either authorize implicitly
// (i.e., all self-signed user IDs) or explicitly. Try both.
for implicit_all in [true, false] {
eprintln!("implicit all = {}", implicit_all);
for explicit_all in [true, false] {
eprintln!("explicit all = {}", explicit_all);
let mut sq = Sq::new();
let (ca, ca_pgp, _ca_rev)
@ -293,9 +301,13 @@ fn retract_explicit() {
// Authorize via all user IDs.
sq.tick(1);
let userids = self_signed_userids;
sq.pki_link_authorize(&["--unconstrained"],
sq.pki_link_authorize(if explicit_all {
&["--unconstrained", "--all"]
} else {
&["--unconstrained"]
},
ca.key_handle(),
if implicit_all {
if explicit_all {
&[]
} else {
userids
@ -519,7 +531,7 @@ fn sq_pki_link_all_revoked() {
// any user IDs so only valid self-signed user IDs should be used.
// That means the revoked user ID should be skipped.
sq.tick(1);
sq.pki_link_authorize(&["--unconstrained"],
sq.pki_link_authorize(&["--unconstrained", "--all"],
ca.key_handle(), NO_USERIDS);
println!("CA: authorized, and unconstrained");

View File

@ -89,7 +89,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes completely to the CA.
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--unconstrained"],
sq.pki_vouch_authorize(&["--unconstrained", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -105,7 +105,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the regex contraint "example".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--regex", "example"],
sq.pki_vouch_authorize(&["--regex", "example", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -121,7 +121,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the domain contraint "example.org".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--domain", "example.org"],
sq.pki_vouch_authorize(&["--domain", "example.org", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -137,7 +137,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the regex contraint "other".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--regex", "other"],
sq.pki_vouch_authorize(&["--regex", "other", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -153,7 +153,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the regex contraint "bob".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--regex", "bob"],
sq.pki_vouch_authorize(&["--regex", "bob", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -169,7 +169,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the regex contraint "bob" or "alice".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--regex", "bob", "--regex", "alice"],
sq.pki_vouch_authorize(&["--regex", "bob", "--regex", "alice", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -187,7 +187,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(
&["--domain", "example.org", "--domain", "other.org"],
&["--domain", "example.org", "--domain", "other.org", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -205,7 +205,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(
&["--domain", "other.org", "--regex", "alice"],
&["--domain", "other.org", "--regex", "alice", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -222,7 +222,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the regex contraint "zoo".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--regex", "zoo"],
sq.pki_vouch_authorize(&["--regex", "zoo", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -238,7 +238,7 @@ fn sq_pki_vouch_authorize_then_authenticate() {
// Otto authorizes to the CA with the domain contraint "example".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--domain", "example"],
sq.pki_vouch_authorize(&["--domain", "example", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());
@ -326,7 +326,7 @@ fn sq_pki_vouch_authorize_all_revoked() {
// That means the revoked user ID should be skipped.
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_vouch_authorize(&["--unconstrained"],
sq.pki_vouch_authorize(&["--unconstrained", "--all"],
otto.key_handle(), ca.key_handle(),
NO_USERIDS,
certification.as_path());