summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-09-25 13:53:58 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-09-25 13:53:58 +0200
commit810e198364ad36e8163bab0de7e2d3f4143d5f0c (patch)
tree902e58bdd95f508dbcbe4a0883e88ecb7a1962ea
parent3ab4829a7a14e087807f807a4e0e643f18d26db8 (diff)
parentfed13c26db8754e0c1686adf621658d918cc43d8 (diff)
downloadmullvadvpn-810e198364ad36e8163bab0de7e2d3f4143d5f0c.tar.xz
mullvadvpn-810e198364ad36e8163bab0de7e2d3f4143d5f0c.zip
Merge branch 'stop-using-the-old-version-endpoint-for-checking-if-an-app-des-2370'
-rw-r--r--Cargo.lock1
-rw-r--r--mullvad-api/Cargo.toml1
-rw-r--r--mullvad-api/src/version.rs52
-rw-r--r--mullvad-daemon/src/version/check.rs51
-rw-r--r--mullvad-update/src/version.rs39
-rw-r--r--test/Cargo.lock1
6 files changed, 106 insertions, 39 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3dad552b8f..801bf03b00 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2986,6 +2986,7 @@ dependencies = [
"mullvad-fs",
"mullvad-types",
"mullvad-update",
+ "mullvad-version",
"rustls-pemfile 2.1.3",
"serde",
"serde_json",
diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml
index 1ffd0bc08c..e7750f183e 100644
--- a/mullvad-api/Cargo.toml
+++ b/mullvad-api/Cargo.toml
@@ -53,6 +53,7 @@ mullvad-api-constants = { path = "./mullvad-api-constants" }
mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" }
mullvad-fs = { path = "../mullvad-fs" }
mullvad-types = { path = "../mullvad-types" }
+mullvad-version = { path = "../mullvad-version" }
talpid-types = { path = "../talpid-types" }
talpid-time = { path = "../talpid-time" }
diff --git a/mullvad-api/src/version.rs b/mullvad-api/src/version.rs
index a78e1d784f..3f3c4a4e7e 100644
--- a/mullvad-api/src/version.rs
+++ b/mullvad-api/src/version.rs
@@ -1,8 +1,9 @@
use std::future::Future;
+use std::str::FromStr;
use std::sync::Arc;
use http::StatusCode;
-use mullvad_update::version::{VersionInfo, VersionParameters};
+use mullvad_update::version::{VersionInfo, VersionParameters, is_version_supported};
type AppVersion = String;
@@ -29,6 +30,8 @@ pub struct AppVersionResponse2 {
/// Index of the metadata version used to sign the response.
/// Used to prevent replay/downgrade attacks.
pub metadata_version: usize,
+ /// Whether or not the current app version (mullvad_version::VERSION) is supported.
+ pub current_version_supported: bool,
}
impl AppVersionProxy {
@@ -66,13 +69,23 @@ impl AppVersionProxy {
architecture: mullvad_update::format::Architecture,
rollout: f32,
lowest_metadata_version: usize,
+ platform_version: String,
) -> impl Future<Output = Result<AppVersionResponse2, rest::Error>> + use<> {
let service = self.handle.service.clone();
let path = format!("app/releases/{platform}.json");
let request = self.handle.factory.get(&path);
async move {
- let request = request?.expected_status(&[StatusCode::OK]);
+ let request = request?
+ .expected_status(&[StatusCode::OK])
+ .header(
+ "M-App-Version",
+ &sanitize_header_value(mullvad_version::VERSION),
+ )?
+ .header(
+ "M-Platform-Version",
+ &sanitize_header_value(&platform_version),
+ )?;
let response = service.request(request).await?;
let bytes = response.body_with_max_size(Self::SIZE_LIMIT).await?;
@@ -90,13 +103,48 @@ impl AppVersionProxy {
lowest_metadata_version,
};
+ let current_version =
+ mullvad_version::Version::from_str(mullvad_version::VERSION).unwrap();
+ let current_version_supported = is_version_supported(current_version, &response.signed);
+
let metadata_version = response.signed.metadata_version;
Ok(AppVersionResponse2 {
version_info: VersionInfo::try_from_response(&params, response.signed)
.map_err(Arc::new)
.map_err(rest::Error::FetchVersions)?,
metadata_version,
+ current_version_supported,
})
}
}
}
+
+// This function makes a string conform to the allowed characters and length of header values.
+// Here's the rule it needs to implement: [A-Za-z0-9_.-]{1,64}
+fn sanitize_header_value(value: &str) -> String {
+ value
+ .chars()
+ .map(|c| if c.is_whitespace() { '_' } else { c })
+ .filter(|&c| c.is_ascii_alphanumeric() || "_.-".contains(c))
+ .take(64)
+ .collect()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_sanitize_header_value() {
+ assert_eq!(sanitize_header_value("2025.5"), "2025.5");
+ assert_eq!(sanitize_header_value("Fedora Linux"), "Fedora_Linux");
+ assert_eq!(sanitize_header_value("macOS 26.1"), "macOS_26.1");
+ assert_eq!(sanitize_header_value("Déjà vu OS"), "Dj_vu_OS");
+
+ let long_value =
+ "abcdefghijklmnopqrstuvxyzabcdefghijklmnopqrstuvxyzabcdefghijklmnopqrstuvxyz";
+ let mut truncated_long_value = long_value.to_owned();
+ truncated_long_value.truncate(64);
+ assert_eq!(sanitize_header_value(long_value), truncated_long_value);
+ }
+}
diff --git a/mullvad-daemon/src/version/check.rs b/mullvad-daemon/src/version/check.rs
index 82cfe035be..92bcec2971 100644
--- a/mullvad-daemon/src/version/check.rs
+++ b/mullvad-daemon/src/version/check.rs
@@ -63,7 +63,7 @@ pub(super) struct VersionCache {
pub current_version_supported: bool,
/// The latest available versions
pub version_info: mullvad_update::version::VersionInfo,
- #[cfg(in_app_upgrade)]
+ #[cfg(not(target_os = "android"))]
pub metadata_version: usize,
}
@@ -120,7 +120,7 @@ impl VersionUpdaterInner {
self.last_app_version_info.as_ref().map(|(info, _)| info)
}
- #[cfg(in_app_upgrade)]
+ #[cfg(not(target_os = "android"))]
pub fn get_min_metadata_version(&self) -> usize {
self.last_app_version_info
.as_ref()
@@ -131,7 +131,7 @@ impl VersionUpdaterInner {
.unwrap_or(mullvad_update::version::MIN_VERIFY_METADATA_VERSION)
}
- #[cfg(not(in_app_upgrade))]
+ #[cfg(target_os = "android")]
pub fn get_min_metadata_version(&self) -> usize {
mullvad_update::version::MIN_VERIFY_METADATA_VERSION
}
@@ -144,7 +144,7 @@ impl VersionUpdaterInner {
update: &impl Fn(VersionCache) -> BoxFuture<'static, Result<(), Error>>,
mut new_version_info: VersionCache,
) {
- #[cfg(in_app_upgrade)]
+ #[cfg(not(target_os = "android"))]
if let Some((current_cache, _)) = self.last_app_version_info.as_ref() {
if current_cache.metadata_version == new_version_info.metadata_version {
log::trace!("Ignoring version info with same metadata version");
@@ -377,27 +377,11 @@ fn do_version_check_in_background(
}
/// Combine the old version and new version endpoint
-#[cfg(in_app_upgrade)]
+#[cfg(not(target_os = "android"))]
fn version_check_inner(
api: &ApiContext,
min_metadata_version: usize,
) -> impl Future<Output = Result<VersionCache, Error>> + use<> {
- use futures::future::Either;
- use mullvad_api::version::AppVersionResponse2;
-
- let supported_fut = if !APP_VERSION.is_dev() {
- let v1_endpoint = api.version_proxy.version_check(
- mullvad_version::VERSION.to_owned(),
- PLATFORM,
- api.platform_version.clone(),
- );
- Either::Left(async { Ok(v1_endpoint.await?.supported) })
- } else {
- // NOTE: Treat all dev versions as unsupported. The old endpoint returns 404 for dev
- // versions.
- Either::Right(async { Ok(false) })
- };
-
let architecture = match talpid_platform_metadata::get_native_arch()
.expect("IO error while getting native architecture")
.expect("Failed to get native architecture")
@@ -407,31 +391,26 @@ fn version_check_inner(
mullvad_update::format::Architecture::Arm64
}
};
- let v2_endpoint = api.version_proxy.version_check_2(
+ let endpoint = api.version_proxy.version_check_2(
PLATFORM,
architecture,
mullvad_update::version::SUPPORTED_VERSION,
min_metadata_version,
+ api.platform_version.clone(),
);
async move {
- let (
- current_version_supported,
- AppVersionResponse2 {
- version_info,
- metadata_version,
- },
- ) = tokio::try_join!(supported_fut, v2_endpoint).map_err(Error::Download)?;
+ let result = endpoint.await.map_err(Error::Download)?;
Ok(VersionCache {
cache_version: APP_VERSION.clone(),
- current_version_supported,
- version_info,
- metadata_version,
+ current_version_supported: result.current_version_supported,
+ version_info: result.version_info,
+ metadata_version: result.metadata_version,
})
}
}
-#[cfg(not(in_app_upgrade))]
+#[cfg(target_os = "android")]
fn version_check_inner(
api: &ApiContext,
// NOTE: This is unused when `update` is disabled
@@ -546,7 +525,7 @@ fn dev_version_cache() -> VersionCache {
},
beta: None,
},
- #[cfg(in_app_upgrade)]
+ #[cfg(not(target_os = "android"))]
metadata_version: 0,
}
}
@@ -606,7 +585,7 @@ mod test {
sha256: [0u8; 32],
}),
},
- #[cfg(in_app_upgrade)]
+ #[cfg(not(target_os = "android"))]
metadata_version: 0,
}
}
@@ -780,7 +759,7 @@ mod test {
},
beta: None,
},
- #[cfg(in_app_upgrade)]
+ #[cfg(not(target_os = "android"))]
metadata_version: 0,
}
}
diff --git a/mullvad-update/src/version.rs b/mullvad-update/src/version.rs
index 13da1ec82d..0fe6d7d66b 100644
--- a/mullvad-update/src/version.rs
+++ b/mullvad-update/src/version.rs
@@ -10,7 +10,7 @@ use anyhow::Context;
use itertools::Itertools;
use mullvad_version::PreStableType;
-use crate::format::{self, Installer};
+use crate::format::{self, Installer, Response};
/// Lowest version to accept using 'verify'
pub const MIN_VERIFY_METADATA_VERSION: usize = 0;
@@ -141,8 +141,22 @@ impl VersionInfo {
}
}
+/// A version is considered supported if the version exists in the metadata. Versions with a
+/// rollout of 0 is still considered supported.
+pub fn is_version_supported(
+ current_version: mullvad_version::Version,
+ response: &Response,
+) -> bool {
+ response
+ .releases
+ .iter()
+ .any(|release| release.version.eq(&current_version))
+}
+
#[cfg(test)]
mod test {
+ use std::str::FromStr;
+
use insta::assert_yaml_snapshot;
use super::*;
@@ -250,4 +264,27 @@ mod test {
Ok(())
}
+
+ #[test]
+ fn test_is_version_supported() -> anyhow::Result<()> {
+ let response = format::SignedResponse::deserialize_insecure(include_bytes!(
+ "../test-version-response.json"
+ ))?;
+
+ let supported_version = mullvad_version::Version::from_str("2025.3").unwrap();
+ let supported_rollout_zero_version = mullvad_version::Version::from_str("2030.3").unwrap();
+ let non_supported_version = mullvad_version::Version::from_str("2025.5").unwrap();
+
+ assert!(is_version_supported(supported_version, &response.signed));
+ assert!(is_version_supported(
+ supported_rollout_zero_version,
+ &response.signed
+ ));
+ assert!(!is_version_supported(
+ non_supported_version,
+ &response.signed
+ ));
+
+ Ok(())
+ }
}
diff --git a/test/Cargo.lock b/test/Cargo.lock
index d03bcc622e..ccdedcc77f 100644
--- a/test/Cargo.lock
+++ b/test/Cargo.lock
@@ -2046,6 +2046,7 @@ dependencies = [
"mullvad-fs",
"mullvad-types",
"mullvad-update",
+ "mullvad-version",
"rustls-pemfile 2.1.3",
"serde",
"serde_json",