web: handle wkd requests

This commit is contained in:
Nora Widdecke 2022-02-17 16:48:55 +01:00
parent 8eb3984560
commit 70711191f1
4 changed files with 98 additions and 0 deletions

View File

@ -182,6 +182,15 @@ impl Filesystem {
].iter().collect()
}
/// Returns the WKD path to the given url-encoded domain and wkd-encoded local part.
fn link_wkd_by_domain_and_hash(&self, domain: &str, hash: &str) -> PathBuf {
[
&self.links_dir_wkd_by_email,
Path::new(&domain),
&path_split(&hash)
].iter().collect()
}
fn read_from_path(&self, path: &Path, allow_internal: bool) -> Option<String> {
use std::fs;
@ -579,6 +588,12 @@ impl Database for Filesystem {
self.read_from_path_bytes(&path, false)
}
// XXX: slow
fn by_domain_and_hash_wkd(&self, domain: &str, hash: &str) -> Option<Vec<u8>> {
let path = self.link_wkd_by_domain_and_hash(domain, hash);
self.read_from_path_bytes(&path, false)
}
// XXX: slow
fn by_kid(&self, kid: &KeyID) -> Option<String> {
let path = self.link_by_keyid(kid);

View File

@ -153,6 +153,7 @@ pub trait Database: Sync + Send {
fn by_kid(&self, kid: &KeyID) -> Option<String>;
fn by_email(&self, email: &Email) -> Option<String>;
fn by_email_wkd(&self, email: &Email) -> Option<Vec<u8>>;
fn by_domain_and_hash_wkd(&self, domain: &str, hash: &str) -> Option<Vec<u8>>;
fn check_link_fpr(&self, fpr: &Fingerprint, target: &Fingerprint) -> Result<Option<Fingerprint>>;

View File

@ -38,6 +38,7 @@ mod vks;
mod vks_web;
mod vks_api;
mod debug_web;
mod wkd;
use crate::web::maintenance::MaintenanceMode;
@ -69,6 +70,8 @@ pub enum MyResponse {
Xml(HagridTemplate),
#[response(status = 200, content_type = "application/pgp-keys")]
Key(String, Header<'static>),
#[response(status = 200, content_type = "application/octet-stream")]
WkdKey(Vec<u8>, Header<'static>),
#[response(status = 500, content_type = "html")]
ServerError(Template),
#[response(status = 404, content_type = "html")]
@ -121,6 +124,20 @@ impl MyResponse {
MyResponse::Key(armored_key, content_disposition)
}
pub fn wkd(binary_key: Vec<u8>, wkd_hash: &str) -> Self {
let content_disposition = Header::new(
rocket::http::hyper::header::CONTENT_DISPOSITION.as_str(),
ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![
DispositionParam::Filename(
Charset::Us_Ascii, None,
(wkd_hash.to_string() + ".pgp").into_bytes()),
],
}.to_string());
MyResponse::WkdKey(binary_key, content_disposition)
}
pub fn ise(e: anyhow::Error) -> Self {
eprintln!("Internal error: {:?}", e);
let ctx = templates::FiveHundred {
@ -384,6 +401,9 @@ fn rocket_factory(mut rocket: rocket::Rocket<rocket::Build>) -> Result<rocket::R
hkp::pks_add_form,
hkp::pks_add_form_data,
hkp::pks_internal_index,
// WKD
wkd::wkd_policy,
wkd::wkd_query,
// Manage
manage::vks_manage,
manage::vks_manage_key,
@ -941,6 +961,12 @@ pub mod tests {
Status::BadRequest, "not supported");
}
#[test]
fn wkd_policy() {
let (_tmpdir, client) = client().unwrap();
check_response(&client, "/.well-known/openpgpkey/example.org/policy",
Status::Ok, "");
}
/// Asserts that the given URI 404s.
pub fn check_null_response(client: &Client, uri: &str) {
@ -957,6 +983,11 @@ pub mod tests {
check_null_response(
&client, &format!("/pks/lookup?op=get&options=mr&search={}",
addr));
let (wkd_hash, domain) = crate::database::wkd::encode_wkd(addr).unwrap();
check_null_response(
&client,
&format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash));
}
/// Asserts that lookups by the given email are successful.
@ -982,6 +1013,12 @@ pub mod tests {
&client,
&format!("/search?q={}", addr),
&tpk, nr_uids);
let (wkd_hash, domain) = crate::database::wkd::encode_wkd(addr).unwrap();
check_wkd_response(
&client,
&format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash),
&tpk, nr_uids);
}
/// Asserts that the given URI returns a Cert matching the given
@ -1137,6 +1174,25 @@ pub mod tests {
&tpk, nr_uids);
}
/// Asserts that the given URI returns correct WKD response with a Cert
/// matching the given one, with the given number of userids.
pub fn check_wkd_response(client: &Client, uri: &str, tpk: &Cert,
nr_uids: usize) {
let response = client.get(uri).dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(),
Some(ContentType::new("application", "octet-stream")));
let body = response.into_bytes().unwrap();
let tpk_ = Cert::from_bytes(&body).unwrap();
assert_eq!(tpk.fingerprint(), tpk_.fingerprint());
assert_eq!(tpk.keys().map(|skb| skb.key().fingerprint())
.collect::<Vec<_>>(),
tpk_.keys().map(|skb| skb.key().fingerprint())
.collect::<Vec<_>>());
assert_eq!(tpk_.userids().count(), nr_uids);
}
fn check_verify_link(client: &Client, token: &str, address: &str, lang: &'static str) {
let encoded = ::url::form_urlencoded::Serializer::new(String::new())
.append_pair("token", token)

26
src/web/wkd.rs Normal file
View File

@ -0,0 +1,26 @@
use crate::database::{Database, KeyDatabase};
use crate::web::MyResponse;
// WKD queries
#[get("/.well-known/openpgpkey/<domain>/hu/<wkd_hash>")]
pub fn wkd_query(
db: &rocket::State<KeyDatabase>,
domain: String,
wkd_hash: String,
) -> MyResponse {
match db.by_domain_and_hash_wkd(&domain, &wkd_hash) {
Some(key) => MyResponse::wkd(key, &wkd_hash),
None => MyResponse::not_found_plain(
"No key found for this email address.",
),
}
}
// Policy requests.
// 200 response with an empty body.
#[get("/.well-known/openpgpkey/<_domain>/policy")]
pub fn wkd_policy(
_domain: String,
) -> MyResponse {
MyResponse::plain("".to_string())
}