#![allow(non_local_definitions)]
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
mod backend;
mod bridge;
mod cert;
pub mod db;
mod export;
pub mod pgp;
mod revocation;
mod secret;
mod storage;
pub mod types;
mod update;
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::str::FromStr;
use std::time::SystemTime;
use anyhow::{Context, Result};
use chrono::offset::Utc;
use chrono::DateTime;
use openpgp_card::algorithm::AlgoSimple;
use openpgp_card_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, Card};
use sequoia_openpgp::packet::{Signature, UserID};
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::Cert;
use crate::backend::card::{check_card_empty, CardBackend};
use crate::backend::softkey::SoftkeyBackend;
use crate::backend::split::SplitCa;
use crate::backend::{card, split, Backend};
use crate::db::models;
use crate::db::models::NewCacert;
use crate::db::OcaDb;
use crate::pgp::CipherSuite;
use crate::secret::{CaSec, CaSecCB};
use crate::storage::{CaStorageRW, DbCa, UninitDb};
use crate::types::CertificationStatus;
pub fn blank_cards() -> Result<Vec<String>> {
let mut idents = vec![];
for backend in PcscBackend::cards(None)? {
let mut card: Card<Open> = backend.into();
let transaction = card.transaction()?;
if check_card_empty(&transaction)? {
idents.push(transaction.application_identifier()?.ident());
}
}
Ok(idents)
}
pub fn matching_cards(ca_cert: &[u8]) -> Result<Vec<String>> {
let ca_cert = Cert::from_bytes(ca_cert).context("Cert::from_bytes failed")?;
let mut idents = vec![];
for backend in PcscBackend::cards(None)? {
let mut card: Card<Open> = backend.into();
let mut transaction = card.transaction()?;
if card::card_matches(&mut transaction, &ca_cert).is_ok() {
idents.push(transaction.application_identifier()?.ident());
}
}
Ok(idents)
}
pub struct Uninit {
storage: UninitDb,
}
pub struct Oca {
storage: Box<dyn CaStorageRW>,
secret: Box<dyn CaSec>,
backend: Backend,
domainname: String,
}
impl Uninit {
pub fn new(db_url: Option<&str>) -> Result<Self> {
let db_url = if let Some(url) = db_url {
url.to_owned()
} else if let Ok(database) = env::var("OPENPGP_CA_DB") {
database
} else {
return Err(anyhow::anyhow!("ERROR: no database configuration found"));
};
let db = Rc::new(OcaDb::new(&db_url)?);
db.diesel_migrations_run();
let storage = UninitDb::new(db);
Ok(Self { storage })
}
fn check_domainname(domainname: &str) -> Result<()> {
use addr::parser::DomainName;
use addr::psl::List;
if List.parse_domain_name(domainname).is_err() {
return Err(anyhow::anyhow!("Invalid domainname: '{}'", domainname));
}
Ok(())
}
pub fn init_softkey(
self,
domainname: &str,
name: Option<&str>,
cipher_suite: Option<CipherSuite>,
) -> Result<Oca> {
Self::check_domainname(domainname)?;
let (cert, _) = pgp::make_ca_cert(domainname, name, cipher_suite)?;
self.storage
.transaction(|| self.storage.ca_init_softkey(domainname, &cert))?;
self.init_from_db_state()
}
pub fn init_card_generate_on_card(
self,
ident: &str,
domain: &str,
name: Option<&str>,
algo: Option<AlgoSimple>,
) -> Result<Oca> {
if self.storage.is_ca_initialized()? {
return Err(anyhow::anyhow!("CA database is already initialized"));
}
let email = format!("openpgp-ca@{domain}");
let uid = pgp::ca_user_id(&email, name);
let uid = String::from_utf8_lossy(uid.value()).to_string();
let (ca_cert, user_pin) = card::generate_on_card(ident, domain, uid, algo)?;
self.ca_init_card(ident, &user_pin, domain, &ca_cert)
}
pub fn init_card_generate_on_host(
self,
ident: &str,
domain: &str,
name: Option<&str>,
cipher_suite: Option<CipherSuite>,
) -> Result<(Oca, String)> {
if self.storage.is_ca_initialized()? {
return Err(anyhow::anyhow!("CA database is already initialized"));
}
let (ca_key, _) = pgp::make_ca_cert(domain, name, cipher_suite)?;
let user_pin = card::import_to_card(ident, &ca_key)?;
let ca = self.ca_init_card(ident, &user_pin, domain, &ca_key)?;
let key = pgp::cert_to_armored_private_key(&ca_key)?;
Ok((ca, key))
}
pub fn init_card_import_card(
self,
card_ident: &str,
user_pin: &str,
domain: &str,
ca_cert: &[u8],
) -> Result<Oca> {
let ca_cert = Cert::from_bytes(ca_cert).context("Cert::from_bytes failed")?;
card::verify_user_pin(card_ident, user_pin)?;
self.ca_init_card(card_ident, user_pin, domain, &ca_cert)
}
pub fn init_card_import_key(
self,
card_ident: &str,
domain: &str,
ca_key: &[u8],
) -> Result<Oca> {
let ca_key = Cert::from_bytes(ca_key).context("Cert::from_bytes failed")?;
if !ca_key.is_tsk() {
return Err(anyhow::anyhow!(
"No private key material found in file. Can't import to OpenPGP card."
));
}
let user_pin = card::import_to_card(card_ident, &ca_key)?;
self.ca_init_card(card_ident, &user_pin, domain, &ca_key)
}
pub fn migrate_card_import_key(self, card_ident: &str) -> Result<Oca> {
self.storage.transaction(|| {
let ca_key = self.storage.ca_get_cert_private()?;
if !ca_key.is_tsk() {
return Err(anyhow::anyhow!(
"No private key material in CA database. Can't migrate to OpenPGP card."
));
}
let user_pin = card::import_to_card(card_ident, &ca_key)?;
let ca_pub = pgp::cert_to_armored(&ca_key.strip_secret_key_material())?;
CardBackend::ca_replace_in_place(&self.storage, card_ident, &user_pin, &ca_pub)?;
Ok(())
})?;
self.storage.vacuum()?;
self.init_from_db_state()
}
fn ca_init_card(
self,
card_ident: &str,
pin: &str,
domainname: &str,
ca_cert: &Cert,
) -> Result<Oca> {
Self::check_domainname(domainname)?;
self.storage.transaction(|| {
if self.storage.is_ca_initialized()? {
return Err(anyhow::anyhow!("CA database is already initialized"));
}
let pubkey = card::check_if_card_matches(card_ident, ca_cert)?;
CardBackend::ca_init(
&self.storage,
domainname,
card_ident,
pin,
&pubkey,
&ca_cert.fingerprint().to_hex(),
)
})?;
self.init_from_db_state()
}
fn init_from_db_state(self) -> Result<Oca> {
let (ca, cacert) = self.storage.ca_cert()?;
let backend = Backend::from_config(cacert.backend.as_deref())?;
let domainname = ca.domainname;
match &backend {
Backend::Softkey => {
let softkey = SoftkeyBackend::new(self.storage.ca_get_cert_private()?);
let ca_cert_pub = self.storage.ca_get_cert_pub()?;
let ca_sec = CaSecCB::new(Rc::new(softkey), ca_cert_pub);
let storage = Box::new(DbCa::new(self.storage.db()));
Ok(Oca {
storage,
secret: Box::new(ca_sec),
backend,
domainname,
})
}
Backend::Card(card) => {
let card_ca = CardBackend::new(&card.ident, &card.user_pin)?;
let ca_cert = self.storage.ca_get_cert_pub()?;
let ca_sec = CaSecCB::new(Rc::new(card_ca), ca_cert);
let storage = Box::new(DbCa::new(self.storage.db()));
Ok(Oca {
storage,
secret: Box::new(ca_sec),
backend,
domainname,
})
}
Backend::SplitFront => {
let oca_db = self.storage.db();
let storage = Box::new(DbCa::new(oca_db.clone()));
let secret = Box::new(SplitCa::new(oca_db)?);
Ok(Oca {
storage,
secret,
backend,
domainname,
})
}
Backend::SplitBack(inner) => {
let secret: Box<dyn CaSec> = match &**inner {
Backend::Softkey => {
let softkey = SoftkeyBackend::new(self.storage.ca_get_cert_private()?);
let ca_cert_pub = self.storage.ca_get_cert_pub()?;
Box::new(CaSecCB::new(Rc::new(softkey), ca_cert_pub))
}
Backend::Card(card) => {
let card_ca = CardBackend::new(&card.ident, &card.user_pin)?;
let ca_cert = self.storage.ca_get_cert_pub()?;
Box::new(CaSecCB::new(Rc::new(card_ca), ca_cert))
}
_ => return Err(anyhow::anyhow!("Illegal inner backend: {}", inner)),
};
let db = match env::var("OPENPGP_CA_FRONT_DB") {
Ok(readonly) => {
println!("Using {readonly} as r/o online datasource");
let ocadb = OcaDb::new(&readonly)?;
split::SplitBackDb::new(Some(Rc::new(ocadb)))
}
Err(_e) => split::SplitBackDb::new(None),
};
let storage = Box::new(db);
Ok(Oca {
storage,
secret,
backend,
domainname,
})
}
}
}
}
impl Oca {
pub fn open(db_url: Option<&str>) -> Result<Self> {
let cau = Uninit::new(db_url)?;
cau.init_from_db_state()
}
pub fn domainname(&self) -> &str {
&self.domainname
}
pub(crate) fn backend(&self) -> &Backend {
&self.backend
}
pub(crate) fn secret(&self) -> &dyn CaSec {
&*self.secret
}
pub fn set_card_backend(self, card_ident: &str, user_pin: &str) -> Result<()> {
let cacert = self.storage.cacert()?;
let b = Backend::from_config(cacert.backend.as_deref())?;
match b {
Backend::Card(_c) => {
card::verify_user_pin(card_ident, user_pin)?;
let ca_cert = self.ca_get_cert_pub()?;
let _pubkey = card::check_if_card_matches(card_ident, &ca_cert)?;
let ca_pub = pgp::cert_to_armored(&ca_cert)?;
let db = self.storage.into_uninit();
CardBackend::ca_replace_in_place(&db, card_ident, user_pin, &ca_pub)?;
Ok(())
}
Backend::Softkey => Err(anyhow::anyhow!(
"Setting card backend from softkey is not supported."
)),
Backend::SplitFront | Backend::SplitBack(_) => Err(anyhow::anyhow!(
"Setting card backend from split mode is not supported."
)),
}
}
pub fn ca_generate_revocations(&self, output: PathBuf) -> Result<()> {
self.secret.ca_generate_revocations(output)
}
pub fn ca_import_tsig(&self, cert: &[u8]) -> Result<()> {
self.storage.ca_import_tsig(cert)
}
pub fn ca_get_cert_pub(&self) -> Result<Cert> {
match self.backend {
Backend::SplitBack(_) => {
if let Ok(ca_cert) = self.storage.ca_get_cert_pub() {
Ok(ca_cert)
} else {
self.secret.cert()
}
}
_ => self.storage.ca_get_cert_pub(),
}
}
pub fn ca_get_pubkey_armored(&self) -> Result<String> {
let cert = self.ca_get_cert_pub()?;
let ca_pub =
pgp::cert_to_armored(&cert).context("Failed to transform CA key to armored pubkey")?;
Ok(ca_pub)
}
pub(crate) fn get_ca_userid(&self) -> Result<UserID> {
let cert = self.ca_get_cert_pub()?;
let uids: Vec<_> = cert.userids().collect();
if uids.len() != 1 {
return Err(anyhow::anyhow!("ERROR: CA has != 1 user_id"));
}
Ok(uids[0].userid().clone())
}
pub fn get_ca_email(&self) -> Result<String> {
let uid = self.get_ca_userid()?;
let email = uid.email2()?;
if let Some(email) = email {
Ok(email.to_string())
} else {
Err(anyhow::anyhow!("CA user_id has no email"))
}
}
pub fn ca_show(&self) -> Result<()> {
let cert = self.secret().cert()?;
let created = cert.primary_key().key().creation_time();
let created: DateTime<Utc> = created.into();
println!(" CA Domain: {}", self.domainname());
println!(" Fingerprint: {}", cert.fingerprint());
println!("Creation time: {}", created.format("%F %T %Z"));
let backend = self.backend();
println!(" CA Backend: {backend}");
Ok(())
}
pub fn ca_print_private(&self) -> Result<()> {
match &self.backend {
Backend::Softkey => {
}
Backend::SplitBack(inner) => match **inner {
Backend::Softkey => {
}
_ => {
return Err(anyhow::anyhow!(
"Operation unsupported for this backend type"
));
}
},
_ => {
return Err(anyhow::anyhow!(
"Operation unsupported for this backend type"
));
}
}
let ca_cert = self
.storage
.cacert()
.context("failed to load CA from database")?;
println!("{}", ca_cert.priv_cert);
Ok(())
}
pub fn ca_re_certify(&self, ca_cert_old: &[u8], validity_days: u64) -> Result<()> {
let ca_cert_old = pgp::to_cert(ca_cert_old)?;
cert::certs_re_certify(self, ca_cert_old, validity_days)
}
pub fn ca_split_into(self, front: &Path, back: &Path) -> Result<()> {
match self.backend {
Backend::Softkey | Backend::Card(_) => {
let uninit_orig = self.storage.into_uninit();
let (orig_ca, orig_cacert) = uninit_orig.ca_cert()?;
let cert = Cert::from_str(&orig_cacert.priv_cert)?;
let pub_ca_cert = pgp::cert_to_armored(&cert)?;
let fp = cert.fingerprint().to_hex();
let db = uninit_orig.db();
let db_url = db.url();
for user in db.users_sorted_by_name()? {
if user.ca_id != 1 {
return Err(anyhow::anyhow!(
"Splitting a multi-CA setup is not currently supported"
));
}
}
std::fs::copy(db_url, front)?;
if let Some(url) = front.to_str() {
let front = OcaDb::new(url)?;
front.cacerts_delete()?;
let backend = Backend::SplitFront.to_config();
let new_ca_cert = NewCacert {
active: true,
ca_id: orig_ca.id,
priv_cert: pub_ca_cert,
fingerprint: &fp,
backend: backend.as_deref(),
};
front.cacert_insert(&new_ca_cert)?;
front.vacuum()?;
} else {
return Err(anyhow::anyhow!("Illegal front filename"));
}
let orig_back = Backend::from_config(orig_cacert.backend.as_deref())?;
let backend = Backend::SplitBack(Box::new(orig_back));
if let Some(url) = back.to_str() {
let back = Uninit::new(Some(url))?;
back.storage.ca_insert(
&orig_ca.domainname,
&orig_cacert.priv_cert,
&fp,
backend.to_config().as_deref(),
)?;
} else {
return Err(anyhow::anyhow!("Illegal back filename"));
}
Ok(())
}
_ => Err(anyhow::anyhow!(
"Splitting operation not supported for this backend type"
)),
}
}
pub fn ca_merge_split(self, back: &Path) -> Result<()> {
match self.backend {
Backend::SplitFront => {
if let Some(url) = back.to_str() {
let back = OcaDb::new(url)?;
let (_back_ca, back_cacert) = back.get_ca()?;
let orig_back = Backend::from_config(back_cacert.backend.as_deref())?;
if let Backend::SplitBack(inner) = orig_back {
let mut front_cacert = self.storage.cacert()?;
if front_cacert.fingerprint != back_cacert.fingerprint {
return Err(anyhow::anyhow!(
"Front {} and back {} instance use different CA fingerprints",
front_cacert.fingerprint,
back_cacert.fingerprint
));
}
let back_cert = pgp::to_cert(back_cacert.priv_cert.as_bytes())?;
let front_cert = pgp::to_cert(front_cacert.priv_cert.as_bytes())?;
let ca_merged = back_cert.merge_public(front_cert)?;
front_cacert.priv_cert = pgp::cert_to_armored_private_key(&ca_merged)?;
front_cacert.backend = inner.to_config();
let db = self.storage;
db.cacert_update(&front_cacert)?;
}
Ok(())
} else {
Err(anyhow::anyhow!(
"Failed to use back instance path ({:?})",
back
))
}
}
_ => Err(anyhow::anyhow!(
"Merge operation not supported for this backend type"
)),
}
}
pub fn ca_split_export(&self, file: PathBuf) -> Result<()> {
match self.backend {
Backend::SplitFront => {
let cacert = self.storage.cacert()?;
let queue = self.storage.queue_not_done()?;
SplitCa::export_csr_queue(file, queue, &cacert.fingerprint)?;
Ok(())
}
_ => Err(anyhow::anyhow!(
"Operation is only supported on split mode front instances."
)),
}
}
pub fn ca_split_certify(&self, import: PathBuf, export: PathBuf, batch: bool) -> Result<()> {
match self.backend {
Backend::SplitBack(_) => split::certify(&*self.secret, import, export, batch),
_ => Err(anyhow::anyhow!(
"Operation is only supported on split mode back instances."
)),
}
}
pub fn ca_split_import(&self, file: PathBuf) -> Result<()> {
match self.backend {
Backend::SplitFront => split::ca_split_import(&*self.storage, file),
_ => Err(anyhow::anyhow!(
"Operation is only supported on split mode front instances."
)),
}
}
pub fn ca_split_show_queue(&self) -> Result<()> {
match self.backend {
Backend::SplitFront => split::ca_split_show_queue(&*self.storage),
_ => Err(anyhow::anyhow!(
"Operation is only supported on split mode front instances."
)),
}
}
pub fn user_certs_get_all(&self) -> Result<Vec<models::Cert>> {
let users = self.storage.users_sorted_by_name()?;
let mut user_certs = Vec::new();
for user in users {
user_certs.append(&mut self.get_certs_by_user(&user)?);
}
Ok(user_certs)
}
pub fn certs_expired(&self, days: u64) -> Result<HashMap<models::Cert, Option<SystemTime>>> {
cert::certs_expired(self, days)
}
pub fn cert_check_ca_sig(&self, cert: &models::Cert) -> Result<CertificationStatus> {
cert::cert_check_ca_sig(self, cert).context("Failed while checking CA sig")
}
pub fn cert_check_tsig_on_ca(&self, cert: &models::Cert) -> Result<bool> {
cert::cert_check_tsig_on_ca(self, cert).context("Failed while checking tsig on CA")
}
pub fn check_tsig_on_bridge(&self, bridge: &models::Bridge) -> Result<bool> {
let ca = self.ca_get_cert_pub()?;
if let Some(br) = self.storage.cert_by_id(bridge.cert_id)? {
let bridge_cert = pgp::to_cert(br.pub_cert.as_bytes())?;
Ok(cert::check_tsig_on_cert(&ca, &bridge_cert)?)
} else {
Err(anyhow::anyhow!(
"No public key found for bridge to '{}'",
bridge.email
))
}
}
pub fn certs_refresh_ca_certifications(
&self,
threshold_days: u64,
validity_days: u64,
) -> Result<()> {
cert::certs_refresh_ca_certifications(self, threshold_days, validity_days)
}
#[allow(clippy::too_many_arguments)]
pub fn user_new(
&self,
name: Option<&str>,
emails: &[&str],
duration_days: Option<u64>,
password: bool,
password_file: Option<String>,
output_format_minimal: bool,
cipher_suite: Option<CipherSuite>,
enable_encryption_subkey: bool,
enable_signing_subkey: bool,
enable_authentication_subkey: bool,
) -> Result<()> {
cert::user_new(
self,
name,
emails,
duration_days,
password,
password_file,
output_format_minimal,
cipher_suite,
enable_encryption_subkey,
enable_signing_subkey,
enable_authentication_subkey,
)
}
pub fn cert_import_new(
&self,
cert: &[u8],
revoc_certs: &[&[u8]],
name: Option<&str>,
emails: &[&str],
duration_days: Option<u64>,
) -> Result<()> {
cert::cert_import_new(self, cert, revoc_certs, name, emails, duration_days)
}
pub fn cert_import_update(&self, cert: &[u8]) -> Result<()> {
cert::cert_import_update(self, cert)
}
pub fn cert_delist(&self, fp: &str) -> Result<()> {
self.storage.cert_delist(fp)
}
pub fn cert_deactivate(&self, fp: &str) -> Result<()> {
self.storage.cert_deactivate(fp)
}
pub fn cert_get_by_fingerprint(&self, fingerprint: &str) -> Result<Option<models::Cert>> {
let fp = pgp::normalize_fp(fingerprint)?;
self.storage.cert_by_fp(&fp)
}
pub fn get_certs_by_user(&self, user: &models::User) -> Result<Vec<models::Cert>> {
self.storage.certs_by_user(user)
}
pub fn users_get_all(&self) -> Result<Vec<models::User>> {
self.storage.users_sorted_by_name()
}
pub fn certs_by_email(&self, email: &str) -> Result<Vec<models::Cert>> {
self.storage.certs_by_email(email)
}
pub fn cert_get_users(&self, cert: &models::Cert) -> Result<Option<models::User>> {
self.storage.user_by_cert(cert)
}
pub fn cert_get_name(&self, cert: &models::Cert) -> Result<String> {
if let Some(user) = self.cert_get_users(cert)? {
Ok(user.name.unwrap_or_else(|| "<no name>".to_string()))
} else {
Ok("<no name>".to_string())
}
}
pub fn print_certifications_status(&self) -> Result<()> {
let mut count_ok = 0;
let db_users = self.users_get_all()?;
for db_user in &db_users {
for db_cert in self.get_certs_by_user(db_user)? {
let sigs_by_ca = self.cert_check_ca_sig(&db_cert)?;
let tsig_on_ca = self.cert_check_tsig_on_ca(&db_cert)?;
let sig_by_ca = !sigs_by_ca.certified.is_empty();
if sig_by_ca && tsig_on_ca {
count_ok += 1;
} else {
println!(
"No mutual certification for {}{}:",
db_cert.fingerprint,
db_user
.name
.as_deref()
.map(|s| format!(" ({s})"))
.unwrap_or_else(|| "".to_string()),
);
if !sig_by_ca {
println!(" No CA certification on any User ID");
}
if !tsig_on_ca {
println!(" Has not tsigned CA key.");
};
println!();
}
}
}
println!(
"Checked {} user keys, {} of them have mutual certifications.",
db_users.len(),
count_ok
);
Ok(())
}
pub fn print_expiry_status(&self, exp_days: u64) -> Result<()> {
let expiries = self.certs_expired(exp_days)?;
if expiries.is_empty() {
println!("No certificates will expire in the next {exp_days} days.");
} else {
println!(
"The following {} certificate{} will expire in the next {} days.",
expiries.len(),
if expiries.len() == 1 { "" } else { "s" },
exp_days
);
println!();
}
for (db_cert, expiry) in expiries {
let name = self.cert_get_name(&db_cert)?;
println!("name {}, fingerprint {}", name, db_cert.fingerprint);
if let Some(exp) = expiry {
let datetime: DateTime<Utc> = exp.into();
println!(" expires: {}", datetime.format("%d/%m/%Y"));
} else {
println!(" no expiration date is set for this user key");
}
println!();
}
Ok(())
}
pub fn print_users(&self) -> Result<()> {
for db_user in self.users_get_all()? {
for db_cert in self.get_certs_by_user(&db_user)? {
let sig_by_ca = self.cert_check_ca_sig(&db_cert)?;
let tsig_on_ca = self.cert_check_tsig_on_ca(&db_cert)?;
println!("OpenPGP certificate {}", db_cert.fingerprint);
if let Some(name) = &db_user.name {
println!(" User '{name}'");
}
if !sig_by_ca.certified.is_empty() {
println!(" Identities certified by this CA:");
for uid in sig_by_ca.certified {
println!(" - '{uid}'");
}
}
if tsig_on_ca {
println!(" Has trust-signed this CA");
}
let c = pgp::to_cert(db_cert.pub_cert.as_bytes())?;
match pgp::get_expiry(&c) {
Ok(Some(exp)) => {
let datetime: DateTime<Utc> = exp.into();
println!(" Expiration {}", datetime.format("%d/%m/%Y"));
}
Ok(None) => println!(" No expiration is set"),
Err(e) => println!(" Expiration unknown ({e})"),
}
let revs = self.revocations_get(&db_cert)?;
if !revs.is_empty() {
println!(" {} revocations available", revs.len());
}
if pgp::is_possibly_revoked(&c) {
println!(" This certificate has (possibly) been REVOKED");
}
println!();
}
}
Ok(())
}
pub fn revocations_get(&self, cert: &models::Cert) -> Result<Vec<models::Revocation>> {
self.storage.revocations_by_cert(cert)
}
pub fn revocation_add(&self, revoc_cert: &[u8]) -> Result<()> {
self.storage.revocation_add(revoc_cert)
}
pub fn revocation_add_from_file(&self, filename: &Path) -> Result<()> {
let rev = std::fs::read(filename)?;
self.revocation_add(&rev)
}
pub fn revocation_get_by_hash(&self, hash: &str) -> Result<models::Revocation> {
if let Some(rev) = self.storage.revocation_by_hash(hash)? {
Ok(rev)
} else {
Err(anyhow::anyhow!("No revocation found for {}", hash))
}
}
pub fn revocation_apply(&self, revoc: models::Revocation) -> Result<()> {
self.storage.revocation_apply(revoc)
}
pub fn revocation_details(
revocation: &models::Revocation,
) -> Result<(String, Option<SystemTime>)> {
let rev = pgp::to_signature(revocation.revocation.as_bytes())?;
let creation = rev.signature_creation_time();
if let Some((code, reason)) = rev.reason_for_revocation() {
let reason = String::from_utf8(reason.to_vec())?;
Ok((format!("{code} ({reason})"), creation))
} else {
Ok(("Revocation reason unknown".to_string(), creation))
}
}
pub fn revoc_to_armored(sig: &Signature) -> Result<String> {
pgp::revoc_to_armored(sig, None)
}
pub fn print_revocations(&self, email: &str) -> Result<()> {
let certs = self.certs_by_email(email)?;
if certs.is_empty() {
println!("No OpenPGP keys found");
} else {
for cert in certs {
let name = self.cert_get_name(&cert)?;
println!(
"Revocations for OpenPGP key {}, user \"{}\"",
cert.fingerprint, name
);
let revoc = self.revocations_get(&cert)?;
for r in revoc {
let (reason, time) = Self::revocation_details(&r)?;
let time = if let Some(time) = time {
let datetime: DateTime<Utc> = time.into();
format!("{}", datetime.format("%d/%m/%Y"))
} else {
"".to_string()
};
println!(" - revocation id {}: {} ({})", r.hash, reason, time);
if r.published {
println!(" this revocation has been APPLIED");
}
println!();
}
}
}
Ok(())
}
pub fn emails_get(&self, cert: &models::Cert) -> Result<Vec<models::CertEmail>> {
self.storage.emails_by_cert(cert)
}
pub fn get_emails_all(&self) -> Result<Vec<models::CertEmail>> {
self.storage.emails()
}
pub fn bridges_get(&self) -> Result<Vec<models::Bridge>> {
self.storage.list_bridges()
}
pub fn bridges_search(&self, email: &str) -> Result<models::Bridge> {
if let Some(bridge) = self.storage.bridge_by_email(email)? {
Ok(bridge)
} else {
Err(anyhow::anyhow!("Bridge not found"))
}
}
pub fn bridge_get_cert(&self, bridge: &models::Bridge) -> Result<models::Cert> {
if let Some(cert) = self.storage.cert_by_id(bridge.cert_id)? {
Ok(cert)
} else {
Err(anyhow::anyhow!("No cert found for bridge {}", bridge.id))
}
}
pub fn add_bridge(
&self,
email: Option<&str>,
key_file: &Path,
scope: Option<&str>,
unscoped: bool,
) -> Result<(String, String)> {
let (bridge, fingerprint) = bridge::bridge_new(self, key_file, email, scope, unscoped)?;
Ok((bridge.email, fingerprint.to_string()))
}
pub fn bridge_revoke(&self, email: &str) -> Result<()> {
bridge::bridge_revoke(self, email)
}
pub fn print_bridges(&self, email: Option<String>) -> Result<()> {
let bridges = if let Some(email) = email {
vec![self.bridges_search(&email)?]
} else {
self.bridges_get()?
};
for bridge in bridges {
println!("Bridge to '{}'", bridge.email);
if let Some(db_cert) = self.storage.cert_by_id(bridge.cert_id)? {
println!("{}", db_cert.pub_cert);
}
println!();
}
Ok(())
}
pub fn list_bridges(&self) -> Result<()> {
for bridge in self.bridges_get()? {
let tsigned = self.check_tsig_on_bridge(&bridge)?;
println!(
"Bridge to '{}'{}, (scope: '{}')",
bridge.email,
if !tsigned {
" [no trust signature]"
} else {
""
},
bridge.scope,
)
}
Ok(())
}
pub fn export_wkd(&self, domain: &str, path: &Path) -> Result<()> {
export::wkd_export(self, domain, path)
}
pub fn export_keylist(&self, path: PathBuf, signature_uri: String, force: bool) -> Result<()> {
export::export_keylist(self, path, signature_uri, force)
}
pub fn export_certs_as_files(&self, email_filter: Option<String>, path: &str) -> Result<()> {
export::export_certs_as_files(self, email_filter, path)
}
pub fn print_certring(&self, email_filter: Option<String>) -> Result<()> {
export::print_certring(self, email_filter)
}
pub fn update_from_wkd(&self) -> Result<()> {
for c in self.user_certs_get_all()? {
match update::update_from_wkd(self, &c) {
Ok(true) => {
println!("Got update for cert {}", c.fingerprint);
}
Ok(false) => {
println!("No changes for cert {}", c.fingerprint);
}
Err(e) => {
eprintln!("Failed to update cert {}: {}", c.fingerprint, e);
}
}
}
Ok(())
}
pub fn update_from_keyserver(&self) -> Result<()> {
for c in self.user_certs_get_all()? {
match update::update_from_hagrid(self, &c) {
Ok(true) => {
println!("Got update for cert {}", c.fingerprint);
}
Ok(false) => {
println!("No changes for cert {}", c.fingerprint);
}
Err(e) => {
eprintln!("Failed to update cert {}: {}", c.fingerprint, e);
}
}
}
Ok(())
}
}