Handle uploads of multiple keys.

- This is required for HKP.

  - I think allowing multiple keys in one upload is a good way to
    handle #75 too.  For old keys that are uploaded by accident, we
    get and publish the revocations, without bothering the user with
    verification mails.

  - One concern that is not addressed is uploading multiple keys with
    the same user id.  But, we do not handle this by design.

  - Fixes #75.
This commit is contained in:
Justus Winter 2019-03-11 12:07:26 +01:00
parent 9de280562b
commit cd750c3d78
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
4 changed files with 119 additions and 13 deletions

View File

@ -73,7 +73,6 @@ the full HKP protocol. The main limitations are:
- the `fingerprint` variable is ignored,
- the `nm` option is ignored,
- `op=index` returns either one or no keys,
- only one key may be submitted in one request,
- uploads are restricted to 1 MiB,
- all packets that aren't public keys, user IDs or signatures are filtered out.

View File

@ -101,7 +101,6 @@
<li>the <code>fingerprint</code> variable is ignored,</li>
<li>the <code>nm</code> option is ignored,</li>
<li><code>op=index</code> returns either one or no keys,</li>
<li>only one key may be submitted in one request,</li>
<li>uploads are restricted to 1 MiB,</li>
<li>all packets that aren't public keys, user IDs or signatures are filtered out.</li>
</ul>

View File

@ -893,6 +893,61 @@ mod tests {
&tpk);
}
#[test]
fn upload_two() {
let (tmpdir, config) = configuration().unwrap();
let filemail_into = tmpdir.path().join("filemail");
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 two keys and upload them.
let tpk_0 = TPKBuilder::autocrypt(
None, Some("foo@invalid.example.com".into()))
.generate().unwrap().0;
let tpk_1 = TPKBuilder::autocrypt(
None, Some("bar@invalid.example.com".into()))
.generate().unwrap().0;
let mut tpk_serialized = Vec::new();
tpk_0.serialize(&mut tpk_serialized).unwrap();
tpk_1.serialize(&mut tpk_serialized).unwrap();
let response = vks_publish_submit(&client, &tpk_serialized);
assert_eq!(response.status(), Status::SeeOther);
assert_eq!(response.headers().get_one("Location"),
Some("/vks/v1/publish?ok"));
// Prior to email confirmation, we should not be able to look
// them up by email address.
check_null_responses_by_email(&client, "foo@invalid.example.com");
check_null_responses_by_email(&client, "bar@invalid.example.com");
// And check that we can get them back via the machine readable
// interface.
check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
// And check that we can see the human-readable result page.
check_hr_responses_by_fingerprint(&client, &tpk_0);
check_hr_responses_by_fingerprint(&client, &tpk_1);
// Now check for the confirmation mails.
check_mails_and_confirm(&client, filemail_into.as_path());
check_mails_and_confirm(&client, filemail_into.as_path());
// Now lookups using the mail address should work.
check_mr_response(
&client,
"/vks/v1/by-email/foo@invalid.example.com",
&tpk_0, 1);
check_mr_response(
&client,
"/vks/v1/by-email/bar@invalid.example.com",
&tpk_1, 1);
}
/// Asserts that the given URI 404s.
fn check_null_response(client: &Client, uri: &str) {
let response = client.get(uri).dispatch();
@ -1063,6 +1118,52 @@ mod tests {
check_hr_responses_by_fingerprint(&client, &tpk);
}
#[test]
fn hkp_add_two() {
let (tmpdir, config) = configuration().unwrap();
let filemail_into = tmpdir.path().join("filemail");
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 two keys and upload them.
let tpk_0 = TPKBuilder::autocrypt(
None, Some("foo@invalid.example.com".into()))
.generate().unwrap().0;
let tpk_1 = TPKBuilder::autocrypt(
None, Some("bar@invalid.example.com".into()))
.generate().unwrap().0;
// 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_0.serialize(&mut w).unwrap();
tpk_1.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 response = client.post("/pks/add")
.body(post_data.as_bytes())
.header(ContentType::Form)
.dispatch();
assert_eq!(response.status(), Status::Ok);
let confirm_mail = pop_mail(filemail_into.as_path()).unwrap();
assert!(confirm_mail.is_none());
check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
check_hr_responses_by_fingerprint(&client, &tpk_0);
check_hr_responses_by_fingerprint(&client, &tpk_1);
}
/// Returns and removes the first mail it finds from the given
/// directory.
fn pop_mail(dir: &Path) -> Result<Option<SimpleSendableEmail>> {

View File

@ -195,20 +195,27 @@ where
R: Read,
{
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::TPK;
use sequoia_openpgp::tpk::TPKParser;
// First, parse all TPKs and error out if one fails.
let mut tpks = Vec::new();
for tpk in TPKParser::from_reader(reader)? {
tpks.push(tpk?);
}
let tpk = TPK::from_reader(reader)?;
let tokens = db.merge_or_publish(tpk)?;
let mut results: Vec<String> = vec!();
for tpk in tpks {
let tokens = db.merge_or_publish(tpk)?;
if let Some(mail_service) = mail_service {
for (email, token) in tokens {
mail_service.send_verification(
&email,
&token,
domain,
)?;
results.push(email.to_string());
if let Some(ref mail_service) = mail_service {
for (email, token) in tokens {
mail_service.send_verification(
&email,
&token,
domain,
)?;
results.push(email.to_string());
}
}
}