Switch from staging to prod for version metadata

This commit is contained in:
David Lönnhager 2025-03-26 09:40:41 +01:00
parent 8fbf46194b
commit 200d4837f1
No known key found for this signature in database
GPG Key ID: AEE9DECFD582E984
15 changed files with 59 additions and 44 deletions

1
.github/CODEOWNERS vendored
View File

@ -8,6 +8,7 @@
# Developer signing keys must be approved by team/tech leads
/ci/keys/ @faern @raksooo @pinkisemils @albin-mullvad
/mullvad-update/trusted-metadata-signing-pubkeys @faern @raksooo @pinkisemils @albin-mullvad
# Desktop build server files owned by desktop leads
/ci/buildserver* @faern @raksooo

3
Cargo.lock generated
View File

@ -2041,7 +2041,6 @@ dependencies = [
"serde",
"talpid-platform-metadata",
"tokio",
"vec1",
"windows-sys 0.52.0",
"winres",
]
@ -2496,7 +2495,6 @@ dependencies = [
"sha2",
"tokio",
"toml 0.8.19",
"vec1",
]
[[package]]
@ -2638,7 +2636,6 @@ dependencies = [
"tokio-socks",
"tower 0.5.1",
"uuid",
"vec1",
]
[[package]]

View File

@ -28,7 +28,6 @@ rand = { version = "0.8.5" }
reqwest = { version = "0.12.9", default-features = false, features = ["rustls-tls"] }
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["rt-multi-thread", "fs"] }
vec1 = { workspace = true }
talpid-platform-metadata = { path = "../talpid-platform-metadata" }
mullvad-update = { path = "../mullvad-update", features = ["client"] }

View File

@ -19,17 +19,13 @@ use tokio::{
sync::{mpsc, oneshot},
task::JoinHandle,
};
use vec1::vec1;
/// ed25519 pubkey used to verify metadata from the Mullvad (stagemole) API
const VERSION_PROVIDER_PUBKEY: &str = include_str!("../../mullvad-update/stagemole-pubkey");
/// Pinned root certificate used when fetching version metadata
const PINNED_CERTIFICATE: &[u8] = include_bytes!("../../mullvad-api/le_root_cert.pem");
/// Base URL for pulling metadata. Actual JSON files should be stored at `<base
/// url>/<platform>.json`
const META_REPOSITORY_URL: &str = "https://api.stagemole.eu/app/releases/";
const META_REPOSITORY_URL: &str = "https://api.mullvad.net/app/releases/";
/// Actions handled by an async worker task in [ActionMessageHandler].
enum TaskMessage {
@ -53,15 +49,11 @@ pub fn initialize_controller<T: AppDelegate + 'static>(delegate: &mut T, environ
// Directory provider to use
type DirProvider = crate::temp::TempDirProvider;
// Version info provider to use
let verifying_key =
mullvad_update::format::key::VerifyingKey::from_hex(VERSION_PROVIDER_PUBKEY)
.expect("valid key");
let cert = reqwest::Certificate::from_pem(PINNED_CERTIFICATE).expect("invalid cert");
let version_provider = HttpVersionInfoProvider {
url: get_metadata_url(),
pinned_certificate: Some(cert),
verifying_keys: vec1![verifying_key],
verifying_keys: mullvad_update::keys::TRUSTED_METADATA_SIGNING_PUBKEYS.clone(),
};
AppController::initialize::<_, Downloader<T>, _, DirProvider>(

View File

@ -47,7 +47,6 @@ 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" }

View File

@ -4,7 +4,6 @@ use std::sync::Arc;
use http::StatusCode;
use mullvad_types::version::AppVersion;
use mullvad_update::version::{VersionInfo, VersionParameters};
use vec1::vec1;
use super::rest;
use super::APP_URL_PREFIX;
@ -23,9 +22,6 @@ pub struct AppVersionResponse {
}
impl AppVersionProxy {
/// Public key to use for `version_check_2` response
const VERSION_PROVIDER_PUBKEY: &str = include_str!("../../mullvad-update/stagemole-pubkey");
/// Maximum size of `version_check_2` response
const SIZE_LIMIT: usize = 1024 * 1024;
@ -65,18 +61,13 @@ impl AppVersionProxy {
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(Self::SIZE_LIMIT).await?;
let response = mullvad_update::format::SignedResponse::deserialize_and_verify(
&verifying_keys,
&mullvad_update::keys::TRUSTED_METADATA_SIGNING_PUBKEYS,
&bytes,
lowest_metadata_version,
)

View File

@ -22,7 +22,6 @@ serde = { workspace = true }
sha2 = "0.10"
tokio = { version = "1", features = ["full"] }
toml = "0.8"
vec1 = { workspace = true }
mullvad-version = { path = "../../mullvad-version", features = ["serde"] }
mullvad-update = { path = "../", features = ["client", "sign"] }

View File

@ -28,9 +28,6 @@ const DEFAULT_ROLLOUT: f32 = 1.;
/// Lowest version to accept using 'verify'
const MIN_VERIFY_METADATA_VERSION: usize = 0;
/// Verification public key
const VERIFYING_PUBKEY: &str = include_str!("../../stagemole-pubkey");
/// A tool that generates signed Mullvad version metadata.
///
/// Unsigned work is stored in `work/`, and signed work is stored in `signed/`

View File

@ -13,7 +13,6 @@ use std::{
sync::LazyLock,
};
use tokio::{fs, io};
use vec1::vec1;
use crate::{
artifacts,
@ -22,7 +21,7 @@ use crate::{
/// Base URL for metadata found with `meta pull`.
/// Actual JSON files should be stored at `<base url>/<platform>.json`.
const META_REPOSITORY_URL: &str = "https://releases.stagemole.eu/desktop/metadata/";
const META_REPOSITORY_URL: &str = "https://releases.mullvad.net/desktop/metadata/";
/// TLS certificate to pin to for `meta pull`.
static PINNED_CERTIFICATE: LazyLock<reqwest::Certificate> = LazyLock::new(|| {
@ -128,14 +127,10 @@ impl Platform {
println!("Pulling {self} metadata from {url}...");
// Pull latest metadata
let verifying_key =
key::VerifyingKey::from_hex(crate::VERIFYING_PUBKEY).expect("Invalid pubkey");
let version_provider = HttpVersionInfoProvider {
pinned_certificate: Some(PINNED_CERTIFICATE.clone()),
url,
verifying_keys: vec1![verifying_key],
verifying_keys: mullvad_update::keys::TRUSTED_METADATA_SIGNING_PUBKEYS.clone(),
};
let response = version_provider
.get_versions(crate::MIN_VERIFY_METADATA_VERSION)
@ -236,11 +231,8 @@ impl Platform {
println!("Verifying signature of {}...", signed_path.display());
let bytes = fs::read(signed_path).await.context("Failed to read file")?;
let public_key = key::VerifyingKey::from_hex(include_str!("../../stagemole-pubkey"))
.expect("Invalid pubkey");
format::SignedResponse::deserialize_and_verify(
&vec1![public_key],
&mullvad_update::keys::TRUSTED_METADATA_SIGNING_PUBKEYS,
&bytes,
crate::MIN_VERIFY_METADATA_VERSION,
)

View File

@ -75,8 +75,7 @@ impl Serialize for SecretKey {
}
/// ed25519 verifying key
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(test, derive(Clone))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct VerifyingKey(pub ed25519_dalek::VerifyingKey);
impl fmt::Display for VerifyingKey {

View File

@ -0,0 +1,41 @@
//! Keys that may be used for verifying data
use crate::format::key::VerifyingKey;
use std::sync::LazyLock;
use vec1::Vec1;
/// Pubkeys used to verify metadata from the Mullvad API (production)
pub static TRUSTED_METADATA_SIGNING_PUBKEYS: LazyLock<Vec1<VerifyingKey>> =
LazyLock::new(|| parse_keys(include_str!("../trusted-metadata-signing-pubkeys")));
fn parse_keys(keys: &str) -> Vec1<VerifyingKey> {
let mut v = vec![];
for key in keys.split('\n') {
let key = key.trim();
if key.starts_with('#') || key.is_empty() {
continue;
}
v.push(VerifyingKey::from_hex(key).expect("invalid pubkey"));
}
v.try_into().expect("need at least one key")
}
#[cfg(test)]
#[test]
fn test_parse_keys() {
let key1 = "AB4EF63FFDCC6BD5A19C30CD23B9DE03099407A04463418F17AE338B98AA09D4".to_lowercase();
let key2 = "BB4EF63FFDCC6BD5A19C30CD23B9DE03099407A04463418F17AE338B98AA09D4".to_lowercase();
let keys = parse_keys(&format!(
r#"
# test
{key1}
# test 2
{key2}
"#
));
assert_eq!(format!("{}", keys[0]), key1);
assert_eq!(format!("{}", keys[1]), key2);
// Test that actual keys are validly parsed
let _prod = &*TRUSTED_METADATA_SIGNING_PUBKEYS;
}

View File

@ -6,6 +6,8 @@ mod client;
#[cfg(feature = "client")]
pub use client::*;
pub mod keys;
pub mod version;
/// Parser and serializer for version metadata

View File

@ -1 +0,0 @@
a0cd8f582e3147d57f7c01ec0fd306c8315290cea55725c7d5c76f835b78b363

View File

@ -0,0 +1,8 @@
# linus
c99b5e6e76bb7ab5b6fc3cdfe146faaa8afcfce0326822fe1629e00e666988b4
# oskar
4f2faeeda078df4dc1eebfb217c475eec9635c65d8b075171c4cf1808356dde5
# emils
299e8b06355031781623de7c9a013eb88b07aee80476e2938ce01a7b623d29d0
# albin
af4f7762e3e13af87f2e8c8af8ce5cce456cd3390af6d881b1eb6f802786cc3f

1
test/Cargo.lock generated
View File

@ -2101,7 +2101,6 @@ dependencies = [
"tokio-socks",
"tower 0.5.1",
"uuid",
"vec1",
]
[[package]]