summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-update/src/format/deserializer.rs73
-rw-r--r--mullvad-update/src/format/key.rs9
-rw-r--r--mullvad-update/src/format/mod.rs20
-rw-r--r--mullvad-update/src/format/serializer.rs13
-rw-r--r--mullvad-update/test-version-response.json11
5 files changed, 99 insertions, 27 deletions
diff --git a/mullvad-update/src/format/deserializer.rs b/mullvad-update/src/format/deserializer.rs
index 0cde83bf1b..28291dde00 100644
--- a/mullvad-update/src/format/deserializer.rs
+++ b/mullvad-update/src/format/deserializer.rs
@@ -4,7 +4,7 @@ use anyhow::Context;
use super::key::*;
use super::Response;
-use super::{PartialSignedResponse, SignedResponse};
+use super::{PartialSignedResponse, ResponseSignature, SignedResponse};
impl SignedResponse {
/// Deserialize some bytes to JSON, and verify them, including signature and expiry.
@@ -26,7 +26,7 @@ impl SignedResponse {
let signed = serde_json::from_value(partial_data.signed)
.context("Failed to deserialize response")?;
Ok(Self {
- signature: partial_data.signature,
+ signatures: partial_data.signatures,
signed,
})
}
@@ -64,7 +64,7 @@ impl SignedResponse {
}
Ok(SignedResponse {
- signature: partial_data.signature,
+ signatures: partial_data.signatures,
signed: signed_response,
})
}
@@ -82,24 +82,26 @@ pub(super) fn deserialize_and_verify(
serde_json::from_slice(bytes).context("Invalid version JSON")?;
// Check if the key matches
- if partial_data.signature.keyid.0 != key.0 {
+ let Some(sig) = partial_data.signatures.iter().find_map(|sig| match sig {
+ // Check if ed25519 key matches
+ ResponseSignature::Ed25519 { keyid, sig } if keyid.0 == key.0 => Some(sig),
+ // Ignore all non-matching key
+ _ => None,
+ }) else {
anyhow::bail!("Unrecognized key");
- }
+ };
// Serialize to canonical json format
let canon_data = json_canon::to_vec(&partial_data.signed)
.context("Failed to serialize to canonical JSON")?;
// Check if the data is signed by our key
- partial_data
- .signature
- .keyid
- .0
- .verify_strict(&canon_data, &partial_data.signature.sig.0)
+ key.0
+ .verify_strict(&canon_data, &sig.0)
.context("Signature verification failed")?;
Ok(PartialSignedResponse {
- signature: partial_data.signature,
+ signatures: partial_data.signatures,
signed: partial_data.signed,
})
}
@@ -147,4 +149,53 @@ mod test {
)
.expect_err("expected rejected version number");
}
+
+ /// Test that invalid key types deserialized to "other"
+ #[test]
+ fn test_response_unknown_keytypes() {
+ //let secret = "F6631A59EBBF8AADEAC64CC30A08A83FC7283F39DE53B7F1BFBA6BE52663DC94";
+ let pubkey = "8F735E412015D8976079E5FA0E090100A43A34937CCFC3A2341219E30291DD39";
+ let fakesig = "08954286A9284718B83CAADA5DF8A9A9DF0CE569F8EFF669D8C2A2E5945C809C465C38168E2F6018461DD8801DBFC74126A2ED9102F99A49F6DD54722C9B3605";
+ let value = serde_json::json!({
+ "signatures": [
+ {
+ "keytype": "ed25519",
+ "keyid": pubkey,
+ "sig": fakesig,
+ },
+ {
+ "keytype": "new shiny key",
+ "keyid": "test 1",
+ "sig": "test 2",
+ }
+ ],
+ "signed": {
+ "metadata_expiry": "3000-01-01T00:00:00Z",
+ "metadata_version": 0,
+ "releases": []
+ }
+ });
+
+ let bytes = serde_json::to_vec(&value).expect("serialize should succeed");
+
+ let response = SignedResponse::deserialize_and_verify_insecure(&bytes)
+ .expect("deserialization failed");
+
+ let expected_key = VerifyingKey::from_hex(pubkey).unwrap();
+ let expected_sig = Signature::from_hex(&fakesig).unwrap();
+
+ // Ed25519 key
+ assert!(
+ matches!(&response.signatures[0], ResponseSignature::Ed25519 { keyid, sig } if keyid == &expected_key && sig == &expected_sig),
+ "unexpected response sig: {:?}",
+ response.signatures[0]
+ );
+
+ // Unrecognized key type
+ assert!(
+ matches!(&response.signatures[1], ResponseSignature::Other { keyid, sig } if keyid == "test 1" && sig == "test 2"),
+ "expected unrecognized key: {:?}",
+ response.signatures[1]
+ );
+ }
}
diff --git a/mullvad-update/src/format/key.rs b/mullvad-update/src/format/key.rs
index 0033ec30a1..a951aff709 100644
--- a/mullvad-update/src/format/key.rs
+++ b/mullvad-update/src/format/key.rs
@@ -111,9 +111,16 @@ impl Serialize for VerifyingKey {
}
/// ed25519 signature
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
pub struct Signature(pub ed25519_dalek::Signature);
+impl Signature {
+ pub fn from_hex(s: &str) -> anyhow::Result<Self> {
+ let bytes = bytes_from_hex::<{ ed25519_dalek::SIGNATURE_LENGTH }>(s)?;
+ Ok(Self(ed25519_dalek::Signature::from_bytes(&bytes)))
+ }
+}
+
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
diff --git a/mullvad-update/src/format/mod.rs b/mullvad-update/src/format/mod.rs
index edbe24ab4f..fc706f4357 100644
--- a/mullvad-update/src/format/mod.rs
+++ b/mullvad-update/src/format/mod.rs
@@ -23,8 +23,8 @@ pub mod serializer;
/// signature verification.
#[derive(Debug, Serialize)]
pub struct SignedResponse {
- /// Signature of the canonicalized JSON of `signed`
- pub signature: ResponseSignature,
+ /// Signatures of the canonicalized JSON of `signed`
+ pub signatures: Vec<ResponseSignature>,
/// Content signed by `signature`
pub signed: Response,
}
@@ -33,8 +33,8 @@ pub struct SignedResponse {
/// Note that deserializing doesn't verify anything
#[derive(Deserialize, Serialize)]
struct PartialSignedResponse {
- /// Signature of the canonicalized JSON of `signed`
- pub signature: ResponseSignature,
+ /// Signatures of the canonicalized JSON of `signed`
+ pub signatures: Vec<ResponseSignature>,
/// Content signed by `signature`
pub signed: serde_json::Value,
}
@@ -102,9 +102,15 @@ pub enum Architecture {
/// JSON response signature
#[derive(Debug, Deserialize, Serialize)]
-pub struct ResponseSignature {
- pub keyid: key::VerifyingKey,
- pub sig: key::Signature,
+#[serde(tag = "keytype")]
+#[serde(rename_all = "lowercase")]
+pub enum ResponseSignature {
+ Ed25519 {
+ keyid: key::VerifyingKey,
+ sig: key::Signature,
+ },
+ #[serde(untagged)]
+ Other { keyid: String, sig: String },
}
#[cfg(test)]
diff --git a/mullvad-update/src/format/serializer.rs b/mullvad-update/src/format/serializer.rs
index 6698c9282f..46c4c8cb7a 100644
--- a/mullvad-update/src/format/serializer.rs
+++ b/mullvad-update/src/format/serializer.rs
@@ -34,7 +34,7 @@ impl SignedResponse {
let response: Response = serde_json::from_value(partial_signed.signed)?;
Ok(SignedResponse {
- signature: partial_signed.signature,
+ signatures: partial_signed.signatures,
signed: response,
})
}
@@ -58,10 +58,10 @@ fn sign<T: Serialize>(
// Attach signature
Ok(PartialSignedResponse {
- signature: ResponseSignature {
+ signatures: vec![ResponseSignature::Ed25519 {
keyid: key.pubkey(),
sig,
- },
+ }],
// Attach now-signed data
signed,
})
@@ -86,7 +86,12 @@ mod test {
// Verify that we can deserialize and verify the data
let partial = sign(&key, &data).context("Signing failed")?;
- assert_eq!(partial.signature.keyid, pubkey);
+ assert!(
+ matches!(&partial.signatures[0], ResponseSignature::Ed25519 {
+ keyid,
+ ..
+ } if keyid == &pubkey)
+ );
let bytes = serde_json::to_vec(&partial)?;
diff --git a/mullvad-update/test-version-response.json b/mullvad-update/test-version-response.json
index d4d72698e6..b6466e48c2 100644
--- a/mullvad-update/test-version-response.json
+++ b/mullvad-update/test-version-response.json
@@ -1,8 +1,11 @@
{
- "signature": {
- "keyid": "bb4ef63ffdcc6bd5a19c30cd23b9de03099407a04463418f17ae338b98aa09d4",
- "sig": "253ec37846dcd909bfc5119c0e0d06535767e179eb8b4465015eaa95f4bed362c8c9186311192c987871722bf7d319d44e4f04eaf79c269820bc13ff1a901f0b"
- },
+ "signatures": [
+ {
+ "keytype": "ed25519",
+ "keyid": "bb4ef63ffdcc6bd5a19c30cd23b9de03099407a04463418f17ae338b98aa09d4",
+ "sig": "253ec37846dcd909bfc5119c0e0d06535767e179eb8b4465015eaa95f4bed362c8c9186311192c987871722bf7d319d44e4f04eaf79c269820bc13ff1a901f0b"
+ }
+ ],
"signed": {
"metadata_version": 0,
"metadata_expiry": "2025-07-02T15:33:00Z",