feat: fail on too new backups (#6580)

this PR checks the number from `DCBACKUP?:` and also adds it to the
backup file and checks it there

closes #2294 if we would reopen it
This commit is contained in:
bjoern
2025-02-26 22:03:08 +01:00
committed by GitHub
parent 8c2207d15e
commit 483f4eaa17
6 changed files with 65 additions and 13 deletions

View File

@@ -23,6 +23,7 @@ use crate::events::EventType;
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
use crate::log::LogExt;
use crate::pgp;
use crate::qr::DCBACKUP_VERSION;
use crate::sql;
use crate::tools::{
create_folder, delete_file, get_filesuffix_lc, read_file, time, write_file, TempPathGuard,
@@ -392,6 +393,9 @@ async fn import_backup_stream_inner<R: tokio::io::AsyncRead + Unpin>(
.await
.context("cannot import unpacked database");
}
if res.is_ok() {
res = check_backup_version(context).await;
}
if res.is_ok() {
res = adjust_bcc_self(context).await;
}
@@ -776,6 +780,10 @@ async fn export_database(
.sql
.set_raw_config_int("backup_time", timestamp)
.await?;
context
.sql
.set_raw_config_int("backup_version", DCBACKUP_VERSION)
.await?;
sql::housekeeping(context).await.log_err(context).ok();
context
.sql
@@ -813,6 +821,15 @@ async fn adjust_bcc_self(context: &Context) -> Result<()> {
Ok(())
}
async fn check_backup_version(context: &Context) -> Result<()> {
let version = (context.sql.get_raw_config_int("backup_version").await?).unwrap_or(2);
ensure!(
version <= DCBACKUP_VERSION,
"Backup too new, please update Delta Chat"
);
Ok(())
}
#[cfg(test)]
mod tests {
use std::time::Duration;

View File

@@ -40,7 +40,11 @@ const HTTPS_SCHEME: &str = "https://";
const SHADOWSOCKS_SCHEME: &str = "ss://";
/// Backup transfer based on iroh-net.
pub(crate) const DCBACKUP2_SCHEME: &str = "DCBACKUP2:";
pub(crate) const DCBACKUP_SCHEME_PREFIX: &str = "DCBACKUP";
/// Version written to Backups and Backup-QR-Codes.
/// Imports will fail when they have a larger version.
pub(crate) const DCBACKUP_VERSION: i32 = 2;
/// Scanned QR code.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -118,6 +122,9 @@ pub enum Qr {
auth_token: String,
},
/// The QR code is a backup, but it is too new. The user has to update its Delta Chat.
BackupTooNew {},
/// Ask the user if they want to use the given service for video chats.
WebrtcInstance {
/// Server domain name.
@@ -296,7 +303,7 @@ pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
decode_tg_socks_proxy(context, qr)?
} else if qr.starts_with(SHADOWSOCKS_SCHEME) {
decode_shadowsocks_proxy(qr)?
} else if starts_with_ignore_case(qr, DCBACKUP2_SCHEME) {
} else if starts_with_ignore_case(qr, DCBACKUP_SCHEME_PREFIX) {
let qr_fixed = fix_add_second_device_qr(qr);
decode_backup2(&qr_fixed)?
} else if qr.starts_with(MAILTO_SCHEME) {
@@ -367,7 +374,9 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
ref auth_token,
} => {
let node_addr = serde_json::to_string(node_addr)?;
Ok(format!("{DCBACKUP2_SCHEME}{auth_token}&{node_addr}"))
Ok(format!(
"{DCBACKUP_SCHEME_PREFIX}{DCBACKUP_VERSION}:{auth_token}&{node_addr}"
))
}
_ => Err(anyhow!("Not a backup QR code")),
}
@@ -643,11 +652,19 @@ fn decode_shadowsocks_proxy(qr: &str) -> Result<Qr> {
})
}
/// Decodes a [`DCBACKUP2_SCHEME`] QR code.
/// Decodes a `DCBACKUP` QR code.
fn decode_backup2(qr: &str) -> Result<Qr> {
let payload = qr
.strip_prefix(DCBACKUP2_SCHEME)
.ok_or_else(|| anyhow!("Invalid DCBACKUP2 scheme"))?;
let version_and_payload = qr
.strip_prefix(DCBACKUP_SCHEME_PREFIX)
.ok_or_else(|| anyhow!("Invalid DCBACKUP scheme"))?;
let (version, payload) = version_and_payload
.split_once(':')
.context("DCBACKUP scheme separator missing")?;
let version: i32 = version.parse().context("Not a valid number")?;
if version > DCBACKUP_VERSION {
return Ok(Qr::BackupTooNew {});
}
let (auth_token, node_addr) = payload
.split_once('&')
.context("Backup QR code has no separator")?;

View File

@@ -917,5 +917,11 @@ async fn test_decode_backup() -> Result<()> {
let qr = check_qr(&ctx, r#"DCBACKUP2:AIvFjRFBt_aMiisSZ8P33JqY&{"node_id":"buzkyd4x76w66qtanjk5fm6ikeuo4quletajowsl3a3p7l6j23pa","info":{"relay_url":null,"direct_addresses":["192.168.1.5:12345"]}}"#).await?;
assert!(matches!(qr, Qr::Backup2 { .. }));
let qr = check_qr(&ctx, r#"DCBACKUP9:from-the-future"#).await?;
assert!(matches!(qr, Qr::BackupTooNew { .. }));
let qr = check_qr(&ctx, r#"DCBACKUP99:far-from-the-future"#).await?;
assert!(matches!(qr, Qr::BackupTooNew { .. }));
Ok(())
}