1#![allow(non_local_definitions)]
50
51#[macro_use]
52extern crate diesel;
53
54#[macro_use]
55extern crate diesel_migrations;
56
57pub const VERSION: &str = env!("CARGO_PKG_VERSION");
59
60mod backend;
61mod bridge;
62mod cert;
63pub mod db;
64mod export;
65pub mod pgp;
66mod revocation;
67mod secret;
68mod storage;
69pub mod types;
70mod update;
71
72use std::collections::HashMap;
73use std::env;
74use std::path::{Path, PathBuf};
75use std::rc::Rc;
76use std::str::FromStr;
77use std::time::SystemTime;
78
79use anyhow::{Context, Result};
80use chrono::offset::Utc;
81use chrono::DateTime;
82use openpgp_card::algorithm::AlgoSimple;
83use openpgp_card_pcsc::PcscBackend;
84use openpgp_card_sequoia::{state::Open, Card};
85use sequoia_openpgp::packet::{Signature, UserID};
86use sequoia_openpgp::parse::Parse;
87use sequoia_openpgp::Cert;
88
89use crate::backend::card::{check_card_empty, CardBackend};
90use crate::backend::softkey::SoftkeyBackend;
91use crate::backend::split::SplitCa;
92use crate::backend::{card, split, Backend};
93use crate::db::models;
94use crate::db::models::NewCacert;
95use crate::db::OcaDb;
96use crate::pgp::CipherSuite;
97use crate::secret::{CaSec, CaSecCB};
98use crate::storage::{CaStorageRW, DbCa, UninitDb};
99use crate::types::CertificationStatus;
100
101pub fn blank_cards() -> Result<Vec<String>> {
103 let mut idents = vec![];
104
105 for backend in PcscBackend::cards(None)? {
106 let mut card: Card<Open> = backend.into();
107 let transaction = card.transaction()?;
108
109 if check_card_empty(&transaction)? {
110 idents.push(transaction.application_identifier()?.ident());
111 }
112 }
113
114 Ok(idents)
115}
116
117pub fn matching_cards(ca_cert: &[u8]) -> Result<Vec<String>> {
119 let ca_cert = Cert::from_bytes(ca_cert).context("Cert::from_bytes failed")?;
120
121 let mut idents = vec![];
122
123 for backend in PcscBackend::cards(None)? {
124 let mut card: Card<Open> = backend.into();
125 let mut transaction = card.transaction()?;
126
127 if card::card_matches(&mut transaction, &ca_cert).is_ok() {
128 idents.push(transaction.application_identifier()?.ident());
129 }
130 }
131
132 Ok(idents)
133}
134
135pub struct Uninit {
138 storage: UninitDb,
139}
140
141pub struct Oca {
144 storage: Box<dyn CaStorageRW>,
145 secret: Box<dyn CaSec>,
146
147 backend: Backend,
148 domainname: String,
149}
150
151impl Uninit {
152 pub fn new(db_url: Option<&str>) -> Result<Self> {
160 let db_url = if let Some(url) = db_url {
161 url.to_owned()
162 } else if let Ok(database) = env::var("OPENPGP_CA_DB") {
163 database
164 } else {
165 return Err(anyhow::anyhow!("ERROR: no database configuration found"));
166 };
167
168 let db = Rc::new(OcaDb::new(&db_url)?);
169 db.diesel_migrations_run();
170
171 let storage = UninitDb::new(db);
172
173 Ok(Self { storage })
174 }
175
176 fn check_domainname(domainname: &str) -> Result<()> {
178 use addr::parser::DomainName;
180 use addr::psl::List;
181 if List.parse_domain_name(domainname).is_err() {
182 return Err(anyhow::anyhow!("Invalid domainname: '{}'", domainname));
183 }
184
185 Ok(())
186 }
187
188 pub fn init_softkey(
196 self,
197 domainname: &str,
198 name: Option<&str>,
199 cipher_suite: Option<CipherSuite>,
200 ) -> Result<Oca> {
201 Self::check_domainname(domainname)?;
202 let (cert, _) = pgp::make_ca_cert(domainname, name, cipher_suite)?;
203
204 self.storage
205 .transaction(|| self.storage.ca_init_softkey(domainname, &cert))?;
206
207 self.init_from_db_state()
208 }
209
210 pub fn init_card_generate_on_card(
220 self,
221 ident: &str,
222 domain: &str,
223 name: Option<&str>,
224 algo: Option<AlgoSimple>,
225 ) -> Result<Oca> {
226 if self.storage.is_ca_initialized()? {
228 return Err(anyhow::anyhow!("CA database is already initialized"));
229 }
230
231 let email = format!("openpgp-ca@{domain}");
232 let uid = pgp::ca_user_id(&email, name);
233 let uid = String::from_utf8_lossy(uid.value()).to_string();
234
235 let (ca_cert, user_pin) = card::generate_on_card(ident, domain, uid, algo)?;
238
239 self.ca_init_card(ident, &user_pin, domain, &ca_cert)
240 }
241
242 pub fn init_card_generate_on_host(
243 self,
244 ident: &str,
245 domain: &str,
246 name: Option<&str>,
247 cipher_suite: Option<CipherSuite>,
248 ) -> Result<(Oca, String)> {
249 if self.storage.is_ca_initialized()? {
251 return Err(anyhow::anyhow!("CA database is already initialized"));
252 }
253
254 let (ca_key, _) = pgp::make_ca_cert(domain, name, cipher_suite)?;
256
257 let user_pin = card::import_to_card(ident, &ca_key)?;
259
260 let ca = self.ca_init_card(ident, &user_pin, domain, &ca_key)?;
262
263 let key = pgp::cert_to_armored_private_key(&ca_key)?;
265
266 Ok((ca, key))
267 }
268
269 pub fn init_card_import_card(
271 self,
272 card_ident: &str,
273 user_pin: &str,
274 domain: &str,
275 ca_cert: &[u8],
276 ) -> Result<Oca> {
277 let ca_cert = Cert::from_bytes(ca_cert).context("Cert::from_bytes failed")?;
278
279 card::verify_user_pin(card_ident, user_pin)?;
281
282 self.ca_init_card(card_ident, user_pin, domain, &ca_cert)
287 }
288
289 pub fn init_card_import_key(
291 self,
292 card_ident: &str,
293 domain: &str,
294 ca_key: &[u8],
295 ) -> Result<Oca> {
296 let ca_key = Cert::from_bytes(ca_key).context("Cert::from_bytes failed")?;
297 if !ca_key.is_tsk() {
298 return Err(anyhow::anyhow!(
299 "No private key material found in file. Can't import to OpenPGP card."
300 ));
301 }
302
303 let user_pin = card::import_to_card(card_ident, &ca_key)?;
307
308 self.ca_init_card(card_ident, &user_pin, domain, &ca_key)
310 }
311
312 pub fn migrate_card_import_key(self, card_ident: &str) -> Result<Oca> {
326 self.storage.transaction(|| {
327 let ca_key = self.storage.ca_get_cert_private()?;
328 if !ca_key.is_tsk() {
329 return Err(anyhow::anyhow!(
330 "No private key material in CA database. Can't migrate to OpenPGP card."
331 ));
332 }
333
334 let user_pin = card::import_to_card(card_ident, &ca_key)?;
336
337 let ca_pub = pgp::cert_to_armored(&ca_key.strip_secret_key_material())?;
339 CardBackend::ca_replace_in_place(&self.storage, card_ident, &user_pin, &ca_pub)?;
340
341 Ok(())
342 })?;
343
344 self.storage.vacuum()?;
347
348 self.init_from_db_state()
349 }
350
351 fn ca_init_card(
353 self,
354 card_ident: &str,
355 pin: &str,
356 domainname: &str,
357 ca_cert: &Cert,
358 ) -> Result<Oca> {
359 Self::check_domainname(domainname)?;
360
361 self.storage.transaction(|| {
362 if self.storage.is_ca_initialized()? {
364 return Err(anyhow::anyhow!("CA database is already initialized"));
365 }
366
367 let pubkey = card::check_if_card_matches(card_ident, ca_cert)?;
368
369 CardBackend::ca_init(
370 &self.storage,
371 domainname,
372 card_ident,
373 pin,
374 &pubkey,
375 &ca_cert.fingerprint().to_hex(),
376 )
377 })?;
378
379 self.init_from_db_state()
380 }
381
382 fn init_from_db_state(self) -> Result<Oca> {
384 let (ca, cacert) = self.storage.ca_cert()?;
386
387 let backend = Backend::from_config(cacert.backend.as_deref())?;
388 let domainname = ca.domainname;
389
390 match &backend {
391 Backend::Softkey => {
392 let softkey = SoftkeyBackend::new(self.storage.ca_get_cert_private()?);
393
394 let ca_cert_pub = self.storage.ca_get_cert_pub()?;
395 let ca_sec = CaSecCB::new(Rc::new(softkey), ca_cert_pub);
396
397 let storage = Box::new(DbCa::new(self.storage.db()));
398
399 Ok(Oca {
400 storage,
401 secret: Box::new(ca_sec),
402 backend,
403 domainname,
404 })
405 }
406 Backend::Card(card) => {
407 let card_ca = CardBackend::new(&card.ident, &card.user_pin)?;
408
409 let ca_cert = self.storage.ca_get_cert_pub()?;
410 let ca_sec = CaSecCB::new(Rc::new(card_ca), ca_cert);
411
412 let storage = Box::new(DbCa::new(self.storage.db()));
413
414 Ok(Oca {
415 storage,
416 secret: Box::new(ca_sec),
417 backend,
418 domainname,
419 })
420 }
421 Backend::SplitFront => {
422 let oca_db = self.storage.db();
423
424 let storage = Box::new(DbCa::new(oca_db.clone()));
425 let secret = Box::new(SplitCa::new(oca_db)?);
426
427 Ok(Oca {
428 storage,
429 secret,
430 backend,
431 domainname,
432 })
433 }
434 Backend::SplitBack(inner) => {
435 let secret: Box<dyn CaSec> = match &**inner {
436 Backend::Softkey => {
437 let softkey = SoftkeyBackend::new(self.storage.ca_get_cert_private()?);
438 let ca_cert_pub = self.storage.ca_get_cert_pub()?;
439 Box::new(CaSecCB::new(Rc::new(softkey), ca_cert_pub))
440 }
441 Backend::Card(card) => {
442 let card_ca = CardBackend::new(&card.ident, &card.user_pin)?;
443
444 let ca_cert = self.storage.ca_get_cert_pub()?;
445 Box::new(CaSecCB::new(Rc::new(card_ca), ca_cert))
446 }
447
448 _ => return Err(anyhow::anyhow!("Illegal inner backend: {}", inner)),
449 };
450
451 let db = match env::var("OPENPGP_CA_FRONT_DB") {
452 Ok(readonly) => {
453 println!("Using {readonly} as r/o online datasource");
454
455 let ocadb = OcaDb::new(&readonly)?;
456 split::SplitBackDb::new(Some(Rc::new(ocadb)))
457 }
458 Err(_e) => split::SplitBackDb::new(None),
459 };
460
461 let storage = Box::new(db);
462
463 Ok(Oca {
464 storage,
465 secret,
466 backend,
467 domainname,
468 })
469 }
470 }
471 }
472}
473
474impl Oca {
475 pub fn open(db_url: Option<&str>) -> Result<Self> {
481 let cau = Uninit::new(db_url)?;
482 cau.init_from_db_state()
483 }
484
485 pub fn domainname(&self) -> &str {
486 &self.domainname
487 }
488
489 pub(crate) fn backend(&self) -> &Backend {
490 &self.backend
491 }
492
493 pub(crate) fn secret(&self) -> &dyn CaSec {
496 &*self.secret
497 }
498
499 pub fn set_card_backend(self, card_ident: &str, user_pin: &str) -> Result<()> {
502 let cacert = self.storage.cacert()?;
503
504 let b = Backend::from_config(cacert.backend.as_deref())?;
505 match b {
506 Backend::Card(_c) => {
507 card::verify_user_pin(card_ident, user_pin)?;
511
512 let ca_cert = self.ca_get_cert_pub()?;
514 let _pubkey = card::check_if_card_matches(card_ident, &ca_cert)?;
515
516 let ca_pub = pgp::cert_to_armored(&ca_cert)?;
518
519 let db = self.storage.into_uninit();
520 CardBackend::ca_replace_in_place(&db, card_ident, user_pin, &ca_pub)?;
521
522 Ok(())
523 }
524 Backend::Softkey => Err(anyhow::anyhow!(
525 "Setting card backend from softkey is not supported."
526 )),
527 Backend::SplitFront | Backend::SplitBack(_) => Err(anyhow::anyhow!(
528 "Setting card backend from split mode is not supported."
529 )),
530 }
531 }
532
533 pub fn ca_generate_revocations(&self, output: PathBuf) -> Result<()> {
537 self.secret.ca_generate_revocations(output)
538 }
539
540 pub fn ca_import_tsig(&self, cert: &[u8]) -> Result<()> {
542 self.storage.ca_import_tsig(cert)
543 }
544
545 pub fn ca_get_cert_pub(&self) -> Result<Cert> {
553 match self.backend {
554 Backend::SplitBack(_) => {
557 if let Ok(ca_cert) = self.storage.ca_get_cert_pub() {
558 Ok(ca_cert)
560 } else {
561 self.secret.cert()
563 }
564 }
565 _ => self.storage.ca_get_cert_pub(),
566 }
567 }
568
569 pub fn ca_get_pubkey_armored(&self) -> Result<String> {
571 let cert = self.ca_get_cert_pub()?;
572
573 let ca_pub =
574 pgp::cert_to_armored(&cert).context("Failed to transform CA key to armored pubkey")?;
575
576 Ok(ca_pub)
577 }
578
579 pub(crate) fn get_ca_userid(&self) -> Result<UserID> {
581 let cert = self.ca_get_cert_pub()?;
582 let uids: Vec<_> = cert.userids().collect();
583
584 if uids.len() != 1 {
585 return Err(anyhow::anyhow!("ERROR: CA has != 1 user_id"));
586 }
587
588 Ok(uids[0].userid().clone())
589 }
590
591 pub fn get_ca_email(&self) -> Result<String> {
593 let uid = self.get_ca_userid()?;
594 let email = uid.email2()?;
595
596 if let Some(email) = email {
597 Ok(email.to_string())
598 } else {
599 Err(anyhow::anyhow!("CA user_id has no email"))
600 }
601 }
602
603 pub fn ca_show(&self) -> Result<()> {
607 let cert = self.secret().cert()?;
608
609 let created = cert.primary_key().key().creation_time();
610 let created: DateTime<Utc> = created.into();
611
612 println!(" CA Domain: {}", self.domainname());
613 println!(" Fingerprint: {}", cert.fingerprint());
614 println!("Creation time: {}", created.format("%F %T %Z"));
615
616 let backend = self.backend();
617 println!(" CA Backend: {backend}");
618
619 Ok(())
620 }
621
622 pub fn ca_print_private(&self) -> Result<()> {
626 match &self.backend {
627 Backend::Softkey => {
628 }
630 Backend::SplitBack(inner) => match **inner {
631 Backend::Softkey => {
632 }
634 _ => {
635 return Err(anyhow::anyhow!(
637 "Operation unsupported for this backend type"
638 ));
639 }
640 },
641 _ => {
642 return Err(anyhow::anyhow!(
643 "Operation unsupported for this backend type"
644 ));
645 }
646 }
647
648 let ca_cert = self
649 .storage
650 .cacert()
651 .context("failed to load CA from database")?;
652 println!("{}", ca_cert.priv_cert);
653
654 Ok(())
655 }
656
657 pub fn ca_re_certify(&self, ca_cert_old: &[u8], validity_days: u64) -> Result<()> {
663 let ca_cert_old = pgp::to_cert(ca_cert_old)?;
664
665 cert::certs_re_certify(self, ca_cert_old, validity_days)
666 }
667
668 pub fn ca_split_into(self, front: &Path, back: &Path) -> Result<()> {
672 match self.backend {
673 Backend::Softkey | Backend::Card(_) => {
674 let uninit_orig = self.storage.into_uninit();
675 let (orig_ca, orig_cacert) = uninit_orig.ca_cert()?;
676
677 let cert = Cert::from_str(&orig_cacert.priv_cert)?;
678 let pub_ca_cert = pgp::cert_to_armored(&cert)?;
679
680 let fp = cert.fingerprint().to_hex();
681
682 let db = uninit_orig.db();
683 let db_url = db.url();
684
685 for user in db.users_sorted_by_name()? {
689 if user.ca_id != 1 {
690 return Err(anyhow::anyhow!(
691 "Splitting a multi-CA setup is not currently supported"
692 ));
693 }
694 }
695
696 std::fs::copy(db_url, front)?;
698
699 if let Some(url) = front.to_str() {
700 let front = OcaDb::new(url)?;
701
702 front.cacerts_delete()?;
704
705 let backend = Backend::SplitFront.to_config();
706
707 let new_ca_cert = NewCacert {
708 active: true,
709 ca_id: orig_ca.id,
710 priv_cert: pub_ca_cert,
711 fingerprint: &fp,
712 backend: backend.as_deref(),
713 };
714 front.cacert_insert(&new_ca_cert)?;
715
716 front.vacuum()?;
718 } else {
719 return Err(anyhow::anyhow!("Illegal front filename"));
720 }
721
722 let orig_back = Backend::from_config(orig_cacert.backend.as_deref())?;
725
726 let backend = Backend::SplitBack(Box::new(orig_back));
727
728 if let Some(url) = back.to_str() {
729 let back = Uninit::new(Some(url))?;
730
731 back.storage.ca_insert(
732 &orig_ca.domainname,
733 &orig_cacert.priv_cert,
734 &fp,
735 backend.to_config().as_deref(),
736 )?;
737 } else {
738 return Err(anyhow::anyhow!("Illegal back filename"));
739 }
740
741 Ok(())
742 }
743 _ => Err(anyhow::anyhow!(
744 "Splitting operation not supported for this backend type"
745 )),
746 }
747 }
748
749 pub fn ca_merge_split(self, back: &Path) -> Result<()> {
751 match self.backend {
752 Backend::SplitFront => {
753 if let Some(url) = back.to_str() {
755 let back = OcaDb::new(url)?;
756 let (_back_ca, back_cacert) = back.get_ca()?;
757
758 let orig_back = Backend::from_config(back_cacert.backend.as_deref())?;
759 if let Backend::SplitBack(inner) = orig_back {
760 let mut front_cacert = self.storage.cacert()?;
763
764 if front_cacert.fingerprint != back_cacert.fingerprint {
765 return Err(anyhow::anyhow!(
766 "Front {} and back {} instance use different CA fingerprints",
767 front_cacert.fingerprint,
768 back_cacert.fingerprint
769 ));
770 }
771
772 let back_cert = pgp::to_cert(back_cacert.priv_cert.as_bytes())?;
776 let front_cert = pgp::to_cert(front_cacert.priv_cert.as_bytes())?;
777
778 let ca_merged = back_cert.merge_public(front_cert)?;
779
780 front_cacert.priv_cert = pgp::cert_to_armored_private_key(&ca_merged)?;
781
782 front_cacert.backend = inner.to_config();
784
785 let db = self.storage;
786 db.cacert_update(&front_cacert)?;
787 }
788
789 Ok(())
790 } else {
791 Err(anyhow::anyhow!(
792 "Failed to use back instance path ({:?})",
793 back
794 ))
795 }
796 }
797
798 _ => Err(anyhow::anyhow!(
799 "Merge operation not supported for this backend type"
800 )),
801 }
802 }
803
804 pub fn ca_split_export(&self, file: PathBuf) -> Result<()> {
817 match self.backend {
818 Backend::SplitFront => {
819 let cacert = self.storage.cacert()?;
820
821 let queue = self.storage.queue_not_done()?;
822 SplitCa::export_csr_queue(file, queue, &cacert.fingerprint)?;
823
824 Ok(())
825 }
826 _ => Err(anyhow::anyhow!(
827 "Operation is only supported on split mode front instances."
828 )),
829 }
830 }
831
832 pub fn ca_split_certify(&self, import: PathBuf, export: PathBuf, batch: bool) -> Result<()> {
839 match self.backend {
840 Backend::SplitBack(_) => split::certify(&*self.secret, import, export, batch),
841 _ => Err(anyhow::anyhow!(
842 "Operation is only supported on split mode back instances."
843 )),
844 }
845 }
846
847 pub fn ca_split_import(&self, file: PathBuf) -> Result<()> {
849 match self.backend {
850 Backend::SplitFront => split::ca_split_import(&*self.storage, file),
851 _ => Err(anyhow::anyhow!(
852 "Operation is only supported on split mode front instances."
853 )),
854 }
855 }
856
857 pub fn ca_split_show_queue(&self) -> Result<()> {
859 match self.backend {
860 Backend::SplitFront => split::ca_split_show_queue(&*self.storage),
861 _ => Err(anyhow::anyhow!(
862 "Operation is only supported on split mode front instances."
863 )),
864 }
865 }
866
867 pub fn user_certs_get_all(&self) -> Result<Vec<models::Cert>> {
871 let users = self.storage.users_sorted_by_name()?;
872 let mut user_certs = Vec::new();
873 for user in users {
874 user_certs.append(&mut self.get_certs_by_user(&user)?);
875 }
876 Ok(user_certs)
877 }
878
879 pub fn certs_expired(&self, days: u64) -> Result<HashMap<models::Cert, Option<SystemTime>>> {
884 cert::certs_expired(self, days)
885 }
886
887 pub fn cert_check_ca_sig(&self, cert: &models::Cert) -> Result<CertificationStatus> {
890 cert::cert_check_ca_sig(self, cert).context("Failed while checking CA sig")
891 }
892
893 pub fn cert_check_tsig_on_ca(&self, cert: &models::Cert) -> Result<bool> {
895 cert::cert_check_tsig_on_ca(self, cert).context("Failed while checking tsig on CA")
896 }
897
898 pub fn check_tsig_on_bridge(&self, bridge: &models::Bridge) -> Result<bool> {
900 let ca = self.ca_get_cert_pub()?;
901
902 if let Some(br) = self.storage.cert_by_id(bridge.cert_id)? {
903 let bridge_cert = pgp::to_cert(br.pub_cert.as_bytes())?;
904
905 Ok(cert::check_tsig_on_cert(&ca, &bridge_cert)?)
906 } else {
907 Err(anyhow::anyhow!(
908 "No public key found for bridge to '{}'",
909 bridge.email
910 ))
911 }
912 }
913
914 pub fn certs_refresh_ca_certifications(
919 &self,
920 threshold_days: u64,
921 validity_days: u64,
922 ) -> Result<()> {
923 cert::certs_refresh_ca_certifications(self, threshold_days, validity_days)
924 }
925
926 #[allow(clippy::too_many_arguments)]
936 pub fn user_new(
937 &self,
938 name: Option<&str>,
939 emails: &[&str],
940 duration_days: Option<u64>,
941 password: bool,
942 password_file: Option<String>,
943 output_format_minimal: bool,
944 cipher_suite: Option<CipherSuite>,
945 enable_encryption_subkey: bool,
946 enable_signing_subkey: bool,
947 enable_authentication_subkey: bool,
948 ) -> Result<()> {
949 cert::user_new(
951 self,
952 name,
953 emails,
954 duration_days,
955 password,
956 password_file,
957 output_format_minimal,
958 cipher_suite,
959 enable_encryption_subkey,
960 enable_signing_subkey,
961 enable_authentication_subkey,
962 )
963 }
964
965 pub fn cert_import_new(
979 &self,
980 cert: &[u8],
981 revoc_certs: &[&[u8]],
982 name: Option<&str>,
983 emails: &[&str],
984 duration_days: Option<u64>,
985 ) -> Result<()> {
986 cert::cert_import_new(self, cert, revoc_certs, name, emails, duration_days)
987 }
988
989 pub fn cert_import_update(&self, cert: &[u8]) -> Result<()> {
992 cert::cert_import_update(self, cert)
993 }
994
995 pub fn cert_delist(&self, fp: &str) -> Result<()> {
1008 self.storage.cert_delist(fp)
1009 }
1010
1011 pub fn cert_deactivate(&self, fp: &str) -> Result<()> {
1018 self.storage.cert_deactivate(fp)
1019 }
1020
1021 pub fn cert_get_by_fingerprint(&self, fingerprint: &str) -> Result<Option<models::Cert>> {
1026 let fp = pgp::normalize_fp(fingerprint)?;
1027
1028 self.storage.cert_by_fp(&fp)
1029 }
1030
1031 pub fn get_certs_by_user(&self, user: &models::User) -> Result<Vec<models::Cert>> {
1033 self.storage.certs_by_user(user)
1034 }
1035
1036 pub fn users_get_all(&self) -> Result<Vec<models::User>> {
1038 self.storage.users_sorted_by_name()
1039 }
1040
1041 pub fn certs_by_email(&self, email: &str) -> Result<Vec<models::Cert>> {
1043 self.storage.certs_by_email(email)
1044 }
1045
1046 pub fn cert_get_users(&self, cert: &models::Cert) -> Result<Option<models::User>> {
1048 self.storage.user_by_cert(cert)
1049 }
1050
1051 pub fn cert_get_name(&self, cert: &models::Cert) -> Result<String> {
1057 if let Some(user) = self.cert_get_users(cert)? {
1058 Ok(user.name.unwrap_or_else(|| "<no name>".to_string()))
1059 } else {
1060 Ok("<no name>".to_string())
1061 }
1062 }
1063
1064 pub fn print_certifications_status(&self) -> Result<()> {
1065 let mut count_ok = 0;
1066
1067 let db_users = self.users_get_all()?;
1068 for db_user in &db_users {
1069 for db_cert in self.get_certs_by_user(db_user)? {
1070 let sigs_by_ca = self.cert_check_ca_sig(&db_cert)?;
1071 let tsig_on_ca = self.cert_check_tsig_on_ca(&db_cert)?;
1072
1073 let sig_by_ca = !sigs_by_ca.certified.is_empty();
1074
1075 if sig_by_ca && tsig_on_ca {
1076 count_ok += 1;
1077 } else {
1078 println!(
1079 "No mutual certification for {}{}:",
1080 db_cert.fingerprint,
1081 db_user
1082 .name
1083 .as_deref()
1084 .map(|s| format!(" ({s})"))
1085 .unwrap_or_else(|| "".to_string()),
1086 );
1087
1088 if !sig_by_ca {
1089 println!(" No CA certification on any User ID");
1090 }
1091
1092 if !tsig_on_ca {
1093 println!(" Has not tsigned CA key.");
1094 };
1095
1096 println!();
1097 }
1098 }
1099 }
1100
1101 println!(
1102 "Checked {} user keys, {} of them have mutual certifications.",
1103 db_users.len(),
1104 count_ok
1105 );
1106
1107 Ok(())
1108 }
1109
1110 pub fn print_expiry_status(&self, exp_days: u64) -> Result<()> {
1111 let expiries = self.certs_expired(exp_days)?;
1112
1113 if expiries.is_empty() {
1114 println!("No certificates will expire in the next {exp_days} days.");
1115 } else {
1116 println!(
1117 "The following {} certificate{} will expire in the next {} days.",
1118 expiries.len(),
1119 if expiries.len() == 1 { "" } else { "s" },
1120 exp_days
1121 );
1122 println!();
1123 }
1124
1125 for (db_cert, expiry) in expiries {
1126 let name = self.cert_get_name(&db_cert)?;
1127 println!("name {}, fingerprint {}", name, db_cert.fingerprint);
1128
1129 if let Some(exp) = expiry {
1130 let datetime: DateTime<Utc> = exp.into();
1131 println!(" expires: {}", datetime.format("%d/%m/%Y"));
1132 } else {
1133 println!(" no expiration date is set for this user key");
1134 }
1135
1136 println!();
1137 }
1138
1139 Ok(())
1140 }
1141
1142 pub fn print_users(&self) -> Result<()> {
1143 for db_user in self.users_get_all()? {
1144 for db_cert in self.get_certs_by_user(&db_user)? {
1145 let sig_by_ca = self.cert_check_ca_sig(&db_cert)?;
1146 let tsig_on_ca = self.cert_check_tsig_on_ca(&db_cert)?;
1147
1148 println!("OpenPGP certificate {}", db_cert.fingerprint);
1149 if let Some(name) = &db_user.name {
1150 println!(" User '{name}'");
1151 }
1152
1153 if !sig_by_ca.certified.is_empty() {
1154 println!(" Identities certified by this CA:");
1155 for uid in sig_by_ca.certified {
1156 println!(" - '{uid}'");
1157 }
1158 }
1159
1160 if tsig_on_ca {
1161 println!(" Has trust-signed this CA");
1162 }
1163
1164 let c = pgp::to_cert(db_cert.pub_cert.as_bytes())?;
1165
1166 match pgp::get_expiry(&c) {
1167 Ok(Some(exp)) => {
1168 let datetime: DateTime<Utc> = exp.into();
1169 println!(" Expiration {}", datetime.format("%d/%m/%Y"));
1170 }
1171 Ok(None) => println!(" No expiration is set"),
1172 Err(e) => println!(" Expiration unknown ({e})"),
1173 }
1174
1175 let revs = self.revocations_get(&db_cert)?;
1176 if !revs.is_empty() {
1177 println!(" {} revocations available", revs.len());
1178 }
1179
1180 if pgp::is_possibly_revoked(&c) {
1181 println!(" This certificate has (possibly) been REVOKED");
1182 }
1183 println!();
1184 }
1185 }
1186
1187 Ok(())
1188 }
1189
1190 pub fn revocations_get(&self, cert: &models::Cert) -> Result<Vec<models::Revocation>> {
1194 self.storage.revocations_by_cert(cert)
1195 }
1196
1197 pub fn revocation_add(&self, revoc_cert: &[u8]) -> Result<()> {
1205 self.storage.revocation_add(revoc_cert)
1206 }
1207
1208 pub fn revocation_add_from_file(&self, filename: &Path) -> Result<()> {
1210 let rev = std::fs::read(filename)?;
1211
1212 self.revocation_add(&rev)
1213 }
1214
1215 pub fn revocation_get_by_hash(&self, hash: &str) -> Result<models::Revocation> {
1217 if let Some(rev) = self.storage.revocation_by_hash(hash)? {
1218 Ok(rev)
1219 } else {
1220 Err(anyhow::anyhow!("No revocation found for {}", hash))
1221 }
1222 }
1223
1224 pub fn revocation_apply(&self, revoc: models::Revocation) -> Result<()> {
1228 self.storage.revocation_apply(revoc)
1229 }
1230
1231 pub fn revocation_details(
1233 revocation: &models::Revocation,
1234 ) -> Result<(String, Option<SystemTime>)> {
1235 let rev = pgp::to_signature(revocation.revocation.as_bytes())?;
1236
1237 let creation = rev.signature_creation_time();
1238
1239 if let Some((code, reason)) = rev.reason_for_revocation() {
1240 let reason = String::from_utf8(reason.to_vec())?;
1241 Ok((format!("{code} ({reason})"), creation))
1242 } else {
1243 Ok(("Revocation reason unknown".to_string(), creation))
1244 }
1245 }
1246
1247 pub fn revoc_to_armored(sig: &Signature) -> Result<String> {
1249 pgp::revoc_to_armored(sig, None)
1250 }
1251
1252 pub fn print_revocations(&self, email: &str) -> Result<()> {
1253 let certs = self.certs_by_email(email)?;
1254 if certs.is_empty() {
1255 println!("No OpenPGP keys found");
1256 } else {
1257 for cert in certs {
1258 let name = self.cert_get_name(&cert)?;
1259
1260 println!(
1261 "Revocations for OpenPGP key {}, user \"{}\"",
1262 cert.fingerprint, name
1263 );
1264 let revoc = self.revocations_get(&cert)?;
1265 for r in revoc {
1266 let (reason, time) = Self::revocation_details(&r)?;
1267 let time = if let Some(time) = time {
1268 let datetime: DateTime<Utc> = time.into();
1269 format!("{}", datetime.format("%d/%m/%Y"))
1270 } else {
1271 "".to_string()
1272 };
1273 println!(" - revocation id {}: {} ({})", r.hash, reason, time);
1274 if r.published {
1275 println!(" this revocation has been APPLIED");
1276 }
1277
1278 println!();
1279 }
1280 }
1281 }
1282 Ok(())
1283 }
1284
1285 pub fn emails_get(&self, cert: &models::Cert) -> Result<Vec<models::CertEmail>> {
1289 self.storage.emails_by_cert(cert)
1290 }
1291
1292 pub fn get_emails_all(&self) -> Result<Vec<models::CertEmail>> {
1294 self.storage.emails()
1295 }
1296
1297 pub fn bridges_get(&self) -> Result<Vec<models::Bridge>> {
1301 self.storage.list_bridges()
1302 }
1303
1304 pub fn bridges_search(&self, email: &str) -> Result<models::Bridge> {
1306 if let Some(bridge) = self.storage.bridge_by_email(email)? {
1307 Ok(bridge)
1308 } else {
1309 Err(anyhow::anyhow!("Bridge not found"))
1310 }
1311 }
1312
1313 pub fn bridge_get_cert(&self, bridge: &models::Bridge) -> Result<models::Cert> {
1315 if let Some(cert) = self.storage.cert_by_id(bridge.cert_id)? {
1316 Ok(cert)
1317 } else {
1318 Err(anyhow::anyhow!("No cert found for bridge {}", bridge.id))
1319 }
1320 }
1321
1322 pub fn add_bridge(
1323 &self,
1324 email: Option<&str>,
1325 key_file: &Path,
1326 scope: Option<&str>,
1327 unscoped: bool,
1328 ) -> Result<(String, String)> {
1329 let (bridge, fingerprint) = bridge::bridge_new(self, key_file, email, scope, unscoped)?;
1330
1331 Ok((bridge.email, fingerprint.to_string()))
1332 }
1333
1334 pub fn bridge_revoke(&self, email: &str) -> Result<()> {
1340 bridge::bridge_revoke(self, email)
1341 }
1342
1343 pub fn print_bridges(&self, email: Option<String>) -> Result<()> {
1344 let bridges = if let Some(email) = email {
1345 vec![self.bridges_search(&email)?]
1346 } else {
1347 self.bridges_get()?
1348 };
1349
1350 for bridge in bridges {
1351 println!("Bridge to '{}'", bridge.email);
1352 if let Some(db_cert) = self.storage.cert_by_id(bridge.cert_id)? {
1353 println!("{}", db_cert.pub_cert);
1354 }
1355 println!();
1356 }
1357
1358 Ok(())
1359 }
1360
1361 pub fn list_bridges(&self) -> Result<()> {
1362 for bridge in self.bridges_get()? {
1363 let tsigned = self.check_tsig_on_bridge(&bridge)?;
1364
1365 println!(
1366 "Bridge to '{}'{}, (scope: '{}')",
1367 bridge.email,
1368 if !tsigned {
1369 " [no trust signature]"
1370 } else {
1371 ""
1372 },
1373 bridge.scope,
1374 )
1375 }
1376
1377 Ok(())
1378 }
1379
1380 pub fn export_wkd(&self, domain: &str, path: &Path) -> Result<()> {
1387 export::wkd_export(self, domain, path)
1388 }
1389
1390 pub fn export_keylist(&self, path: PathBuf, signature_uri: String, force: bool) -> Result<()> {
1403 export::export_keylist(self, path, signature_uri, force)
1404 }
1405
1406 pub fn export_certs_as_files(&self, email_filter: Option<String>, path: &str) -> Result<()> {
1409 export::export_certs_as_files(self, email_filter, path)
1410 }
1411
1412 pub fn print_certring(&self, email_filter: Option<String>) -> Result<()> {
1413 export::print_certring(self, email_filter)
1414 }
1415
1416 pub fn update_from_wkd(&self) -> Result<()> {
1421 for c in self.user_certs_get_all()? {
1422 match update::update_from_wkd(self, &c) {
1423 Ok(true) => {
1424 println!("Got update for cert {}", c.fingerprint);
1425 }
1426 Ok(false) => {
1427 println!("No changes for cert {}", c.fingerprint);
1428 }
1429 Err(e) => {
1430 eprintln!("Failed to update cert {}: {}", c.fingerprint, e);
1431 }
1432 }
1433 }
1434 Ok(())
1435 }
1436
1437 pub fn update_from_keyserver(&self) -> Result<()> {
1440 for c in self.user_certs_get_all()? {
1441 match update::update_from_hagrid(self, &c) {
1442 Ok(true) => {
1443 println!("Got update for cert {}", c.fingerprint);
1444 }
1445 Ok(false) => {
1446 println!("No changes for cert {}", c.fingerprint);
1447 }
1448 Err(e) => {
1449 eprintln!("Failed to update cert {}: {}", c.fingerprint, e);
1450 }
1451 }
1452 }
1453 Ok(())
1454 }
1455}