use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{Context, Result};
use openpgp_keylist::{Key, Keylist, Metadata};
use crate::pgp;
use crate::Oca;
const KEYLIST_FILE: &str = "keylist.json";
pub fn print_certring(oca: &Oca, email_filter: Option<String>) -> Result<()> {
let certs = match &email_filter {
Some(email) => oca.certs_by_email(email)?,
None => oca.user_certs_get_all()?,
};
let mut c = Vec::new();
if email_filter.is_none() {
c.push(oca.ca_get_cert_pub()?);
}
for cert in certs {
c.push(pgp::to_cert(cert.pub_cert.as_bytes())?);
}
println!("{}", pgp::certs_to_armored(&c)?);
Ok(())
}
pub fn export_certs_as_files(oca: &Oca, email_filter: Option<String>, path: &str) -> Result<()> {
if email_filter.is_none() {
let ca_cert = oca.ca_get_cert_pub()?;
std::fs::write(
path_append(path, &format!("{}.asc", oca.get_ca_email()?))?,
pgp::certs_to_armored(&[ca_cert])?,
)?;
}
let emails = if let Some(email) = email_filter {
vec![email]
} else {
oca.get_emails_all()?
.iter()
.map(|ce| ce.addr.clone())
.collect()
};
for email in &emails {
let certs = oca
.certs_by_email(email)
.context(format!("Failed to load certs for email '{email}'"))?;
if !certs.is_empty() {
let mut c: Vec<_> = vec![];
for cert in certs {
c.push(pgp::to_cert(cert.pub_cert.as_bytes())?);
}
std::fs::write(
path_append(path, &format!("{email}.asc"))?,
pgp::certs_to_armored(&c)?,
)?;
}
}
Ok(())
}
fn open_file(name: PathBuf, overwrite: bool) -> std::io::Result<File> {
if overwrite {
File::create(name)
} else {
OpenOptions::new().write(true).create_new(true).open(name)
}
}
fn path_append(path: &str, filename: &str) -> Result<PathBuf> {
if filename.chars().any(std::path::is_separator) || filename.chars().any(|c| c == ':') {
Err(anyhow::anyhow!(
"Filename contains special character. May be a path traversal \
attack? {}",
filename
))
} else {
let mut pb = PathBuf::from_str(path)?;
pb.push(filename);
Ok(pb)
}
}
pub fn wkd_export(oca: &Oca, domain: &str, path: &Path) -> Result<()> {
use sequoia_net::wkd;
let ca_cert = oca.ca_get_cert_pub()?;
wkd::insert(path, domain, None, &ca_cert)?;
for cert in oca.user_certs_get_all()? {
if !cert.delisted {
let c = pgp::to_cert(cert.pub_cert.as_bytes())?;
if pgp::cert_has_uid_in_domain(&c, domain)? {
if let Err(err) = wkd::insert(path, domain, None, &c) {
println!("WARN: skipped cert {} ({})", c.fingerprint(), err);
}
}
}
}
Ok(())
}
pub fn export_keylist(
oca: &Oca,
path: PathBuf,
signature_uri: String,
overwrite: bool,
) -> Result<()> {
let sigfile_name = match signature_uri.split('/').next_back() {
Some(file) => file,
None => {
return Err(anyhow::anyhow!("Unexpected signature_uri format"));
}
};
let mut ukl = Keylist {
metadata: Metadata {
signature_uri: signature_uri.clone(),
keyserver: None,
comment: Some("Exported from OpenPGP CA".to_string()),
},
keys: vec![],
};
let fingerprint = oca.ca_get_cert_pub()?.fingerprint().to_hex();
ukl.keys.push(Key {
fingerprint,
name: Some(format!("OpenPGP CA at {}", oca.domainname())),
email: Some(oca.get_ca_email()?),
comment: None,
keyserver: None,
});
for user in &oca.users_get_all()? {
for cert in oca.get_certs_by_user(user)? {
for uid in oca.cert_check_ca_sig(&cert)?.certified {
if let Ok(Some(email)) = uid.email2() {
ukl.keys.push(Key {
fingerprint: cert.fingerprint.clone(),
name: user.name.clone(),
email: Some(email.to_string()),
comment: None,
keyserver: None,
});
}
}
}
}
let signer = Box::new(|text: &str| oca.secret().sign_detached(text.as_bytes()));
let skl = ukl.sign(signer)?;
let mut keylist = path.clone();
keylist.push(KEYLIST_FILE);
open_file(keylist, overwrite)?.write_all(skl.keylist.as_bytes())?;
let mut sigfile = path;
sigfile.push(sigfile_name);
open_file(sigfile, overwrite)?.write_all(skl.sig.as_bytes())?;
Ok(())
}