feat: verify signed launcher manifests

This commit is contained in:
emil28092005
2026-09-06 01:12:09 +03:00
parent ad98fb701d
commit 693330e7e8
4 changed files with 171 additions and 9 deletions
+30 -2
View File
@@ -1,9 +1,22 @@
use crate::manifest::{self, Manifest};
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use reqwest::{blocking::Client, redirect::Policy};
use serde::Deserialize;
use std::{fmt, time::Duration};
const AERONAUTICS_MANIFEST: &str =
"https://shacraft.ru/api/launcher/v2/profiles/aeronautics/manifest";
"https://shacraft.ru/api/launcher/v2/profiles/aeronautics/signed-manifest";
const MANIFEST_PUBLIC_KEY: &str = "2S3FRdZj4Xw5nJpZ3IhqVITBg3nTH9AtGSo1Ew9+qVQ=";
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignedManifest {
schema_version: u32,
key_id: String,
payload: String,
signature: String,
}
#[derive(Debug)]
pub enum RemoteError {
@@ -11,6 +24,7 @@ pub enum RemoteError {
Network(reqwest::Error),
Status(reqwest::StatusCode),
TooLarge,
InvalidSignature,
InvalidManifest(manifest::ManifestError),
}
@@ -21,6 +35,7 @@ impl fmt::Display for RemoteError {
Self::Network(error) => write!(formatter, "Cannot load ShaCraft manifest: {error}"),
Self::Status(status) => write!(formatter, "ShaCraft manifest request failed: {status}"),
Self::TooLarge => formatter.write_str("ShaCraft manifest is too large"),
Self::InvalidSignature => formatter.write_str("ShaCraft manifest signature is invalid"),
Self::InvalidManifest(error) => write!(formatter, "ShaCraft manifest is invalid: {error}"),
}
}
@@ -44,5 +59,18 @@ pub fn fetch_manifest(profile_id: &str) -> Result<Manifest, RemoteError> {
return Err(RemoteError::TooLarge);
}
let source = response.text().map_err(RemoteError::Network)?;
manifest::validate_json(&source).map_err(RemoteError::InvalidManifest)
let envelope = serde_json::from_str::<SignedManifest>(&source)
.map_err(|_| RemoteError::InvalidSignature)?;
if envelope.schema_version != 1 || envelope.key_id != "2026-09-06" {
return Err(RemoteError::InvalidSignature);
}
let payload = STANDARD.decode(envelope.payload).map_err(|_| RemoteError::InvalidSignature)?;
let signature_bytes = STANDARD.decode(envelope.signature).map_err(|_| RemoteError::InvalidSignature)?;
let public_key_bytes = STANDARD.decode(MANIFEST_PUBLIC_KEY).expect("embedded public key must be valid");
let public_key = VerifyingKey::from_bytes(&public_key_bytes.try_into().expect("embedded public key must be 32 bytes"))
.expect("embedded public key must be valid");
let signature = Signature::from_slice(&signature_bytes).map_err(|_| RemoteError::InvalidSignature)?;
public_key.verify(&payload, &signature).map_err(|_| RemoteError::InvalidSignature)?;
let payload = String::from_utf8(payload).map_err(|_| RemoteError::InvalidSignature)?;
manifest::validate_json(&payload).map_err(RemoteError::InvalidManifest)
}