Refactor sq pki certify, sq pki link add and sq pki link retract.
- Pull similar functionality out of the implementation of `sq pki certify`, `sq pki link add`, and `sq pki link retract`, and put it in a new module, `common::pki::certify`. - This slightly changes the human readable output.
This commit is contained in:
parent
56b8065b82
commit
4a3c360f41
@ -77,7 +77,7 @@ pub fn dispatch(sq: Sq, command: SqCommand) -> Result<()>
|
||||
///
|
||||
/// Note: if `n` User IDs are provided, then the returned vector has
|
||||
/// `n` elements.
|
||||
fn active_certification(
|
||||
pub fn active_certification(
|
||||
sq: &Sq,
|
||||
cert: &Cert, userids: Vec<UserID>,
|
||||
issuer: &Key<openpgp::packet::key::PublicParts,
|
||||
|
@ -1,27 +1,36 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
use openpgp::packet::UserID;
|
||||
use openpgp::Result;
|
||||
use openpgp::packet::prelude::*;
|
||||
use openpgp::packet::signature::subpacket::NotationDataFlags;
|
||||
use openpgp::serialize::Serialize;
|
||||
use openpgp::types::SignatureType;
|
||||
use openpgp::types::KeyFlags;
|
||||
|
||||
use sequoia_cert_store as cert_store;
|
||||
use cert_store::StoreUpdate;
|
||||
|
||||
use crate::Sq;
|
||||
use crate::parse_notations;
|
||||
use crate::cli::pki::certify;
|
||||
use crate::cli::types::FileOrStdin;
|
||||
use crate::cli::types::FileStdinOrKeyHandle;
|
||||
use crate::commands::FileOrStdout;
|
||||
use crate::parse_notations;
|
||||
|
||||
/// How to match on user IDs.
|
||||
#[derive(Debug)]
|
||||
enum UserIDQuery {
|
||||
/// Exact match.
|
||||
Exact(String),
|
||||
|
||||
/// Match on the email address.
|
||||
Email(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for UserIDQuery {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
UserIDQuery::Exact(q) => f.write_str(q),
|
||||
UserIDQuery::Email(q) => write!(f, "<{}>", q),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn certify(sq: Sq, mut c: certify::Command)
|
||||
-> Result<()>
|
||||
@ -35,6 +44,9 @@ pub fn certify(sq: Sq, mut c: certify::Command)
|
||||
panic!("clap enforces --certifier or --certifier-file is set");
|
||||
};
|
||||
|
||||
let certifier = sq.lookup_one(
|
||||
certifier, Some(KeyFlags::empty().set_certification()), true)?;
|
||||
|
||||
// XXX: Change this interface: it's dangerous to guess whether an
|
||||
// identifier is a file or a key handle.
|
||||
let cert = if let Ok(kh) = c.certificate.parse::<KeyHandle>() {
|
||||
@ -51,36 +63,16 @@ pub fn certify(sq: Sq, mut c: certify::Command)
|
||||
}
|
||||
}
|
||||
|
||||
let userid = c.userid;
|
||||
|
||||
let certifier = sq.lookup_one(
|
||||
certifier, Some(KeyFlags::empty().set_certification()), true)?;
|
||||
|
||||
let cert = sq.lookup_one(cert, None, true)?;
|
||||
|
||||
let trust_depth: u8 = c.depth;
|
||||
let trust_amount: u8 = c.amount.amount();
|
||||
let 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 local = c.local;
|
||||
let non_revocable = c.non_revocable;
|
||||
|
||||
let time = sq.time;
|
||||
|
||||
let vc = cert.with_policy(sq.policy, Some(time))?;
|
||||
|
||||
let query = if c.email {
|
||||
UserIDQuery::Email(userid)
|
||||
} else {
|
||||
UserIDQuery::Exact(userid)
|
||||
};
|
||||
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
|
||||
|
||||
// Find the matching User ID.
|
||||
let query = if c.email {
|
||||
UserIDQuery::Email(c.userid)
|
||||
} else {
|
||||
UserIDQuery::Exact(c.userid)
|
||||
};
|
||||
|
||||
let mut userids = Vec::new();
|
||||
for ua in vc.userids() {
|
||||
match &query {
|
||||
@ -133,125 +125,22 @@ pub fn certify(sq: Sq, mut c: certify::Command)
|
||||
return Err(anyhow::format_err!("No matching User ID found"));
|
||||
};
|
||||
|
||||
// Get the signer to certify with.
|
||||
let mut signer = sq.get_certification_key(certifier, None)?;
|
||||
let notations = parse_notations(&c.notation)?;
|
||||
|
||||
// Create the certifications.
|
||||
let mut new_packets: Vec<Packet> = Vec::new();
|
||||
for userid in userids {
|
||||
let mut builder
|
||||
= SignatureBuilder::new(SignatureType::GenericCertification);
|
||||
|
||||
if trust_depth != 0 || trust_amount != 120 {
|
||||
builder = builder.set_trust_signature(trust_depth, trust_amount)?;
|
||||
}
|
||||
|
||||
for regex in ®ex {
|
||||
builder = builder.add_regular_expression(regex)?;
|
||||
}
|
||||
|
||||
if local {
|
||||
builder = builder.set_exportable_certification(false)?;
|
||||
}
|
||||
|
||||
if non_revocable {
|
||||
builder = builder.set_revocable(false)?;
|
||||
}
|
||||
|
||||
// Creation time.
|
||||
builder = builder.set_signature_creation_time(time)?;
|
||||
|
||||
if let Some(validity) = c
|
||||
.expiration
|
||||
.as_duration(DateTime::<Utc>::from(sq.time))?
|
||||
{
|
||||
builder = builder.set_signature_validity_period(validity)?;
|
||||
}
|
||||
|
||||
let notations = parse_notations(&c.notation)?;
|
||||
for (critical, n) in notations {
|
||||
builder = builder.add_notation(
|
||||
n.name(),
|
||||
n.value(),
|
||||
NotationDataFlags::empty().set_human_readable(),
|
||||
critical)?;
|
||||
};
|
||||
|
||||
// Sign it.
|
||||
let certification = builder
|
||||
.sign_userid_binding(
|
||||
&mut signer,
|
||||
cert.primary_key().component(),
|
||||
&userid)?;
|
||||
|
||||
new_packets.push(userid.into());
|
||||
new_packets.push(certification.into());
|
||||
}
|
||||
|
||||
let cert = cert.insert_packets(new_packets)?;
|
||||
|
||||
if let Some(output) = c.output {
|
||||
// And export it.
|
||||
let path = output.path().map(Clone::clone);
|
||||
let mut message = output.create_pgp_safe(
|
||||
&sq,
|
||||
c.binary,
|
||||
sequoia_openpgp::armor::Kind::PublicKey,
|
||||
)?;
|
||||
cert.serialize(&mut message)?;
|
||||
message.finalize()?;
|
||||
|
||||
if let Some(path) = path {
|
||||
sq.hint(format_args!(
|
||||
"Updated certificate written to {}. \
|
||||
To make the update effective, it has to be published \
|
||||
so that others can find it, for example using:",
|
||||
path.display()))
|
||||
.sq().arg("network").arg("keyserver").arg("publish")
|
||||
.arg_value("--file", path.display())
|
||||
.done();
|
||||
} else {
|
||||
sq.hint(format_args!(
|
||||
"To make the update effective, it has to be published \
|
||||
so that others can find it."));
|
||||
}
|
||||
} else {
|
||||
// Import it.
|
||||
let cert_store = sq.cert_store_or_else()?;
|
||||
|
||||
let fipr = cert.fingerprint();
|
||||
if let Err(err) = cert_store.update(Arc::new(cert.into())) {
|
||||
wprintln!("Error importing updated cert: {}", err);
|
||||
return Err(err);
|
||||
} else {
|
||||
sq.hint(format_args!(
|
||||
"Imported updated cert into the cert store. \
|
||||
To make the update effective, it has to be published \
|
||||
so that others can find it, for example using:"))
|
||||
.sq().arg("network").arg("keyserver").arg("publish")
|
||||
.arg_value("--cert", fipr)
|
||||
.done();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// How to match on user IDs.
|
||||
#[derive(Debug)]
|
||||
enum UserIDQuery {
|
||||
/// Exact match.
|
||||
Exact(String),
|
||||
|
||||
/// Match on the email address.
|
||||
Email(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for UserIDQuery {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
UserIDQuery::Exact(q) => f.write_str(q),
|
||||
UserIDQuery::Email(q) => write!(f, "<{}>", q),
|
||||
}
|
||||
}
|
||||
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,
|
||||
¬ations[..],
|
||||
c.output,
|
||||
c.binary)
|
||||
}
|
||||
|
@ -1,30 +1,24 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::Result;
|
||||
use openpgp::cert::prelude::*;
|
||||
use openpgp::packet::prelude::*;
|
||||
use openpgp::packet::signature::subpacket::NotationDataFlags;
|
||||
use openpgp::types::RevocationStatus;
|
||||
use openpgp::types::SignatureType;
|
||||
|
||||
use sequoia_cert_store as cert_store;
|
||||
use cert_store::Store;
|
||||
use cert_store::StoreUpdate;
|
||||
use cert_store::store::UserIDQueryParams;
|
||||
|
||||
use crate::Sq;
|
||||
use crate::commands::active_certification;
|
||||
use crate::commands::pki::TrustAmount;
|
||||
use crate::parse_notations;
|
||||
use crate::print_error_chain;
|
||||
|
||||
use crate::cli::pki::link;
|
||||
use crate::cli::types::Expiration;
|
||||
|
||||
/// Checks that the search terms provided to --userid, --email, and
|
||||
/// patterns match known User IDs.
|
||||
@ -201,124 +195,6 @@ pub fn check_userids(sq: &Sq, cert: &Cert, self_signed: bool,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether two signatures have the same parameters.
|
||||
//
|
||||
// This does some normalization and only considers things that are
|
||||
// relevant to links.
|
||||
fn diff_link(sq: &Sq, old: &Signature, new: &SignatureBuilder, new_ct: SystemTime)
|
||||
-> bool
|
||||
{
|
||||
make_qprintln!(sq.quiet);
|
||||
let mut changed = false;
|
||||
|
||||
let a_expiration = old.signature_expiration_time();
|
||||
let b_expiration = if let Some(vp) = new.signature_validity_period() {
|
||||
Some(new_ct + vp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if a_expiration != b_expiration {
|
||||
changed = true;
|
||||
qprintln!(
|
||||
" Updating expiration time: {} -> {}.",
|
||||
if let Some(a_expiration) = a_expiration {
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
a_expiration).to_string()
|
||||
} else {
|
||||
"no expiration".to_string()
|
||||
},
|
||||
if let Some(b_expiration) = b_expiration {
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
b_expiration).to_string()
|
||||
} else {
|
||||
"no expiration".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
let (a_depth, a_amount) = old.trust_signature().unwrap_or((0, 120));
|
||||
let (b_depth, b_amount) = new.trust_signature().unwrap_or((0, 120));
|
||||
|
||||
if a_amount != b_amount {
|
||||
changed = true;
|
||||
qprintln!(" Updating trust amount: {} -> {}.",
|
||||
a_amount, b_amount);
|
||||
}
|
||||
if a_depth != b_depth {
|
||||
changed = true;
|
||||
qprintln!(" Update trust depth: {} -> {}.",
|
||||
a_depth, b_depth);
|
||||
}
|
||||
|
||||
let mut a_regex: Vec<_> = old.regular_expressions().collect();
|
||||
a_regex.sort();
|
||||
a_regex.dedup();
|
||||
let mut b_regex: Vec<_> = new.regular_expressions().collect();
|
||||
b_regex.sort();
|
||||
b_regex.dedup();
|
||||
|
||||
if a_regex != b_regex {
|
||||
changed = true;
|
||||
qprintln!(" Updating regular expressions:");
|
||||
let a_regex: Vec<String> = a_regex.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, r)| {
|
||||
format!("{}. {:?}",
|
||||
i + 1, String::from_utf8_lossy(r))
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Current link:\n {}",
|
||||
a_regex.join("\n "));
|
||||
|
||||
let b_regex: Vec<String> = b_regex.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, r)| {
|
||||
format!("{}. {:?}",
|
||||
i + 1, String::from_utf8_lossy(r))
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Updated link:\n {}",
|
||||
b_regex.join("\n "));
|
||||
}
|
||||
|
||||
let a_notations: Vec<_> = old.notation_data()
|
||||
.filter(|n| n.name() != "salt@notations.sequoia-pgp.org")
|
||||
.collect();
|
||||
let b_notations: Vec<_> = new.notation_data()
|
||||
.filter(|n| n.name() != "salt@notations.sequoia-pgp.org")
|
||||
.collect();
|
||||
if a_notations != b_notations {
|
||||
changed = true;
|
||||
qprintln!(" Updating notations.");
|
||||
let a_notations: Vec<String> = a_notations.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| {
|
||||
format!("{}. {:?}", i + 1, n)
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Current link:\n {}",
|
||||
a_notations.join("\n "));
|
||||
|
||||
let b_notations: Vec<String> = b_notations.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| {
|
||||
format!("{}. {:?}", i + 1, n)
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Updated link:\n {}",
|
||||
b_notations.join("\n "));
|
||||
}
|
||||
|
||||
let a_exportable = old.exportable_certification().unwrap_or(true);
|
||||
let b_exportable = new.exportable_certification().unwrap_or(true);
|
||||
if a_exportable != b_exportable {
|
||||
changed = true;
|
||||
qprintln!(" Updating exportable flag: {} -> {}.",
|
||||
a_exportable, b_exportable);
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn link(sq: Sq, c: link::Command) -> Result<()> {
|
||||
use link::Subcommands::*;
|
||||
match c.subcommand {
|
||||
@ -332,8 +208,6 @@ pub fn link(sq: Sq, c: link::Command) -> Result<()> {
|
||||
pub fn add(sq: Sq, c: link::AddCommand)
|
||||
-> Result<()>
|
||||
{
|
||||
make_qprintln!(sq.quiet);
|
||||
|
||||
let trust_root = sq.local_trust_root()?;
|
||||
let trust_root = trust_root.to_cert()?;
|
||||
|
||||
@ -381,7 +255,6 @@ pub fn add(sq: Sq, c: link::AddCommand)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let trust_amount: u8 = c.amount.amount();
|
||||
|
||||
let mut regex = c.regex;
|
||||
if trust_depth == 0 && !regex.is_empty() {
|
||||
@ -430,189 +303,49 @@ pub fn add(sq: Sq, c: link::AddCommand)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the certification.
|
||||
let mut builder
|
||||
= SignatureBuilder::new(SignatureType::GenericCertification);
|
||||
|
||||
if trust_depth != 0 || trust_amount != 120 {
|
||||
builder = builder.set_trust_signature(trust_depth, trust_amount)?;
|
||||
}
|
||||
|
||||
for regex in regex {
|
||||
builder = builder.add_regular_expression(regex)?;
|
||||
}
|
||||
|
||||
builder = builder.set_exportable_certification(false)?;
|
||||
|
||||
// Creation time.
|
||||
builder = builder.set_signature_creation_time(sq.time)?;
|
||||
|
||||
let notations = parse_notations(c.notation)?;
|
||||
for (critical, n) in notations {
|
||||
builder = builder.add_notation(
|
||||
n.name(),
|
||||
n.value(),
|
||||
NotationDataFlags::empty().set_human_readable(),
|
||||
critical)?;
|
||||
};
|
||||
|
||||
let builders: Vec<SignatureBuilder> = if c.temporary {
|
||||
let templates = if c.temporary {
|
||||
// Make the partially trusted link one second younger. When
|
||||
// the fully trusted link expired, then this link will come
|
||||
// into effect. If the user has fully linked the binding in
|
||||
// the meantime, then this won't override that, which is
|
||||
// exactly what we want.
|
||||
let mut partial = builder.clone();
|
||||
partial = partial.set_signature_creation_time(
|
||||
sq.time - Duration::new(1, 0))?;
|
||||
partial = partial.set_trust_signature(trust_depth, 40)?;
|
||||
let week = Duration::new(7 * 24 * 60 * 60, 0);
|
||||
|
||||
builder = builder.set_signature_validity_period(
|
||||
Duration::new(7 * 24 * 60 * 60, 0))?;
|
||||
|
||||
vec![ builder, partial ]
|
||||
vec![
|
||||
(TrustAmount::Other(40), c.expiration),
|
||||
(c.amount, Expiration::Duration(week)),
|
||||
]
|
||||
} else {
|
||||
if let Some(validity) = c
|
||||
.expiration
|
||||
.as_duration(DateTime::<Utc>::from(sq.time))? {
|
||||
builder = builder.set_signature_validity_period(validity)?;
|
||||
}
|
||||
vec![ builder ]
|
||||
vec![
|
||||
(c.amount, c.expiration),
|
||||
]
|
||||
};
|
||||
|
||||
// Sign it.
|
||||
let mut signer = sq.get_certification_key(trust_root, None)
|
||||
.context("Looking up local trust root")?;
|
||||
|
||||
let certifications = active_certification(
|
||||
&sq, &cert, userids,
|
||||
signer.public())
|
||||
.into_iter()
|
||||
.map(|(userid, active_certification)| {
|
||||
let userid_str = || String::from_utf8_lossy(userid.value());
|
||||
|
||||
if let Some(ua) = vc.userids().find(|ua| ua.userid() == &userid) {
|
||||
if let RevocationStatus::Revoked(_) = ua.revocation_status() {
|
||||
// It's revoked.
|
||||
if user_supplied_userids {
|
||||
// It was explicitly mentioned. Return an
|
||||
// error.
|
||||
return Err(anyhow::anyhow!(
|
||||
"Can't link {:?} with {}, it's revoked",
|
||||
userid_str(), cert.fingerprint()));
|
||||
} else {
|
||||
// We're just considering valid, self-signed
|
||||
// User IDs. Silently, skip it.
|
||||
return Ok(vec![]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qprintln!("Note: {:?} is NOT a self signed User ID. \
|
||||
If this was a mistake, use \
|
||||
`sq pki link retract {} \"{}\"` to undo it.",
|
||||
userid_str(), cert.fingerprint(), userid);
|
||||
}
|
||||
|
||||
if let Some(active_certification) = active_certification {
|
||||
let active_certification_ct
|
||||
= active_certification.signature_creation_time()
|
||||
.expect("valid signature");
|
||||
|
||||
let retracted = matches!(active_certification.trust_signature(),
|
||||
Some((_depth, 0)));
|
||||
if retracted {
|
||||
qprintln!("{}, {} was retracted at {}.",
|
||||
cert.fingerprint(), userid_str(),
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
active_certification_ct));
|
||||
} else {
|
||||
qprintln!("{}, {} was already linked at {}.",
|
||||
cert.fingerprint(), userid_str(),
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
active_certification_ct));
|
||||
}
|
||||
|
||||
let changed = diff_link(
|
||||
&sq,
|
||||
&active_certification,
|
||||
&builders[0], sq.time);
|
||||
|
||||
if ! changed && c.recreate {
|
||||
qprintln!(" Link parameters are unchanged, but \
|
||||
updating anyway as \"--recreate\" was specified.");
|
||||
} else if c.temporary {
|
||||
qprintln!(" Creating a temporary link, \
|
||||
which expires in a week.");
|
||||
} else if ! changed {
|
||||
qprintln!(" Link parameters are unchanged, no update \
|
||||
needed (specify \"--recreate\" to update anyway).");
|
||||
|
||||
// Return a signature packet to indicate that we
|
||||
// processed something. But don't return a
|
||||
// signature.
|
||||
return Ok(vec![ Packet::from(userid.clone()) ]);
|
||||
} else {
|
||||
qprintln!(" Link parameters changed, updating link.");
|
||||
}
|
||||
}
|
||||
|
||||
qprintln!("Linking {} and {:?}.",
|
||||
cert.fingerprint(), userid_str());
|
||||
|
||||
let mut sigs = builders.iter()
|
||||
.map(|builder| {
|
||||
builder.clone().sign_userid_binding(
|
||||
&mut signer,
|
||||
cert.primary_key().key(),
|
||||
&userid)
|
||||
.with_context(|| {
|
||||
format!("Creating certification for {:?}",
|
||||
userid_str())
|
||||
})
|
||||
.map(Into::into)
|
||||
})
|
||||
.collect::<Result<Vec<Packet>>>()?;
|
||||
|
||||
qprintln!();
|
||||
|
||||
let mut packets = vec![ Packet::from(userid.clone()) ];
|
||||
packets.append(&mut sigs);
|
||||
Ok(packets)
|
||||
})
|
||||
.collect::<Result<Vec<Vec<Packet>>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<Packet>>();
|
||||
|
||||
if certifications.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Can't link {} to anything. The certificate has no self-signed \
|
||||
User IDs and you didn't specify any User IDs to link to it.",
|
||||
cert.fingerprint()));
|
||||
}
|
||||
|
||||
if certifications.iter().all(|p| matches!(p, Packet::UserID(_))) {
|
||||
// There are no signatures to insert. We're done.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cert = cert.insert_packets(certifications.clone())?;
|
||||
|
||||
let cert_store = sq.cert_store_or_else()?;
|
||||
cert_store.update(Arc::new(cert.into()))
|
||||
.with_context(|| format!("Updating {}", c.certificate))?;
|
||||
|
||||
Ok(())
|
||||
crate::common::pki::certify::certify(
|
||||
&sq,
|
||||
c.recreate, // Recreate.
|
||||
&trust_root,
|
||||
&cert,
|
||||
&userids[..],
|
||||
true, // Add userid.
|
||||
user_supplied_userids,
|
||||
&templates,
|
||||
trust_depth,
|
||||
®ex[..],
|
||||
true, // Local.
|
||||
false, // Non-revocable.
|
||||
¬ations[..],
|
||||
None, // Output.
|
||||
false) // Binary.
|
||||
}
|
||||
|
||||
pub fn retract(sq: Sq, c: link::RetractCommand)
|
||||
-> Result<()>
|
||||
{
|
||||
make_qprintln!(sq.quiet);
|
||||
|
||||
let trust_root = sq.local_trust_root()?;
|
||||
let trust_root = trust_root.to_cert()?;
|
||||
let trust_root_kh = trust_root.key_handle();
|
||||
|
||||
let cert = sq.lookup_one(&c.certificate, None, true)?;
|
||||
|
||||
@ -620,142 +353,34 @@ pub fn retract(sq: Sq, c: link::RetractCommand)
|
||||
check_userids(&sq, &cert, false, &c.userid, &c.email, &c.pattern)
|
||||
.context("sq pki link retract: Invalid User IDs")?;
|
||||
|
||||
// Nothing was specified. Retract all known User IDs.
|
||||
if userids.is_empty() {
|
||||
let user_supplied_userids = if userids.is_empty() {
|
||||
// Nothing was specified. Retract all known User IDs.
|
||||
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
|
||||
userids = vc.userids().map(|ua| ua.userid().clone()).collect();
|
||||
}
|
||||
|
||||
// Create the certification.
|
||||
let mut builder
|
||||
= SignatureBuilder::new(SignatureType::GenericCertification);
|
||||
|
||||
builder = builder.set_trust_signature(0, 0)?;
|
||||
builder = builder.set_exportable_certification(false)?;
|
||||
|
||||
// Creation time.
|
||||
builder = builder.set_signature_creation_time(sq.time)?;
|
||||
|
||||
let notations = parse_notations(c.notation)?;
|
||||
for (critical, n) in notations {
|
||||
builder = builder.add_notation(
|
||||
n.name(),
|
||||
n.value(),
|
||||
NotationDataFlags::empty().set_human_readable(),
|
||||
critical)?;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
// Sign it.
|
||||
let mut signer = sq.get_certification_key(trust_root, None)
|
||||
.context("Looking up local trust root")?;
|
||||
let notations = parse_notations(c.notation)?;
|
||||
|
||||
let certifications = active_certification(
|
||||
&sq, &cert, userids, signer.public())
|
||||
.into_iter()
|
||||
.map(|(userid, active_certification)| {
|
||||
let userid_str = || String::from_utf8_lossy(userid.value());
|
||||
|
||||
if let Some(ua) = cert.userids().find(|ua| ua.userid() == &userid) {
|
||||
if ! ua.certifications().any(|c| {
|
||||
c.get_issuers().into_iter()
|
||||
.any(|issuer| issuer.aliases(&trust_root_kh))
|
||||
})
|
||||
{
|
||||
qprintln!("You never linked {:?} to {}, \
|
||||
no need to retract it.",
|
||||
userid_str(), cert.fingerprint());
|
||||
return Ok(vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_certification) = active_certification {
|
||||
let active_certification_ct
|
||||
= active_certification.signature_creation_time()
|
||||
.expect("valid signature");
|
||||
|
||||
let retracted = matches!(active_certification.trust_signature(),
|
||||
Some((_depth, 0)));
|
||||
if retracted {
|
||||
qprintln!("{}, {} was already retracted at {}.",
|
||||
cert.fingerprint(), userid_str(),
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
active_certification_ct));
|
||||
} else {
|
||||
qprintln!("{}, {} was linked at {}.",
|
||||
cert.fingerprint(), userid_str(),
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
active_certification_ct));
|
||||
}
|
||||
|
||||
let changed = diff_link(
|
||||
&sq,
|
||||
&active_certification,
|
||||
&builder, sq.time);
|
||||
|
||||
if ! changed && c.recreate {
|
||||
qprintln!(" Link parameters are unchanged, but \
|
||||
updating anyway as \"--recreate\" was specified.");
|
||||
} else if ! changed {
|
||||
qprintln!(" Link parameters are unchanged, no update \
|
||||
needed (specify \"--recreate\" to update anyway).");
|
||||
|
||||
// Return a signature packet to indicate that we
|
||||
// processed something. But don't return a
|
||||
// signature.
|
||||
return Ok(vec![ Packet::from(userid.clone()) ]);
|
||||
} else {
|
||||
qprintln!(" Link parameters changed, updating link.");
|
||||
}
|
||||
} else if c.recreate {
|
||||
qprintln!("There is no link to retract between {} and {:?}, \
|
||||
retracting anyways as \"--recreate\" was specified.",
|
||||
cert.fingerprint(), userid_str());
|
||||
} else {
|
||||
qprintln!("There is no link to retract between {} and {:?} \
|
||||
(specify \"--recreate\" to mark as retracted anyways).",
|
||||
cert.fingerprint(), userid_str());
|
||||
|
||||
// Return a signature packet to indicate that we
|
||||
// processed something. But don't return a
|
||||
// signature.
|
||||
return Ok(vec![ Packet::from(userid.clone()) ]);
|
||||
}
|
||||
|
||||
qprintln!("Breaking link between {} and {:?}.",
|
||||
cert.fingerprint(), userid_str());
|
||||
|
||||
// XXX: If we already have exactly this signature (modulo
|
||||
// the creation time), then don't add it! Note: it is
|
||||
// explicitly NOT enough to check that there is a
|
||||
// certification from the local trust root.
|
||||
|
||||
let sig = builder.clone().sign_userid_binding(
|
||||
&mut signer,
|
||||
cert.primary_key().key(),
|
||||
&userid)
|
||||
.with_context(|| {
|
||||
format!("Creating certification for {:?}", userid_str())
|
||||
})?;
|
||||
|
||||
Ok(vec![ Packet::from(userid.clone()), Packet::from(sig) ])
|
||||
})
|
||||
.collect::<Result<Vec<Vec<Packet>>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<Packet>>();
|
||||
|
||||
if certifications.is_empty() {
|
||||
qprintln!("Nothing to retract.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cert = cert.insert_packets(certifications.clone())?;
|
||||
|
||||
let cert_store = sq.cert_store_or_else()?;
|
||||
cert_store.update(Arc::new(cert.into()))
|
||||
.with_context(|| format!("Updating {}", c.certificate))?;
|
||||
|
||||
Ok(())
|
||||
crate::common::pki::certify::certify(
|
||||
&sq,
|
||||
c.recreate, // Recreate.
|
||||
&trust_root,
|
||||
&cert,
|
||||
&userids[..],
|
||||
false, // Add userid.
|
||||
user_supplied_userids,
|
||||
&[(TrustAmount::None, Expiration::Never)],
|
||||
0,
|
||||
&[][..],
|
||||
true, // Local.
|
||||
false, // Non-revocable.
|
||||
¬ations[..],
|
||||
None, // Output.
|
||||
false) // Binary.
|
||||
}
|
||||
|
||||
pub fn list(sq: Sq, c: link::ListCommand)
|
||||
|
@ -17,6 +17,7 @@ pub use revoke::RevocationOutput;
|
||||
pub mod key;
|
||||
|
||||
pub mod password;
|
||||
pub mod pki;
|
||||
pub mod userid;
|
||||
|
||||
pub const NULL_POLICY: &NullPolicy = &NullPolicy::new();
|
||||
|
1
src/common/pki.rs
Normal file
1
src/common/pki.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod certify;
|
429
src/common/pki/certify.rs
Normal file
429
src/common/pki/certify.rs
Normal file
@ -0,0 +1,429 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::Cert;
|
||||
use openpgp::Result;
|
||||
use openpgp::cert::amalgamation::ValidAmalgamation;
|
||||
use openpgp::packet::prelude::*;
|
||||
use openpgp::packet::signature::subpacket::NotationData;
|
||||
use openpgp::packet::signature::subpacket::NotationDataFlags;
|
||||
use openpgp::serialize::Serialize;
|
||||
use openpgp::types::RevocationStatus;
|
||||
use openpgp::types::SignatureType;
|
||||
|
||||
use sequoia_cert_store as cert_store;
|
||||
use cert_store::StoreUpdate;
|
||||
|
||||
use crate::Sq;
|
||||
use crate::cli::types::Expiration;
|
||||
use crate::cli::types::FileOrStdout;
|
||||
use crate::cli::types::TrustAmount;
|
||||
use crate::commands::active_certification;
|
||||
|
||||
// Returns whether two certifications have the same parameters.
|
||||
//
|
||||
// This does some normalization and only considers things that are
|
||||
// relevant to certifications:
|
||||
//
|
||||
// - Expiration time
|
||||
// - Trust depth
|
||||
// - Trust amount
|
||||
// - Regular expressions
|
||||
// - Notations
|
||||
// - Exportable
|
||||
pub fn diff_certification(sq: &Sq, old: &Signature, new: &SignatureBuilder,
|
||||
new_ct: SystemTime)
|
||||
-> bool
|
||||
{
|
||||
make_qprintln!(sq.quiet);
|
||||
let mut changed = false;
|
||||
|
||||
let a_expiration = old.signature_expiration_time();
|
||||
let b_expiration = if let Some(vp) = new.signature_validity_period() {
|
||||
Some(new_ct + vp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if a_expiration != b_expiration {
|
||||
changed = true;
|
||||
qprintln!(
|
||||
" Updating expiration time: {} -> {}.",
|
||||
if let Some(a_expiration) = a_expiration {
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
a_expiration).to_string()
|
||||
} else {
|
||||
"no expiration".to_string()
|
||||
},
|
||||
if let Some(b_expiration) = b_expiration {
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
b_expiration).to_string()
|
||||
} else {
|
||||
"no expiration".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
let (a_depth, a_amount) = old.trust_signature().unwrap_or((0, 120));
|
||||
let (b_depth, b_amount) = new.trust_signature().unwrap_or((0, 120));
|
||||
|
||||
if a_amount != b_amount {
|
||||
changed = true;
|
||||
qprintln!(" Updating trust amount: {} -> {}.",
|
||||
a_amount, b_amount);
|
||||
}
|
||||
if a_depth != b_depth {
|
||||
changed = true;
|
||||
qprintln!(" Updating trust depth: {} -> {}.",
|
||||
a_depth, b_depth);
|
||||
}
|
||||
|
||||
let mut a_regex: Vec<_> = old.regular_expressions().collect();
|
||||
a_regex.sort();
|
||||
a_regex.dedup();
|
||||
let mut b_regex: Vec<_> = new.regular_expressions().collect();
|
||||
b_regex.sort();
|
||||
b_regex.dedup();
|
||||
|
||||
if a_regex != b_regex {
|
||||
changed = true;
|
||||
qprintln!(" Updating regular expressions:");
|
||||
let a_regex: Vec<String> = a_regex.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, r)| {
|
||||
format!("{}. {:?}",
|
||||
i + 1, String::from_utf8_lossy(r))
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Current certification:\n {}",
|
||||
a_regex.join("\n "));
|
||||
|
||||
let b_regex: Vec<String> = b_regex.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, r)| {
|
||||
format!("{}. {:?}",
|
||||
i + 1, String::from_utf8_lossy(r))
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" New certification:\n {}",
|
||||
b_regex.join("\n "));
|
||||
}
|
||||
|
||||
let a_notations: Vec<_> = old.notation_data()
|
||||
.filter(|n| n.name() != "salt@notations.sequoia-pgp.org")
|
||||
.collect();
|
||||
let b_notations: Vec<_> = new.notation_data()
|
||||
.filter(|n| n.name() != "salt@notations.sequoia-pgp.org")
|
||||
.collect();
|
||||
if a_notations != b_notations {
|
||||
changed = true;
|
||||
qprintln!(" Updating notations.");
|
||||
let a_notations: Vec<String> = a_notations.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| {
|
||||
format!("{}. {:?}", i + 1, n)
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Current certification:\n {}",
|
||||
a_notations.join("\n "));
|
||||
|
||||
let b_notations: Vec<String> = b_notations.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| {
|
||||
format!("{}. {:?}", i + 1, n)
|
||||
})
|
||||
.collect();
|
||||
qprintln!(" Updated certification:\n {}",
|
||||
b_notations.join("\n "));
|
||||
}
|
||||
|
||||
let a_exportable = old.exportable_certification().unwrap_or(true);
|
||||
let b_exportable = new.exportable_certification().unwrap_or(true);
|
||||
if a_exportable != b_exportable {
|
||||
changed = true;
|
||||
qprintln!(" Updating exportable flag: {} -> {}.",
|
||||
a_exportable, b_exportable);
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
/// This function is used for certifications and retractions.
|
||||
///
|
||||
/// If the trust amount is 0, the operation is interpreted as a
|
||||
/// retraction and the wording is changed accordingly.
|
||||
pub fn certify(sq: &Sq,
|
||||
recreate: bool,
|
||||
certifier: &Cert,
|
||||
cert: &Cert,
|
||||
userids: &[UserID],
|
||||
add_userid: bool,
|
||||
user_supplied_userids: bool,
|
||||
templates: &[(TrustAmount<u8>, Expiration)],
|
||||
trust_depth: u8,
|
||||
regex: &[String],
|
||||
local: bool,
|
||||
non_revocable: bool,
|
||||
notations: &[(bool, NotationData)],
|
||||
output: Option<FileOrStdout>,
|
||||
binary: bool)
|
||||
-> Result<()>
|
||||
{
|
||||
assert!(templates.len() > 0);
|
||||
make_qprintln!(sq.quiet);
|
||||
|
||||
if trust_depth == 0 && !regex.is_empty() {
|
||||
return Err(
|
||||
anyhow::format_err!("A regex constraint only makes sense \
|
||||
if the trust depth is greater than 0"));
|
||||
}
|
||||
|
||||
let vc = cert.with_policy(sq.policy, sq.time)?;
|
||||
|
||||
// Get the signer to certify with.
|
||||
let mut signer = sq.get_certification_key(certifier, None)?;
|
||||
|
||||
let mut base
|
||||
= SignatureBuilder::new(SignatureType::GenericCertification);
|
||||
|
||||
for regex in regex {
|
||||
base = base.add_regular_expression(regex)?;
|
||||
}
|
||||
|
||||
if local {
|
||||
base = base.set_exportable_certification(false)?;
|
||||
}
|
||||
|
||||
if non_revocable {
|
||||
base = base.set_revocable(false)?;
|
||||
}
|
||||
|
||||
for (critical, n) in notations {
|
||||
base = base.add_notation(
|
||||
n.name(),
|
||||
n.value(),
|
||||
NotationDataFlags::empty().set_human_readable(),
|
||||
*critical)?;
|
||||
};
|
||||
|
||||
let mut retract = false;
|
||||
|
||||
let mut builders = Vec::with_capacity(templates.len());
|
||||
for (i, (trust_amount, expiration)) in templates.into_iter().enumerate() {
|
||||
let mut builder = base.clone();
|
||||
|
||||
let trust_amount: u8 = trust_amount.amount();
|
||||
if trust_amount == 0 {
|
||||
retract = true;
|
||||
}
|
||||
if trust_depth != 0 || trust_amount != 120 {
|
||||
builder = builder.set_trust_signature(
|
||||
trust_depth, trust_amount)?;
|
||||
}
|
||||
|
||||
// Creation time.
|
||||
//
|
||||
// If we should make two certifications, then the first one
|
||||
// should be at `sq.time - 1`, and the second one at
|
||||
// `sq.time`. That is, the first one is a second earlier.
|
||||
let backdate = Duration::new((templates.len() - 1 - i) as u64, 0);
|
||||
let ct = sq.time - backdate;
|
||||
builder = builder.set_signature_creation_time(ct)?;
|
||||
|
||||
// Expiration.
|
||||
if let Some(validity) = expiration
|
||||
.as_duration(DateTime::<Utc>::from(sq.time))?
|
||||
{
|
||||
builder = builder.set_signature_validity_period(validity)?;
|
||||
}
|
||||
|
||||
builders.push(builder);
|
||||
}
|
||||
|
||||
// Get the active certification as of the reference time.
|
||||
let certifications = active_certification(
|
||||
&sq, &cert, userids.to_vec(),
|
||||
certifier.primary_key().key().role_as_unspecified())
|
||||
.into_iter()
|
||||
.map(|(userid, active_certification)| {
|
||||
let userid_str = || String::from_utf8_lossy(userid.value());
|
||||
|
||||
if let Some(ua) = vc.userids().find(|ua| ua.userid() == &userid) {
|
||||
if retract {
|
||||
// Check if we certified it.
|
||||
if ! ua.certifications().any(|c| {
|
||||
c.get_issuers().into_iter()
|
||||
.any(|issuer| issuer.aliases(&certifier.key_handle()))
|
||||
})
|
||||
{
|
||||
qprintln!("You never certified {:?} for {}, \
|
||||
there is nothing to retract.",
|
||||
userid_str(), cert.fingerprint());
|
||||
return Ok(vec![ Packet::from(userid.clone()) ]);
|
||||
}
|
||||
} else {
|
||||
if let RevocationStatus::Revoked(_) = ua.revocation_status() {
|
||||
// It's revoked.
|
||||
if user_supplied_userids {
|
||||
// It was explicitly mentioned. Return an
|
||||
// error.
|
||||
return Err(anyhow::anyhow!(
|
||||
"Can't certify {:?} for {}, it's revoked",
|
||||
userid_str(), cert.fingerprint()));
|
||||
} else {
|
||||
// We're just considering valid, self-signed
|
||||
// user IDs. Silently, skip it.
|
||||
return Ok(vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ! add_userid {
|
||||
if retract {
|
||||
qprintln!("You never certified {:?} for {}, \
|
||||
there is nothing to retract.",
|
||||
userid_str(), cert.fingerprint());
|
||||
// Return a signature packet to indicate that we
|
||||
// processed something. But don't return a
|
||||
// signature.
|
||||
return Ok(vec![ Packet::from(userid.clone()) ]);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{:?} is NOT a self signed user ID. \
|
||||
Supply \"--add-userid\" to certify it anyways.",
|
||||
userid_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_certification) = active_certification {
|
||||
let active_certification_ct
|
||||
= active_certification.signature_creation_time()
|
||||
.expect("valid signature");
|
||||
|
||||
let retracted = matches!(active_certification.trust_signature(),
|
||||
Some((_depth, 0)));
|
||||
if retracted {
|
||||
qprintln!("A prior certification for {}, {} was retracted at {}.",
|
||||
cert.fingerprint(), userid_str(),
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
active_certification_ct));
|
||||
} else {
|
||||
qprintln!("{}, {} was previously certified at {}.",
|
||||
cert.fingerprint(), userid_str(),
|
||||
chrono::DateTime::<chrono::offset::Utc>::from(
|
||||
active_certification_ct));
|
||||
}
|
||||
|
||||
let changed = diff_certification(
|
||||
&sq,
|
||||
&active_certification,
|
||||
&builders[0], sq.time);
|
||||
|
||||
if ! changed {
|
||||
qprintln!(" Certification parameters are unchanged.");
|
||||
|
||||
if ! recreate {
|
||||
// Return a signature packet to indicate that we
|
||||
// processed something. But don't return a
|
||||
// signature.
|
||||
return Ok(vec![ Packet::from(userid.clone()) ]);
|
||||
}
|
||||
} else {
|
||||
qprintln!(" Parameters changed, creating a new certification.");
|
||||
}
|
||||
}
|
||||
|
||||
qprintln!("Certifying {:?} for {}.",
|
||||
userid_str(), cert.fingerprint());
|
||||
|
||||
let mut sigs = builders.iter()
|
||||
.map(|builder| {
|
||||
builder.clone().sign_userid_binding(
|
||||
&mut signer,
|
||||
cert.primary_key().key(),
|
||||
&userid)
|
||||
.with_context(|| {
|
||||
format!("Creating certification for {:?}",
|
||||
userid_str())
|
||||
})
|
||||
.map(Into::into)
|
||||
})
|
||||
.collect::<Result<Vec<Packet>>>()?;
|
||||
|
||||
qprintln!();
|
||||
|
||||
let mut packets = vec![ Packet::from(userid.clone()) ];
|
||||
packets.append(&mut sigs);
|
||||
Ok(packets)
|
||||
})
|
||||
.collect::<Result<Vec<Vec<Packet>>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<Packet>>();
|
||||
|
||||
if certifications.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Can't certify {}. The certificate has no self-signed \
|
||||
user IDs and you didn't specify any user IDs to certify.",
|
||||
cert.fingerprint()));
|
||||
}
|
||||
|
||||
if certifications.iter().all(|p| matches!(p, Packet::UserID(_))) {
|
||||
// There are no signatures to insert. We're done.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cert = cert.clone().insert_packets(certifications)?;
|
||||
|
||||
if let Some(output) = output {
|
||||
// And export it.
|
||||
let path = output.path().map(Clone::clone);
|
||||
let mut message = output.create_pgp_safe(
|
||||
&sq,
|
||||
binary,
|
||||
sequoia_openpgp::armor::Kind::PublicKey,
|
||||
)?;
|
||||
cert.serialize(&mut message)?;
|
||||
message.finalize()?;
|
||||
|
||||
if ! local {
|
||||
if let Some(path) = path {
|
||||
sq.hint(format_args!(
|
||||
"Updated certificate written to {}. \
|
||||
To make the update effective, it has to be published \
|
||||
so that others can find it, for example using:",
|
||||
path.display()))
|
||||
.sq().arg("network").arg("keyserver").arg("publish")
|
||||
.arg_value("--file", path.display())
|
||||
.done();
|
||||
} else {
|
||||
sq.hint(format_args!(
|
||||
"To make the update effective, it has to be published \
|
||||
so that others can find it."));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Import it.
|
||||
let cert_store = sq.cert_store_or_else()?;
|
||||
|
||||
let fipr = cert.fingerprint();
|
||||
if let Err(err) = cert_store.update(Arc::new(cert.into())) {
|
||||
wprintln!("Error importing updated cert: {}", err);
|
||||
return Err(err);
|
||||
} else if ! local {
|
||||
sq.hint(format_args!(
|
||||
"Imported updated cert into the cert store. \
|
||||
To make the update effective, it has to be published \
|
||||
so that others can find it, for example using:"))
|
||||
.sq().arg("network").arg("keyserver").arg("publish")
|
||||
.arg_value("--cert", fipr)
|
||||
.done();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -443,7 +443,7 @@ fn sq_pki_link_update_detection() -> Result<()> {
|
||||
|
||||
// Retract it. There is nothing to retract (but this doesn't fail).
|
||||
let output = sq_retract(&sq, &alice_fpr, &[]);
|
||||
assert!(output.2.contains("You never linked"),
|
||||
assert!(output.2.contains("You never certified"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
@ -454,44 +454,44 @@ fn sq_pki_link_update_detection() -> Result<()> {
|
||||
// As no parameters changed, this should succeeded, but no
|
||||
// certification should be written.
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &["--all"], true);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
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 already linked"),
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[],
|
||||
&["--ca", "*", "--all"], true);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
// Make her a partially trusted CA.
|
||||
let output = sq_link(&sq, &alice_fpr, &[],
|
||||
&["--amount", "30", "--all"], true);
|
||||
assert!(output.2.contains("was already linked"),
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[],
|
||||
&["--amount", "30", "--all"], true);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
// Retract the link.
|
||||
let output = sq_retract(&sq, &alice_fpr, &[]);
|
||||
assert!(output.2.contains("was linked at"),
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_retract(&sq, &alice_fpr, &[]);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
@ -505,31 +505,31 @@ fn sq_pki_link_update_detection() -> Result<()> {
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[],
|
||||
&["--depth", "10", "--amount", "10", "--all"], true);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
// Use a notation.
|
||||
let output = sq_link(&sq, &alice_fpr, &[],
|
||||
&["--notation", "foo", "10", "--all"], true);
|
||||
assert!(output.2.contains("was already linked"),
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[],
|
||||
&["--notation", "foo", "10", "--all"], true);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
// The default link again.
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &["--all"], true);
|
||||
assert!(output.2.contains("was already linked"),
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &["--all"], true);
|
||||
assert!(output.2.contains("Link parameters are unchanged, no update needed"),
|
||||
assert!(output.2.contains("Certification parameters are unchanged"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, true);
|
||||
|
||||
@ -577,7 +577,7 @@ fn sq_pki_link_add_temporary() -> Result<()> {
|
||||
sq_verify(&sq, None, &[], &[], &alice_sig_file, 0, 1);
|
||||
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &["--temporary", "--all"], true);
|
||||
assert!(output.2.contains("Linking "),
|
||||
assert!(output.2.contains("Certifying "),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
||||
@ -598,7 +598,7 @@ fn sq_pki_link_add_temporary() -> Result<()> {
|
||||
// Now mark it as fully trusted. It should be trusted now, in 6
|
||||
// days and in 8 days.
|
||||
let output = sq_link(&sq, &alice_fpr, &[], &["--all"], true);
|
||||
assert!(output.2.contains("was already linked"),
|
||||
assert!(output.2.contains("was previously"),
|
||||
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
|
||||
eprintln!("{:?}", output);
|
||||
let bytes = compare(bytes, &alice_cert_pgp, false);
|
||||
|
Loading…
Reference in New Issue
Block a user