Split authorization functionality out of sq pki link add.
- Split authorization functionality out of `sq pki link add` into a new command, `sq pki link authorize`. - Align `sq pki link authorize`'s arguments with `sq pki authorize` arguments.
This commit is contained in:
parent
cd7b79dbae
commit
609c5aab16
4
NEWS
4
NEWS
@ -85,6 +85,10 @@
|
||||
pki certify`.
|
||||
- Removed `sq pki link add`'s `--petname` argument. Use `--userid`
|
||||
in conjunction with `--add-userid` instead.
|
||||
- Previously `sq pki link certify` could create certifications, and
|
||||
mark a certificate as a trusted introducer (when the user set
|
||||
`--depth` to be greater than zero). The latter functionality has
|
||||
been split off to the new subcommand `sq pki link authorize`.
|
||||
|
||||
* Changes in 0.38.0
|
||||
** Notable changes
|
||||
|
@ -17,7 +17,7 @@ use crate::cli::types::TrustAmount;
|
||||
long_about =
|
||||
"Manage authenticated certificate and User ID links
|
||||
|
||||
Link a certificate and User ID is one way of making `sq` consider a \
|
||||
Linking a certificate and User ID is one way of making `sq` consider a \
|
||||
binding to be authentic. Another way is to use `sq pki certify` to \
|
||||
certify the binding with an explicitly configured trust root. The \
|
||||
linking functionality is often easier to work with, and the \
|
||||
@ -35,8 +35,7 @@ authenticated certificate to be authentic.
|
||||
|
||||
Users can create a link using `sq pki link add`. That link can later be \
|
||||
retracted using `sq pki link retract`. A certificate can also be \
|
||||
accepted as a trusted introducer by passing the `--ca` option to \
|
||||
`sq pki link add`.
|
||||
accepted as a trusted introducer by using `sq pki link authorize`.
|
||||
|
||||
`sq` implements linking using non-exportable certifications, and an \
|
||||
implicit trust root. An OpenPGP certificate directory, the default \
|
||||
@ -94,10 +93,9 @@ with all of its self-signed user IDs as a trusted certification \
|
||||
authority constrained to the domain example.org. That is, the \
|
||||
certificate is considered a trusted introducer for example.org.",
|
||||
command: &[
|
||||
"sq", "pki", "link", "add",
|
||||
"--ca=example.org",
|
||||
"sq", "pki", "link", "authorize",
|
||||
"--domain=example.org",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
|
||||
"--all",
|
||||
],
|
||||
}),
|
||||
|
||||
@ -124,6 +122,7 @@ test_examples!(sq_pki_link, LINK_EXAMPLES);
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Subcommands {
|
||||
Add(AddCommand),
|
||||
Authorize(AuthorizeCommand),
|
||||
Retract(RetractCommand),
|
||||
List(ListCommand),
|
||||
}
|
||||
@ -131,16 +130,16 @@ pub enum Subcommands {
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "add",
|
||||
about = "Link a certificate and a User ID",
|
||||
about = "Link a certificate and a user ID",
|
||||
long_about =
|
||||
"Link a certificate and a User ID
|
||||
"Link a certificate and a user ID
|
||||
|
||||
This cause `sq` to considers the certificate and User ID binding to be \
|
||||
authentic.
|
||||
|
||||
A certificate can also be accepted as a certification authority, which \
|
||||
is also known as a trusted introducer, by using the `--ca` or \
|
||||
`--depth` option.
|
||||
This causes `sq` to consider the certificate and user ID binding to be \
|
||||
authentic. You would do this if you are confident that a particular \
|
||||
certificate should be associated with Alice, for example. Note: this \
|
||||
does not consider the certificate to be a trusted introducer; it only \
|
||||
considers the binding to be authentic. To authorize a certificate to \
|
||||
be a trusted introducer use `sq pki link authorize`.
|
||||
|
||||
A link can be retracted using `sq pki link retract`.
|
||||
|
||||
@ -152,13 +151,12 @@ especially when the user's certification key is offline. And the \
|
||||
latter improves the user's privacy, by reducing the chance that parts \
|
||||
of the user's social graph is leaked when a certificate is shared.
|
||||
|
||||
By default a link never expires. \
|
||||
Using the `--expiration` argument specific validity periods may be defined. \
|
||||
It allows for providing a point in time for validity to end or a validity \
|
||||
duration.
|
||||
By default a link never expires. This can be overridden using \
|
||||
`--expiration` argument.
|
||||
|
||||
`sq pki link` respects the reference time set by the top-level `--time` \
|
||||
argument. It sets the link's creation time to the reference time.
|
||||
`sq pki link add` respects the reference time set by the top-level \
|
||||
`--time` argument. It sets the link's creation time to the reference \
|
||||
time.
|
||||
",
|
||||
after_help = ADD_EXAMPLES,
|
||||
)]
|
||||
@ -187,49 +185,6 @@ pub struct AddCommand {
|
||||
)]
|
||||
pub amount: TrustAmount<u8>,
|
||||
|
||||
#[clap(
|
||||
long = "depth",
|
||||
value_name = "TRUST_DEPTH",
|
||||
help = "Set the trust depth",
|
||||
long_help =
|
||||
"Set the trust depth (sometimes referred to as the trust level). \
|
||||
0 means a normal certification of <CERTIFICATE, USERID>. \
|
||||
1 means CERTIFICATE is also a trusted introducer, 2 means \
|
||||
CERTIFICATE is a meta-trusted introducer, etc.",
|
||||
)]
|
||||
pub depth: Option<u8>,
|
||||
#[clap(
|
||||
long = "ca",
|
||||
value_name = "*|DOMAIN",
|
||||
help = "Mark the certificate as a certification authority for a domain",
|
||||
long_help =
|
||||
"Mark the certificate as a certification authority for a \
|
||||
domain. Use `*` to make the certificate a certification
|
||||
authority for any User ID.
|
||||
|
||||
A certification authority is also referred to as a trusted \
|
||||
introducer. This command is equivalent to making the trust \
|
||||
depth unconstrained, i.e., setting the depth to 255. See \
|
||||
`--depth` for more information.",
|
||||
)]
|
||||
pub ca: Vec<String>,
|
||||
#[clap(
|
||||
long = "regex",
|
||||
value_name = "REGEX",
|
||||
help = "Add a regular expression to constrain \
|
||||
what a trusted introducer can certify",
|
||||
long_help =
|
||||
"Add a regular expression to constrain \
|
||||
what a trusted introducer can certify. \
|
||||
The regular expression must match \
|
||||
the certified User ID in all intermediate \
|
||||
introducers, and the certified certificate. \
|
||||
Multiple regular expressions may be \
|
||||
specified. In that case, at least \
|
||||
one must match.",
|
||||
)]
|
||||
pub regex: Vec<String>,
|
||||
|
||||
#[clap(
|
||||
long = "temporary",
|
||||
conflicts_with_all = &[ "amount" ],
|
||||
@ -336,50 +291,224 @@ user IDs.",
|
||||
"--all",
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
test_examples!(sq_pki_link_add, ADD_EXAMPLES);
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "authorize",
|
||||
about = "Make a certificate a trusted introducer",
|
||||
long_about = "\
|
||||
Make a certificate a trusted introducer.
|
||||
|
||||
This causes `sq` to consider the certificate to be a be a trusted \
|
||||
introducer. Trusted introducer is another word for certification \
|
||||
authority (CA). When you link a trusted introducer, you consider \
|
||||
certifications made by the trusted introducer to be valid. A trusted \
|
||||
introducer can also designate further trusted introducers.
|
||||
|
||||
As is, a trusted introducer has a lot of power. This power can be \
|
||||
limited in several ways.
|
||||
|
||||
- The ability to specify further introducers can be constrained \
|
||||
using the `--depth` parameter.
|
||||
|
||||
- The degree to which an introducer is trusted can be changed using \
|
||||
the `--amount` parameter.
|
||||
|
||||
- The user IDs that an introducer can certify can be constrained by \
|
||||
domain using the `--domain` parameter or a regular expression using \
|
||||
the `--regex` parameter.
|
||||
|
||||
These mechanisms allow you to say that you are willing to rely on the \
|
||||
CA for example.org, but only for user IDs that have an email address \
|
||||
for example.org, for instance.
|
||||
|
||||
A link can be retracted using `sq pki link retract`.
|
||||
|
||||
This command is similar to `sq pki authorize`, but the certifications \
|
||||
it makes are done using the certificate directory's trust root, not an \
|
||||
arbitrary key. Further, the certificates are marked as \
|
||||
non-exportable. The former makes it easier to manage certifications, \
|
||||
especially when your certification key is offline. And the latter \
|
||||
improves your privacy, by reducing the chance that parts of your \
|
||||
social graph are leaked when a certificate is shared.
|
||||
|
||||
By default a link never expires. Using the `--expiration` argument \
|
||||
specific validity periods may be defined. It allows for providing a \
|
||||
point in time for validity to end or a validity duration.
|
||||
|
||||
`sq pki link authorize` respects the reference time set by the \
|
||||
top-level `--time` argument. It sets the link's creation time to the \
|
||||
reference time.
|
||||
",
|
||||
after_help = AUTHORIZE_EXAMPLES,
|
||||
)]
|
||||
#[clap(group(ArgGroup::new("constraint").args(&["regex", "domain", "unconstrained"]).required(true).multiple(true)))]
|
||||
pub struct AuthorizeCommand {
|
||||
#[command(flatten)]
|
||||
pub cert: CertDesignators<
|
||||
cert_designator::CertArg,
|
||||
cert_designator::CertPrefix,
|
||||
cert_designator::OneValue>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub userids: UserIDDesignators<
|
||||
userid_designator::MaybeSelfSignedUserIDEmailAllArgs,
|
||||
userid_designator::OptionalValue>,
|
||||
|
||||
#[clap(
|
||||
long = "amount",
|
||||
value_name = "AMOUNT",
|
||||
default_value = "full",
|
||||
help = "Set the amount of trust",
|
||||
long_help =
|
||||
"Set the amount of trust. Values between 1 and 120 are meaningful. \
|
||||
120 means fully trusted. Values less than 120 indicate the degree \
|
||||
of trust. 60 is usually used for partially trusted.",
|
||||
)]
|
||||
pub amount: TrustAmount<u8>,
|
||||
|
||||
#[clap(
|
||||
long = "depth",
|
||||
value_name = "TRUST_DEPTH",
|
||||
default_value = "255",
|
||||
help = "Set the trust depth",
|
||||
long_help = "\
|
||||
Set the trust depth (sometimes referred to as the trust level). 1 \
|
||||
means CERTIFICATE is a trusted introducer (default), 2 means \
|
||||
CERTIFICATE is a meta-trusted introducer and can authorize another \
|
||||
trusted introducer, etc.",
|
||||
)]
|
||||
pub depth: u8,
|
||||
|
||||
#[clap(
|
||||
long = "domain",
|
||||
value_name = "DOMAIN",
|
||||
help = "Add a domain constraint to the introducer",
|
||||
long_help = "\
|
||||
Add a domain constraint to the introducer.
|
||||
|
||||
Add a domain to constrain what certifications are respected. A \
|
||||
certification made by the certificate is only respected if it is over \
|
||||
a user ID with an email address in the specified domain. Multiple \
|
||||
domains may be specified. In that case, one must match.",
|
||||
)]
|
||||
pub domain: Vec<String>,
|
||||
#[clap(
|
||||
long = "regex",
|
||||
value_name = "REGEX",
|
||||
help = "Add a regular expression to constrain the introducer",
|
||||
long_help = "\
|
||||
Add a regular expression to constrain the introducer.
|
||||
|
||||
Add a regular expression to constrain what certifications are \
|
||||
respected. A certification made by the certificate is only respected \
|
||||
if it is over a user ID that matches one of the specified regular \
|
||||
expression. Multiple regular expressions may be specified. In that \
|
||||
case, at least one must match.",
|
||||
)]
|
||||
pub regex: Vec<String>,
|
||||
#[clap(
|
||||
long,
|
||||
conflicts_with = "regex",
|
||||
help = "Don't constrain the introducer",
|
||||
long_help = "\
|
||||
Don't constrain the introducer.
|
||||
|
||||
Normally an introducer is constrained so that only certain user IDs \
|
||||
are respected, e.g., those that have an email address for a certain \
|
||||
domain name. This option authorizes an introducer without \
|
||||
constraining it in this way. Because this grants the introducer a lot \
|
||||
of power, you have to opt in to this behavior explicitly.",
|
||||
)]
|
||||
pub unconstrained: bool,
|
||||
|
||||
#[clap(
|
||||
long = "expiration",
|
||||
value_name = "EXPIRATION",
|
||||
default_value_t =
|
||||
Expiration::Never,
|
||||
help =
|
||||
"Define EXPIRATION for the acceptance as ISO 8601 formatted string or \
|
||||
custom duration.",
|
||||
long_help =
|
||||
"Define EXPIRATION for the acceptance as ISO 8601 formatted string or \
|
||||
custom duration. \
|
||||
If an ISO 8601 formatted string is provided, the validity period \
|
||||
reaches from the reference time (may be set using `--time`) to \
|
||||
the provided time. \
|
||||
Custom durations starting from the reference time may be set using \
|
||||
`N[ymwds]`, for N years, months, weeks, days, or seconds. \
|
||||
The special keyword `never` sets an unlimited expiry.",
|
||||
)]
|
||||
pub expiration: Expiration,
|
||||
|
||||
#[clap(
|
||||
long = "recreate",
|
||||
help = "Recreate the signature even if the parameters did not change",
|
||||
long_help = "\
|
||||
Recreate the signature even if the parameters did not change
|
||||
|
||||
If the link parameters did not change, and thus creating a signature \
|
||||
should not be necessary, we omit the operation. This flag can be given \
|
||||
to force the signature to be recreated anyway.",
|
||||
)]
|
||||
pub recreate: bool,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
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>,
|
||||
}
|
||||
|
||||
const AUTHORIZE_EXAMPLES: Actions = Actions {
|
||||
actions: &[
|
||||
Action::Example(Example {
|
||||
comment: "\
|
||||
Accept the certificate EB28F26E2739A4870ECC47726F0073F60FD0CBF0 \
|
||||
with all of its self-signed user IDs as a trusted certification \
|
||||
authority. That is, the certificate is considered a trust root.",
|
||||
Add an unconstrained trusted introducer.",
|
||||
command: &[
|
||||
"sq", "pki", "link", "add",
|
||||
"--ca=*",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
|
||||
"--all",
|
||||
"sq", "pki", "link", "authorize",
|
||||
"--unconstrained",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0"
|
||||
],
|
||||
}),
|
||||
|
||||
Action::Example(Example {
|
||||
comment: "\
|
||||
Accept the certificate EB28F26E2739A4870ECC47726F0073F60FD0CBF0 \
|
||||
with all of its self-signed user IDs as a trusted certification \
|
||||
authority constrained to the domain example.org. That is, the \
|
||||
certificate is considered a trusted introducer for example.org.",
|
||||
Add a trusted introducer for example.org and example.com.",
|
||||
command: &[
|
||||
"sq", "pki", "link", "add",
|
||||
"--ca=example.org",
|
||||
"sq", "pki", "link", "authorize",
|
||||
"--domain=example.org",
|
||||
"--domain=example.com",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
|
||||
"--all",
|
||||
],
|
||||
}),
|
||||
|
||||
Action::Example(Example {
|
||||
comment: "\
|
||||
Accept the certificate EB28F26E2739A4870ECC47726F0073F60FD0CBF0 \
|
||||
with all of its self-signed user IDs as a partially trusted \
|
||||
certification authority.",
|
||||
Add a partially trusted introducer.",
|
||||
command: &[
|
||||
"sq", "pki", "link", "add",
|
||||
"--ca=*",
|
||||
"sq", "pki", "link", "authorize",
|
||||
"--unconstrained",
|
||||
"--amount=60",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
|
||||
"--all",
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
test_examples!(sq_pki_link_add, ADD_EXAMPLES);
|
||||
test_examples!(sq_pki_link_authorize, AUTHORIZE_EXAMPLES);
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -389,7 +518,8 @@ test_examples!(sq_pki_link_add, ADD_EXAMPLES);
|
||||
"Retract links
|
||||
|
||||
This command retracts links that were previously created using `sq \
|
||||
pki link add`. See that subcommand's documentation for more details. \
|
||||
pki link add` or `sq pki link authorize`. See that subcommand's \
|
||||
documentation for more details. \
|
||||
Note: this is called `retract` and not `remove`, because the \
|
||||
certifications are not removed. Instead a new certification is added, \
|
||||
which says that the binding has not been authenticated.
|
||||
|
@ -256,8 +256,8 @@ pub fn generate(
|
||||
// Writing to key store. Provide some guidance.
|
||||
sq.hint(format_args!("If this is your key, you should mark it as a \
|
||||
fully trusted introducer:"))
|
||||
.sq().arg("pki").arg("link").arg("add")
|
||||
.arg_value("--ca", "*")
|
||||
.sq().arg("pki").arg("link").arg("authorize")
|
||||
.arg("--unconstrained")
|
||||
.arg("--cert").arg(cert.fingerprint())
|
||||
.arg("--all")
|
||||
.done();
|
||||
|
@ -639,7 +639,7 @@ impl Method {
|
||||
wprintln!(
|
||||
"Created the local CA {} for certifying \
|
||||
certificates downloaded from this service. \
|
||||
Use `sq pki link add --ca '*' --amount N {}` \
|
||||
Use `sq pki link authorize --unconstrained --amount N {}` \
|
||||
to change how much it is trusted. Or \
|
||||
`sq pki link retract {}` to disable it.",
|
||||
if let Ok(cert) = cert.to_cert() {
|
||||
|
@ -18,6 +18,7 @@ pub fn link(sq: Sq, c: link::Command) -> Result<()> {
|
||||
use link::Subcommands::*;
|
||||
match c.subcommand {
|
||||
Add(c) => add(sq, c)?,
|
||||
Authorize(c) => authorize(sq, c)?,
|
||||
Retract(c) => retract(sq, c)?,
|
||||
List(c) => list(sq, c)?,
|
||||
}
|
||||
@ -36,34 +37,6 @@ 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 trust_depth: u8 = if let Some(depth) = c.depth {
|
||||
depth
|
||||
} else if ! c.ca.is_empty() {
|
||||
255
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let mut regex = c.regex;
|
||||
if trust_depth == 0 && !regex.is_empty() {
|
||||
return Err(
|
||||
anyhow::format_err!("A regex only makes sense \
|
||||
if the trust depth is greater than 0"));
|
||||
}
|
||||
|
||||
let mut star = false;
|
||||
for domain in c.ca.iter() {
|
||||
if domain == "*" {
|
||||
star = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a catch all, we don't need to add any regular
|
||||
// expressions.
|
||||
if star {
|
||||
regex = Vec::new();
|
||||
}
|
||||
|
||||
let notations = parse_notations(c.notation)?;
|
||||
|
||||
let templates = if c.temporary {
|
||||
@ -93,13 +66,59 @@ pub fn add(sq: Sq, c: link::AddCommand)
|
||||
true, // Add userid.
|
||||
true, // User-supplied user IDs.
|
||||
&templates,
|
||||
trust_depth,
|
||||
if star {
|
||||
&[][..]
|
||||
} else {
|
||||
&c.ca[..]
|
||||
},
|
||||
®ex[..],
|
||||
0, // Trust depth.
|
||||
&[][..], // Domain.
|
||||
&[][..], // Regex.
|
||||
true, // Local.
|
||||
false, // Non-revocable.
|
||||
¬ations[..],
|
||||
None, // Output.
|
||||
false) // Binary.
|
||||
}
|
||||
|
||||
pub fn authorize(sq: Sq, c: link::AuthorizeCommand)
|
||||
-> Result<()>
|
||||
{
|
||||
let trust_root = sq.local_trust_root()?;
|
||||
let trust_root = trust_root.to_cert()?;
|
||||
|
||||
let (cert, _from_file)
|
||||
= 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 = vc.userids()
|
||||
.map(|ua| ua.userid().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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 notations = parse_notations(c.notation)?;
|
||||
|
||||
crate::common::pki::certify::certify(
|
||||
&sq,
|
||||
c.recreate, // Recreate.
|
||||
&trust_root,
|
||||
&cert,
|
||||
&userids[..],
|
||||
c.userids.add_userid().unwrap_or(false),
|
||||
user_supplied_userids,
|
||||
&[(c.amount, c.expiration)][..],
|
||||
c.depth,
|
||||
&c.domain[..],
|
||||
&c.regex[..],
|
||||
true, // Local.
|
||||
false, // Non-revocable.
|
||||
¬ations[..],
|
||||
|
@ -23,6 +23,7 @@ mod integration {
|
||||
mod sq_pki_certify;
|
||||
mod sq_pki_authorize;
|
||||
mod sq_pki_link;
|
||||
mod sq_pki_link_authorize;
|
||||
mod sq_sign;
|
||||
mod sq_toolbox_keyring_filter;
|
||||
mod sq_toolbox_packet_decrypt;
|
||||
|
@ -1520,6 +1520,74 @@ impl Sq {
|
||||
self.pki_link_add_maybe(args, cert, userids).expect("success")
|
||||
}
|
||||
|
||||
/// Add a link for the binding.
|
||||
pub fn pki_link_retract_maybe(&self, extra_args: &[&str],
|
||||
cert: KeyHandle,
|
||||
userids: &[&str])
|
||||
-> Result<()>
|
||||
{
|
||||
let mut cmd = self.command();
|
||||
cmd.args([ "pki", "link", "retract" ]);
|
||||
for arg in extra_args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
cmd.arg("--cert").arg(cert.to_string());
|
||||
for userid in userids {
|
||||
cmd.arg("--userid").arg(userid);
|
||||
}
|
||||
|
||||
let output = self.run(cmd, None);
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(format!(
|
||||
"Command failed:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr))))
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a link for the binding.
|
||||
pub fn pki_link_retract(&self, args: &[&str],
|
||||
cert: KeyHandle, userids: &[&str])
|
||||
{
|
||||
self.pki_link_retract_maybe(args, cert, userids)
|
||||
.expect("success")
|
||||
}
|
||||
|
||||
/// Add a link for the binding.
|
||||
pub fn pki_link_authorize_maybe(&self, extra_args: &[&str],
|
||||
cert: KeyHandle,
|
||||
userids: &[&str])
|
||||
-> Result<()>
|
||||
{
|
||||
let mut cmd = self.command();
|
||||
cmd.args([ "pki", "link", "authorize" ]);
|
||||
for arg in extra_args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
cmd.arg("--cert").arg(cert.to_string());
|
||||
for userid in userids {
|
||||
cmd.arg("--userid").arg(userid);
|
||||
}
|
||||
|
||||
let output = self.run(cmd, None);
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(format!(
|
||||
"Command failed:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr))))
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a link for the binding.
|
||||
pub fn pki_link_authorize(&self, args: &[&str],
|
||||
cert: KeyHandle, userids: &[&str])
|
||||
{
|
||||
self.pki_link_authorize_maybe(args, cert, userids)
|
||||
.expect("success")
|
||||
}
|
||||
|
||||
/// Authenticate a binding.
|
||||
pub fn pki_authenticate(&self, extra_args: &[&str],
|
||||
cert: &str, userid: &str)
|
||||
|
@ -321,7 +321,9 @@ fn sq_pki_link_add_retract() -> Result<()> {
|
||||
|
||||
// Accept Alice as a trusted introducer. We should be able to
|
||||
// verify Alice, Bob, and Carol's signatures.
|
||||
sq_link(&sq, &alice_fpr, &[ &alice_userid ], &[], &["--ca", "*"], true);
|
||||
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
|
||||
alice.cert.key_handle(),
|
||||
&[ &alice_userid ]);
|
||||
|
||||
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
|
||||
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
|
||||
@ -338,7 +340,9 @@ fn sq_pki_link_add_retract() -> Result<()> {
|
||||
|
||||
// Accept Alice as a trusted introducer again. We should be able
|
||||
// to verify Alice, Bob, and Carol's signatures.
|
||||
sq_link(&sq, &alice_fpr, &[ &alice_userid ], &[], &["--ca", "*"], true);
|
||||
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
|
||||
alice.cert.key_handle(),
|
||||
&[ &alice_userid ]);
|
||||
|
||||
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
|
||||
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
|
||||
@ -367,8 +371,9 @@ fn sq_pki_link_add_retract() -> Result<()> {
|
||||
|
||||
// Change Alice's acceptance to be a ca, but only for example.org,
|
||||
// i.e., not for Dave.
|
||||
sq_link(&sq, &alice_fpr, &[ &alice_userid ], &[], &["--ca", "example.org"],
|
||||
true);
|
||||
sq.pki_link_authorize(&["--time", &tick(), "--domain", "example.org"],
|
||||
alice.cert.key_handle(),
|
||||
&[ &alice_userid ]);
|
||||
|
||||
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
|
||||
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
|
||||
@ -493,16 +498,14 @@ fn sq_pki_link_update_detection() -> Result<()> {
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
// Make Alice a CA.
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &[],
|
||||
&["--ca", "*", "--all"], true);
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
|
||||
alice.key_handle(),
|
||||
&[]);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &[],
|
||||
&["--ca", "*", "--all"], true);
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained", "--all"],
|
||||
alice.key_handle(),
|
||||
&[]);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
// Make her a partially trusted CA.
|
||||
@ -532,13 +535,13 @@ fn sq_pki_link_update_detection() -> Result<()> {
|
||||
|
||||
// Link it again.
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &[],
|
||||
&["--depth", "10", "--amount", "10", "--all"], true);
|
||||
&["--amount", "10", "--all"], true);
|
||||
assert!(output.2.contains("was retracted"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &[],
|
||||
&["--depth", "10", "--amount", "10", "--all"], true);
|
||||
&["--amount", "10", "--all"], true);
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
455
tests/integration/sq_pki_link_authorize.rs
Normal file
455
tests/integration/sq_pki_link_authorize.rs
Normal file
@ -0,0 +1,455 @@
|
||||
use super::common::Sq;
|
||||
|
||||
#[test]
|
||||
fn sq_pki_link_authorize_then_authenticate() {
|
||||
let ca_example_org = "<ca@example.org>";
|
||||
|
||||
for (e, userids) in &[
|
||||
(&[][..], &[ca_example_org][..]),
|
||||
// Implicitly use all self-signed user IDs.
|
||||
(&[], &[]),
|
||||
// Use a non-self signed user ID.
|
||||
(&["--add-userid"], &["frank"]),
|
||||
] {
|
||||
let mut sq = Sq::new();
|
||||
|
||||
let (ca, ca_pgp, _ca_rev)
|
||||
= sq.key_generate(&[], &[ca_example_org]);
|
||||
sq.key_import(&ca_pgp);
|
||||
|
||||
let alice_example_org = "<alice@example.org>";
|
||||
let (alice, alice_pgp, _alice_rev)
|
||||
= sq.key_generate(&[], &[alice_example_org]);
|
||||
sq.key_import(&alice_pgp);
|
||||
|
||||
let bob_example_org = "<bob@example.org>";
|
||||
let bob_other_org = "<bob@other.org>";
|
||||
let (bob, bob_pgp, _bob_rev)
|
||||
= sq.key_generate(&[], &[bob_example_org, bob_other_org]);
|
||||
sq.key_import(&bob_pgp);
|
||||
|
||||
sq.tick(1);
|
||||
|
||||
// The ca certifies alice's and bob's certificates for each of
|
||||
// their user IDs.
|
||||
let certification = sq.scratch_file(None);
|
||||
sq.pki_certify(&[],
|
||||
ca.key_handle(), alice.key_handle(),
|
||||
&[ alice_example_org ],
|
||||
certification.as_path());
|
||||
sq.cert_import(&certification);
|
||||
|
||||
let certification = sq.scratch_file(None);
|
||||
sq.pki_certify(&[],
|
||||
ca.key_handle(), bob.key_handle(),
|
||||
&[ bob_example_org, bob_other_org ],
|
||||
certification.as_path());
|
||||
sq.cert_import(certification);
|
||||
|
||||
// Check whether we can authenticate alice's and bob's
|
||||
// certificates for their user ID.
|
||||
let check = |sq: &Sq,
|
||||
can_authenticate_alice,
|
||||
can_authenticate_bob1,
|
||||
can_authenticate_bob2|
|
||||
{
|
||||
for (cert, userid, can_authenticate) in &[
|
||||
(&alice, alice_example_org, can_authenticate_alice),
|
||||
(&bob, bob_example_org, can_authenticate_bob1),
|
||||
(&bob, bob_other_org, can_authenticate_bob2),
|
||||
]
|
||||
{
|
||||
let r = sq.pki_authenticate(
|
||||
&[],
|
||||
&cert.fingerprint().to_string(),
|
||||
userid);
|
||||
|
||||
match (can_authenticate, r.is_ok()) {
|
||||
(true, false) => {
|
||||
panic!("Expected to authenticated {}, but didn't.",
|
||||
userid);
|
||||
}
|
||||
(false, true) => {
|
||||
panic!("Expected to NOT authenticated {}, but did.",
|
||||
userid);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn concat<'a>(args1: &[&'a str], args2: &[&'a str])
|
||||
-> Vec<&'a str>
|
||||
{
|
||||
let mut args = args1.to_vec();
|
||||
args.extend(args2.into_iter());
|
||||
args
|
||||
}
|
||||
|
||||
// No delegation yet.
|
||||
println!("CA: not authorized");
|
||||
check(
|
||||
&sq,
|
||||
false, // Alice.
|
||||
false, // bob@example.org
|
||||
false);// bob@other.org
|
||||
|
||||
// The user completely authorizes the CA.
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--unconstrained"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized, and unconstrained");
|
||||
check(
|
||||
&sq,
|
||||
true, // alice@example.org
|
||||
true, // bob@example.org
|
||||
true);// bob@other.org
|
||||
|
||||
// The user authorizes the CA with the regex contraint "example".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--regex", "example"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"example\"");
|
||||
check(
|
||||
&sq,
|
||||
true, // alice@example.org
|
||||
true, // bob@example.org
|
||||
false);// bob@other.org
|
||||
|
||||
// The user authorizes the CA with the domain contraint
|
||||
// "example.org".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--domain", "example.org"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"example.org\"");
|
||||
check(
|
||||
&sq,
|
||||
true, // alice@example.org
|
||||
true, // bob@example.org
|
||||
false);// bob@other.org
|
||||
|
||||
// The user authorizes the CA with the regex contraint "other".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--regex", "other"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"other\"");
|
||||
check(
|
||||
&sq,
|
||||
false, // alice@example.org
|
||||
false, // bob@example.org
|
||||
true); // bob@other.org
|
||||
|
||||
// The user authorizes the CA with the regex contraint "bob".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--regex", "bob"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"bob\"");
|
||||
check(
|
||||
&sq,
|
||||
false, // alice@example.org
|
||||
true, // bob@example.org
|
||||
true); // bob@other.org
|
||||
|
||||
// The user authorizes the CA with the regex contraint "bob" or
|
||||
// "alice".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--regex", "bob",
|
||||
"--regex", "alice"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"bob\" or \"alice\"");
|
||||
check(
|
||||
&sq,
|
||||
true, // alice@example.org
|
||||
true, // bob@example.org
|
||||
true);// bob@other.org
|
||||
|
||||
|
||||
// The user authorizes the CA for the domains example.org and
|
||||
// other.org.
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--domain", "example.org",
|
||||
"--domain", "other.org"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for the domains example.org and other.org");
|
||||
check(
|
||||
&sq,
|
||||
true, // alice@example.org
|
||||
true, // bob@example.org
|
||||
true);// bob@other.org
|
||||
|
||||
|
||||
// The user authorizes the CA for the domain example.com and the
|
||||
// regex alice.
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--domain", "other.org",
|
||||
"--regex", "alice"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for the domains example.org and other.org");
|
||||
check(
|
||||
&sq,
|
||||
true, // alice@example.org
|
||||
false, // bob@example.org
|
||||
true); // bob@other.org
|
||||
|
||||
|
||||
// The user authorizes the CA with the regex contraint "zoo".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--regex", "zoo"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"zoo\"");
|
||||
check(
|
||||
&sq,
|
||||
false, // alice@example.org
|
||||
false, // bob@example.org
|
||||
false);// bob@other.org
|
||||
|
||||
// The user authorizes the CA with the domain contraint "example".
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&concat(e, &["--domain", "example"]),
|
||||
ca.key_handle(),
|
||||
userids);
|
||||
|
||||
println!("CA: authorized for \"zoo\"");
|
||||
check(
|
||||
&sq,
|
||||
false, // alice@example.org
|
||||
false, // bob@example.org
|
||||
false);// bob@other.org
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retract_explicit() {
|
||||
// Authorize a CA via multiple user IDs. Retract one
|
||||
// authorization at a time. Make sure the CA remains a trusted
|
||||
// introducer until all authorizations are retracted.
|
||||
|
||||
let ca_example_org = "<ca@example.org>";
|
||||
let ca_example_com = "<ca@example.com>";
|
||||
let self_signed_userids = &[ca_example_org, ca_example_com][..];
|
||||
|
||||
// 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);
|
||||
|
||||
let mut sq = Sq::new();
|
||||
let (ca, ca_pgp, _ca_rev)
|
||||
= sq.key_generate(&[], self_signed_userids);
|
||||
sq.key_import(&ca_pgp);
|
||||
|
||||
let alice_example_org = "<alice@example.org>";
|
||||
let (alice, alice_pgp, _alice_rev)
|
||||
= sq.key_generate(&[], &[alice_example_org]);
|
||||
sq.key_import(&alice_pgp);
|
||||
|
||||
sq.tick(1);
|
||||
|
||||
// The ca certifies alice's certificate
|
||||
let certification = sq.scratch_file(None);
|
||||
sq.pki_certify(&[],
|
||||
ca.key_handle(), alice.key_handle(),
|
||||
&[ alice_example_org ],
|
||||
certification.as_path());
|
||||
sq.cert_import(&certification);
|
||||
|
||||
let check = |sq: &Sq, can_authenticate: bool| {
|
||||
let r = sq.pki_authenticate(
|
||||
&[],
|
||||
&alice.fingerprint().to_string(),
|
||||
alice_example_org);
|
||||
|
||||
match (can_authenticate, r.is_ok()) {
|
||||
(true, false) => {
|
||||
panic!("Expected to authenticated {}, but didn't.",
|
||||
alice_example_org);
|
||||
}
|
||||
(false, true) => {
|
||||
panic!("Expected to NOT authenticated {}, but did.",
|
||||
alice_example_org);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
// The ca is not yet authorized so we shouldn't be able to
|
||||
// authenticate alice.
|
||||
check(&sq, false);
|
||||
|
||||
// Authorize via all user IDs.
|
||||
sq.tick(1);
|
||||
let userids = self_signed_userids;
|
||||
sq.pki_link_authorize(&["--unconstrained"],
|
||||
ca.key_handle(),
|
||||
if implicit_all {
|
||||
&[]
|
||||
} else {
|
||||
userids
|
||||
});
|
||||
check(&sq, true);
|
||||
|
||||
for (i, userid) in userids.iter().enumerate() {
|
||||
// Retract the authorization of one user ID. It should
|
||||
// still be a trusted introducer.
|
||||
sq.tick(1);
|
||||
sq.pki_link_retract(&[], ca.key_handle(), &[userid]);
|
||||
|
||||
if i == userids.len() - 1 {
|
||||
// We've retracted all authorizations. This should
|
||||
// now fail.
|
||||
check(&sq, false);
|
||||
} else {
|
||||
check(&sq, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retract_non_self_signed() {
|
||||
// Make sure we can retract non-self-signed user IDs.
|
||||
|
||||
let self_signed = "<ca@example.org>";
|
||||
let non_self_signed = "<ca@example.com>";
|
||||
|
||||
let mut sq = Sq::new();
|
||||
let (ca, ca_pgp, _ca_rev)
|
||||
= sq.key_generate(&[], &[self_signed]);
|
||||
sq.key_import(&ca_pgp);
|
||||
|
||||
let alice_example_org = "<alice@example.org>";
|
||||
let (alice, alice_pgp, _alice_rev)
|
||||
= sq.key_generate(&[], &[alice_example_org]);
|
||||
sq.key_import(&alice_pgp);
|
||||
|
||||
sq.tick(1);
|
||||
|
||||
// The ca certifies alice's certificate
|
||||
let certification = sq.scratch_file(None);
|
||||
sq.pki_certify(&[],
|
||||
ca.key_handle(), alice.key_handle(),
|
||||
&[ alice_example_org ],
|
||||
certification.as_path());
|
||||
sq.cert_import(&certification);
|
||||
|
||||
let check = |sq: &Sq, can_authenticate: bool| {
|
||||
let r = sq.pki_authenticate(
|
||||
&[],
|
||||
&alice.fingerprint().to_string(),
|
||||
alice_example_org);
|
||||
|
||||
match (can_authenticate, r.is_ok()) {
|
||||
(true, false) => {
|
||||
panic!("Expected to authenticated {}, but didn't.",
|
||||
alice_example_org);
|
||||
}
|
||||
(false, true) => {
|
||||
panic!("Expected to NOT authenticated {}, but did.",
|
||||
alice_example_org);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
// The ca is not yet authorized so we shouldn't be able to
|
||||
// authenticate alice.
|
||||
check(&sq, false);
|
||||
|
||||
// Authorize the CA via a non-self-signed user ID. Now we can
|
||||
// authenticate alice.
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&["--unconstrained", "--add-userid"],
|
||||
ca.key_handle(),
|
||||
&[non_self_signed]);
|
||||
check(&sq, true);
|
||||
|
||||
// Retract the authorization. It should no longer be considered a
|
||||
// trusted introducer.
|
||||
sq.tick(1);
|
||||
sq.pki_link_retract(&[], ca.key_handle(), &[non_self_signed]);
|
||||
check(&sq, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retract_all() {
|
||||
// Link all self-signed user IDs and a non-self-signed user ID.
|
||||
// When we retract all, make sure they are all retracted.
|
||||
|
||||
let self_signed = "<ca@example.org>";
|
||||
let non_self_signed = "<ca@example.com>";
|
||||
|
||||
let mut sq = Sq::new();
|
||||
let (ca, ca_pgp, _ca_rev)
|
||||
= sq.key_generate(&[], &[self_signed]);
|
||||
sq.key_import(&ca_pgp);
|
||||
|
||||
let alice_example_org = "<alice@example.org>";
|
||||
let (alice, alice_pgp, _alice_rev)
|
||||
= sq.key_generate(&[], &[alice_example_org]);
|
||||
sq.key_import(&alice_pgp);
|
||||
|
||||
sq.tick(1);
|
||||
|
||||
// The ca certifies alice's certificate
|
||||
let certification = sq.scratch_file(None);
|
||||
sq.pki_certify(&[],
|
||||
ca.key_handle(), alice.key_handle(),
|
||||
&[ alice_example_org ],
|
||||
certification.as_path());
|
||||
sq.cert_import(&certification);
|
||||
|
||||
let check = |sq: &Sq, can_authenticate: bool| {
|
||||
let r = sq.pki_authenticate(
|
||||
&[],
|
||||
&alice.fingerprint().to_string(),
|
||||
alice_example_org);
|
||||
|
||||
match (can_authenticate, r.is_ok()) {
|
||||
(true, false) => {
|
||||
panic!("Expected to authenticated {}, but didn't.",
|
||||
alice_example_org);
|
||||
}
|
||||
(false, true) => {
|
||||
panic!("Expected to NOT authenticated {}, but did.",
|
||||
alice_example_org);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
// The ca is not yet authorized so we shouldn't be able to
|
||||
// authenticate alice.
|
||||
check(&sq, false);
|
||||
|
||||
// Authorize the CA via a non-self-signed user ID. Now we can
|
||||
// authenticate alice.
|
||||
sq.tick(1);
|
||||
sq.pki_link_authorize(&["--unconstrained", "--add-userid"],
|
||||
ca.key_handle(),
|
||||
&[self_signed, non_self_signed]);
|
||||
check(&sq, true);
|
||||
|
||||
// Retract all authorizations. It should no longer be considered
|
||||
// a trusted introducer.
|
||||
sq.tick(1);
|
||||
sq.pki_link_retract(&[], ca.key_handle(), &[]);
|
||||
check(&sq, false);
|
||||
}
|
Loading…
Reference in New Issue
Block a user