Add new subcommand sq pki authorize.

- Previously `sq pki certify` could create certifications, and mark
    a certificate as a trusted introducer (when the user set `--depth`
    to be greater than zero).  Anecdotal evidence indicates that
    combining these two actions in a single command is confusing.

  - Split the latter functionality off, and put it in a new subcommand,
    `sq pki authorize`.

  - See https://gitlab.com/sequoia-pgp/sequoia-sq/-/issues/249#note_1865470753
This commit is contained in:
Neal H. Walfield 2024-10-14 10:42:14 +02:00
parent bea0a5b732
commit 22284ed9b1
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
13 changed files with 740 additions and 42 deletions

4
NEWS
View File

@ -65,6 +65,10 @@
- `sq pki certify`'s positional argument for specifying the
certificate to certify must now be specified using a named
argument, `--cert` or `--cert-file`.
- Previously `sq pki 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 authorize`.
* Changes in 0.38.0
** Notable changes

View File

@ -11,6 +11,7 @@ use openpgp::packet::UserID;
use crate::cli::types::TrustAmount;
pub mod authorize;
pub mod certify;
pub mod link;
@ -55,6 +56,7 @@ pub enum Subcommands {
Lookup(LookupCommand),
Identify(IdentifyCommand),
Certify(certify::Command),
Authorize(authorize::Command),
Link(link::Command),
List(ListCommand),
Path(PathCommand),

223
src/cli/pki/authorize.rs Normal file
View File

@ -0,0 +1,223 @@
//! Command-line parser for `sq pki authorize`.
use clap::ArgGroup;
use clap::Parser;
use sequoia_openpgp as openpgp;
use openpgp::KeyHandle;
use crate::cli::THIRD_PARTY_CERTIFICATION_VALIDITY_DURATION;
use crate::cli::THIRD_PARTY_CERTIFICATION_VALIDITY_IN_YEARS;
use crate::cli::types::CertDesignators;
use crate::cli::types::ClapData;
use crate::cli::types::Expiration;
use crate::cli::types::FileOrStdin;
use crate::cli::types::FileOrStdout;
use crate::cli::types::TrustAmount;
use crate::cli::types::cert_designator::CertFileArgs;
use crate::cli::types::cert_designator::CertPrefix;
use crate::cli::types::cert_designator::NoPrefix;
use crate::cli::types::cert_designator::OneValue;
use crate::cli::types::cert_designator::OptionalValue;
use crate::cli::types::cert_designator::UserIDEmailArgs;
use crate::cli::examples::*;
const AUTHORIZE_EXAMPLES: Actions = Actions {
actions: &[
],
};
test_examples!(sq_pki_authorize, AUTHORIZE_EXAMPLES);
#[derive(Parser, Debug)]
#[clap(
name = "authorize",
about = "Mark a certificate as a trusted introducer",
long_about = format!(
"Mark a certificate as a trusted introducer.
Creates a certification that says that the issuer considers the \
certificate to be a trusted introducer. Trusted introducer is another \
word for certification authority (CA). When a user relies on a \
trusted introducer, the user considers 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 \
a regular expression using the `--regex` parameter.
These mechanisms allow Alice to say that she is willing to rely on the \
CA for example.org, but only for user IDs that have an email address \
for example.org, for instance.
By default a delegation expires after {} years. Use the `--expiration` \
argument to override this.
This subcommand respects the reference time set by the top-level \
`--time` argument. It sets the certification's creation time to the \
reference time.
",
THIRD_PARTY_CERTIFICATION_VALIDITY_IN_YEARS,
),
after_help = AUTHORIZE_EXAMPLES,
)]
#[clap(group(ArgGroup::new("certifier_input").args(&["certifier_file", "certifier"]).required(true)))]
#[clap(group(ArgGroup::new("constraint").args(&["regex", "unconstrained"]).required(true)))]
pub struct Command {
#[clap(
long,
value_name = "KEY",
help = "Create the certification using KEY.",
)]
pub certifier: Option<KeyHandle>,
#[clap(
long,
value_name = "KEY-FILE",
help = "Create the certification using KEY-FILE.",
)]
pub certifier_file: Option<FileOrStdin>,
#[command(flatten)]
pub cert: CertDesignators<CertFileArgs, CertPrefix, OneValue>,
#[command(flatten)]
pub userids: CertDesignators<UserIDEmailArgs, NoPrefix, OptionalValue>,
#[clap(
long,
help = "Add the given user ID if it doesn't exist.",
long_help =
"Add the given user ID if it doesn't exist in the certificate.",
)]
pub add_userid: bool,
#[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 = "1",
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 = "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 = "local",
help = "Make the certification a local certification",
long_help =
"Make the certification a local \
certification. Normally, local \
certifications are not exported.",
)]
pub local: bool,
#[clap(
long = "non-revocable",
help = "Mark the certification as being non-revocable",
long_help =
"Mark the certification as being non-revocable. \
That is, you cannot later revoke this \
certification. This should normally only \
be used with an expiration.",
)]
pub non_revocable: bool,
#[clap(
long = "expiration",
value_name = "EXPIRATION",
default_value_t =
Expiration::Duration(THIRD_PARTY_CERTIFICATION_VALIDITY_DURATION),
help =
"Define EXPIRATION for the certification as ISO 8601 formatted string or \
custom duration.",
long_help =
"Define EXPIRATION for the certification 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,
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>,
#[clap(
help = FileOrStdout::HELP_OPTIONAL,
long,
value_name = FileOrStdout::VALUE_NAME,
)]
pub output: Option<FileOrStdout>,
#[clap(
long,
help = "Emit binary data",
)]
pub binary: bool,
}

View File

@ -131,37 +131,6 @@ pub struct Command {
)]
pub amount: TrustAmount<u8>,
#[clap(
long = "depth",
value_name = "TRUST_DEPTH",
default_value = "0",
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: u8,
#[clap(
long = "regex",
value_name = "REGEX",
requires = "depth",
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 = "expiration",
value_name = "EXPIRATION",

View File

@ -14,6 +14,7 @@ use sequoia_wot as wot;
use wot::store::Backend;
use wot::store::Store;
pub mod authorize;
pub mod certify;
pub mod link;
pub mod output;
@ -656,6 +657,9 @@ pub fn dispatch(sq: Sq, cli: cli::pki::Command) -> Result<()> {
Subcommands::Certify(command) =>
self::certify::certify(sq, command)?,
Subcommands::Authorize(command) =>
self::authorize::authorize(sq, command)?,
Subcommands::Link(command) =>
self::link::link(sq, command)?,
}

View File

@ -0,0 +1,161 @@
use sequoia_openpgp as openpgp;
use openpgp::packet::UserID;
use openpgp::Result;
use openpgp::types::KeyFlags;
use crate::Sq;
use crate::cli::pki::authorize;
use crate::cli::types::FileStdinOrKeyHandle;
use crate::cli::types::cert_designator::CertDesignator;
use crate::commands::FileOrStdout;
use crate::parse_notations;
pub fn authorize(sq: Sq, mut c: authorize::Command)
-> Result<()>
{
let certifier: FileStdinOrKeyHandle = if let Some(file) = c.certifier_file {
assert!(c.certifier.is_none());
file.into()
} else if let Some(kh) = c.certifier {
kh.into()
} else {
panic!("clap enforces --certifier or --certifier-file is set");
};
let certifier = sq.lookup_one(
certifier, Some(KeyFlags::empty().set_certification()), true)?;
let (cert, from_file) = sq.resolve_cert(&c.cert, sequoia_wot::FULLY_TRUSTED)?;
if from_file {
// If the cert is read from a file, we default to stdout.
// (None means write to the cert store.)
if c.output.is_none() {
c.output = Some(FileOrStdout::new(None));
}
}
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
// Find the matching User ID.
let mut userids = Vec::new();
// Don't stop at the first error.
let mut missing = false;
let mut bad = None;
for designator in c.userids.iter() {
match designator {
CertDesignator::UserID(userid) => {
let userid = UserID::from(&userid[..]);
// If --add-userid is specified, we use the user ID as
// is. Otherwise, we make sure there is a matching
// self-signed user ID.
if c.add_userid {
userids.push(userid.clone());
} else if let Some(_) = vc.userids()
.find(|ua| {
ua.userid() == &userid
})
{
userids.push(userid.clone());
} else {
wprintln!("{:?} is not a self-signed user ID.",
String::from_utf8_lossy(userid.value()));
missing = true;
}
}
CertDesignator::Email(email) => {
// Validate the email address.
let userid = match UserID::from_address(None, None, email) {
Ok(userid) => userid,
Err(err) => {
wprintln!("{:?} is not a valid email address: {}",
email, err);
bad = Some(err);
continue;
}
};
// Extract a normalized version for comparison
// purposes.
let email_normalized = match userid.email_normalized() {
Ok(Some(email)) => email,
Ok(None) => {
wprintln!("{:?} is not a valid email address", email);
bad = Some(anyhow::anyhow!(format!(
"{:?} is not a valid email address", email)));
continue;
}
Err(err) => {
wprintln!("{:?} is not a valid email address: {}",
email, err);
bad = Some(err);
continue;
}
};
// Find any the matching self-signed user IDs.
let mut found = false;
for ua in vc.userids() {
if Some(&email_normalized)
== ua.email_normalized().unwrap_or(None).as_ref()
{
userids.push(ua.userid().clone());
found = true;
}
}
if ! found {
if c.add_userid {
// Add the bare email address.
userids.push(userid);
} else {
eprintln!("The email address {:?} does not match any \
user IDs.",
email);
missing = true;
}
}
}
_ => unreachable!("enforced by clap"),
}
}
if missing || 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()));
}
};
if let Some(err) = bad {
return Err(err);
}
let notations = parse_notations(&c.notation)?;
crate::common::pki::certify::certify(
&sq,
true, // Always recreate.
&certifier,
&cert,
&userids[..],
c.add_userid,
true, // User supplied user IDs.
&[(c.amount, c.expiration)],
c.depth,
&c.regex[..],
c.local,
c.non_revocable,
&notations[..],
c.output,
c.binary)
}

View File

@ -154,8 +154,8 @@ pub fn certify(sq: Sq, mut c: certify::Command)
c.add_userid,
true, // User supplied user IDs.
&[(c.amount, c.expiration)],
c.depth,
&c.regex[..],
0,
&[][..],
c.local,
c.non_revocable,
&notations[..],

View File

@ -175,6 +175,7 @@ pub fn certify(sq: &Sq,
-> Result<()>
{
assert!(templates.len() > 0);
assert!(userids.len() > 0);
make_qprintln!(sq.quiet);
if trust_depth == 0 && !regex.is_empty() {

View File

@ -21,6 +21,7 @@ mod integration {
mod sq_key_userid;
mod sq_pki;
mod sq_pki_certify;
mod sq_pki_authorize;
mod sq_pki_link;
mod sq_sign;
mod sq_toolbox_keyring_filter;

View File

@ -1386,6 +1386,108 @@ impl Sq {
.expect("success")
}
/// Try to make an authorization.
///
/// If `output_file` is `Some`, then the output is written to that
/// file. Otherwise, the default behavior is followed.
pub fn pki_authorize_p<'a, H, C, Q>(&self, extra_args: &[&str],
certifier: H,
cert: C,
userids: &[&str],
output_file: Q,
success: bool)
-> Result<Cert>
where H: Into<FileOrKeyHandle>,
C: Into<FileOrKeyHandle>,
Q: Into<Option<&'a Path>>,
{
let certifier = certifier.into();
let cert = cert.into();
let output_file = output_file.into();
let mut cmd = self.command();
cmd.args([ "pki", "authorize" ]);
for arg in extra_args {
cmd.arg(arg);
}
match &certifier {
FileOrKeyHandle::FileOrStdin(file) => {
cmd.arg("--certifier-file").arg(file);
}
FileOrKeyHandle::KeyHandle((_kh, s)) => {
cmd.arg("--certifier").arg(s);
}
}
match &cert {
FileOrKeyHandle::FileOrStdin(file) => {
cmd.arg("--cert-file").arg(file);
}
FileOrKeyHandle::KeyHandle((_kh, s)) => {
cmd.arg("--cert").arg(s);
}
}
for userid in userids {
cmd.arg("--userid").arg(userid);
}
if let Some(output_file) = output_file {
cmd.arg("--overwrite").arg("--output").arg(output_file);
}
let output = self.run(cmd, Some(success));
if output.status.success() {
if let Some(output_file) = output_file {
// The output was explicitly written to a file.
if output_file == &PathBuf::from("-") {
Ok(Cert::from_bytes(&output.stdout)
.expect("can parse certificate"))
} else {
Ok(Cert::from_file(&output_file)
.expect("can parse certificate"))
}
} else {
match cert {
FileOrKeyHandle::FileOrStdin(_) => {
// When the cert is from a file, the output is
// written to stdout by default.
Ok(Cert::from_bytes(&output.stdout)
.with_context(|| {
format!("Importing result from the file {:?}",
cert)
})
.expect("can parse certificate"))
}
FileOrKeyHandle::KeyHandle((kh, _s)) => {
// When the cert is from the cert store, the
// output is written to the cert store by
// default.
Ok(self.cert_export(kh.clone()))
}
}
}
} else {
Err(anyhow::anyhow!(format!(
"Failed (expected):\n{}",
String::from_utf8_lossy(&output.stderr))))
}
}
/// Authorize a certificate.
pub fn pki_authorize<'a, H, C, Q>(&self, extra_args: &[&str],
certifier: H,
cert: C,
userids: &[&str],
output_file: Q)
-> Cert
where H: Into<FileOrKeyHandle>,
C: Into<FileOrKeyHandle>,
Q: Into<Option<&'a Path>>,
{
self.pki_authorize_p(
extra_args, certifier, cert, userids, output_file, true)
.expect("success")
}
/// Add a link for the binding.
pub fn pki_link_add_maybe(&self, extra_args: &[&str],
cert: KeyHandle, userid: &str)
@ -1416,6 +1518,29 @@ impl Sq {
self.pki_link_add_maybe(args, cert, userid).expect("success")
}
/// Authenticate a binding.
pub fn pki_authenticate(&self, extra_args: &[&str],
cert: &str, userid: &str)
-> Result<()>
{
let mut cmd = self.command();
cmd.args([ "pki", "authenticate" ]);
for arg in extra_args {
cmd.arg(arg);
}
cmd.arg(cert);
cmd.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))))
}
}
pub fn sign<'a, H, Q>(&self,
signer: H,
password_file: Option<&Path>,

View File

@ -0,0 +1,182 @@
use super::common::Sq;
#[test]
fn sq_pki_authorize_then_authenticate() {
let mut sq = Sq::new();
let otto_somewhere_com = "<otto@somewhere.com>";
let (otto, otto_pgp, _otto_rev)
= sq.key_generate(&[], &[otto_somewhere_com]);
sq.key_import(&otto_pgp);
let ca_example_org = "<ca@example.org>";
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 using otto as the trust root.
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(
&["--trust-root", &otto.fingerprint().to_string() ],
&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);
}
_ => (),
}
}
};
// No delegation yet.
println!("CA: not authorized");
check(
&sq,
false, // Alice.
false, // bob@example.org
false);// bob@other.org
// Otto authorizes completely to the CA.
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_authorize(&["--unconstrained"],
otto.key_handle(), ca.key_handle(),
&[],
certification.as_path());
sq.cert_import(certification);
println!("CA: authorized, and unconstrained");
check(
&sq,
true, // alice@example.org
true, // bob@example.org
true);// bob@other.org
// Otto authorizes to the CA with the regex contraint "example".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_authorize(&["--regex", "example"],
otto.key_handle(), ca.key_handle(),
&[],
certification.as_path());
sq.cert_import(certification);
println!("CA: authorized for \"example\"");
check(
&sq,
true, // alice@example.org
true, // bob@example.org
false);// bob@other.org
// Otto authorizes to the CA with the regex contraint "other".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_authorize(&["--regex", "other"],
otto.key_handle(), ca.key_handle(),
&[],
certification.as_path());
sq.cert_import(certification);
println!("CA: authorized for \"other\"");
check(
&sq,
false, // alice@example.org
false, // bob@example.org
true); // bob@other.org
// Otto authorizes to the CA with the regex contraint "bob".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_authorize(&["--regex", "bob"],
otto.key_handle(), ca.key_handle(),
&[],
certification.as_path());
sq.cert_import(certification);
println!("CA: authorized for \"bob\"");
check(
&sq,
false, // alice@example.org
true, // bob@example.org
true); // bob@other.org
// Otto authorizes to the CA with the regex contraint "bob" or "alice".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_authorize(&["--regex", "bob", "--regex", "alice"],
otto.key_handle(), ca.key_handle(),
&[],
certification.as_path());
sq.cert_import(certification);
println!("CA: authorized for \"bob\" or \"alice\"");
check(
&sq,
true, // alice@example.org
true, // bob@example.org
true);// bob@other.org
// Otto authorizes to the CA with the regex contraint "zoo".
let certification = sq.scratch_file(None);
sq.tick(1);
sq.pki_authorize(&["--regex", "zoo"],
otto.key_handle(), ca.key_handle(),
&[],
certification.as_path());
sq.cert_import(certification);
println!("CA: authorized for \"zoo\"");
check(
&sq,
false, // alice@example.org
false, // bob@example.org
false);// bob@other.org
}

View File

@ -117,7 +117,7 @@ fn sq_pki_certify() -> Result<()> {
// Have alice certify <bob@example.org> for 0xB0B.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_certify(
let cert = sq.pki_authorize(
&["--depth", "10",
"--amount", "5",
"--regex", "a",

View File

@ -136,7 +136,7 @@ fn sq_retract(sq: &Sq, cert: &str, userids: &[&str])
// The certification is imported into the cert store.
fn sq_certify(sq: &Sq,
certifier: &str, cert: &str, userid: &str,
trust_amount: Option<usize>, depth: Option<usize>)
trust_amount: Option<usize>)
{
let mut extra_args = Vec::new();
let trust_amount_;
@ -145,6 +145,32 @@ fn sq_certify(sq: &Sq,
trust_amount_ = format!("{}", trust_amount);
extra_args.push(&trust_amount_);
}
let certification = sq.scratch_file(Some(&format!(
"certification {} {} by {}", cert, userid, certifier)[..]));
let cert = if let Ok(kh) = cert.parse::<KeyHandle>() {
kh.into()
} else {
FileOrKeyHandle::FileOrStdin(cert.into())
};
sq.pki_certify(&extra_args, certifier, cert, &[userid],
Some(certification.as_path()));
sq.cert_import(&certification);
}
fn sq_authorize(sq: &Sq,
certifier: &str, cert: &str, userid: &str,
trust_amount: Option<usize>, depth: Option<usize>)
{
let mut extra_args = vec![ "--unconstrained" ];
let trust_amount_;
if let Some(trust_amount) = trust_amount {
extra_args.push("--amount");
trust_amount_ = format!("{}", trust_amount);
extra_args.push(&trust_amount_);
}
let depth_;
if let Some(depth) = depth {
extra_args.push("--depth");
@ -161,8 +187,8 @@ fn sq_certify(sq: &Sq,
FileOrKeyHandle::FileOrStdin(cert.into())
};
sq.pki_certify(&extra_args, certifier, cert, &[userid],
Some(certification.as_path()));
sq.pki_authorize(&extra_args, certifier, cert, &[userid],
Some(certification.as_path()));
sq.cert_import(&certification);
}
@ -244,12 +270,12 @@ fn sq_pki_link_add_retract() -> Result<()> {
// Have Alice certify Bob as a trusted introducer and have Bob
// certify Carol.
sq_certify(&sq, &alice.key_file,
&bob.cert.fingerprint().to_string(), bob_userid,
None, Some(1));
sq_authorize(&sq, &alice.key_file,
&bob.cert.fingerprint().to_string(), bob_userid,
None, Some(1));
sq_certify(&sq, &bob.key_file,
&carol.cert.fingerprint().to_string(), carol_userid,
None, None);
None);
// We should be able to verify Alice's, Bob's and Carol's
// signatures Alice as the trust root. And Bob's and Carols' with
@ -312,7 +338,7 @@ fn sq_pki_link_add_retract() -> Result<()> {
// verify.
sq_certify(&sq, &bob.key_file,
&dave.cert.fingerprint().to_string(), dave_userid,
None, None);
None);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);