feat: add secondary verified key

When a key is gossiped for the contact in a verified chat,
it is stored in the secondary verified key slot.

The messages are then encrypted to the secondary verified key
if they are also encrypted to the contact introducing this secondary key.

Chat-Group-Member-Added no longer updates the verified key.
Verified group recovery only relies on the secondary verified key.

When a message is received from a contact
signed with a secondary verified key,
secondary verified key replaces the primary verified key.
When verified key is changed for the contact
in response to receiving a message
signed with a secondary verified key,
"Setup changed" message is added
to the same chat where the message is received.
This commit is contained in:
link2xt
2023-11-01 23:23:25 +00:00
parent 57e34abe98
commit ce016eb567
7 changed files with 246 additions and 60 deletions

View File

@@ -1,7 +1,5 @@
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module.
use std::collections::HashSet;
use anyhow::{Context as _, Error, Result};
use num_traits::FromPrimitive;
@@ -40,7 +38,7 @@ pub enum PeerstateVerifiedStatus {
}
/// Peerstate represents the state of an Autocrypt peer.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Peerstate {
/// E-mail address of the contact.
pub addr: String,
@@ -82,13 +80,24 @@ pub struct Peerstate {
/// Fingerprint of the verified public key.
pub verified_key_fingerprint: Option<Fingerprint>,
/// The address that introduced this verified key.
pub verifier: Option<String>,
/// Secondary public verified key of the contact.
/// It could be a contact gossiped by another verified contact in a shared group
/// or a key that was previously used as a verified key.
pub secondary_verified_key: Option<SignedPublicKey>,
/// Fingerprint of the secondary verified public key.
pub secondary_verified_key_fingerprint: Option<Fingerprint>,
/// The address that introduced secondary verified key.
pub secondary_verifier: Option<String>,
/// True if it was detected
/// that the fingerprint of the key used in chats with
/// opportunistic encryption was changed after Peerstate creation.
pub fingerprint_changed: bool,
/// The address that verified this contact
pub verifier: Option<String>,
}
impl Peerstate {
@@ -106,8 +115,11 @@ impl Peerstate {
gossip_timestamp: 0,
verified_key: None,
verified_key_fingerprint: None,
fingerprint_changed: false,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
fingerprint_changed: false,
}
}
@@ -132,8 +144,11 @@ impl Peerstate {
gossip_timestamp: message_time,
verified_key: None,
verified_key_fingerprint: None,
fingerprint_changed: false,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
fingerprint_changed: false,
}
}
@@ -141,7 +156,10 @@ impl Peerstate {
pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint, verifier \
verified_key, verified_key_fingerprint, \
verifier, \
secondary_verified_key, secondary_verified_key_fingerprint, \
secondary_verifier \
FROM acpeerstates \
WHERE addr=? COLLATE NOCASE LIMIT 1;";
Self::from_stmt(context, query, (addr,)).await
@@ -154,7 +172,10 @@ impl Peerstate {
) -> Result<Option<Peerstate>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint, verifier \
verified_key, verified_key_fingerprint, \
verifier, \
secondary_verified_key, secondary_verified_key_fingerprint, \
secondary_verifier \
FROM acpeerstates \
WHERE public_key_fingerprint=? \
OR gossip_key_fingerprint=? \
@@ -174,7 +195,10 @@ impl Peerstate {
) -> Result<Option<Peerstate>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint, verifier \
verified_key, verified_key_fingerprint, \
verifier, \
secondary_verified_key, secondary_verified_key_fingerprint, \
secondary_verifier \
FROM acpeerstates \
WHERE verified_key_fingerprint=? \
OR addr=? COLLATE NOCASE \
@@ -191,11 +215,6 @@ impl Peerstate {
let peerstate = context
.sql
.query_row_optional(query, params, |row| {
// all the above queries start with this: SELECT
// addr, last_seen, last_seen_autocrypt, prefer_encrypted,
// public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
// gossip_key_fingerprint, verified_key, verified_key_fingerprint
let res = Peerstate {
addr: row.get("addr")?,
last_seen: row.get("last_seen")?,
@@ -230,11 +249,24 @@ impl Peerstate {
.map(|s| s.parse::<Fingerprint>())
.transpose()
.unwrap_or_default(),
fingerprint_changed: false,
verifier: {
let verifier: Option<String> = row.get("verifier")?;
verifier.filter(|verifier| !verifier.is_empty())
verifier.filter(|s| !s.is_empty())
},
secondary_verified_key: row
.get("secondary_verified_key")
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok()),
secondary_verified_key_fingerprint: row
.get::<_, Option<String>>("secondary_verified_key_fingerprint")?
.map(|s| s.parse::<Fingerprint>())
.transpose()
.unwrap_or_default(),
secondary_verifier: {
let secondary_verifier: Option<String> = row.get("secondary_verifier")?;
secondary_verifier.filter(|s| !s.is_empty())
},
fingerprint_changed: false,
};
Ok(res)
@@ -421,8 +453,8 @@ impl Peerstate {
/// Make sure to call `self.save_to_db` to save these changes
/// Params:
/// verifier:
/// The address which verifies the given contact
/// If we are verifying the contact, use that contacts address
/// The address which introduces the given contact.
/// If we are verifying the contact, use that contacts address.
pub fn set_verified(
&mut self,
which_key: PeerstateKeyType,
@@ -461,6 +493,19 @@ impl Peerstate {
}
}
/// Sets current gossiped key as the secondary verified key.
///
/// If gossiped key is the same as the current verified key,
/// do nothing to avoid overwriting secondary verified key
/// which may be different.
pub fn set_secondary_verified_key_from_gossip(&mut self, verifier: String) {
if self.gossip_key_fingerprint != self.verified_key_fingerprint {
self.secondary_verified_key = self.gossip_key.clone();
self.secondary_verified_key_fingerprint = self.gossip_key_fingerprint.clone();
self.secondary_verifier = Some(verifier);
}
}
/// Saves the peerstate to the database.
pub async fn save_to_db(&self, sql: &Sql) -> Result<()> {
sql.execute(
@@ -475,9 +520,12 @@ impl Peerstate {
gossip_key_fingerprint,
verified_key,
verified_key_fingerprint,
addr,
verifier)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
verifier,
secondary_verified_key,
secondary_verified_key_fingerprint,
secondary_verifier,
addr)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT (addr)
DO UPDATE SET
last_seen = excluded.last_seen,
@@ -490,7 +538,10 @@ impl Peerstate {
gossip_key_fingerprint = excluded.gossip_key_fingerprint,
verified_key = excluded.verified_key,
verified_key_fingerprint = excluded.verified_key_fingerprint,
verifier = excluded.verifier",
verifier = excluded.verifier,
secondary_verified_key = excluded.secondary_verified_key,
secondary_verified_key_fingerprint = excluded.secondary_verified_key_fingerprint,
secondary_verifier = excluded.secondary_verifier",
(
self.last_seen,
self.last_seen_autocrypt,
@@ -502,23 +553,19 @@ impl Peerstate {
self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
&self.addr,
self.verifier.as_deref().unwrap_or(""),
self.secondary_verified_key.as_ref().map(|k| k.to_bytes()),
self.secondary_verified_key_fingerprint
.as_ref()
.map(|fp| fp.hex()),
self.secondary_verifier.as_deref().unwrap_or(""),
&self.addr,
),
)
.await?;
Ok(())
}
/// Returns true if verified key is contained in the given set of fingerprints.
pub fn has_verified_key(&self, fingerprints: &HashSet<Fingerprint>) -> bool {
if let Some(vkc) = &self.verified_key_fingerprint {
fingerprints.contains(vkc) && self.verified_key.is_some()
} else {
false
}
}
/// Returns the address that verified the contact
pub fn get_verifier(&self) -> Option<&str> {
self.verifier.as_deref()
@@ -769,8 +816,11 @@ mod tests {
gossip_key_fingerprint: Some(pub_key.fingerprint()),
verified_key: Some(pub_key.clone()),
verified_key_fingerprint: Some(pub_key.fingerprint()),
fingerprint_changed: false,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
fingerprint_changed: false,
};
assert!(
@@ -809,8 +859,11 @@ mod tests {
gossip_key_fingerprint: None,
verified_key: None,
verified_key_fingerprint: None,
fingerprint_changed: false,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
fingerprint_changed: false,
};
assert!(
@@ -842,8 +895,11 @@ mod tests {
gossip_key_fingerprint: None,
verified_key: None,
verified_key_fingerprint: None,
fingerprint_changed: false,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
fingerprint_changed: false,
};
assert!(
@@ -905,8 +961,11 @@ mod tests {
gossip_key_fingerprint: None,
verified_key: None,
verified_key_fingerprint: None,
fingerprint_changed: false,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
fingerprint_changed: false,
};
peerstate.apply_header(&header, 100);