diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-09-25 13:53:58 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-09-25 13:53:58 +0200 |
| commit | 810e198364ad36e8163bab0de7e2d3f4143d5f0c (patch) | |
| tree | 902e58bdd95f508dbcbe4a0883e88ecb7a1962ea | |
| parent | 3ab4829a7a14e087807f807a4e0e643f18d26db8 (diff) | |
| parent | fed13c26db8754e0c1686adf621658d918cc43d8 (diff) | |
| download | mullvadvpn-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.lock | 1 | ||||
| -rw-r--r-- | mullvad-api/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-api/src/version.rs | 52 | ||||
| -rw-r--r-- | mullvad-daemon/src/version/check.rs | 51 | ||||
| -rw-r--r-- | mullvad-update/src/version.rs | 39 | ||||
| -rw-r--r-- | test/Cargo.lock | 1 |
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(¶ms, 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(¤t_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", |
