summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNora Widdecke <nora@sequoia-pgp.org>2022-02-17 16:48:55 +0100
committerVincent Breitmoser <look@my.amazin.horse>2022-02-26 16:40:54 +0100
commitc1a88f88404ff6affdc97313b67085034e01bcb3 (patch)
tree9e1af3b27fad2134ac9f03a5c69ab7cadfde270b
parent329a9c09b0d49edc92c7688aaf9091cc121da549 (diff)
web: handle wkd requests
-rw-r--r--database/src/fs.rs15
-rw-r--r--database/src/lib.rs1
-rw-r--r--src/web/mod.rs56
-rw-r--r--src/web/wkd.rs26
4 files changed, 98 insertions, 0 deletions
diff --git a/database/src/fs.rs b/database/src/fs.rs
index c7f3a33..d45bf23 100644
--- a/database/src/fs.rs
+++ b/database/src/fs.rs
@@ -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()
+ }
+
#[allow(clippy::nonminimal_bool)]
fn read_from_path(&self, path: &Path, allow_internal: bool) -> Option<String> {
use std::fs;
@@ -576,6 +585,12 @@ impl Database for Filesystem {
}
// 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);
self.read_from_path(&path, false)
diff --git a/database/src/lib.rs b/database/src/lib.rs
index a1ca706..bca1514 100644
--- a/database/src/lib.rs
+++ b/database/src/lib.rs
@@ -149,6 +149,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>>;
diff --git a/src/web/mod.rs b/src/web/mod.rs
index 78c7d2e..0c0e984 100644
--- a/src/web/mod.rs
+++ b/src/web/mod.rs
@@ -37,6 +37,7 @@ mod vks;
mod vks_web;
mod vks_api;
mod debug_web;
+mod wkd;
use crate::web::maintenance::MaintenanceMode;
@@ -68,6 +69,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")]
@@ -120,6 +123,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 {
@@ -383,6 +400,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,
@@ -938,6 +958,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) {
@@ -954,6 +980,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.
@@ -979,6 +1010,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
@@ -1134,6 +1171,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)
diff --git a/src/web/wkd.rs b/src/web/wkd.rs
new file mode 100644
index 0000000..f195b46
--- /dev/null
+++ b/src/web/wkd.rs
@@ -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())
+}