mirror of
https://github.com/chatmail/core.git
synced 2026-05-03 21:36:29 +03:00
Don't include the broadcast's shared secret in the QR code
This commit is contained in:
@@ -46,10 +46,6 @@ pub enum QrObject {
|
|||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
|
|
||||||
/// The secret shared between all members,
|
|
||||||
/// used to symmetrically encrypt&decrypt messages.
|
|
||||||
shared_secret: String,
|
|
||||||
},
|
},
|
||||||
/// Contact fingerprint is verified.
|
/// Contact fingerprint is verified.
|
||||||
///
|
///
|
||||||
@@ -230,7 +226,6 @@ impl From<Qr> for QrObject {
|
|||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
authcode,
|
authcode,
|
||||||
shared_secret,
|
|
||||||
} => {
|
} => {
|
||||||
let contact_id = contact_id.to_u32();
|
let contact_id = contact_id.to_u32();
|
||||||
let fingerprint = fingerprint.to_string();
|
let fingerprint = fingerprint.to_string();
|
||||||
@@ -240,7 +235,6 @@ impl From<Qr> for QrObject {
|
|||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
authcode,
|
authcode,
|
||||||
shared_secret,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Qr::FprOk { contact_id } => {
|
Qr::FprOk { contact_id } => {
|
||||||
|
|||||||
55
src/qr.rs
55
src/qr.rs
@@ -20,7 +20,7 @@ use crate::message::Message;
|
|||||||
use crate::net::http::post_empty;
|
use crate::net::http::post_empty;
|
||||||
use crate::net::proxy::{DEFAULT_SOCKS_PORT, ProxyConfig};
|
use crate::net::proxy::{DEFAULT_SOCKS_PORT, ProxyConfig};
|
||||||
use crate::token;
|
use crate::token;
|
||||||
use crate::tools::{validate_broadcast_shared_secret, validate_id};
|
use crate::tools::validate_id;
|
||||||
|
|
||||||
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
|
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
|
||||||
const IDELTACHAT_SCHEME: &str = "https://i.delta.chat/#";
|
const IDELTACHAT_SCHEME: &str = "https://i.delta.chat/#";
|
||||||
@@ -97,8 +97,6 @@ pub enum Qr {
|
|||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
|
|
||||||
shared_secret: String,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Contact fingerprint is verified.
|
/// Contact fingerprint is verified.
|
||||||
@@ -398,7 +396,7 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
|
|||||||
|
|
||||||
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
|
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=BROADCAST_NAME&x=BROADCAST_ID&s=AUTH&b=BROADCAST_SHARED_SECRET`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&b=BROADCAST_NAME&x=BROADCAST_ID&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
|
||||||
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||||
let payload = qr
|
let payload = qr
|
||||||
@@ -457,24 +455,9 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
.get("x")
|
.get("x")
|
||||||
.filter(|&s| validate_id(s))
|
.filter(|&s| validate_id(s))
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
let broadcast_shared_secret = param
|
|
||||||
.get("b")
|
|
||||||
.filter(|&s| validate_broadcast_shared_secret(s))
|
|
||||||
.map(|s| s.to_string());
|
|
||||||
|
|
||||||
let grpname = if grpid.is_some() {
|
let grpname = decode_chat_name(¶m, &grpid, "g")?;
|
||||||
if let Some(encoded_name) = param.get("g") {
|
let broadcast_name = decode_chat_name(¶m, &grpid, "b")?;
|
||||||
let encoded_name = encoded_name.replace('+', "%20"); // sometimes spaces are encoded as `+`
|
|
||||||
match percent_decode_str(&encoded_name).decode_utf8() {
|
|
||||||
Ok(name) => Some(name.to_string()),
|
|
||||||
Err(err) => bail!("Invalid group name: {}", err),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let (Some(addr), Some(invitenumber), Some(authcode)) =
|
if let (Some(addr), Some(invitenumber), Some(authcode)) =
|
||||||
(&addr, invitenumber, authcode.clone())
|
(&addr, invitenumber, authcode.clone())
|
||||||
@@ -549,13 +532,8 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
authcode,
|
authcode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if let (
|
} else if let (Some(addr), Some(broadcast_name), Some(grpid), Some(authcode)) =
|
||||||
Some(addr),
|
(&addr, broadcast_name, grpid, authcode)
|
||||||
Some(broadcast_name),
|
|
||||||
Some(grpid),
|
|
||||||
Some(authcode),
|
|
||||||
Some(shared_secret),
|
|
||||||
) = (&addr, grpname, grpid, authcode, broadcast_shared_secret)
|
|
||||||
{
|
{
|
||||||
// This is a broadcast channel invite link.
|
// This is a broadcast channel invite link.
|
||||||
// TODO code duplication with the previous block
|
// TODO code duplication with the previous block
|
||||||
@@ -577,7 +555,6 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
authcode,
|
authcode,
|
||||||
shared_secret,
|
|
||||||
})
|
})
|
||||||
} else if let Some(addr) = addr {
|
} else if let Some(addr) = addr {
|
||||||
let fingerprint = fingerprint.hex();
|
let fingerprint = fingerprint.hex();
|
||||||
@@ -600,6 +577,26 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_chat_name(
|
||||||
|
param: &BTreeMap<&str, &str>,
|
||||||
|
grpid: &Option<String>,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<Option<String>> {
|
||||||
|
if grpid.is_some() {
|
||||||
|
if let Some(encoded_name) = param.get(key) {
|
||||||
|
let encoded_name = encoded_name.replace('+', "%20"); // sometimes spaces are encoded as `+`
|
||||||
|
match percent_decode_str(&encoded_name).decode_utf8() {
|
||||||
|
Ok(name) => Ok(Some(name.to_string())),
|
||||||
|
Err(err) => bail!("Invalid group name: {}", err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// scheme: `https://i.delta.chat[/]#FINGERPRINT&a=ADDR[&OPTIONAL_PARAMS]`
|
/// scheme: `https://i.delta.chat[/]#FINGERPRINT&a=ADDR[&OPTIONAL_PARAMS]`
|
||||||
async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result<Qr> {
|
async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result<Qr> {
|
||||||
let qr = qr.replacen(prefix, OPENPGP4FPR_SCHEME, 1);
|
let qr = qr.replacen(prefix, OPENPGP4FPR_SCHEME, 1);
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on
|
|||||||
use crate::simplify;
|
use crate::simplify;
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
use crate::sync::Sync::*;
|
use crate::sync::Sync::*;
|
||||||
use crate::tools::{self, buf_compress, create_broadcast_shared_secret, remove_subject_prefix};
|
use crate::tools::{
|
||||||
|
self, buf_compress, create_broadcast_shared_secret, remove_subject_prefix,
|
||||||
|
validate_broadcast_shared_secret,
|
||||||
|
};
|
||||||
use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
|
use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
|
||||||
use crate::{contact, imap};
|
use crate::{contact, imap};
|
||||||
|
|
||||||
@@ -3569,7 +3572,11 @@ async fn apply_in_broadcast_changes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
|
if let Some(secret) = mime_parser.get_header(HeaderDef::ChatBroadcastSecret) {
|
||||||
|
if validate_broadcast_shared_secret(secret) {
|
||||||
save_broadcast_shared_secret(context, chat.id, secret).await?;
|
save_broadcast_shared_secret(context, chat.id, secret).await?;
|
||||||
|
} else {
|
||||||
|
warn!(context, "Not saving invalid broadcast secret");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if send_event_chat_modified {
|
if send_event_chat_modified {
|
||||||
|
|||||||
@@ -109,17 +109,13 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
|
|||||||
let broadcast_name = chat.get_name();
|
let broadcast_name = chat.get_name();
|
||||||
let broadcast_name_urlencoded =
|
let broadcast_name_urlencoded =
|
||||||
utf8_percent_encode(broadcast_name, NON_ALPHANUMERIC).to_string();
|
utf8_percent_encode(broadcast_name, NON_ALPHANUMERIC).to_string();
|
||||||
let broadcast_secret = load_broadcast_shared_secret(context, chat.id)
|
|
||||||
.await?
|
|
||||||
.context("Could not find broadcast secret")?;
|
|
||||||
format!(
|
format!(
|
||||||
"https://i.delta.chat/#{}&a={}&g={}&x={}&s={}&b={}",
|
"https://i.delta.chat/#{}&a={}&b={}&x={}&s={}",
|
||||||
fingerprint.hex(),
|
fingerprint.hex(),
|
||||||
self_addr_urlencoded,
|
self_addr_urlencoded,
|
||||||
&broadcast_name_urlencoded,
|
&broadcast_name_urlencoded,
|
||||||
&chat.grpid,
|
&chat.grpid,
|
||||||
&auth,
|
&auth,
|
||||||
broadcast_secret
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// parameters used: a=g=x=i=s=
|
// parameters used: a=g=x=i=s=
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ pub enum QrInvite {
|
|||||||
broadcast_name: String,
|
broadcast_name: String,
|
||||||
grpid: String,
|
grpid: String,
|
||||||
authcode: String,
|
authcode: String,
|
||||||
shared_secret: String,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,14 +128,12 @@ impl TryFrom<Qr> for QrInvite {
|
|||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
authcode,
|
authcode,
|
||||||
shared_secret,
|
|
||||||
} => Ok(QrInvite::Broadcast {
|
} => Ok(QrInvite::Broadcast {
|
||||||
broadcast_name,
|
broadcast_name,
|
||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
authcode,
|
authcode,
|
||||||
shared_secret,
|
|
||||||
}),
|
}),
|
||||||
_ => bail!("Unsupported QR type: {qr:?}"),
|
_ => bail!("Unsupported QR type: {qr:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ pub(crate) fn validate_id(s: &str) -> bool {
|
|||||||
|
|
||||||
pub(crate) fn validate_broadcast_shared_secret(s: &str) -> bool {
|
pub(crate) fn validate_broadcast_shared_secret(s: &str) -> bool {
|
||||||
let alphabet = base64::alphabet::URL_SAFE.as_str();
|
let alphabet = base64::alphabet::URL_SAFE.as_str();
|
||||||
s.chars().all(|c| alphabet.contains(c)) && s.len() == 43
|
s.chars().all(|c| alphabet.contains(c)) && s.len() >= 43 && s.len() <= 100
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function generates a Message-ID that can be used for a new outgoing message.
|
/// Function generates a Message-ID that can be used for a new outgoing message.
|
||||||
|
|||||||
Reference in New Issue
Block a user