fix: harden Windows install and launch pipeline

This commit is contained in:
Emil
2026-09-07 22:41:59 +03:00
parent 5fa58b0013
commit ff666ba55f
9 changed files with 1002 additions and 205 deletions
+61 -17
View File
@@ -1,14 +1,17 @@
use crate::manifest::{self, Manifest};
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use reqwest::header::ACCEPT_ENCODING;
use reqwest::{blocking::Client, redirect::Policy};
use serde::{Deserialize, Serialize};
use std::{fmt, time::Duration};
use std::{fmt, thread, time::Duration};
const AERONAUTICS_MANIFEST: &str =
"https://shacraft.ru/api/launcher/v2/profiles/aeronautics/signed-manifest";
const AERONAUTICS_ONLINE: &str = "https://shacraft.ru/api/online/aoc";
const MANIFEST_PUBLIC_KEY: &str = "2S3FRdZj4Xw5nJpZ3IhqVITBg3nTH9AtGSo1Ew9+qVQ=";
const MAX_MANIFEST_SIZE: u64 = 2 * 1024 * 1024;
const MANIFEST_ATTEMPTS: u32 = 3;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -47,7 +50,9 @@ impl fmt::Display for RemoteError {
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}"),
Self::InvalidManifest(error) => {
write!(formatter, "ShaCraft manifest is invalid: {error}")
}
}
}
}
@@ -62,26 +67,63 @@ pub fn fetch_manifest(profile_id: &str) -> Result<Manifest, RemoteError> {
.redirect(Policy::none())
.build()
.map_err(RemoteError::Network)?;
let response = client.get(url).send().map_err(RemoteError::Network)?;
if !response.status().is_success() {
return Err(RemoteError::Status(response.status()));
let mut last_network_error = None;
let mut source = None;
for attempt in 1..=MANIFEST_ATTEMPTS {
match client.get(url).header(ACCEPT_ENCODING, "identity").send() {
Ok(response) => {
if !response.status().is_success() {
return Err(RemoteError::Status(response.status()));
}
if response
.content_length()
.is_some_and(|size| size > MAX_MANIFEST_SIZE)
{
return Err(RemoteError::TooLarge);
}
match response.bytes() {
Ok(bytes) if bytes.len() as u64 <= MAX_MANIFEST_SIZE => {
source = Some(bytes);
break;
}
Ok(_) => return Err(RemoteError::TooLarge),
Err(error) => last_network_error = Some(error),
}
}
Err(error) => last_network_error = Some(error),
}
if attempt < MANIFEST_ATTEMPTS {
thread::sleep(Duration::from_millis(250 * attempt as u64));
}
}
if response.content_length().is_some_and(|size| size > 2 * 1024 * 1024) {
return Err(RemoteError::TooLarge);
}
let source = response.text().map_err(RemoteError::Network)?;
let envelope = serde_json::from_str::<SignedManifest>(&source)
let source = source.ok_or_else(|| {
RemoteError::Network(last_network_error.expect("a network attempt failed"))
})?;
let envelope = serde_json::from_slice::<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"))
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 signature = Signature::from_slice(&signature_bytes).map_err(|_| RemoteError::InvalidSignature)?;
public_key.verify(&payload, &signature).map_err(|_| RemoteError::InvalidSignature)?;
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)
}
@@ -100,5 +142,7 @@ pub fn fetch_server_status(profile_id: &str) -> Result<ServerStatus, RemoteError
if !response.status().is_success() {
return Err(RemoteError::Status(response.status()));
}
response.json::<ServerStatus>().map_err(RemoteError::Network)
response
.json::<ServerStatus>()
.map_err(RemoteError::Network)
}