diff options
| -rw-r--r-- | Cargo.lock | 3 | ||||
| -rw-r--r-- | mullvad-api/Cargo.toml | 3 | ||||
| -rw-r--r-- | mullvad-api/src/lib.rs | 48 | ||||
| -rw-r--r-- | mullvad-api/src/rest.rs | 26 | ||||
| -rw-r--r-- | test/Cargo.lock | 79 |
5 files changed, 155 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock index e9d7219758..37ad7063b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2609,6 +2609,7 @@ dependencies = [ name = "mullvad-api" version = "0.0.0" dependencies = [ + "anyhow", "async-trait", "cbindgen 0.28.0", "chrono", @@ -2624,6 +2625,7 @@ dependencies = [ "mullvad-encrypted-dns-proxy", "mullvad-fs", "mullvad-types", + "mullvad-update", "rustls-pemfile 2.1.3", "serde", "serde_json", @@ -2636,6 +2638,7 @@ dependencies = [ "tokio-socks", "tower 0.5.1", "uuid", + "vec1", ] [[package]] diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml index 46eb8fbaff..72c8370e65 100644 --- a/mullvad-api/Cargo.toml +++ b/mullvad-api/Cargo.toml @@ -15,6 +15,7 @@ workspace = true api-override = [] [dependencies] +anyhow = { workspace = true } async-trait = "0.1" libc = "0.2" chrono = { workspace = true } @@ -46,10 +47,12 @@ tokio-rustls = { version = "0.26.0", features = [ tokio-socks = "0.5.1" rustls-pemfile = "2.1.3" uuid = { version = "1.4.1", features = ["v4"] } +vec1 = { workspace = true } mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" } mullvad-fs = { path = "../mullvad-fs" } mullvad-types = { path = "../mullvad-types" } +mullvad-update = { path = "../mullvad-update", features = ["client"] } talpid-types = { path = "../talpid-types" } talpid-time = { path = "../talpid-time" } diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index d8980a17b5..86f83aa842 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -9,6 +9,7 @@ use mullvad_types::{ account::{AccountData, AccountNumber, VoucherSubmission}, version::AppVersion, }; +use mullvad_update::version::{VersionInfo, VersionParameters}; use proxy::{ApiConnectionMode, ConnectionModeProvider}; use std::{ collections::BTreeMap, @@ -19,6 +20,7 @@ use std::{ sync::Arc, }; use talpid_types::ErrorExt; +use vec1::vec1; pub mod availability; use availability::ApiAvailability; @@ -712,6 +714,8 @@ pub struct AppVersionResponse { } impl AppVersionProxy { + const VERSION_PROVIDER_PUBKEY: &str = include_str!("../../mullvad-update/stagemole-pubkey"); + pub fn new(handle: rest::MullvadRestHandle) -> Self { Self { handle } } @@ -735,6 +739,50 @@ impl AppVersionProxy { response.deserialize().await } } + + /// Get versions from `/app/releases/<platform>.json` + pub fn version_check_2( + &self, + platform: &str, + architecture: mullvad_update::format::Architecture, + rollout: f32, + lowest_metadata_version: usize, + ) -> impl Future<Output = Result<VersionInfo, rest::Error>> + use<> { + // Maximum size of version response + const SIZE_LIMIT: usize = 1024 * 1024; + + let service = self.handle.service.clone(); + let path = format!("app/releases/{platform}.json"); + let request = self.handle.factory.get(&path); + + let verifying_key = + mullvad_update::format::key::VerifyingKey::from_hex(Self::VERSION_PROVIDER_PUBKEY) + .expect("valid key"); + let verifying_keys = vec1![verifying_key]; + + async move { + let request = request?.expected_status(&[StatusCode::OK]); + let response = service.request(request).await?; + let bytes = response.body_with_max_size(SIZE_LIMIT).await?; + + let response = mullvad_update::format::SignedResponse::deserialize_and_verify( + &verifying_keys, + &bytes, + lowest_metadata_version, + ) + .map_err(|err| rest::Error::FetchVersions(Arc::new(err)))?; + + let params = VersionParameters { + architecture, + rollout, + lowest_metadata_version, + }; + + VersionInfo::try_from_response(¶ms, response.signed) + .map_err(Arc::new) + .map_err(rest::Error::FetchVersions) + } + } } #[derive(Clone)] diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs index bab6d8112a..94c43392ad 100644 --- a/mullvad-api/src/rest.rs +++ b/mullvad-api/src/rest.rs @@ -13,7 +13,7 @@ use futures::{ }; use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; use hyper::{ - body::{Body, Bytes, Incoming}, + body::{Body, Buf, Bytes, Incoming}, header::{self, HeaderValue}, Method, Uri, }; @@ -73,6 +73,14 @@ pub enum Error { #[error("Set account number on factory with no access token store")] NoAccessTokenStore, + + /// Failed to obtain versions + #[error("Failed to obtain versions")] + FetchVersions(#[from] Arc<anyhow::Error>), + + /// Body exceeded size limit + #[error("Body exceeded size limit")] + BodyTooLarge, } impl From<Infallible> for Error { @@ -493,7 +501,7 @@ pub struct Response<B> { response: hyper::Response<B>, } -impl<B: Body> Response<B> +impl<B: Body + Unpin> Response<B> where Error: From<<B as Body>::Error>, { @@ -516,6 +524,20 @@ where pub async fn body(self) -> Result<Vec<u8>> { Ok(BodyExt::collect(self.response).await?.to_bytes().to_vec()) } + + pub async fn body_with_max_size(self, size_limit: usize) -> Result<Vec<u8>> { + let mut data: Vec<u8> = vec![]; + let mut stream = self.response.into_data_stream(); + + while let Some(chunk) = stream.next().await { + data.extend(chunk?.chunk()); + if data.len() > size_limit { + return Err(Error::BodyTooLarge); + } + } + + Ok(data) + } } #[derive(serde::Deserialize)] diff --git a/test/Cargo.lock b/test/Cargo.lock index e2e014cce3..3cde6c40bd 100644 --- a/test/Cargo.lock +++ b/test/Cargo.lock @@ -642,6 +642,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest", "fiat-crypto", "rustc_version", "subtle", @@ -769,6 +770,21 @@ dependencies = [ ] [[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] name = "educe" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1800,6 +1816,17 @@ dependencies = [ ] [[package]] +name = "json-canon" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447ae153a2bd47d61acc0d131295408e32ef87ed9785825a6f4ecef85afc0edb" +dependencies = [ + "ryu-js", + "serde", + "serde_json", +] + +[[package]] name = "kqueue" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2046,6 +2073,7 @@ dependencies = [ name = "mullvad-api" version = "0.0.0" dependencies = [ + "anyhow", "async-trait", "cbindgen", "chrono", @@ -2060,6 +2088,7 @@ dependencies = [ "mullvad-encrypted-dns-proxy", "mullvad-fs", "mullvad-types", + "mullvad-update", "rustls-pemfile 2.1.3", "serde", "serde_json", @@ -2072,6 +2101,7 @@ dependencies = [ "tokio-socks", "tower 0.5.1", "uuid", + "vec1", ] [[package]] @@ -2162,10 +2192,32 @@ dependencies = [ ] [[package]] +name = "mullvad-update" +version = "0.0.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "ed25519-dalek", + "hex", + "json-canon", + "mullvad-version", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.3", + "tokio", + "vec1", + "zeroize", +] + +[[package]] name = "mullvad-version" version = "0.0.0" dependencies = [ "regex-lite", + "serde", ] [[package]] @@ -2906,9 +2958,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.0", "bytes", @@ -3116,6 +3168,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] +name = "ryu-js" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3252,6 +3310,17 @@ dependencies = [ ] [[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] name = "shadowsocks" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4248,6 +4317,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] +name = "vec1" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab68b56840f69efb0fefbe3ab6661499217ffdc58e2eef7c3f6f69835386322" + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" |
