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