diff --git a/src/commands/introducer/remove.rs b/src/commands/introducer/remove.rs index aaf4210..be283e4 100644 --- a/src/commands/introducer/remove.rs +++ b/src/commands/introducer/remove.rs @@ -1,6 +1,7 @@ use anyhow::Result; use sequoia_openpgp::{Fingerprint, KeyHandle}; +use sequoia_openpgp::cert::ValidCert; use sequoia_cert_store::Store; use crate::cli::cli_args::IntroducerRemoveCommand; @@ -23,6 +24,19 @@ pub async fn dispatch(cmd: IntroducerRemoveCommand, config: HuskConfigContainer) let certs = cert_store.lookup_by_cert(&KeyHandle::try_from(&fpr)?) .map_err(|_| CommandError::CertNotFound(fpr.clone()))?; + // Filter out valid certificates. If the certificate is not + // valid according to the policy, it is already no introducer. + let certs: Vec = certs.iter() + .filter_map(|c| + c.with_policy(policy, None).ok() + ) + .collect(); + + if let Some(cert) = certs.first() { + Introducer::remove(&context, cert, cmd.demote)?; + } else { + return Err(CommandError::CertNotFound(fpr).into()); + } Ok(()) diff --git a/src/common/crypto.rs b/src/common/crypto.rs index 17a8cc6..753d9b9 100644 --- a/src/common/crypto.rs +++ b/src/common/crypto.rs @@ -7,15 +7,19 @@ use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; use anyhow; +use sequoia_openpgp::cert::ValidCert; use tokio::task::JoinSet; use bytes::Bytes; use sequoia_net::{KeyServer, wkd, dane}; +use sequoia_openpgp::packet::prelude::SignatureBuilder; use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse}; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::stream::{Message, Armorer, Encryptor, LiteralWriter}; use sequoia_openpgp::{Fingerprint, Cert, Packet}; use sequoia_openpgp::cert::raw::RawCertParser; +use sequoia_openpgp::types::SignatureType; use sequoia_cert_store::{Store, CertStore, LazyCert, StoreUpdate}; +use sequoia_cert_store::store::MergePublicCollectStats; use sequoia_wot::{self as wot}; use wot::store::Store as _; use wot::{Depth, Path}; @@ -24,6 +28,8 @@ use crate::types::errors::HuskError; use crate::types::husk_context::HuskContext; use crate::types::introducer::Introducer; use crate::types::recipient::Recipient; +use crate::mail; +use crate::common::escape; /// Returns the local trust root from the cert_store. If the trust root /// cannot be found, it returns an error. @@ -368,6 +374,104 @@ pub fn encrypt(context: &HuskContext<'_>, body: &Bytes, recipients: &Vec>) + -> anyhow::Result<()> { + + let policy = &context.policy; + let root_signing_key = match context.local_trust_root.with_policy(policy, None)? + .keys() + .supported() + .alive() + .revoked(false) + .for_certification() + .next() { + Some(k) => k.key(), + None => { + return Err(HuskError::DamangedLocalTrustRoot( + "missing certification capability".to_string()) + .into()); + } + }; + let mut root_signer = root_signing_key + .clone() + .parts_into_secret()? + .into_keypair()?; + + // Create the SignatureBuilder with a default preconfiguration. + let mut builder = + SignatureBuilder::new(SignatureType::GenericCertification) + .set_signature_creation_time(std::time::SystemTime::now())? + .set_exportable_certification(false)? + .set_revocable(true)?; + // set no expiration date + + // Add domains + if activate { + // set the trust level and depth for an active introducer. + builder = builder.set_trust_signature(1, 120)?; + + if let Some(domains) = domains { + for domain in domains { + if !mail::is_domain(domain.as_str()) { + return Err(HuskError::InvalidDomainName(domain).into()); + } + + builder = builder.add_regular_expression(format!("<[^>]+[@.]{}>$", + escape::regex_ctrl_escape(domain.as_str())? + ))?; + } + } else { + // An introducer without domains would be unlimited. + return Err(HuskError::UnlimitedIntroducer.into()); + } + } else { + // deactive the introducer + builder = builder.set_trust_signature(0, 1)?; + } + + // For a certification, user ids are needed. Build a list of + // valid (non self revoked) user ids. + let user_ids: Vec<_> = cert.userids() + .filter(|u| { + u.self_revocations().count() == 0 + }) + .collect(); + + if user_ids.is_empty() { + return Err(HuskError::NoUserIDs(cert.fingerprint().to_hex()).into()); + } + + // now certify + let signatures: Vec<_> = user_ids.into_iter() + .filter_map(|u| { + let b = builder.clone(); + + b.sign_userid_binding( + &mut root_signer, + cert.primary_key().key(), + u.userid()).ok() + }) + .collect(); + let (new_cert, _) = cert.cert().clone().insert_packets(signatures)?; + + // and (re)import + let new_cert = Arc::new(new_cert.into()); + let stats = MergePublicCollectStats::new(); + let cert_store = &context.cert_store; + cert_store.update_by(new_cert, &stats)?; + + log::debug!("{:?}", stats); + + // XXX: do something with the created stats + Ok(()) +} + #[cfg(test)] pub mod tests { diff --git a/src/types/errors.rs b/src/types/errors.rs index 58fd52b..5e1f66d 100644 --- a/src/types/errors.rs +++ b/src/types/errors.rs @@ -8,12 +8,20 @@ pub enum HuskError { ConfigFileOpenError(String), #[error("Error while parsing config file: {0}")] ConfigFileParseError(String), - #[error("Cannot find local trust root")] + #[error("Cannot find the local trust root")] NoLocalTrustRoot, + #[error("The local trust root is damaged - {0}")] + DamangedLocalTrustRoot(String), #[error("Cannot access cert store")] NoCertStore, #[error("No encryption keys found")] NoEncryptionKeys, #[error("Error while looking up email {0}: {1}")] KeyLookupError(String, String), + #[error("Unlimited introducer")] + UnlimitedIntroducer, + #[error("Invalid domain name '{0}'")] + InvalidDomainName(String), + #[error("Certificate has no user ids - {0}")] + NoUserIDs(String), } diff --git a/src/types/introducer.rs b/src/types/introducer.rs index efaac37..17e71de 100644 --- a/src/types/introducer.rs +++ b/src/types/introducer.rs @@ -1,22 +1,15 @@ //! introducers -use std::sync::Arc; - use anyhow::Result; -use sequoia_cert_store::StoreUpdate; -use sequoia_cert_store::store::MergePublicCollectStats; -use sequoia_openpgp::packet::prelude::SignatureBuilder; use sequoia_wot as wot; use wot::{CertSynopsis, Certification}; use sequoia_openpgp::{cert::ValidCert, policy::StandardPolicy}; use sequoia_openpgp::Cert; use sequoia_openpgp::Fingerprint; -use sequoia_openpgp::types::SignatureType; -use crate::mail; use crate::types::husk_context::HuskContext; -use crate::common::escape; +use crate::common::crypto; #[derive(thiserror::Error, Debug)] pub enum IntroducerError { @@ -87,86 +80,19 @@ impl Introducer { return Err(IntroducerError::UnsuitableCertificate.into()); } - let policy = &context.policy; - let root_signing_key = match context.local_trust_root.with_policy(policy, None)? - .keys() - .supported() - .alive() - .revoked(false) - .for_certification() - .next() { - Some(k) => k.key(), - None => { - return Err(IntroducerError::RootDamaged.into()); - } - }; - let mut root_signer = root_signing_key - .clone() - .parts_into_secret()? - .into_keypair()?; + crypto::authenticate(context, cert, true, Some(domains)) + } + pub fn remove(context: &HuskContext, cert: &ValidCert, _demote: bool) + -> Result<()> { - // Check if domain list is not empty. - // An empty list would create an unlimited introducer. - if domains.is_empty() { - return Err(IntroducerError::UnlimitedIntroducer.into()); + // Check if the local trust root certificate should be made an + // introducer, which would be an error. + if cert.fingerprint() == context.local_trust_root.fingerprint() { + return Err(IntroducerError::UnsuitableCertificate.into()); } - // Create the SignatureBuilder with a default preconfiguration. - let mut builder = - SignatureBuilder::new(SignatureType::GenericCertification) - .set_signature_creation_time(std::time::SystemTime::now())? - .set_trust_signature(1, 120)? - .set_exportable_certification(false)? - .set_revocable(true)?; - // set no expiration date - - // Add domains - for domain in domains { - if !mail::is_domain(domain.as_str()) { - return Err(IntroducerError::InvalidDomainName(domain).into()); - } - - builder = builder.add_regular_expression(format!("<[^>]+[@.]{}>$", - escape::regex_ctrl_escape(domain.as_str())? - ))?; - } - - // For a certification, user ids are needed. Build a list of - // valid (non self revoked) user ids. - let user_ids: Vec<_> = cert.userids() - .filter(|u| { - u.self_revocations().count() == 0 - }) - .collect(); - - if user_ids.is_empty() { - return Err(IntroducerError::NoUserIDs(cert.fingerprint()).into()); - } - - // now certify - let signatures: Vec<_> = user_ids.into_iter() - .filter_map(|u| { - let b = builder.clone(); - - b.sign_userid_binding( - &mut root_signer, - cert.primary_key().key(), - u.userid()).ok() - }) - .collect(); - let (new_cert, _) = cert.cert().clone().insert_packets(signatures)?; - - // and (re)import - let new_cert = Arc::new(new_cert.into()); - let stats = MergePublicCollectStats::new(); - let cert_store = &context.cert_store; - cert_store.update_by(new_cert, &stats)?; - - log::debug!("{:?}", stats); - - // XXX: do something with the created stats - Ok(()) + crypto::authenticate(context, cert, false, None) } }