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:
Neal H. Walfield 2024-10-17 13:46:16 +02:00
parent cd7b79dbae
commit 609c5aab16
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
9 changed files with 817 additions and 137 deletions

4
NEWS
View File

@ -85,6 +85,10 @@
pki certify`. pki certify`.
- Removed `sq pki link add`'s `--petname` argument. Use `--userid` - Removed `sq pki link add`'s `--petname` argument. Use `--userid`
in conjunction with `--add-userid` instead. 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 * Changes in 0.38.0
** Notable changes ** Notable changes

View File

@ -17,7 +17,7 @@ use crate::cli::types::TrustAmount;
long_about = long_about =
"Manage authenticated certificate and User ID links "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 \ binding to be authentic. Another way is to use `sq pki certify` to \
certify the binding with an explicitly configured trust root. The \ certify the binding with an explicitly configured trust root. The \
linking functionality is often easier to work with, and 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 \ 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 \ retracted using `sq pki link retract`. A certificate can also be \
accepted as a trusted introducer by passing the `--ca` option to \ accepted as a trusted introducer by using `sq pki link authorize`.
`sq pki link add`.
`sq` implements linking using non-exportable certifications, and an \ `sq` implements linking using non-exportable certifications, and an \
implicit trust root. An OpenPGP certificate directory, the default \ 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 \ authority constrained to the domain example.org. That is, the \
certificate is considered a trusted introducer for example.org.", certificate is considered a trusted introducer for example.org.",
command: &[ command: &[
"sq", "pki", "link", "add", "sq", "pki", "link", "authorize",
"--ca=example.org", "--domain=example.org",
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
], ],
}), }),
@ -124,6 +122,7 @@ test_examples!(sq_pki_link, LINK_EXAMPLES);
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum Subcommands { pub enum Subcommands {
Add(AddCommand), Add(AddCommand),
Authorize(AuthorizeCommand),
Retract(RetractCommand), Retract(RetractCommand),
List(ListCommand), List(ListCommand),
} }
@ -131,16 +130,16 @@ pub enum Subcommands {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap( #[clap(
name = "add", name = "add",
about = "Link a certificate and a User ID", about = "Link a certificate and a user ID",
long_about = 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 \ This causes `sq` to consider the certificate and user ID binding to be \
authentic. authentic. You would do this if you are confident that a particular \
certificate should be associated with Alice, for example. Note: this \
A certificate can also be accepted as a certification authority, which \ does not consider the certificate to be a trusted introducer; it only \
is also known as a trusted introducer, by using the `--ca` or \ considers the binding to be authentic. To authorize a certificate to \
`--depth` option. be a trusted introducer use `sq pki link authorize`.
A link can be retracted using `sq pki link retract`. 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 \ 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. of the user's social graph is leaked when a certificate is shared.
By default a link never expires. \ By default a link never expires. This can be overridden using \
Using the `--expiration` argument specific validity periods may be defined. \ `--expiration` argument.
It allows for providing a point in time for validity to end or a validity \
duration.
`sq pki link` respects the reference time set by the top-level `--time` \ `sq pki link add` respects the reference time set by the top-level \
argument. It sets the link's creation time to the reference time. `--time` argument. It sets the link's creation time to the reference \
time.
", ",
after_help = ADD_EXAMPLES, after_help = ADD_EXAMPLES,
)] )]
@ -187,49 +185,6 @@ pub struct AddCommand {
)] )]
pub amount: TrustAmount<u8>, 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( #[clap(
long = "temporary", long = "temporary",
conflicts_with_all = &[ "amount" ], conflicts_with_all = &[ "amount" ],
@ -336,50 +291,224 @@ user IDs.",
"--all", "--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 { Action::Example(Example {
comment: "\ comment: "\
Accept the certificate EB28F26E2739A4870ECC47726F0073F60FD0CBF0 \ Add an unconstrained trusted introducer.",
with all of its self-signed user IDs as a trusted certification \
authority. That is, the certificate is considered a trust root.",
command: &[ command: &[
"sq", "pki", "link", "add", "sq", "pki", "link", "authorize",
"--ca=*", "--unconstrained",
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0"
"--all",
], ],
}), }),
Action::Example(Example { Action::Example(Example {
comment: "\ comment: "\
Accept the certificate EB28F26E2739A4870ECC47726F0073F60FD0CBF0 \ Add a trusted introducer for example.org and example.com.",
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: &[ command: &[
"sq", "pki", "link", "add", "sq", "pki", "link", "authorize",
"--ca=example.org", "--domain=example.org",
"--domain=example.com",
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
], ],
}), }),
Action::Example(Example { Action::Example(Example {
comment: "\ comment: "\
Accept the certificate EB28F26E2739A4870ECC47726F0073F60FD0CBF0 \ Add a partially trusted introducer.",
with all of its self-signed user IDs as a partially trusted \
certification authority.",
command: &[ command: &[
"sq", "pki", "link", "add", "sq", "pki", "link", "authorize",
"--ca=*", "--unconstrained",
"--amount=60", "--amount=60",
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--all",
], ],
}), }),
], ],
}; };
test_examples!(sq_pki_link_add, ADD_EXAMPLES); test_examples!(sq_pki_link_authorize, AUTHORIZE_EXAMPLES);
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap( #[clap(
@ -389,7 +518,8 @@ test_examples!(sq_pki_link_add, ADD_EXAMPLES);
"Retract links "Retract links
This command retracts links that were previously created using `sq \ 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 \ Note: this is called `retract` and not `remove`, because the \
certifications are not removed. Instead a new certification is added, \ certifications are not removed. Instead a new certification is added, \
which says that the binding has not been authenticated. which says that the binding has not been authenticated.

View File

@ -256,8 +256,8 @@ pub fn generate(
// Writing to key store. Provide some guidance. // Writing to key store. Provide some guidance.
sq.hint(format_args!("If this is your key, you should mark it as a \ sq.hint(format_args!("If this is your key, you should mark it as a \
fully trusted introducer:")) fully trusted introducer:"))
.sq().arg("pki").arg("link").arg("add") .sq().arg("pki").arg("link").arg("authorize")
.arg_value("--ca", "*") .arg("--unconstrained")
.arg("--cert").arg(cert.fingerprint()) .arg("--cert").arg(cert.fingerprint())
.arg("--all") .arg("--all")
.done(); .done();

View File

@ -639,7 +639,7 @@ impl Method {
wprintln!( wprintln!(
"Created the local CA {} for certifying \ "Created the local CA {} for certifying \
certificates downloaded from this service. \ 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 \ to change how much it is trusted. Or \
`sq pki link retract {}` to disable it.", `sq pki link retract {}` to disable it.",
if let Ok(cert) = cert.to_cert() { if let Ok(cert) = cert.to_cert() {

View File

@ -18,6 +18,7 @@ pub fn link(sq: Sq, c: link::Command) -> Result<()> {
use link::Subcommands::*; use link::Subcommands::*;
match c.subcommand { match c.subcommand {
Add(c) => add(sq, c)?, Add(c) => add(sq, c)?,
Authorize(c) => authorize(sq, c)?,
Retract(c) => retract(sq, c)?, Retract(c) => retract(sq, c)?,
List(c) => list(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 vc = cert.with_policy(sq.policy, Some(sq.time))?;
let userids = c.userids.resolve(&vc)?; 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 notations = parse_notations(c.notation)?;
let templates = if c.temporary { let templates = if c.temporary {
@ -93,13 +66,59 @@ pub fn add(sq: Sq, c: link::AddCommand)
true, // Add userid. true, // Add userid.
true, // User-supplied user IDs. true, // User-supplied user IDs.
&templates, &templates,
trust_depth, 0, // Trust depth.
if star { &[][..], // Domain.
&[][..] &[][..], // Regex.
} else { true, // Local.
&c.ca[..] false, // Non-revocable.
}, &notations[..],
&regex[..], 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. true, // Local.
false, // Non-revocable. false, // Non-revocable.
&notations[..], &notations[..],

View File

@ -23,6 +23,7 @@ mod integration {
mod sq_pki_certify; mod sq_pki_certify;
mod sq_pki_authorize; mod sq_pki_authorize;
mod sq_pki_link; mod sq_pki_link;
mod sq_pki_link_authorize;
mod sq_sign; mod sq_sign;
mod sq_toolbox_keyring_filter; mod sq_toolbox_keyring_filter;
mod sq_toolbox_packet_decrypt; mod sq_toolbox_packet_decrypt;

View File

@ -1520,6 +1520,74 @@ impl Sq {
self.pki_link_add_maybe(args, cert, userids).expect("success") 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. /// Authenticate a binding.
pub fn pki_authenticate(&self, extra_args: &[&str], pub fn pki_authenticate(&self, extra_args: &[&str],
cert: &str, userid: &str) cert: &str, userid: &str)

View File

@ -321,7 +321,9 @@ fn sq_pki_link_add_retract() -> Result<()> {
// Accept Alice as a trusted introducer. We should be able to // Accept Alice as a trusted introducer. We should be able to
// verify Alice, Bob, and Carol's signatures. // 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, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.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 // Accept Alice as a trusted introducer again. We should be able
// to verify Alice, Bob, and Carol's signatures. // 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, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.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, // Change Alice's acceptance to be a ca, but only for example.org,
// i.e., not for Dave. // i.e., not for Dave.
sq_link(&sq, &alice_fpr, &[ &alice_userid ], &[], &["--ca", "example.org"], sq.pki_link_authorize(&["--time", &tick(), "--domain", "example.org"],
true); alice.cert.key_handle(),
&[ &alice_userid ]);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0); sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.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); let bytes = compare(bytes, &alice_cert_pgp, true);
// Make Alice a CA. // Make Alice a CA.
let output = sq_link(&sq, &alice_fpr, &[], &[], sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
&["--ca", "*", "--all"], true); alice.key_handle(),
assert!(output.2.contains("was previously"), &[]);
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false); let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_link(&sq, &alice_fpr, &[], &[], sq.pki_link_authorize(&["--time", &tick(), "--unconstrained", "--all"],
&["--ca", "*", "--all"], true); alice.key_handle(),
assert!(output.2.contains("Certification parameters are unchanged"), &[]);
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true); let bytes = compare(bytes, &alice_cert_pgp, true);
// Make her a partially trusted CA. // Make her a partially trusted CA.
@ -532,13 +535,13 @@ fn sq_pki_link_update_detection() -> Result<()> {
// Link it again. // Link it again.
let output = sq_link(&sq, &alice_fpr, &[], &[], let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--depth", "10", "--amount", "10", "--all"], true); &["--amount", "10", "--all"], true);
assert!(output.2.contains("was retracted"), assert!(output.2.contains("was retracted"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2); "stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false); let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_link(&sq, &alice_fpr, &[], &[], 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"), assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2); "stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true); let bytes = compare(bytes, &alice_cert_pgp, true);

View 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);
}