use std::cell::Ref;
use std::cell::RefCell;
use sequoia_openpgp as openpgp;
use openpgp::Cert;
use openpgp::KeyHandle;
use openpgp::Packet;
use openpgp::packet::Signature;
use openpgp::packet::Tag;
use openpgp::packet::UserID;
use openpgp::types::KeyFlags;
use openpgp::types::SignatureType;
use sequoia_cert_store as cert_store;
use cert_store::Store;
use crate::Result;
use crate::Sq;
use crate::common::key_handle_dealias;
use crate::common::ui;
const TRACE: bool = false;
pub enum CertOrKeyHandle {
Cert(RefCell<Cert>),
KeyHandle(KeyHandle),
}
impl CertOrKeyHandle {
pub fn key_handle(&self) -> KeyHandle {
match self {
CertOrKeyHandle::Cert(cert) => cert.borrow().key_handle(),
CertOrKeyHandle::KeyHandle(kh) => kh.clone(),
}
}
pub fn cert(&self) -> Option<Ref<Cert>> {
match self {
CertOrKeyHandle::Cert(cert) => Some(cert.borrow()),
CertOrKeyHandle::KeyHandle(_) => None,
}
}
}
impl From<RefCell<Cert>> for CertOrKeyHandle {
fn from(cert: RefCell<Cert>) -> CertOrKeyHandle {
CertOrKeyHandle::Cert(cert)
}
}
impl From<&KeyHandle> for CertOrKeyHandle {
fn from(kh: &KeyHandle) -> CertOrKeyHandle {
CertOrKeyHandle::KeyHandle(kh.clone())
}
}
impl From<KeyHandle> for CertOrKeyHandle {
fn from(kh: KeyHandle) -> CertOrKeyHandle {
CertOrKeyHandle::KeyHandle(kh)
}
}
pub fn list_certifications(sq: &Sq,
certifier: Option<RefCell<Cert>>,
target_cert: Option<RefCell<Cert>>)
-> Result<Vec<(CertOrKeyHandle, RefCell<Cert>, UserID, Signature)>>
{
tracer!(TRACE, "list_certifications");
let cert_store = sq.cert_store_or_else()?;
let mut candidates: Vec<(CertOrKeyHandle, RefCell<Cert>, UserID, Signature)>
= Vec::new();
if let Some(target_cert) = target_cert {
let target_cert_fpr = target_cert.borrow().fingerprint();
for ua in target_cert.borrow().userids() {
for certification in ua.certifications() {
let issuers = certification.get_issuers();
if let Some(ref certifier) = certifier {
let certifier_kh = certifier.borrow().key_handle();
if issuers.into_iter().any(|kh| kh.aliases(&certifier_kh)) {
candidates.push(
(RefCell::clone(certifier).into(),
RefCell::clone(&target_cert).into(),
ua.userid().clone(),
certification.clone()));
}
} else {
for kh in key_handle_dealias(&issuers) {
if let Ok(certifiers)
= sq.lookup(std::iter::once(&kh),
Some(KeyFlags::certification()),
false, true)
{
for certifier in certifiers.into_iter() {
t!(" Possible certification of {}, {:?} by {}, {}",
target_cert_fpr,
String::from_utf8_lossy(ua.userid().value()),
certifier.fingerprint(),
sq.best_userid(&certifier, true).display());
candidates.push(
(RefCell::new(certifier).into(),
RefCell::clone(&target_cert),
ua.userid().clone(),
certification.clone()));
}
} else {
t!(" Possible certification by {}, but we can't \
check it as we don't have the certificate",
kh);
candidates.push(
(kh.into(),
RefCell::clone(&target_cert),
ua.userid().clone(),
certification.clone()));
}
}
}
}
}
} else if let Some(certifier) = certifier {
let certifier_fpr = certifier.borrow().fingerprint();
let certifier_kh = KeyHandle::from(&certifier_fpr);
for cert in cert_store.certs() {
t!("Considering {}, {}",
cert.fingerprint(),
cert.userids().next()
.map(|userid| {
String::from_utf8_lossy(userid.value()).to_string()
})
.unwrap_or_else(|| "<no userids>".to_string()));
if cert.userids().next().is_none() {
t!(" Skipping: no user IDs.");
continue;
}
if cert.fingerprint() == certifier_fpr {
t!(" Skipping: is certifier.");
continue;
}
if let Some(cert) = cert.raw_cert() {
let mut possible_certification = false;
for raw_packet in cert.packets() {
let sig = if raw_packet.tag() == Tag::Signature {
match Packet::try_from(raw_packet) {
Ok(packet) => {
if let Packet::Signature(sig) = packet {
sig
} else {
unreachable!();
}
}
Err(err) => {
t!(" Invalid signature: {}.", err);
continue;
}
}
} else {
continue;
};
if ! matches!(
sig.typ(),
SignatureType::GenericCertification
| SignatureType::PersonaCertification
| SignatureType::CasualCertification
| SignatureType::PositiveCertification)
{
continue;
}
let issuers = sig.get_issuers();
if issuers.into_iter().any(|issuer| {
issuer.aliases(
KeyHandle::from(cert.fingerprint()))
})
{
t!(" Have a possible certification");
possible_certification = true;
break;
}
}
if ! possible_certification {
t!(" Skipping: No certifications found.");
continue;
}
}
match cert.to_cert() {
Ok(cert) => {
let cert_cell: RefCell<Cert> = RefCell::new(cert.clone());
let cert = cert_cell.borrow();
for ua in cert.userids() {
for certification in ua.certifications() {
let issuers = certification.get_issuers();
if issuers.into_iter().any(|issuer| {
issuer.aliases(&certifier_kh)
})
{
t!(" Possible certification of {}, {:?}",
cert.fingerprint(),
String::from_utf8_lossy(ua.userid().value()));
candidates.push(
(RefCell::clone(&certifier).into(),
RefCell::clone(&cert_cell).into(),
ua.userid().clone(),
certification.clone()));
}
}
}
}
Err(err) => {
t!("Error parsing {}: {}", cert.fingerprint(), err);
continue;
}
}
}
} else {
return Err(clap::Error::raw(
clap::error::ErrorKind::MissingRequiredArgument,
"Either a certifier (using, e.g., --certifier) \
or a target certificate (using, e.g., --cert) \
must be supplied").into());
}
Ok(candidates)
}
pub fn summarize_certification(o: &mut dyn std::io::Write,
indent: &str,
certification: &Signature,
link: bool)
-> Result<()>
{
let (link, linked) = if link {
("link", "linked")
} else {
("certification", "certified")
};
let indent = &format!("{} - ", indent)[..];
if let Some(t) = certification.signature_creation_time() {
wwriteln!(stream=o, initial_indent=indent,
"created at {}",
chrono::DateTime::<chrono::Utc>::from(t)
.format("%Y‑%m‑%d %H:%M:%S"));
} else {
wwriteln!(stream=o, initial_indent=indent,
"creation time missing");
}
let (depth, amount) = certification.trust_signature()
.unwrap_or((0, sequoia_wot::FULLY_TRUSTED as u8));
if amount == 0 {
wwriteln!(stream=o, initial_indent=indent,
"{} was retracted", link);
} else {
let mut regex: Vec<_> = certification.regular_expressions()
.map(|re| ui::Safe(re).to_string())
.collect();
regex.sort();
regex.dedup();
if depth > 0 {
if amount == sequoia_wot::FULLY_TRUSTED as u8
&& regex.is_empty()
{
wwriteln!(stream=o, initial_indent=indent,
"{} as a fully trusted CA", linked);
} else {
wwriteln!(stream=o, initial_indent=indent,
"{} as a partially trusted CA", linked);
}
}
if let Some(e) = certification.signature_expiration_time() {
wwriteln!(stream=o, initial_indent=indent,
"expiration: {}",
chrono::DateTime::<chrono::Utc>::from(e)
.format("%Y‑%m‑%d"));
}
if depth != 0 && depth != 255 {
wwriteln!(stream=o, initial_indent=indent,
"trust depth: {}", depth);
}
if amount != sequoia_wot::FULLY_TRUSTED as u8 {
wwriteln!(stream=o, initial_indent=indent,
"trust amount: {}", amount);
}
if ! regex.is_empty() {
wwriteln!(stream=o, initial_indent=indent,
"regular expressions: {}", regex.join("; "));
}
}
Ok(())
}