2019-05-23 23:01:24 +00:00
|
|
|
use failure::Fallible as Result;
|
|
|
|
|
2019-07-15 22:42:07 +00:00
|
|
|
use database::{Database, KeyDatabase, StatefulTokens, EmailAddressStatus, TpkStatus, ImportResult};
|
2019-05-23 23:01:24 +00:00
|
|
|
use database::types::{Fingerprint,Email};
|
|
|
|
use mail;
|
2019-07-19 18:46:16 +00:00
|
|
|
use counters;
|
2019-05-23 23:01:24 +00:00
|
|
|
use tokens::{self, StatelessSerializable};
|
|
|
|
use rate_limiter::RateLimiter;
|
2019-06-22 23:39:36 +00:00
|
|
|
use web::RequestOrigin;
|
2019-05-23 23:01:24 +00:00
|
|
|
|
|
|
|
use sequoia_openpgp::TPK;
|
|
|
|
|
|
|
|
use std::io::Read;
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
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<String>,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub mod response {
|
2019-07-15 22:42:07 +00:00
|
|
|
use database::types::Email;
|
|
|
|
|
2019-05-23 23:01:24 +00:00
|
|
|
#[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,
|
2019-06-05 11:21:37 +00:00
|
|
|
is_revoked: bool,
|
2019-05-23 23:01:24 +00:00
|
|
|
status: HashMap<String,EmailStatus>,
|
2019-06-08 15:15:46 +00:00
|
|
|
count_unparsed: usize,
|
2019-07-15 22:42:07 +00:00
|
|
|
is_new_key: bool,
|
|
|
|
primary_uid: Option<Email>,
|
2019-05-23 23:01:24 +00:00
|
|
|
},
|
|
|
|
OkMulti { key_fprs: Vec<String> },
|
|
|
|
Error(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UploadResponse {
|
|
|
|
pub fn err(err: &str) -> Self {
|
|
|
|
UploadResponse::Error(err.to_owned())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum PublishResponse {
|
2019-06-12 14:56:21 +00:00
|
|
|
Ok { fingerprint: String, email: String },
|
2019-05-23 23:01:24 +00:00
|
|
|
Error(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PublishResponse {
|
|
|
|
pub fn err(err: &str) -> Self {
|
|
|
|
PublishResponse::Error(err.to_owned())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize,Deserialize)]
|
|
|
|
struct VerifyTpkState {
|
|
|
|
fpr: Fingerprint,
|
|
|
|
addresses: Vec<Email>,
|
|
|
|
requested: Vec<Email>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StatelessSerializable for VerifyTpkState {
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_key(
|
|
|
|
db: &KeyDatabase,
|
|
|
|
tokens_stateless: &tokens::Service,
|
|
|
|
rate_limiter: &RateLimiter,
|
|
|
|
reader: impl Read,
|
|
|
|
) -> response::UploadResponse {
|
2019-06-08 15:14:13 +00:00
|
|
|
use sequoia_openpgp::parse::{Parse, PacketParserBuilder, Dearmor};
|
2019-05-23 23:01:24 +00:00
|
|
|
use sequoia_openpgp::tpk::TPKParser;
|
2019-06-08 15:14:13 +00:00
|
|
|
use sequoia_openpgp::armor::ReaderMode;
|
2019-05-23 23:01:24 +00:00
|
|
|
|
|
|
|
// First, parse all TPKs and error out if one fails.
|
2019-06-08 15:14:13 +00:00
|
|
|
let parser = match PacketParserBuilder::from_reader(reader)
|
|
|
|
.and_then(|ppb| {
|
|
|
|
ppb.dearmor(Dearmor::Auto(ReaderMode::VeryTolerant)).finalize()
|
|
|
|
})
|
|
|
|
{
|
|
|
|
Ok(ppr) => TPKParser::from_packet_parser(ppr),
|
2019-05-23 23:01:24 +00:00
|
|
|
Err(_) => return UploadResponse::err("Failed parsing key"),
|
|
|
|
};
|
|
|
|
let mut tpks = Vec::new();
|
|
|
|
for tpk in parser {
|
|
|
|
tpks.push(match tpk {
|
|
|
|
Ok(t) => {
|
|
|
|
if t.is_tsk() {
|
2019-07-20 15:00:02 +00:00
|
|
|
counters::inc_key_upload("secret");
|
2019-05-23 23:01:24 +00:00
|
|
|
return UploadResponse::err("Whoops, please don't upload secret keys!");
|
|
|
|
}
|
|
|
|
t
|
|
|
|
},
|
|
|
|
Err(_) => return UploadResponse::err("No keys uploaded"),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
match tpks.len() {
|
|
|
|
0 => UploadResponse::err("No key submitted"),
|
|
|
|
1 => process_key_single(db, tokens_stateless, rate_limiter, tpks.into_iter().next().unwrap()),
|
|
|
|
_ => process_key_multiple(db, tpks),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-19 18:46:16 +00:00
|
|
|
fn log_db_merge(import_result: Result<ImportResult>) -> Result<ImportResult> {
|
|
|
|
match import_result {
|
2019-07-20 15:00:02 +00:00
|
|
|
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"),
|
2019-07-19 18:46:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
import_result
|
|
|
|
}
|
|
|
|
|
2019-05-23 23:01:24 +00:00
|
|
|
fn process_key_multiple(
|
|
|
|
db: &KeyDatabase,
|
|
|
|
tpks: Vec<TPK>,
|
|
|
|
) -> response::UploadResponse {
|
|
|
|
let key_fprs: Vec<_> = tpks
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint())
|
|
|
|
.map(|fpr| (fpr, tpk)))
|
2019-07-19 18:46:16 +00:00
|
|
|
.flat_map(|(fpr, tpk)| log_db_merge(db.merge(tpk)).map(|_| fpr.to_string()))
|
2019-05-23 23:01:24 +00:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
response::UploadResponse::OkMulti { key_fprs }
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_key_single(
|
|
|
|
db: &KeyDatabase,
|
|
|
|
tokens_stateless: &tokens::Service,
|
|
|
|
rate_limiter: &RateLimiter,
|
|
|
|
tpk: TPK,
|
|
|
|
) -> response::UploadResponse {
|
|
|
|
let fp = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
|
|
|
|
2019-07-19 18:46:16 +00:00
|
|
|
let (tpk_status, is_new_key) = match log_db_merge(db.merge(tpk)) {
|
2019-07-15 22:42:07 +00:00
|
|
|
Ok(ImportResult::New(tpk_status)) => (tpk_status, true),
|
|
|
|
Ok(ImportResult::Updated(tpk_status)) => (tpk_status, false),
|
|
|
|
Ok(ImportResult::Unchanged(tpk_status)) => (tpk_status, false),
|
2019-06-06 20:06:38 +00:00
|
|
|
Err(_) => return UploadResponse::err(&format!(
|
|
|
|
"Something went wrong processing key {}", fp)),
|
2019-05-23 23:01:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let verify_state = {
|
|
|
|
let emails = tpk_status.email_status.iter()
|
|
|
|
.map(|(email,_)| email.clone())
|
|
|
|
.collect();
|
|
|
|
VerifyTpkState {
|
|
|
|
fpr: fp.clone(),
|
|
|
|
addresses: emails,
|
|
|
|
requested: vec!(),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let token = tokens_stateless.create(&verify_state);
|
|
|
|
|
2019-07-15 22:42:07 +00:00
|
|
|
show_upload_verify(rate_limiter, token, tpk_status, verify_state, is_new_key)
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn request_verify(
|
|
|
|
db: rocket::State<KeyDatabase>,
|
2019-06-22 23:39:36 +00:00
|
|
|
request_origin: RequestOrigin,
|
2019-05-23 23:01:24 +00:00
|
|
|
token_stateful: rocket::State<StatefulTokens>,
|
|
|
|
token_stateless: rocket::State<tokens::Service>,
|
|
|
|
mail_service: rocket::State<mail::Service>,
|
|
|
|
rate_limiter: rocket::State<RateLimiter>,
|
|
|
|
token: String,
|
|
|
|
addresses: Vec<String>,
|
|
|
|
) -> response::UploadResponse {
|
|
|
|
let (verify_state, tpk_status) = match check_tpk_state(&db, &token_stateless, &token) {
|
|
|
|
Ok(ok) => ok,
|
|
|
|
Err(e) => return UploadResponse::err(&e.to_string()),
|
|
|
|
};
|
|
|
|
|
|
|
|
if tpk_status.is_revoked {
|
|
|
|
return show_upload_verify(
|
2019-07-15 22:42:07 +00:00
|
|
|
&rate_limiter, token, tpk_status, verify_state, false);
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let emails_requested: Vec<_> = addresses.into_iter()
|
|
|
|
.map(|address| address.parse::<Email>())
|
|
|
|
.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 {
|
2019-06-22 23:39:36 +00:00
|
|
|
if send_verify_email(&request_origin, &mail_service, &token_stateful, &verify_state.fpr, &email).is_err() {
|
2019-05-23 23:01:24 +00:00
|
|
|
return UploadResponse::err(&format!("error sending email to {}", &email));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-15 22:42:07 +00:00
|
|
|
show_upload_verify(&rate_limiter, token, tpk_status, verify_state, false)
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn check_tpk_state(
|
|
|
|
db: &KeyDatabase,
|
|
|
|
token_stateless: &tokens::Service,
|
|
|
|
token: &str,
|
|
|
|
) -> Result<(VerifyTpkState,TpkStatus)> {
|
|
|
|
let verify_state = token_stateless.check::<VerifyTpkState>(token)?;
|
|
|
|
let tpk_status = db.get_tpk_status(&verify_state.fpr, &verify_state.addresses)?;
|
|
|
|
Ok((verify_state, tpk_status))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn send_verify_email(
|
2019-06-22 23:39:36 +00:00
|
|
|
request_origin: &RequestOrigin,
|
2019-05-23 23:01:24 +00:00
|
|
|
mail_service: &mail::Service,
|
|
|
|
token_stateful: &StatefulTokens,
|
|
|
|
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(
|
2019-06-22 23:39:36 +00:00
|
|
|
request_origin.get_base_uri(),
|
2019-05-23 23:01:24 +00:00
|
|
|
fpr.to_string(),
|
|
|
|
&email,
|
|
|
|
&token_verify,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify_confirm(
|
|
|
|
db: rocket::State<KeyDatabase>,
|
|
|
|
token_service: rocket::State<StatefulTokens>,
|
|
|
|
token: String,
|
|
|
|
) -> response::PublishResponse {
|
2019-06-12 14:56:21 +00:00
|
|
|
let (fingerprint, email) = match check_publish_token(&db, &token_service, token) {
|
|
|
|
Ok(x) => x,
|
2019-05-23 23:01:24 +00:00
|
|
|
Err(_) => return PublishResponse::err("token verification failed"),
|
|
|
|
};
|
|
|
|
|
2019-06-12 14:56:21 +00:00
|
|
|
response::PublishResponse::Ok {
|
|
|
|
fingerprint: fingerprint.to_string(),
|
|
|
|
email: email.to_string()
|
|
|
|
}
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn check_publish_token(
|
|
|
|
db: &KeyDatabase,
|
|
|
|
token_service: &StatefulTokens,
|
|
|
|
token: String,
|
2019-06-12 14:56:21 +00:00
|
|
|
) -> Result<(Fingerprint,Email)> {
|
2019-05-23 23:01:24 +00:00
|
|
|
let payload = token_service.pop_token("verify", &token)?;
|
|
|
|
let (fingerprint, email) = serde_json::from_str(&payload)?;
|
2019-07-19 18:46:16 +00:00
|
|
|
|
2019-05-23 23:01:24 +00:00
|
|
|
db.set_email_published(&fingerprint, &email)?;
|
2019-07-20 15:00:02 +00:00
|
|
|
counters::inc_address_published(&email);
|
2019-05-23 23:01:24 +00:00
|
|
|
|
2019-06-12 14:56:21 +00:00
|
|
|
Ok((fingerprint, email))
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn show_upload_verify(
|
|
|
|
rate_limiter: &RateLimiter,
|
|
|
|
token: String,
|
|
|
|
tpk_status: TpkStatus,
|
|
|
|
verify_state: VerifyTpkState,
|
2019-07-15 22:42:07 +00:00
|
|
|
is_new_key: bool,
|
2019-05-23 23:01:24 +00:00
|
|
|
) -> response::UploadResponse {
|
|
|
|
let key_fpr = verify_state.fpr.to_string();
|
|
|
|
if tpk_status.is_revoked {
|
2019-07-15 22:42:07 +00:00
|
|
|
return response::UploadResponse::Ok {
|
|
|
|
token,
|
|
|
|
key_fpr,
|
|
|
|
count_unparsed: 0,
|
|
|
|
is_revoked: true,
|
|
|
|
status: HashMap::new(),
|
|
|
|
is_new_key: false,
|
|
|
|
primary_uid: None,
|
|
|
|
};
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2019-07-15 22:42:07 +00:00
|
|
|
let primary_uid = tpk_status.email_status
|
|
|
|
.get(0)
|
|
|
|
.map(|(email, _)| email)
|
|
|
|
.cloned();
|
2019-05-23 23:01:24 +00:00
|
|
|
|
2019-06-08 15:15:46 +00:00
|
|
|
let count_unparsed = tpk_status.unparsed_uids;
|
|
|
|
|
2019-07-15 22:42:07 +00:00
|
|
|
response::UploadResponse::Ok { token, key_fpr, count_unparsed, is_revoked: false, status, is_new_key, primary_uid }
|
2019-05-23 23:01:24 +00:00
|
|
|
}
|