src/update_metadata_tests.rs
Rama master · réplica de lectura del repositorio original.
use super::*;
use hbb_common::{
base64::engine::general_purpose::STANDARD,
sodiumoxide::{self, crypto::sign},
ResultType,
};
use serde_json::{json, Value};
const KEY_ID: &str = "test-ed25519-main";
const SHA256: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
struct Fixture {
metadata: Vec<u8>,
signature: Vec<u8>,
public_key: [u8; 32],
}
fn artifact() -> Value {
json!({"platform":"windows","arch":"x86_64","format":"exe","url":"https://github.com/rustdesk/rustdesk/releases/download/v1.4.6/rustdesk-1.4.6-x86_64.exe","file_name":"rustdesk-1.4.6-x86_64.exe","size":123456,"sha256":SHA256})
}
fn metadata() -> Value {
json!({"schema_version":1,"app":"rustdesk","package_id":"rustdesk","version":"1.4.6","release_id":"v1.4.6","published_at":"2026-05-14T00:00:00Z","signature_key_id":KEY_ID,"artifacts":[artifact()]})
}
fn set_value(metadata: &mut Value, key: &str, value: Value, artifact_field: bool) {
let target = if artifact_field {
&mut metadata["artifacts"][0]
} else {
metadata
};
target
.as_object_mut()
.expect("JSON object")
.insert(key.to_string(), value);
}
fn sign_fixture(metadata: Value, signature_key_id: &str) -> Fixture {
sodiumoxide::init().expect("test sodiumoxide init");
let metadata = serde_json::to_vec(&metadata).expect("metadata JSON");
let (public_key, secret_key) = sign::gen_keypair();
let mut signed = UPDATE_METADATA_SIGNATURE_CONTEXT.to_vec();
signed.extend_from_slice(&metadata);
let sig = sign::sign_detached(&signed, &secret_key);
let sig_json = json!({"schema_version":1,"algorithm":"ed25519","key_id":signature_key_id,"signature":STANDARD.encode(sig.to_bytes())});
Fixture {
metadata,
signature: serde_json::to_vec(&sig_json).expect("signature JSON"),
public_key: public_key.0,
}
}
fn valid_fixture() -> Fixture {
sign_fixture(metadata(), KEY_ID)
}
fn key(public_key: [u8; 32]) -> TrustedUpdateKey {
TrustedUpdateKey {
key_id: KEY_ID,
algorithm: "ed25519",
public_key,
}
}
fn query<'a>() -> UpdateArtifactQuery<'a> {
UpdateArtifactQuery {
platform: "windows",
arch: "x86_64",
format: "exe",
}
}
fn requirements<'a>(artifact: UpdateArtifactQuery<'a>) -> MetadataRequirements<'a> {
MetadataRequirements {
expected_version: "1.4.6",
expected_release_id: "v1.4.6",
artifact,
}
}
fn verify_with_query(
fixture: &Fixture,
query: &UpdateArtifactQuery<'_>,
) -> ResultType<VerifiedUpdateArtifact> {
let trusted_keys = [key(fixture.public_key)];
verify_update_metadata_with_options(
&fixture.metadata,
&fixture.signature,
VerificationOptions {
requirements: requirements(*query),
expected_package_id: PUBLIC_UPDATE_PACKAGE_ID,
artifact_url_policy: ArtifactUrlPolicy::ExactPrefix(
"https://github.com/rustdesk/rustdesk/releases/download/v1.4.6/",
),
trusted_keys: &trusted_keys,
},
)
}
fn verify(fixture: &Fixture) -> ResultType<VerifiedUpdateArtifact> {
verify_with_query(fixture, &query())
}
#[test]
fn accepts_valid_metadata_and_signature() {
let artifact = verify(&valid_fixture()).expect("verified artifact");
assert_eq!("rustdesk-1.4.6-x86_64.exe", artifact.file_name);
assert_eq!(123456, artifact.size);
assert_eq!(SHA256, artifact.sha256);
assert_eq!("1.4.6", artifact.version);
}
#[test]
fn rejects_bad_signature_or_tampered_metadata() {
let mut fixture = valid_fixture();
fixture.metadata.push(b' ');
assert!(verify(&fixture).is_err());
for (key, value) in [
("schema_version", json!(2)),
("algorithm", json!("rsa")),
("signature", json!("not base64")),
("signature", json!(STANDARD.encode([1u8; 63]))),
] {
let mut fixture = valid_fixture();
let mut sig: Value = serde_json::from_slice(&fixture.signature).expect("signature");
sig.as_object_mut()
.expect("signature object")
.insert(key.to_string(), value);
fixture.signature = serde_json::to_vec(&sig).expect("signature bytes");
assert!(verify(&fixture).is_err(), "{}", key);
}
}
#[test]
fn rejects_schema_policy_artifact_and_url_mismatches() {
let cases = [
("schema_version", json!(2), false), ("app", json!("other"), false),
("version", json!("1.4.7"), false), ("release_id", json!("v1.4.7"), false),
("package_id", json!("custom"), false), ("signature_key_id", json!("different-key"), false),
("arch", json!("x86"), true),
("sha256", json!("not-a-sha256"), true),
("url", json!("https://github.com/rustdesk/rustdesk/releases/download/v1.4.7/rustdesk-1.4.6-x86_64.exe"), true),
("url", json!("https://github.com/rustdesk/rustdesk/releases/download/v1.4.6/other.exe"), true),
];
for (key, value, artifact_field) in cases {
let mut metadata = metadata();
set_value(&mut metadata, key, value, artifact_field);
assert!(verify(&sign_fixture(metadata, KEY_ID)).is_err(), "{}", key);
}
}
#[test]
fn rejects_artifact_file_name_path_components() {
for file_name in [
"",
"nested/rustdesk.exe",
r"nested\rustdesk.exe",
"..",
"rust desk.exe",
"rustdesk*.exe",
"rustdesk\n.exe",
"rüstdesk.exe",
] {
let mut invalid_metadata = metadata();
invalid_metadata["artifacts"][0]["file_name"] = json!(file_name);
invalid_metadata["artifacts"][0]["url"] = json!(format!(
"https://github.com/rustdesk/rustdesk/releases/download/v1.4.6/{file_name}"
));
let fixture = sign_fixture(invalid_metadata, KEY_ID);
let error = verify_with_query(&fixture, &query())
.err()
.expect("invalid file name");
assert_eq!("invalid update artifact file name", error.to_string());
}
}
#[test]
fn verifies_metadata_generated_by_python_release_script_fixture() {
const PYTHON_KEY_ID: &str = "python-test-ed25519-main";
let metadata = br#"{"schema_version":1,"app":"rustdesk","package_id":"rustdesk","version":"1.4.6","release_id":"v1.4.6","published_at":"2026-05-14T00:00:00Z","signature_key_id":"python-test-ed25519-main","artifacts":[{"platform":"windows","arch":"x86_64","format":"exe","url":"https://github.com/rustdesk/rustdesk/releases/download/v1.4.6/rustdesk-1.4.6-x86_64.exe","file_name":"rustdesk-1.4.6-x86_64.exe","size":8,"sha256":"304ca1638c5effa6832e0e15b958a8f74847efe4df9c3f3187216e921c168fed"}]}"#;
let signature = br#"{"schema_version":1,"algorithm":"ed25519","key_id":"python-test-ed25519-main","signature":"ZMcod9VNaEGTYK0gIfGmMQ44HAvrYAkYDyIL9JTKjzUH+hVFYs8KpvnGSlteAHwqiJuJDdiKBVCPEdtcRRAJCA=="}"#;
let trusted_key = TrustedUpdateKey {
key_id: PYTHON_KEY_ID,
algorithm: "ed25519",
public_key: [
3, 161, 7, 191, 243, 206, 16, 190, 29, 112, 221, 24, 231, 75, 192, 153, 103, 228, 214,
48, 155, 165, 13, 95, 29, 220, 134, 100, 18, 85, 49, 184,
],
};
let trusted_keys = [trusted_key];
let artifact = verify_update_metadata_with_options(
metadata,
signature,
VerificationOptions {
requirements: requirements(query()),
expected_package_id: PUBLIC_UPDATE_PACKAGE_ID,
artifact_url_policy: ArtifactUrlPolicy::ExactPrefix(
"https://github.com/rustdesk/rustdesk/releases/download/v1.4.6/",
),
trusted_keys: &trusted_keys,
},
)
.expect("Python-generated update metadata verifies");
assert_eq!("rustdesk-1.4.6-x86_64.exe", artifact.file_name);
}
#[path = "update_metadata/offline_tests.rs"]
mod offline_tests;