mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 22:36:30 +03:00
feat: Securejoin v3, encrypt all securejoin messages (#7754)
Close https://github.com/chatmail/core/issues/7396. Before reviewing,
you should read the issue description of
https://github.com/chatmail/core/issues/7396.
I recommend to review with hidden whitespace changes.
TODO:
- [x] Implement the new protocol
- [x] Make Rust tests pass
- [x] Make Python tests pass
- [x] Test it manually on a phone
- [x] Print the sent messages, and check that they look how they should:
[test_secure_join_group_with_mime_printed.txt](https://github.com/user-attachments/files/24800556/test_secure_join_group.txt)
- [x] Fix bug: If Alice has a second device, then Bob's chat won't be
shown yet on that second device. Also, Bob's contact isn't shown in her
contact list. As soon as either party writes something into the chat,
the that shows up and everything is fine. All of this is still a way
better UX than in WhatsApp, where Bob always has to write first 😂
Still, I should fix that.
- This is actually caused by a larger bug: AUTH tokens aren't synced if
there is no corresponding INVITE token.
- Fixed by 6b658a0e0
- [x] Either make a new `auth_tokens` table with a proper UNIQUE bound,
or put a UNIQUE bound on the `tokens` table
- [x] Benchmarking
- [x] TODOs in the code, maybe change naming of the new functions
- [x] Write test for interop with older DC (esp. that the original
securejoin runs if you remove the &v=3 param)
- [x] From a cryptography perspective, is it fine that vc-request is
encrypted with AUTH, rather than a separate secret (like INVITE)?
- [x] Make sure that QR codes without INVITE work, so that we can remove
it eventually
- [x] Self-review, and comment on some of my code changes to explain
what they do
- [x] ~~Maybe use a new table rather than reusing AUTH token.~~ See
https://github.com/chatmail/core/pull/7754#discussion_r2728544725
- [ ] Update documentation; I'll do that in a separate PR. All necessary
information is in the https://github.com/chatmail/core/issues/7396 issue
description
- [ ] Update tests and other code to use the new names (e.g.
`request-pubkey` rather than `request` and `pubkey` rather than
`auth-required`); I'll do that in a follow-up PR
**Backwards compatibility:**
Everything works seamlessly in my tests. If both devices are updated,
then the new protocol is used; otherwise, the old protocol is used. If
there is a not-yet-updated second device, it will correctly observe the
protocol, and mark the chat partner as verified.
Note that I removed the `Auto-Submitted: auto-replied` header from
securejoin messages. We don't need it ourselves, it's a cleartext header
that leaks too much information, and I can't see any reason to have it.
---------
Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
This commit is contained in:
30
src/qr.rs
30
src/qr.rs
@@ -61,6 +61,9 @@ pub enum Qr {
|
||||
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
|
||||
/// Whether the inviter supports the new Securejoin v3 protocol
|
||||
is_v3: bool,
|
||||
},
|
||||
|
||||
/// Ask the user whether to join the group.
|
||||
@@ -82,6 +85,9 @@ pub enum Qr {
|
||||
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
|
||||
/// Whether the inviter supports the new Securejoin v3 protocol
|
||||
is_v3: bool,
|
||||
},
|
||||
|
||||
/// Ask whether to join the broadcast channel.
|
||||
@@ -106,6 +112,9 @@ pub enum Qr {
|
||||
invitenumber: String,
|
||||
/// Authentication code.
|
||||
authcode: String,
|
||||
|
||||
/// Whether the inviter supports the new Securejoin v3 protocol
|
||||
is_v3: bool,
|
||||
},
|
||||
|
||||
/// Contact fingerprint is verified.
|
||||
@@ -483,7 +492,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
|
||||
let name = decode_name(¶m, "n")?.unwrap_or_default();
|
||||
|
||||
let invitenumber = param
|
||||
let mut invitenumber = param
|
||||
.get("i")
|
||||
// For historic reansons, broadcasts currently use j instead of i for the invitenumber:
|
||||
.or_else(|| param.get("j"))
|
||||
@@ -501,6 +510,16 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
let grpname = decode_name(¶m, "g")?;
|
||||
let broadcast_name = decode_name(¶m, "b")?;
|
||||
|
||||
let mut is_v3 = param.get("v") == Some(&"3");
|
||||
|
||||
if authcode.is_some() && invitenumber.is_none() {
|
||||
// Securejoin v3 doesn't need an invitenumber.
|
||||
// We want to remove the invitenumber and the `v=3` parameter eventually;
|
||||
// therefore, we accept v3 QR codes without an invitenumber.
|
||||
is_v3 = true;
|
||||
invitenumber = Some("".to_string());
|
||||
}
|
||||
|
||||
if let (Some(addr), Some(invitenumber), Some(authcode)) = (&addr, invitenumber, authcode) {
|
||||
let addr = ContactAddress::new(addr)?;
|
||||
let (contact_id, _) = Contact::add_or_lookup_ex(
|
||||
@@ -519,7 +538,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
.await
|
||||
.with_context(|| format!("can't check if address {addr:?} is our address"))?
|
||||
{
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
if token::exists(context, token::Namespace::Auth, &authcode).await? {
|
||||
Ok(Qr::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
@@ -546,6 +565,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
})
|
||||
}
|
||||
} else if let (Some(grpid), Some(name)) = (grpid, broadcast_name) {
|
||||
@@ -554,7 +574,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
.await
|
||||
.with_context(|| format!("Can't check if {addr:?} is our address"))?
|
||||
{
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
if token::exists(context, token::Namespace::Auth, &authcode).await? {
|
||||
Ok(Qr::WithdrawJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
@@ -581,10 +601,11 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
})
|
||||
}
|
||||
} else if context.is_self_addr(&addr).await? {
|
||||
if token::exists(context, token::Namespace::InviteNumber, &invitenumber).await? {
|
||||
if token::exists(context, token::Namespace::Auth, &authcode).await? {
|
||||
Ok(Qr::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
@@ -605,6 +626,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
})
|
||||
}
|
||||
} else if let Some(addr) = addr {
|
||||
|
||||
Reference in New Issue
Block a user