summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock524
-rw-r--r--installer-downloader/src/controller.rs28
-rw-r--r--installer-downloader/src/ui_downloader.rs3
-rw-r--r--installer-downloader/tests/controller.rs45
-rw-r--r--installer-downloader/tests/snapshots/controller__download-3.snap4
-rw-r--r--installer-downloader/tests/snapshots/controller__failed_verification.snap4
-rw-r--r--mullvad-update/Cargo.toml8
-rw-r--r--mullvad-update/src/api.rs157
-rw-r--r--mullvad-update/src/app.rs75
-rw-r--r--mullvad-update/src/deserializer.rs16
-rw-r--r--mullvad-update/src/format/mod.rs91
-rw-r--r--mullvad-update/src/verify.rs135
12 files changed, 394 insertions, 696 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9ba618673a..2f59ea0fbe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -65,15 +65,6 @@ dependencies = [
]
[[package]]
-name = "aes-kw"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c"
-dependencies = [
- "aes",
-]
-
-[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -176,19 +167,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
-name = "argon2"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
-dependencies = [
- "base64ct",
- "blake2",
- "cpufeatures",
- "password-hash",
- "zeroize",
-]
-
-[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -369,12 +347,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
-name = "bitfield"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
-
-[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -387,15 +359,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
-name = "blake2"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
-dependencies = [
- "digest",
-]
-
-[[package]]
name = "blake3"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -424,15 +387,6 @@ dependencies = [
]
[[package]]
-name = "block-padding"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
name = "block2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -442,35 +396,6 @@ dependencies = [
]
[[package]]
-name = "blowfish"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
-dependencies = [
- "byteorder",
- "cipher",
-]
-
-[[package]]
-name = "bstr"
-version = "1.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
-dependencies = [
- "memchr",
- "serde",
-]
-
-[[package]]
-name = "buffer-redux"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e8acf87c5b9f5897cd3ebb9a327f420e0cae9dd4e5c1d2e36f2c84c571a58f1"
-dependencies = [
- "memchr",
-]
-
-[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -523,15 +448,6 @@ dependencies = [
]
[[package]]
-name = "cast5"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911"
-dependencies = [
- "cipher",
-]
-
-[[package]]
name = "cbindgen"
version = "0.24.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -583,15 +499,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
-name = "cfb-mode"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330"
-dependencies = [
- "cipher",
-]
-
-[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -718,17 +625,6 @@ dependencies = [
]
[[package]]
-name = "cmac"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa"
-dependencies = [
- "cipher",
- "dbl",
- "digest",
-]
-
-[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -845,12 +741,6 @@ dependencies = [
]
[[package]]
-name = "crc24"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0"
-
-[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -945,7 +835,7 @@ dependencies = [
"cpufeatures",
"curve25519-dalek-derive",
"digest",
- "fiat-crypto 0.2.8",
+ "fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
@@ -1017,15 +907,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
-name = "dbl"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
name = "dbus"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1053,7 +934,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
- "pem-rfc7468",
"zeroize",
]
@@ -1109,43 +989,12 @@ dependencies = [
]
[[package]]
-name = "derive_more"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
-dependencies = [
- "derive_more-impl",
-]
-
-[[package]]
-name = "derive_more-impl"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
-dependencies = [
- "proc-macro2 1.0.92",
- "quote 1.0.36",
- "syn 2.0.89",
- "unicode-xid 0.2.6",
-]
-
-[[package]]
-name = "des"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
-dependencies = [
- "cipher",
-]
-
-[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
- "const-oid",
"crypto-common",
"subtle",
]
@@ -1189,22 +1038,6 @@ dependencies = [
]
[[package]]
-name = "dsa"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689"
-dependencies = [
- "digest",
- "num-bigint-dig",
- "num-traits",
- "pkcs8",
- "rfc6979",
- "sha2",
- "signature",
- "zeroize",
-]
-
-[[package]]
name = "duct"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1217,30 +1050,14 @@ dependencies = [
]
[[package]]
-name = "eax"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28"
-dependencies = [
- "aead",
- "cipher",
- "cmac",
- "ctr",
- "subtle",
-]
-
-[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
- "digest",
"elliptic-curve",
- "rfc6979",
"signature",
- "spki",
]
[[package]]
@@ -1268,17 +1085,6 @@ dependencies = [
]
[[package]]
-name = "ed448-goldilocks"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79"
-dependencies = [
- "fiat-crypto 0.1.20",
- "hex",
- "subtle",
-]
-
-[[package]]
name = "either"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1296,9 +1102,6 @@ dependencies = [
"ff",
"generic-array",
"group",
- "hkdf",
- "pem-rfc7468",
- "pkcs8",
"rand_core 0.6.4",
"sec1",
"subtle",
@@ -1446,12 +1249,6 @@ dependencies = [
[[package]]
name = "fiat-crypto"
-version = "0.1.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
-
-[[package]]
-name = "fiat-crypto"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e"
@@ -2192,15 +1989,6 @@ dependencies = [
]
[[package]]
-name = "idea"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477"
-dependencies = [
- "cipher",
-]
-
-[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2423,12 +2211,6 @@ dependencies = [
]
[[package]]
-name = "iter-read"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294"
-
-[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2537,20 +2319,6 @@ dependencies = [
]
[[package]]
-name = "k256"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
-dependencies = [
- "cfg-if",
- "ecdsa",
- "elliptic-curve",
- "once_cell",
- "sha2",
- "signature",
-]
-
-[[package]]
name = "keccak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2594,9 +2362,6 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-dependencies = [
- "spin 0.5.2",
-]
[[package]]
name = "libc"
@@ -2799,12 +2564,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
-name = "minimal-lexical"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-
-[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3236,11 +2995,11 @@ dependencies = [
"json-canon",
"mockito",
"mullvad-version",
- "pgp",
"rand 0.8.5",
"reqwest",
"serde",
"serde_json",
+ "sha2",
"tokio",
]
@@ -3477,16 +3236,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
[[package]]
-name = "nom"
-version = "7.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-dependencies = [
- "memchr",
- "minimal-lexical",
-]
-
-[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3525,24 +3274,6 @@ dependencies = [
]
[[package]]
-name = "num-bigint-dig"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
-dependencies = [
- "byteorder",
- "lazy_static",
- "libm 0.2.8",
- "num-integer",
- "num-iter",
- "num-traits",
- "rand 0.8.5",
- "serde",
- "smallvec",
- "zeroize",
-]
-
-[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3600,27 +3331,6 @@ dependencies = [
]
[[package]]
-name = "num_enum"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
-dependencies = [
- "num_enum_derive",
-]
-
-[[package]]
-name = "num_enum_derive"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2 1.0.92",
- "quote 1.0.36",
- "syn 2.0.89",
-]
-
-[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3750,18 +3460,6 @@ dependencies = [
]
[[package]]
-name = "ocb3"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb"
-dependencies = [
- "aead",
- "cipher",
- "ctr",
- "subtle",
-]
-
-[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3874,8 +3572,6 @@ checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
"ecdsa",
"elliptic-curve",
- "primeorder",
- "sha2",
]
[[package]]
@@ -3887,21 +3583,6 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
- "sha2",
-]
-
-[[package]]
-name = "p521"
-version = "0.13.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
-dependencies = [
- "base16ct",
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "rand_core 0.6.4",
- "sha2",
]
[[package]]
@@ -3942,17 +3623,6 @@ dependencies = [
]
[[package]]
-name = "password-hash"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
-dependencies = [
- "base64ct",
- "rand_core 0.6.4",
- "subtle",
-]
-
-[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3976,15 +3646,6 @@ dependencies = [
]
[[package]]
-name = "pem-rfc7468"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
-dependencies = [
- "base64ct",
-]
-
-[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4058,73 +3719,6 @@ dependencies = [
]
[[package]]
-name = "pgp"
-version = "0.14.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1877a97fd422433220ad272eb008ec55691944b1200e9eb204e3cb2cb69d34e9"
-dependencies = [
- "aes",
- "aes-gcm",
- "aes-kw",
- "argon2",
- "base64 0.22.1",
- "bitfield",
- "block-padding",
- "blowfish",
- "bstr",
- "buffer-redux",
- "byteorder",
- "camellia",
- "cast5",
- "cfb-mode",
- "chrono",
- "cipher",
- "const-oid",
- "crc24",
- "curve25519-dalek",
- "derive_builder",
- "derive_more",
- "des",
- "digest",
- "dsa",
- "eax",
- "ecdsa",
- "ed25519-dalek",
- "elliptic-curve",
- "flate2",
- "generic-array",
- "hex",
- "hkdf",
- "idea",
- "iter-read",
- "k256",
- "log",
- "md-5",
- "nom",
- "num-bigint-dig",
- "num-traits",
- "num_enum",
- "ocb3",
- "p256",
- "p384",
- "p521",
- "rand 0.8.5",
- "ripemd",
- "rsa",
- "sha1",
- "sha1-checked",
- "sha2",
- "sha3",
- "signature",
- "smallvec",
- "thiserror 1.0.59",
- "twofish",
- "x25519-dalek",
- "x448",
- "zeroize",
-]
-
-[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4195,17 +3789,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
-name = "pkcs1"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
-dependencies = [
- "der",
- "pkcs8",
- "spki",
-]
-
-[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4400,21 +3983,12 @@ dependencies = [
]
[[package]]
-name = "proc-macro-crate"
-version = "3.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
-dependencies = [
- "toml_edit",
-]
-
-[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
- "unicode-xid 0.1.0",
+ "unicode-xid",
]
[[package]]
@@ -4842,16 +4416,6 @@ dependencies = [
]
[[package]]
-name = "rfc6979"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
-dependencies = [
- "hmac",
- "subtle",
-]
-
-[[package]]
name = "ring"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4861,7 +4425,7 @@ dependencies = [
"cfg-if",
"getrandom 0.2.14",
"libc",
- "spin 0.9.8",
+ "spin",
"untrusted",
"windows-sys 0.52.0",
]
@@ -4886,41 +4450,12 @@ dependencies = [
]
[[package]]
-name = "ripemd"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
-dependencies = [
- "digest",
-]
-
-[[package]]
name = "rs-release"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21efba391745f92fc14a5cccb008e711a1a3708d8dacd2e69d88d5de513c117a"
[[package]]
-name = "rsa"
-version = "0.9.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519"
-dependencies = [
- "const-oid",
- "digest",
- "num-bigint-dig",
- "num-integer",
- "num-traits",
- "pkcs1",
- "pkcs8",
- "rand_core 0.6.4",
- "signature",
- "spki",
- "subtle",
- "zeroize",
-]
-
-[[package]]
name = "rtnetlink"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5124,7 +4659,6 @@ dependencies = [
"base16ct",
"der",
"generic-array",
- "pkcs8",
"subtle",
"zeroize",
]
@@ -5239,17 +4773,6 @@ dependencies = [
]
[[package]]
-name = "sha1-checked"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423"
-dependencies = [
- "digest",
- "sha1",
- "zeroize",
-]
-
-[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5309,7 +4832,7 @@ dependencies = [
"serde_urlencoded",
"shadowsocks-crypto",
"socket2",
- "spin 0.9.8",
+ "spin",
"thiserror 1.0.59",
"tokio",
"tokio-tfo",
@@ -5368,7 +4891,7 @@ dependencies = [
"serde",
"shadowsocks",
"socket2",
- "spin 0.9.8",
+ "spin",
"thiserror 1.0.59",
"tokio",
"windows-sys 0.59.0",
@@ -5411,7 +4934,6 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
- "digest",
"rand_core 0.6.4",
]
@@ -5464,12 +4986,6 @@ dependencies = [
[[package]]
name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
-[[package]]
-name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
@@ -5539,7 +5055,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
- "unicode-xid 0.1.0",
+ "unicode-xid",
]
[[package]]
@@ -6389,15 +5905,6 @@ dependencies = [
]
[[package]]
-name = "twofish"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013"
-dependencies = [
- "cipher",
-]
-
-[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6447,12 +5954,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
-name = "unicode-xid"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-
-[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7305,17 +6806,6 @@ dependencies = [
]
[[package]]
-name = "x448"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb"
-dependencies = [
- "ed448-goldilocks",
- "hex",
- "rand_core 0.5.1",
-]
-
-[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/installer-downloader/src/controller.rs b/installer-downloader/src/controller.rs
index 058b5049b9..773ed1d3bd 100644
--- a/installer-downloader/src/controller.rs
+++ b/installer-downloader/src/controller.rs
@@ -5,7 +5,7 @@ use crate::resource;
use crate::ui_downloader::{UiAppDownloader, UiAppDownloaderParameters, UiProgressUpdater};
use mullvad_update::{
- api::{self, Version, VersionInfoProvider},
+ api::{self, Version, VersionInfoProvider, VersionParameters},
app::{self, AppDownloader},
};
@@ -24,14 +24,14 @@ pub struct AppController {}
/// Public entry function for registering a [AppDelegate].
pub fn initialize_controller<T: AppDelegate + 'static>(delegate: &mut T) {
- use mullvad_update::{api::LatestVersionInfoProvider, app::HttpAppDownloader};
+ use mullvad_update::{api::ApiVersionInfoProvider, app::HttpAppDownloader};
- // App downloader (factory) to use
- type DownloaderFactory<T> = HttpAppDownloader<UiProgressUpdater<T>, UiProgressUpdater<T>>;
+ // App downloader to use
+ type Downloader<T> = HttpAppDownloader<UiProgressUpdater<T>>;
// Version info provider to use
- type VersionInfoProvider = LatestVersionInfoProvider;
+ type VersionInfoProvider = ApiVersionInfoProvider;
- AppController::initialize::<_, DownloaderFactory<T>, VersionInfoProvider>(delegate)
+ AppController::initialize::<_, Downloader<T>, VersionInfoProvider>(delegate)
}
impl AppController {
@@ -85,8 +85,15 @@ where
let queue = delegate.queue();
async move {
+ let version_params = VersionParameters {
+ // TODO: detect current architecture
+ architecture: api::VersionArchitecture::X86,
+ // For the downloader, the rollout version is always preferred
+ rollout: 1.,
+ };
+
// TODO: handle errors, retry
- let Ok(version_info) = VersionProvider::get_version_info().await else {
+ let Ok(version_info) = VersionProvider::get_version_info(version_params).await else {
queue.queue_main(move |self_| {
self_.set_status_text("Failed to fetch version info");
});
@@ -134,9 +141,7 @@ where
let Some(app_url) = version_info.stable.urls.first() else {
return;
};
- let Some(signature_url) = version_info.stable.signature_urls.first() else {
- return;
- };
+ let app_sha256 = version_info.stable.sha256;
let app_size = version_info.stable.size;
self_.set_download_text("");
@@ -147,11 +152,10 @@ where
self_.show_download_progress();
let downloader = A::from(UiAppDownloaderParameters {
- signature_url: signature_url.to_owned(),
app_url: app_url.to_owned(),
app_size,
- sig_progress: UiProgressUpdater::new(self_.queue()),
app_progress: UiProgressUpdater::new(self_.queue()),
+ app_sha256,
});
let ui_downloader = UiAppDownloader::new(self_, downloader);
diff --git a/installer-downloader/src/ui_downloader.rs b/installer-downloader/src/ui_downloader.rs
index 41b6fa3efb..18eeb0074d 100644
--- a/installer-downloader/src/ui_downloader.rs
+++ b/installer-downloader/src/ui_downloader.rs
@@ -19,8 +19,7 @@ pub struct UiAppDownloader<Delegate: AppDelegate, Downloader> {
}
/// Parameters for [UiAppDownloader]
-pub type UiAppDownloaderParameters<Delegate> =
- AppDownloaderParameters<UiProgressUpdater<Delegate>, UiProgressUpdater<Delegate>>;
+pub type UiAppDownloaderParameters<Delegate> = AppDownloaderParameters<UiProgressUpdater<Delegate>>;
impl<Delegate: AppDelegate, Downloader: AppDownloader + Send + 'static>
UiAppDownloader<Delegate, Downloader>
diff --git a/installer-downloader/tests/controller.rs b/installer-downloader/tests/controller.rs
index 83ed08edb8..5f227bbdb2 100644
--- a/installer-downloader/tests/controller.rs
+++ b/installer-downloader/tests/controller.rs
@@ -8,7 +8,7 @@ use insta::assert_yaml_snapshot;
use installer_downloader::controller::AppController;
use installer_downloader::delegate::{AppDelegate, AppDelegateQueue};
use installer_downloader::ui_downloader::UiAppDownloaderParameters;
-use mullvad_update::api::{Version, VersionInfo, VersionInfoProvider};
+use mullvad_update::api::{Version, VersionInfo, VersionInfoProvider, VersionParameters};
use mullvad_update::app::{AppDownloader, DownloadError};
use mullvad_update::fetch::ProgressUpdater;
use std::sync::{Arc, LazyLock, Mutex};
@@ -19,32 +19,33 @@ pub struct FakeVersionInfoProvider {}
static FAKE_VERSION: LazyLock<VersionInfo> = LazyLock::new(|| VersionInfo {
stable: Version {
- version: "2025.1".to_owned(),
+ version: "2025.1".parse().unwrap(),
urls: vec!["https://mullvad.net/fakeapp".to_owned()],
size: 1234,
- signature_urls: vec!["https://mullvad.net/fakesig".to_owned()],
+ changelog: "a changelog".to_owned(),
+ sha256: [0u8; 32],
},
beta: None,
});
#[async_trait::async_trait]
impl VersionInfoProvider for FakeVersionInfoProvider {
- async fn get_version_info() -> anyhow::Result<VersionInfo> {
+ async fn get_version_info(_params: VersionParameters) -> anyhow::Result<VersionInfo> {
Ok(FAKE_VERSION.clone())
}
}
/// Downloader for which all steps immediately succeed
-pub type FakeAppDownloaderHappyPath = FakeAppDownloader<true, true, true>;
+pub type FakeAppDownloaderHappyPath = FakeAppDownloader<true, true>;
/// Downloader for which the download step fails
-pub type FakeAppDownloaderDownloadFail = FakeAppDownloader<true, false, false>;
+pub type FakeAppDownloaderDownloadFail = FakeAppDownloader<false, false>;
-/// Downloader for which all but the final verification step succeed
-pub type FakeAppDownloaderVerifyFail = FakeAppDownloader<true, true, false>;
+/// Downloader for which the final verification step fails
+pub type FakeAppDownloaderVerifyFail = FakeAppDownloader<true, false>;
-impl<const A: bool, const B: bool, const C: bool> From<UiAppDownloaderParameters<FakeAppDelegate>>
- for FakeAppDownloader<A, B, C>
+impl<const A: bool, const B: bool> From<UiAppDownloaderParameters<FakeAppDelegate>>
+ for FakeAppDownloader<A, B>
{
fn from(params: UiAppDownloaderParameters<FakeAppDelegate>) -> Self {
FakeAppDownloader { params }
@@ -54,32 +55,18 @@ impl<const A: bool, const B: bool, const C: bool> From<UiAppDownloaderParameters
/// Fake app downloader
///
/// Parameters:
-/// * SIG_SUCCEED - whether fetching the signature succeeds
/// * EXE_SUCCEED - whether fetching the binary succeeds
-/// * VERIFY_SUCCEED - whether verifying the signature succeeds
-pub struct FakeAppDownloader<
- const SIG_SUCCEED: bool,
- const EXE_SUCCEED: bool,
- const VERIFY_SUCCEED: bool,
-> {
+/// * VERIFY_SUCCEED - whether verifying the binary succeeds
+pub struct FakeAppDownloader<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool> {
params: UiAppDownloaderParameters<FakeAppDelegate>,
}
#[async_trait::async_trait]
-impl<const SIG_SUCCEED: bool, const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool> AppDownloader
- for FakeAppDownloader<SIG_SUCCEED, EXE_SUCCEED, VERIFY_SUCCEED>
+impl<const EXE_SUCCEED: bool, const VERIFY_SUCCEED: bool> AppDownloader
+ for FakeAppDownloader<EXE_SUCCEED, VERIFY_SUCCEED>
{
async fn download_signature(&mut self) -> Result<(), DownloadError> {
- self.params.sig_progress.set_url(&self.params.signature_url);
- self.params.sig_progress.set_progress(0.);
- if SIG_SUCCEED {
- self.params.sig_progress.set_progress(1.);
- Ok(())
- } else {
- Err(DownloadError::FetchSignature(anyhow::anyhow!(
- "fetching signature failed"
- )))
- }
+ Ok(())
}
async fn download_executable(&mut self) -> Result<(), DownloadError> {
diff --git a/installer-downloader/tests/snapshots/controller__download-3.snap b/installer-downloader/tests/snapshots/controller__download-3.snap
index 140e6d6320..d7c86160de 100644
--- a/installer-downloader/tests/snapshots/controller__download-3.snap
+++ b/installer-downloader/tests/snapshots/controller__download-3.snap
@@ -33,10 +33,6 @@ call_log:
- "set_download_text: Downloading from mullvad.net... (0%)"
- "set_download_progress: 100"
- "set_download_text: Downloading from mullvad.net... (100%)"
- - "set_download_progress: 0"
- - "set_download_text: Downloading from mullvad.net... (0%)"
- - "set_download_progress: 100"
- - "set_download_text: Downloading from mullvad.net... (100%)"
- "set_download_text: Download complete. Verifying..."
- disable_cancel_button
- "set_download_text: Verification successful. Starting install..."
diff --git a/installer-downloader/tests/snapshots/controller__failed_verification.snap b/installer-downloader/tests/snapshots/controller__failed_verification.snap
index bb92678de9..d345795b7e 100644
--- a/installer-downloader/tests/snapshots/controller__failed_verification.snap
+++ b/installer-downloader/tests/snapshots/controller__failed_verification.snap
@@ -33,10 +33,6 @@ call_log:
- "set_download_text: Downloading from mullvad.net... (0%)"
- "set_download_progress: 100"
- "set_download_text: Downloading from mullvad.net... (100%)"
- - "set_download_progress: 0"
- - "set_download_text: Downloading from mullvad.net... (0%)"
- - "set_download_progress: 100"
- - "set_download_text: Downloading from mullvad.net... (100%)"
- "set_download_text: Download complete. Verifying..."
- disable_cancel_button
- "set_download_text: ERROR: Verification failed!"
diff --git a/mullvad-update/Cargo.toml b/mullvad-update/Cargo.toml
index f3a067c3af..f858f0d917 100644
--- a/mullvad-update/Cargo.toml
+++ b/mullvad-update/Cargo.toml
@@ -13,13 +13,13 @@ workspace = true
[dependencies]
anyhow = "1.0"
json-canon = "0.1"
-chrono = { workspace = true, features = ["serde"] }
-ed25519-dalek = { version = "2.1", default-features = false }
-hex = { version = "0.4", default-features = false }
-pgp = "0.14.0"
+chrono = { workspace = true, features = ["serde", "now"] }
+ed25519-dalek = { version = "2.1" }
+hex = { version = "0.4" }
reqwest = { version = "0.12.9", features = ["blocking", "json"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
+sha2 = "0.10"
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
diff --git a/mullvad-update/src/api.rs b/mullvad-update/src/api.rs
index 16e578d485..d4fbd0e91c 100644
--- a/mullvad-update/src/api.rs
+++ b/mullvad-update/src/api.rs
@@ -1,10 +1,32 @@
//! Fetch information about app versions from the Mullvad API
+use anyhow::Context;
+
+use crate::deserializer;
+
+/// Parameters for [VersionInfoProvider]
+#[derive(Debug)]
+pub struct VersionParameters {
+ /// Architecture to retrieve data for
+ pub architecture: VersionArchitecture,
+ /// Rollout threshold. Any version in the response below this threshold will be ignored
+ pub rollout: f32,
+}
+
+/// Architecture to retrieve data for
+#[derive(Debug, Clone, Copy)]
+pub enum VersionArchitecture {
+ /// x86-64 architecture
+ X86,
+ /// ARM64 architecture
+ Arm64,
+}
+
/// See [module-level](self) docs.
#[async_trait::async_trait]
pub trait VersionInfoProvider {
/// Return info about the stable version
- async fn get_version_info() -> anyhow::Result<VersionInfo>;
+ async fn get_version_info(params: VersionParameters) -> anyhow::Result<VersionInfo>;
}
/// Contains information about all versions
@@ -20,39 +42,122 @@ pub struct VersionInfo {
#[derive(Debug, Clone)]
pub struct Version {
/// Version
- pub version: String,
+ pub version: mullvad_version::Version,
/// URLs to use for downloading the app installer
pub urls: Vec<String>,
/// Size of installer, in bytes
pub size: usize,
- /// URLs pointing to app PGP signatures
- pub signature_urls: Vec<String>,
+ /// Version changelog
+ pub changelog: String,
+ /// App installer checksum
+ pub sha256: [u8; 32],
}
-/// Use hardcoded URL to fetch installer
-/// TODO: This is temporary
-pub struct LatestVersionInfoProvider;
+/// Obtain version data from the Mullvad API
+pub struct ApiVersionInfoProvider;
#[async_trait::async_trait]
-impl VersionInfoProvider for LatestVersionInfoProvider {
- async fn get_version_info() -> anyhow::Result<VersionInfo> {
- Ok(VersionInfo {
- stable: Version {
- version: "2025.3".to_string(),
- urls: vec!["https://mullvad.net/en/download/app/exe/latest".to_owned()],
- size: 200 * 1024 * 1024,
- signature_urls: vec![
- "https://mullvad.net/en/download/app/exe/latest/signature".to_owned()
- ],
- },
- beta: Some(Version {
- version: "2025.3-beta1".to_string(),
- urls: vec!["https://mullvad.net/en/download/app/exe/latest-beta".to_owned()],
- size: 200 * 1024 * 1024,
- signature_urls: vec![
- "https://mullvad.net/en/download/app/exe/latest-beta/signature".to_owned(),
- ],
- }),
+impl VersionInfoProvider for ApiVersionInfoProvider {
+ async fn get_version_info(params: VersionParameters) -> anyhow::Result<VersionInfo> {
+ // FIXME: Replace with actual API response
+ use deserializer::*;
+
+ const TEST_PUBKEY: &str =
+ "AEC24A08466F3D6A1EDCDB2AD3C234428AB9D991B6BEA7F53CB9F172E6CB40D8";
+ let pubkey = hex::decode(TEST_PUBKEY).unwrap();
+ let verifying_key =
+ ed25519_dalek::VerifyingKey::from_bytes(&pubkey.try_into().unwrap()).unwrap();
+
+ let response = SignedResponse::deserialize_and_verify(
+ VerifyingKey(verifying_key),
+ include_bytes!("../test-version-response.json"),
+ )?;
+
+ VersionInfo::try_from_signed_response(&params, response)
+ }
+}
+
+impl VersionInfo {
+ /// Convert signed response data to public version type
+ /// NOTE: `response` is assumed to be verified and untampered. It is not verified.
+ fn try_from_signed_response(
+ params: &VersionParameters,
+ response: deserializer::SignedResponse,
+ ) -> anyhow::Result<Self> {
+ let stable = Version::try_from_signed_response(params, response.signed.stable)?;
+ let beta = response
+ .signed
+ .beta
+ .map(|response| Version::try_from_signed_response(params, response))
+ .transpose()
+ .context("Failed to parse beta version")?;
+
+ Ok(Self { stable, beta })
+ }
+}
+
+impl Version {
+ /// Convert response data to public version type
+ fn try_from_signed_response(
+ params: &VersionParameters,
+ response: deserializer::VersionResponse,
+ ) -> anyhow::Result<Self> {
+ // Check if the rollout version is acceptable according to threshold
+ if let Some(next) = response.next {
+ if next.rollout >= params.rollout {
+ // Use the version being rolled out
+ return Self::try_for_arch(params, next.version);
+ }
+ }
+
+ // Return the version not being rolled out
+ Self::try_for_arch(params, response.current)
+ }
+
+ /// Convert version response to the public version type for a given architecture
+ /// This may fail if the current architecture isn't included in the response
+ fn try_for_arch(
+ params: &VersionParameters,
+ response: deserializer::SpecificVersionResponse,
+ ) -> anyhow::Result<Self> {
+ let installer = match params.architecture {
+ VersionArchitecture::X86 => response.installers.x86,
+ VersionArchitecture::Arm64 => response.installers.arm64,
+ };
+ let installer = installer.context("Installer missing for architecture")?;
+ let sha256 = hex::decode(installer.sha256)
+ .context("Invalid checksum hex")?
+ .try_into()
+ .map_err(|_| anyhow::anyhow!("Invalid checksum length"))?;
+
+ Ok(Self {
+ changelog: response.changelog,
+ version: response.version,
+ urls: installer.urls,
+ size: installer.size,
+ sha256,
})
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ /// Test API version responses can be parsed
+ #[test]
+ fn test_api_version_info_provider_parser() -> anyhow::Result<()> {
+ let response = deserializer::SignedResponse::deserialize_and_verify_insecure(
+ include_bytes!("../test-version-response.json"),
+ )?;
+
+ let params = VersionParameters {
+ architecture: VersionArchitecture::X86,
+ rollout: 1.,
+ };
+
+ VersionInfo::try_from_signed_response(&params, response)?;
+
+ Ok(())
+ }
+}
diff --git a/mullvad-update/src/app.rs b/mullvad-update/src/app.rs
index 5aaeb1b7cc..ef08971bb0 100644
--- a/mullvad-update/src/app.rs
+++ b/mullvad-update/src/app.rs
@@ -4,7 +4,7 @@ use std::path::PathBuf;
use crate::{
fetch::{self, ProgressUpdater},
- verify::{AppVerifier, PgpVerifier},
+ verify::{AppVerifier, Sha256Verifier},
};
#[derive(Debug)]
@@ -15,12 +15,12 @@ pub enum DownloadError {
}
/// Parameters required to construct an [AppDownloader].
-pub struct AppDownloaderParameters<SigProgress, AppProgress> {
- pub signature_url: String,
+#[derive(Clone)]
+pub struct AppDownloaderParameters<AppProgress> {
pub app_url: String,
pub app_size: usize,
- pub sig_progress: SigProgress,
pub app_progress: AppProgress,
+ pub app_sha256: [u8; 32],
}
/// See the [module-level documentation](self).
@@ -44,63 +44,40 @@ pub async fn install_and_upgrade(mut downloader: impl AppDownloader) -> Result<(
}
#[derive(Clone)]
-pub struct HttpAppDownloader<SigProgress, AppProgress> {
- signature_url: String,
- app_url: String,
- app_size: usize,
- signature_progress_updater: SigProgress,
- app_progress_updater: AppProgress,
+pub struct HttpAppDownloader<AppProgress> {
+ params: AppDownloaderParameters<AppProgress>,
// TODO: set permissions
tmp_dir: PathBuf,
}
-impl<SigProgress, AppProgress> HttpAppDownloader<SigProgress, AppProgress> {
- const MAX_SIGNATURE_SIZE: usize = 1024;
-
- pub fn new(parameters: AppDownloaderParameters<SigProgress, AppProgress>) -> Self {
+impl<AppProgress> HttpAppDownloader<AppProgress> {
+ pub fn new(params: AppDownloaderParameters<AppProgress>) -> Self {
let tmp_dir = std::env::temp_dir();
- Self {
- signature_url: parameters.signature_url,
- app_url: parameters.app_url,
- app_size: parameters.app_size,
- signature_progress_updater: parameters.sig_progress,
- app_progress_updater: parameters.app_progress,
- tmp_dir,
- }
+ Self { params, tmp_dir }
}
}
-impl<SigProgress: ProgressUpdater, AppProgress: ProgressUpdater>
- From<AppDownloaderParameters<SigProgress, AppProgress>>
- for HttpAppDownloader<SigProgress, AppProgress>
+impl<AppProgress: ProgressUpdater> From<AppDownloaderParameters<AppProgress>>
+ for HttpAppDownloader<AppProgress>
{
- fn from(parameters: AppDownloaderParameters<SigProgress, AppProgress>) -> Self {
+ fn from(parameters: AppDownloaderParameters<AppProgress>) -> Self {
HttpAppDownloader::new(parameters)
}
}
#[async_trait::async_trait]
-impl<SigProgress: ProgressUpdater, AppProgress: ProgressUpdater> AppDownloader
- for HttpAppDownloader<SigProgress, AppProgress>
-{
+impl<AppProgress: ProgressUpdater> AppDownloader for HttpAppDownloader<AppProgress> {
async fn download_signature(&mut self) -> Result<(), DownloadError> {
- fetch::get_to_file(
- self.sig_path(),
- &self.signature_url,
- &mut self.signature_progress_updater,
- fetch::SizeHint::Maximum(Self::MAX_SIGNATURE_SIZE),
- )
- .await
- .map_err(DownloadError::FetchSignature)
+ // TODO: no-op, remove
+ Ok(())
}
async fn download_executable(&mut self) -> Result<(), DownloadError> {
fetch::get_to_file(
self.bin_path(),
- &self.app_url,
- &mut self.app_progress_updater,
- // FIXME: use exact size hint
- fetch::SizeHint::Maximum(self.app_size),
+ &self.params.app_url,
+ &mut self.params.app_progress,
+ fetch::SizeHint::Exact(self.params.app_size),
)
.await
.map_err(DownloadError::FetchApp)
@@ -108,21 +85,19 @@ impl<SigProgress: ProgressUpdater, AppProgress: ProgressUpdater> AppDownloader
async fn verify(&mut self) -> Result<(), DownloadError> {
let bin_path = self.bin_path();
- let sig_path = self.sig_path();
- tokio::task::spawn_blocking(move || {
- PgpVerifier::verify(bin_path, sig_path).map_err(DownloadError::Verification)
- })
- .await
- .expect("verifier panicked")
+ let hash = self.hash_sha256();
+ Sha256Verifier::verify(bin_path, *hash)
+ .await
+ .map_err(DownloadError::Verification)
}
}
-impl<SigProgress, AppProgress> HttpAppDownloader<SigProgress, AppProgress> {
+impl<AppProgress> HttpAppDownloader<AppProgress> {
fn bin_path(&self) -> PathBuf {
self.tmp_dir.join("temp.exe")
}
- fn sig_path(&self) -> PathBuf {
- self.tmp_dir.join("temp.exe.sig")
+ fn hash_sha256(&self) -> &[u8; 32] {
+ &self.params.app_sha256
}
}
diff --git a/mullvad-update/src/deserializer.rs b/mullvad-update/src/deserializer.rs
index 5ddb4a45d8..5f1a0326b8 100644
--- a/mullvad-update/src/deserializer.rs
+++ b/mullvad-update/src/deserializer.rs
@@ -30,6 +30,20 @@ impl SignedResponse {
Self::deserialize_and_verify_at_time(key, bytes, chrono::Utc::now())
}
+ /// This method is used for testing, and skips all verification.
+ /// Own method to prevent accidental misuse.
+ #[cfg(test)]
+ pub fn deserialize_and_verify_insecure(bytes: &[u8]) -> Result<Self, anyhow::Error> {
+ let partial_data: PartialSignedResponse =
+ serde_json::from_slice(bytes).context("Invalid version JSON")?;
+ let signed = serde_json::from_value(partial_data.signed)
+ .context("Failed to deserialize response")?;
+ Ok(Self {
+ signature: partial_data.signature,
+ signed,
+ })
+ }
+
/// Deserialize some bytes to JSON, and verify them, including signature and expiry.
/// If successful, the deserialized data is returned.
fn deserialize_and_verify_at_time(
@@ -193,7 +207,7 @@ pub struct SpecificVersionArchitectureResponse {
pub urls: Vec<String>,
/// Size of the installer, in bytes
pub size: usize,
- /// TODO: hash of the installer, in bytes
+ /// Hash of the installer, hexadecimal string
pub sha256: String,
}
diff --git a/mullvad-update/src/format/mod.rs b/mullvad-update/src/format/mod.rs
new file mode 100644
index 0000000000..98428babf4
--- /dev/null
+++ b/mullvad-update/src/format/mod.rs
@@ -0,0 +1,91 @@
+use serde::{Deserialize, Serialize};
+
+pub mod deserializer;
+pub mod key;
+#[cfg(feature = "sign")]
+pub mod serializer;
+
+/// JSON response including signature and signed content
+/// This type does not implement [serde::Deserialize] to prevent accidental deserialization without
+/// signature verification.
+#[derive(Serialize)]
+pub struct SignedResponse {
+ /// Signature of the canonicalized JSON of `signed`
+ pub signature: ResponseSignature,
+ /// Content signed by `signature`
+ pub signed: Response,
+}
+
+/// Helper class that leaves the signed data untouched
+/// Note that deserializing doesn't verify anything
+#[derive(Deserialize, Serialize)]
+struct PartialSignedResponse {
+ /// Signature of the canonicalized JSON of `signed`
+ pub signature: ResponseSignature,
+ /// Content signed by `signature`
+ pub signed: serde_json::Value,
+}
+
+/// Signed JSON response, not including the signature
+#[derive(Deserialize, Serialize)]
+#[serde(deny_unknown_fields)]
+pub struct Response {
+ /// When the signature expires
+ pub expires: chrono::DateTime<chrono::Utc>,
+ /// Stable version response
+ pub stable: VersionResponse,
+ /// Beta version response
+ pub beta: Option<VersionResponse>,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct VersionResponse {
+ /// The current version in this channel
+ pub current: SpecificVersionResponse,
+ /// The version being rolled out in this channel
+ pub next: Option<NextSpecificVersionResponse>,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct NextSpecificVersionResponse {
+ /// The percentage of users that should receive the new version.
+ pub rollout: f32,
+ #[serde(flatten)]
+ pub version: SpecificVersionResponse,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct SpecificVersionResponse {
+ /// Mullvad app version
+ pub version: mullvad_version::Version,
+ /// Changelog entries
+ pub changelog: String,
+ /// Installer details for different architectures
+ pub installers: SpecificVersionArchitectureResponses,
+}
+
+/// Version details for supported architectures
+#[derive(Deserialize, Serialize)]
+pub struct SpecificVersionArchitectureResponses {
+ /// Details for x86 installer
+ pub x86: Option<SpecificVersionArchitectureResponse>,
+ /// Details for ARM64 installer
+ pub arm64: Option<SpecificVersionArchitectureResponse>,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct SpecificVersionArchitectureResponse {
+ /// Mirrors that host the artifact
+ pub urls: Vec<String>,
+ /// Size of the installer, in bytes
+ pub size: usize,
+ /// Hash of the installer, hexadecimal string
+ pub sha256: String,
+}
+
+/// JSON response signature
+#[derive(Deserialize, Serialize)]
+pub struct ResponseSignature {
+ pub keyid: key::VerifyingKey,
+ pub sig: key::Signature,
+}
diff --git a/mullvad-update/src/verify.rs b/mullvad-update/src/verify.rs
index 74d8790c4b..a6bc3c9bc0 100644
--- a/mullvad-update/src/verify.rs
+++ b/mullvad-update/src/verify.rs
@@ -1,61 +1,80 @@
-use std::{fs::File, io::BufReader, path::Path};
-
use anyhow::Context;
-use pgp::{
- armor,
- packet::{Packet, PacketParser},
- types::PublicKeyTrait,
- Deserializable, SignedPublicKey,
+use sha2::Digest;
+use tokio::{
+ fs,
+ io::{AsyncRead, AsyncReadExt, BufReader},
};
-/// A verifier of digital file signatures
+use std::{future::Future, path::Path};
+
+/// A verifier of digital file signatures or hashes
pub trait AppVerifier: 'static + Clone {
- /// Verify `bin_path` using the signature at `sig_path`, and return an error if this fails for
- /// any reason.
- fn verify(bin_path: impl AsRef<Path>, sig_path: impl AsRef<Path>) -> anyhow::Result<()>;
+ type Parameters;
+
+ /// Verify `bin_path` using `parameters`, and return an error if this fails for any reason.
+ fn verify(
+ bin_path: impl AsRef<Path>,
+ parameters: Self::Parameters,
+ ) -> impl Future<Output = anyhow::Result<()>>;
}
-/// Verification using pgp
+/// Checksum verifier that uses SHA256
#[derive(Clone)]
-pub struct PgpVerifier;
+pub struct Sha256Verifier;
-impl PgpVerifier {
- const SIGNING_PUBKEY: &[u8] = include_bytes!("../mullvad-code-signing.gpg");
+impl Sha256Verifier {
+ /// Maximum number of bytes to read at a time
+ const BUF_SIZE: usize = 1024 * 1024;
}
-impl AppVerifier for PgpVerifier {
- fn verify(bin_path: impl AsRef<Path>, sig_path: impl AsRef<Path>) -> anyhow::Result<()> {
- let pubkey = SignedPublicKey::from_bytes(Self::SIGNING_PUBKEY)?;
+impl AppVerifier for Sha256Verifier {
+ /// The checksum
+ type Parameters = [u8; 32];
+
+ fn verify(
+ bin_path: impl AsRef<Path>,
+ expected_hash: Self::Parameters,
+ ) -> impl Future<Output = anyhow::Result<()>> {
+ let bin_path = bin_path.as_ref().to_owned();
+
+ async move {
+ let file = fs::File::open(&bin_path)
+ .await
+ .context(format!("Failed to open file at {}", bin_path.display()))?;
+ let file = BufReader::new(file);
+
+ Self::verify_inner(file, expected_hash).await
+ }
+ }
+}
- let sig_reader = BufReader::new(File::open(sig_path).context("Open signature file")?);
- let signature = PacketParser::new(armor::Dearmor::new(sig_reader))
- .find_map(|packet| {
- if let Ok(Packet::Signature(sig)) = packet {
- Some(sig)
- } else {
- None
- }
- })
- .context("Missing signature")?;
- let issuer = signature
- .issuer()
- .into_iter()
- .next()
- .context("Find issuer key ID")?;
+impl Sha256Verifier {
+ async fn verify_inner(
+ mut reader: impl AsyncRead + Unpin,
+ expected_hash: [u8; 32],
+ ) -> anyhow::Result<()> {
+ let mut hasher = sha2::Sha256::new();
- // Find subkey used for signing
- let subkey = pubkey
- .public_subkeys
- .iter()
- .find(|subkey| &subkey.key_id() == issuer)
- .context("Find signing subkey")?;
- //subkey.verify(&pubkey)?;
+ // Read data into hasher
+ let mut buffer = vec![0u8; Self::BUF_SIZE];
+ loop {
+ let read_n = reader
+ .read(&mut buffer)
+ .await
+ .context("Error reading bin file")?;
+ if read_n == 0 {
+ // We're done
+ break;
+ }
+ hasher.update(&buffer[..read_n]);
+ }
- let bin = BufReader::with_capacity(1024 * 1024, File::open(bin_path)?);
+ let actual_hash = hasher.finalize();
- signature
- .verify(subkey, bin)
- .context("Verification failed")?;
+ // Verify that hash is correct
+ if expected_hash != actual_hash[..] {
+ anyhow::bail!("Invalid checksum for bin file");
+ }
Ok(())
}
@@ -63,10 +82,32 @@ impl AppVerifier for PgpVerifier {
#[cfg(test)]
mod test {
+ use rand::RngCore;
+ use std::io::Cursor;
+
use super::*;
- #[test]
- fn test_pgp_signing_pubkey() {
- SignedPublicKey::from_bytes(PgpVerifier::SIGNING_PUBKEY).unwrap();
+ #[tokio::test]
+ async fn test_sha256_checksum() {
+ // Generate some random data
+ let mut data = vec![0u8; 1024 * 1024];
+ rand::thread_rng().fill_bytes(&mut data);
+
+ // Hash it
+ let mut hasher = sha2::Sha256::new();
+ hasher.update(&data);
+ let expected_hash = hasher.finalize();
+ let expected_hash: [u8; 32] = expected_hash[..].try_into().unwrap();
+
+ // Same data should be accepted
+ Sha256Verifier::verify_inner(Cursor::new(&data), expected_hash)
+ .await
+ .expect("expected checksum match");
+
+ // Compare the hash against some random data, which should fail
+ rand::thread_rng().fill_bytes(&mut data);
+ Sha256Verifier::verify_inner(Cursor::new(&data), expected_hash)
+ .await
+ .expect_err("expected checksum mismatch");
}
}