diff options
author | Nora Widdecke <nora@sequoia-pgp.org> | 2022-02-17 16:48:55 +0100 |
---|---|---|
committer | Vincent Breitmoser <look@my.amazin.horse> | 2022-02-26 16:40:54 +0100 |
commit | c1a88f88404ff6affdc97313b67085034e01bcb3 (patch) | |
tree | 9e1af3b27fad2134ac9f03a5c69ab7cadfde270b | |
parent | 329a9c09b0d49edc92c7688aaf9091cc121da549 (diff) |
web: handle wkd requests
-rw-r--r-- | database/src/fs.rs | 15 | ||||
-rw-r--r-- | database/src/lib.rs | 1 | ||||
-rw-r--r-- | src/web/mod.rs | 56 | ||||
-rw-r--r-- | src/web/wkd.rs | 26 |
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()) +} |