use crate::Result; use crate::counters; use crate::database::types::{Email, Fingerprint}; use crate::database::{ Database, EmailAddressStatus, ImportResult, KeyDatabase, StatefulTokens, TpkStatus, }; use crate::mail; use crate::rate_limiter::RateLimiter; use crate::tokens::{self, StatelessSerializable}; use crate::web::RequestOrigin; use gettext_macros::i18n; use rocket_i18n::I18n; use sequoia_openpgp::armor::ReaderMode; use sequoia_openpgp::cert::CertParser; use sequoia_openpgp::parse::{Dearmor, PacketParserBuilder, Parse}; use sequoia_openpgp::Cert; use std::collections::HashMap; use std::convert::TryFrom; use std::io::Read; use self::response::*; pub mod request { #[derive(Deserialize)] pub struct UploadRequest { pub keytext: String, } #[derive(Deserialize)] pub struct VerifyRequest { pub token: String, pub addresses: Vec, } } pub mod response { use crate::database::types::Email; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum EmailStatus { #[serde(rename = "unpublished")] Unpublished, #[serde(rename = "pending")] Pending, #[serde(rename = "published")] Published, #[serde(rename = "revoked")] Revoked, } use std::collections::HashMap; pub enum UploadResponse { Ok { token: String, key_fpr: String, is_revoked: bool, status: HashMap, count_unparsed: usize, is_new_key: bool, primary_uid: Option, }, OkMulti { key_fprs: Vec, }, Error(String), } impl UploadResponse { pub fn err(err: impl Into) -> Self { UploadResponse::Error(err.into()) } } pub enum PublishResponse { Ok { fingerprint: String, email: String }, Error(String), } impl PublishResponse { pub fn err(err: impl Into) -> Self { PublishResponse::Error(err.into()) } } } #[derive(Serialize, Deserialize)] struct VerifyTpkState { fpr: Fingerprint, addresses: Vec, requested: Vec, } impl StatelessSerializable for VerifyTpkState {} pub fn process_key( db: &KeyDatabase, i18n: &I18n, tokens_stateless: &tokens::Service, rate_limiter: &RateLimiter, reader: impl Read + Send + Sync, ) -> response::UploadResponse { // First, parse all Certs and error out if one fails. let parser = match PacketParserBuilder::from_reader(reader) .and_then(|ppb| ppb.dearmor(Dearmor::Auto(ReaderMode::VeryTolerant)).build()) { Ok(ppr) => CertParser::from(ppr), Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed.")), }; let mut tpks = Vec::new(); for tpk in parser { tpks.push(match tpk { Ok(t) => { if t.is_tsk() { counters::inc_key_upload("secret"); return UploadResponse::err(i18n!( i18n.catalog, "Whoops, please don't upload secret keys!" )); } t } Err(_) => { return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed.")); } }); } match tpks.len() { 0 => UploadResponse::err(i18n!(i18n.catalog, "No key uploaded.")), 1 => process_key_single( db, i18n, tokens_stateless, rate_limiter, tpks.into_iter().next().unwrap(), ), _ => process_key_multiple(db, tpks), } } fn log_db_merge(import_result: Result) -> Result { match import_result { Ok(ImportResult::New(_)) => counters::inc_key_upload("new"), Ok(ImportResult::Updated(_)) => counters::inc_key_upload("updated"), Ok(ImportResult::Unchanged(_)) => counters::inc_key_upload("unchanged"), Err(_) => counters::inc_key_upload("error"), }; import_result } fn process_key_multiple(db: &KeyDatabase, tpks: Vec) -> response::UploadResponse { let key_fprs: Vec<_> = tpks .into_iter() .flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint()).map(|fpr| (fpr, tpk))) .flat_map(|(fpr, tpk)| log_db_merge(db.merge(tpk)).map(|_| fpr.to_string())) .collect(); response::UploadResponse::OkMulti { key_fprs } } fn process_key_single( db: &KeyDatabase, i18n: &I18n, tokens_stateless: &tokens::Service, rate_limiter: &RateLimiter, tpk: Cert, ) -> response::UploadResponse { let fp = Fingerprint::try_from(tpk.fingerprint()).unwrap(); let (tpk_status, is_new_key) = match log_db_merge(db.merge(tpk)) { Ok(ImportResult::New(tpk_status)) => (tpk_status, true), Ok(ImportResult::Updated(tpk_status)) => (tpk_status, false), Ok(ImportResult::Unchanged(tpk_status)) => (tpk_status, false), Err(_) => { return UploadResponse::err(i18n!(i18n.catalog, "Error processing uploaded key.")) } }; let verify_state = { let emails = tpk_status .email_status .iter() .map(|(email, _)| email.clone()) .collect(); VerifyTpkState { fpr: fp, addresses: emails, requested: vec![], } }; let token = tokens_stateless.create(&verify_state); show_upload_verify(rate_limiter, token, tpk_status, verify_state, is_new_key) } pub fn request_verify( db: &rocket::State, origin: &RequestOrigin, token_stateful: &rocket::State, token_stateless: &rocket::State, mail_service: &rocket::State, rate_limiter: &rocket::State, i18n: &I18n, token: String, addresses: Vec, ) -> response::UploadResponse { let (verify_state, tpk_status) = match check_tpk_state(db, token_stateless, i18n, &token) { Ok(ok) => ok, Err(e) => return UploadResponse::err(&e.to_string()), }; if tpk_status.is_revoked { return show_upload_verify(rate_limiter, token, tpk_status, verify_state, false); } let emails_requested: Vec<_> = addresses .into_iter() .map(|address| address.parse::()) .flatten() .filter(|email| verify_state.addresses.contains(email)) .filter(|email| { tpk_status.email_status.iter().any(|(uid_email, status)| { uid_email == email && *status == EmailAddressStatus::NotPublished }) }) .collect(); for email in emails_requested { let rate_limit_ok = rate_limiter.action_perform(format!("verify-{}", &email)); if rate_limit_ok && send_verify_email( origin, mail_service, token_stateful, i18n, &verify_state.fpr, &email, ) .is_err() { return UploadResponse::err(&format!("error sending email to {}", &email)); } } show_upload_verify(rate_limiter, token, tpk_status, verify_state, false) } fn check_tpk_state( db: &KeyDatabase, token_stateless: &tokens::Service, i18n: &I18n, token: &str, ) -> Result<(VerifyTpkState, TpkStatus)> { let verify_state = token_stateless .check::(token) .map_err(|_| { anyhow!(i18n!( i18n.catalog, "Upload session expired. Please try again." )) })?; let tpk_status = db.get_tpk_status(&verify_state.fpr, &verify_state.addresses)?; Ok((verify_state, tpk_status)) } fn send_verify_email( origin: &RequestOrigin, mail_service: &mail::Service, token_stateful: &StatefulTokens, i18n: &I18n, fpr: &Fingerprint, email: &Email, ) -> Result<()> { let token_content = (fpr.clone(), email.clone()); let token_str = serde_json::to_string(&token_content)?; let token_verify = token_stateful.new_token("verify", token_str.as_bytes())?; mail_service.send_verification( i18n, origin.get_base_uri(), fpr.to_string(), email, &token_verify, ) } pub fn verify_confirm( db: &rocket::State, i18n: &I18n, token_service: &rocket::State, token: String, ) -> response::PublishResponse { let (fingerprint, email) = match check_publish_token(db, token_service, token) { Ok(x) => x, Err(_) => return PublishResponse::err(i18n!(i18n.catalog, "Invalid verification link.")), }; response::PublishResponse::Ok { fingerprint: fingerprint.to_string(), email: email.to_string(), } } fn check_publish_token( db: &KeyDatabase, token_service: &StatefulTokens, token: String, ) -> Result<(Fingerprint, Email)> { let payload = token_service.pop_token("verify", &token)?; let (fingerprint, email) = serde_json::from_str(&payload)?; db.set_email_published(&fingerprint, &email)?; counters::inc_address_published(&email); Ok((fingerprint, email)) } fn show_upload_verify( rate_limiter: &RateLimiter, token: String, tpk_status: TpkStatus, verify_state: VerifyTpkState, is_new_key: bool, ) -> response::UploadResponse { let key_fpr = verify_state.fpr.to_string(); if tpk_status.is_revoked { return response::UploadResponse::Ok { token, key_fpr, count_unparsed: 0, is_revoked: true, status: HashMap::new(), is_new_key: false, primary_uid: None, }; } let status: HashMap<_, _> = tpk_status .email_status .iter() .map(|(email, status)| { let is_pending = (*status == EmailAddressStatus::NotPublished) && !rate_limiter.action_check(format!("verify-{}", &email)); if is_pending { (email.to_string(), EmailStatus::Pending) } else { ( email.to_string(), match status { EmailAddressStatus::NotPublished => EmailStatus::Unpublished, EmailAddressStatus::Published => EmailStatus::Published, EmailAddressStatus::Revoked => EmailStatus::Revoked, }, ) } }) .collect(); let primary_uid = tpk_status .email_status .get(0) .map(|(email, _)| email) .cloned(); let count_unparsed = tpk_status.unparsed_uids; response::UploadResponse::Ok { token, key_fpr, count_unparsed, is_revoked: false, status, is_new_key, primary_uid, } }