db: correctly deal with user ids uploaded for already-verified email addresses

This commit is contained in:
Vincent Breitmoser 2020-05-10 23:53:48 +02:00
parent a02b5ac9ca
commit cdda40e126
No known key found for this signature in database
GPG Key ID: 7BD18320DEADFA11
3 changed files with 104 additions and 35 deletions

View File

@ -895,6 +895,13 @@ mod tests {
db.check_consistency().expect("inconsistent database");
}
#[test]
fn same_email_4() {
let (_tmp_dir, mut db, log_path) = open_db();
test::test_same_email_4(&mut db, &log_path);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn no_selfsig() {
let (_tmp_dir, mut db, log_path) = open_db();

View File

@ -230,12 +230,7 @@ pub trait Database: Sync + Send {
let published_tpk_old = self
.by_fpr(&fpr_primary)
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok());
let published_uids: Vec<UserID> = published_tpk_old
.as_ref()
.map(|tpk| tpk.userids()
.map(|binding| binding.userid().clone())
.collect()
).unwrap_or_default();
let published_emails = published_tpk_old.as_ref().map(|cert| tpk_get_emails(cert)).unwrap_or_default();
let unparsed_uids = full_tpk_new
.userids()
@ -246,21 +241,24 @@ pub trait Database: Sync + Send {
let mut email_status: Vec<_> = full_tpk_new
.userids()
.bundles()
.filter(|binding| known_uids.contains(binding.userid()))
.flat_map(|binding| {
let uid = binding.userid();
if let Ok(email) = Email::try_from(uid) {
if is_status_revoked(binding.revoked(&*POLICY, None)) {
Some((email, EmailAddressStatus::Revoked))
} else if !is_revoked && published_uids.contains(uid) {
Some((email, EmailAddressStatus::Published))
} else {
Some((email, EmailAddressStatus::NotPublished))
}
.map(|binding| {
if let Ok(email) = Email::try_from(binding.userid()) {
Some((binding, email))
} else {
None
}
})
.flatten()
.filter(|(binding, email)| known_uids.contains(binding.userid()) || published_emails.contains(email))
.flat_map(|(binding, email)| {
if is_status_revoked(binding.revoked(&*POLICY, None)) {
Some((email, EmailAddressStatus::Revoked))
} else if !is_revoked && published_emails.contains(&email) {
Some((email, EmailAddressStatus::Published))
} else {
Some((email, EmailAddressStatus::NotPublished))
}
})
.collect();
email_status.sort();
// EmailAddressStatus is ordered published, unpublished, revoked. if there are multiple for
@ -274,7 +272,7 @@ pub trait Database: Sync + Send {
// If the key is revoked, consider all uids revoked
let newly_revoked_uids: Vec<&UserID> = if is_revoked {
published_uids.iter().collect()
full_tpk_new.userids().bundles().map(|binding| binding.userid()).collect()
} else {
let revoked_uids: Vec<UserID> = full_tpk_new
.userids()
@ -283,19 +281,28 @@ pub trait Database: Sync + Send {
.map(|binding| binding.userid().clone())
.collect();
published_uids.iter()
.filter(|uid| revoked_uids.contains(uid))
.collect()
published_tpk_old
.as_ref()
.map(|tpk| tpk
.userids()
.bundles()
.map(|binding| binding.userid())
.filter(|uid| revoked_uids.contains(uid))
.collect()
).unwrap_or_default()
};
let published_tpk_new = tpk_filter_userids(
&full_tpk_new, |uid| {
published_uids.contains(uid) && !newly_revoked_uids.contains(&uid)
if let Ok(email) = Email::try_from(uid) {
published_emails.contains(&email) && !newly_revoked_uids.contains(&uid)
} else {
false
}
})?;
let newly_revoked_emails: Vec<Email> = published_uids.iter()
.map(|uid| Email::try_from(uid).ok())
.flatten()
let newly_revoked_emails: Vec<&Email> = published_emails
.iter()
.filter(|email| {
let has_unrevoked_userid = published_tpk_new
.userids()
@ -304,7 +311,7 @@ pub trait Database: Sync + Send {
.map(|binding| binding.userid())
.map(|uid| Email::try_from(uid).ok())
.flatten()
.any(|unrevoked_email| unrevoked_email == *email);
.any(|unrevoked_email| &unrevoked_email == *email);
!has_unrevoked_userid
}).collect();
@ -675,6 +682,14 @@ pub trait Database: Sync + Send {
fn check_consistency(&self) -> Result<()>;
}
fn tpk_get_emails(cert: &Cert) -> Vec<Email> {
cert
.userids()
.map(|binding| Email::try_from(binding.userid()))
.flatten()
.collect()
}
pub fn tpk_get_linkable_fprs(tpk: &Cert) -> Vec<Fingerprint> {
let ref signing_capable = KeyFlags::empty()
.set_signing(true)

View File

@ -180,20 +180,13 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) {
// publish w/ one uid less
{
let packets =
tpk.clone().into_packet_pile().into_children().filter(|pkt| {
match pkt {
Packet::UserID(ref uid) => *uid != uid1,
_ => true,
}
});
let pile : PacketPile = packets.collect::<Vec<Packet>>().into();
let short_tpk = Cert::from_packet_pile(pile).unwrap();
let short_tpk = cert_without_uid(tpk.clone(), &uid1);
let tpk_status = db.merge(short_tpk).unwrap().into_tpk_status();
assert_eq!(TpkStatus {
is_revoked: false,
email_status: vec!(
(email1.clone(), EmailAddressStatus::Published),
(email2.clone(), EmailAddressStatus::Published),
),
unparsed_uids: 0,
@ -1013,6 +1006,48 @@ pub fn test_same_email_3(db: &mut impl Database, log_path: &Path) {
vec![ uid2.clone() ]);
}
// If a key has a verified email address, make sure newly uploaded user
// ids with the same email are published as well.
pub fn test_same_email_4(db: &mut impl Database, log_path: &Path) {
let str_uid1 = "A <test@example.com>";
let str_uid2 = "B <test@example.com>";
let tpk = CertBuilder::new()
.add_userid(str_uid1)
.add_userid(str_uid2)
.generate()
.unwrap()
.0;
let uid1 = UserID::from(str_uid1);
let uid2 = UserID::from(str_uid2);
let email = Email::from_str(str_uid1).unwrap();
let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
let cert_uid_1 = cert_without_uid(tpk.clone(), &uid2);
let cert_uid_2 = cert_without_uid(tpk.clone(), &uid1);
// upload key
let tpk_status = db.merge(cert_uid_1.clone()).unwrap().into_tpk_status();
check_log_entry(log_path, &fpr);
db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap();
assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]),
vec![ uid1.clone() ]);
let tpk_status = db.merge(cert_uid_2.clone()).unwrap().into_tpk_status();
check_log_entry(log_path, &fpr);
assert_eq!(TpkStatus {
is_revoked: false,
email_status: vec!(
(email.clone(), EmailAddressStatus::Published),
),
unparsed_uids: 0,
}, tpk_status);
// fetch by both user ids. We should still get both user ids.
assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]),
vec![ uid1.clone(), uid2.clone() ]);
}
pub fn test_bad_uids(db: &mut impl Database, log_path: &Path) {
let str_uid1 = "foo@bar.example <foo@bar.example>";
let str_uid2 = "A <test@example.com>";
@ -1083,3 +1118,15 @@ fn check_log_entry(log_path: &Path, fpr: &Fingerprint) {
assert_eq!(last_entry, fpr.to_string());
}
fn cert_without_uid(cert: Cert, removed_uid: &UserID) -> Cert {
let packets =
cert.into_packet_pile().into_children().filter(|pkt| {
match pkt {
Packet::UserID(ref uid) => uid != removed_uid,
_ => true,
}
});
let pile : PacketPile = packets.collect::<Vec<Packet>>().into();
Cert::from_packet_pile(pile).unwrap()
}