Implement upload via HKP.

- Fixes #68.
This commit is contained in:
Justus Winter 2019-03-05 16:15:03 +01:00
parent 946f0dd7e5
commit d38b80f26d
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
3 changed files with 86 additions and 18 deletions

View File

@ -36,8 +36,7 @@ OpenKeychain can use it directly. The differences to SKS are
- only exact matches for user IDs are returned (i.e. `exact=on` is
always assumed),
- `op=index` returns either one or no keys,
- all packets that aren't public keys, user IDs or signatures are filtered out,
- Uploading a key via the HKP interface is not supported.
- all packets that aren't public keys, user IDs or signatures are filtered out.
### VKS

View File

@ -51,7 +51,7 @@ mod queries {
use rocket::http::hyper::header::ContentDisposition;
#[derive(Responder)]
enum MyResponse {
pub enum MyResponse {
#[response(status = 200, content_type = "html")]
Success(Template),
#[response(status = 200, content_type = "plain")]
@ -638,6 +638,7 @@ fn rocket_factory(rocket: rocket::Rocket, db: Polymorphic) -> rocket::Rocket {
by_keyid,
// HKP
lookup,
upload::pks_add,
upload::vks_publish,
upload::vks_publish_submit,
// verification & deletion
@ -953,6 +954,61 @@ mod tests {
&tpk);
}
#[test]
fn hkp() {
let (tmpdir, config) = configuration().unwrap();
let filemail_into = tmpdir.path().join("filemail");
// eprintln!("LEAKING: {:?}", tmpdir);
// ::std::mem::forget(tmpdir);
let db = Polymorphic::Filesystem(
Filesystem::new(config.root().unwrap().to_path_buf()).unwrap());
let rocket = rocket_factory(rocket::custom(config), db);
let client = Client::new(rocket).expect("valid rocket instance");
// Generate a key and upload it.
let (tpk, _) = TPKBuilder::autocrypt(
None, Some("foo@invalid.example.com".into()))
.generate().unwrap();
// Prepare to /pks/add
let mut armored = Vec::new();
{
use sequoia_openpgp::armor::{Writer, Kind};
let mut w = Writer::new(&mut armored, Kind::PublicKey, &[])
.unwrap();
tpk.serialize(&mut w).unwrap();
}
let mut post_data = String::from("keytext=");
for enc in url::form_urlencoded::byte_serialize(&armored) {
post_data.push_str(enc);
}
// Add!
let mut response = client.post("/pks/add")
.body(post_data.as_bytes())
.header(ContentType::Form)
.dispatch();
assert_eq!(response.status(), Status::Ok);
let body = response.body_string().unwrap();
eprintln!("response: {}", body);
// Check that we do not get a confirmation mail.
let confirm_mail = pop_mail(filemail_into.as_path()).unwrap();
assert!(confirm_mail.is_none());
// We should not be able to look it up by email address.
check_null_responses_by_email(&client, "foo@invalid.example.com");
// And check that we can get it back via the machine readable
// interface.
check_mr_responses_by_fingerprint(&client, &tpk, 0);
// And check that we can see the human-readable result page.
check_hr_responses_by_fingerprint(&client, &tpk);
}
/// Returns and removes the first mail it finds from the given
/// directory.
fn pop_mail(dir: &Path) -> Result<Option<SimpleSendableEmail>> {

View File

@ -18,6 +18,8 @@ use web::Domain;
use std::io::Read;
use super::MyResponse;
mod template {
#[derive(Serialize)]
pub struct Upload {
@ -83,16 +85,26 @@ pub fn vks_publish_submit(
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
mail_service: State<mail::Service>, domain: State<Domain>,
) -> Flash<Redirect> {
match do_upload_hkp(db, cont_type, data, mail_service, domain) {
match do_upload_hkp(db, cont_type, data, Some(mail_service), domain) {
Ok(ok) => ok,
Err(err) => Flash::error(Redirect::to("/vks/v1/publish?err"), err.to_string()),
}
}
#[post("/pks/add", data = "<data>")]
pub fn pks_add(db: State<Polymorphic>, cont_type: &ContentType, data: Data,
domain: State<Domain>)
-> MyResponse {
match do_upload_hkp(db, cont_type, data, None, domain) {
Ok(_) => MyResponse::plain("Ok".into()),
Err(err) => MyResponse::ise(err),
}
}
// signature requires the request to have a `Content-Type`
fn do_upload_hkp(
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
mail_service: State<mail::Service>, domain: State<Domain>,
mail_service: Option<State<mail::Service>>, domain: State<Domain>,
) -> Result<Flash<Redirect>> {
if cont_type.is_form_data() {
// multipart/form-data
@ -142,7 +154,7 @@ fn do_upload_hkp(
fn process_upload(
boundary: &str, data: Data, db: &Polymorphic,
mail_service: State<mail::Service>,
mail_service: Option<State<mail::Service>>,
domain: &str,
) -> Result<Flash<Redirect>> {
// saves all fields, any field longer than 10kB goes to a temporary directory
@ -159,10 +171,9 @@ fn process_upload(
}
}
fn process_multipart(
entries: Entries, db: &Polymorphic, mail_service: State<mail::Service>,
domain: &str,
) -> Result<Flash<Redirect>> {
fn process_multipart(entries: Entries, db: &Polymorphic,
mail_service: Option<State<mail::Service>>,
domain: &str) -> Result<Flash<Redirect>> {
match entries.fields.get("keytext") {
Some(ent) if ent.len() == 1 => {
let reader = ent[0].data.readable()?;
@ -175,7 +186,7 @@ fn process_multipart(
}
fn process_key<R>(
reader: R, db: &Polymorphic, mail_service: State<mail::Service>,
reader: R, db: &Polymorphic, mail_service: Option<State<mail::Service>>,
domain: &str,
) -> Result<Flash<Redirect>>
where
@ -188,13 +199,15 @@ where
let tokens = db.merge_or_publish(tpk)?;
let mut results: Vec<String> = vec!();
for (email,token) in tokens {
mail_service.send_verification(
&email,
&token,
domain,
)?;
results.push(email.to_string());
if let Some(mail_service) = mail_service {
for (email, token) in tokens {
mail_service.send_verification(
&email,
&token,
domain,
)?;
results.push(email.to_string());
}
}
let json = serde_json::to_string(&results).unwrap();