mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 14:14:53 +08:00
fix(release):完善当前分发证明与质量门禁
This commit is contained in:
@@ -71,6 +71,12 @@ const DOWNLOAD_MANIFEST_FILE: &str = "official-download-manifest.json";
|
||||
const DOWNLOAD_QUARANTINE_FILE: &str = "official-download-quarantine.json";
|
||||
/// Independent publication fact for the official distribution manifest.
|
||||
pub const OFFICIAL_DISTRIBUTION_PUBLICATION_FILE: &str = "official-distribution-publication.json";
|
||||
/// Current official distribution verification result.
|
||||
pub const OFFICIAL_DISTRIBUTION_ATTESTATION_FILE: &str = "official-distribution-attestation.json";
|
||||
/// Persisted attestation schema version.
|
||||
pub const OFFICIAL_DISTRIBUTION_ATTESTATION_VERSION: u32 = 1;
|
||||
/// Attestations older than this are no longer allowed to authorize current CDN.
|
||||
pub const OFFICIAL_DISTRIBUTION_ATTESTATION_MAX_AGE_SECONDS: u64 = 900;
|
||||
/// 记录一个已发布官方 release 获取的 CAS 引用。
|
||||
pub const OFFICIAL_CAS_REUSE_REFERENCES_FILE: &str = "official-cas-reuse-references.json";
|
||||
const OFFICIAL_CAS_REUSE_REFERENCES_VERSION: u32 = 1;
|
||||
@@ -566,6 +572,154 @@ pub(crate) struct OfficialDistributionPublicationAnchor {
|
||||
|
||||
const OFFICIAL_DISTRIBUTION_PUBLICATION_VERSION: u32 = 1;
|
||||
|
||||
/// Rust-owned lightweight proof that the current official publication is safe
|
||||
/// for the read-only distribution path.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct OfficialDistributionAttestation {
|
||||
/// Attestation schema version.
|
||||
pub version: u32,
|
||||
/// Distribution channel; currently always `official`.
|
||||
pub channel: String,
|
||||
/// Stable official release ID.
|
||||
pub official_release_id: String,
|
||||
/// Published version root this result describes.
|
||||
pub resource_root: PathBuf,
|
||||
/// Identity of the publication anchor and its manifest generation.
|
||||
pub publication_identity: String,
|
||||
/// Identity of the complete destination mapping.
|
||||
pub mapping_identity: String,
|
||||
/// BLAKE3 identity of the manifest bytes.
|
||||
pub manifest_identity: String,
|
||||
/// Number of entries in the bound manifest.
|
||||
pub entry_count: u64,
|
||||
/// `verified`, `stale`, `invalid`, or `unavailable`.
|
||||
pub integrity_status: String,
|
||||
/// Human-readable stable state label.
|
||||
pub status: String,
|
||||
/// Namespaced status code consumed by RPC clients.
|
||||
pub status_code: String,
|
||||
/// Whether this attestation currently authorizes distribution.
|
||||
pub ready: bool,
|
||||
/// Monotonic verification generation for this published root.
|
||||
pub verification_generation: u64,
|
||||
/// Time of the last successful full local verification.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub verified_at: Option<u64>,
|
||||
/// Freshness window used by the lightweight RPC reader.
|
||||
pub max_age_seconds: u64,
|
||||
/// Diagnostics retained with the result.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub diagnostics: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn official_distribution_publication_identity(
|
||||
anchor: &OfficialDistributionPublicationAnchor,
|
||||
) -> String {
|
||||
format!(
|
||||
"odp-v1-{}-{}",
|
||||
anchor.mapping_identity, anchor.manifest_identity
|
||||
)
|
||||
}
|
||||
|
||||
fn official_distribution_attestation_status_code(integrity_status: &str) -> &'static str {
|
||||
match integrity_status {
|
||||
"verified" => "distribution.ready",
|
||||
"stale" => "distribution.attestation_stale",
|
||||
"invalid" => "distribution.attestation_invalid",
|
||||
_ => "distribution.attestation_unavailable",
|
||||
}
|
||||
}
|
||||
|
||||
fn official_distribution_attestation_status(integrity_status: &str) -> &'static str {
|
||||
match integrity_status {
|
||||
"verified" => "ready",
|
||||
"stale" => "stale",
|
||||
"invalid" => "invalid",
|
||||
_ => "unavailable",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_official_distribution_attestation_at(
|
||||
release_root: &Path,
|
||||
) -> Result<Option<OfficialDistributionAttestation>, String> {
|
||||
let path = release_root.join(OFFICIAL_DISTRIBUTION_ATTESTATION_FILE);
|
||||
let Some(bytes) = read_file_no_symlink(&path, "官方 distribution attestation")? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let attestation: OfficialDistributionAttestation = serde_json::from_slice(&bytes)
|
||||
.map_err(|error| format!("解析官方 distribution attestation 失败:{error}"))?;
|
||||
if attestation.version != OFFICIAL_DISTRIBUTION_ATTESTATION_VERSION {
|
||||
return Err(format!(
|
||||
"不支持的官方 distribution attestation 版本:{}",
|
||||
attestation.version
|
||||
));
|
||||
}
|
||||
Ok(Some(attestation))
|
||||
}
|
||||
|
||||
/// Records a verification result for one already published official root.
|
||||
///
|
||||
/// The publication anchor is reused as the immutable generation identity.
|
||||
/// The caller chooses `verified` only after the existing full local audit has
|
||||
/// passed; this function itself never turns a partial audit into a healthy
|
||||
/// result.
|
||||
pub(crate) fn write_official_distribution_attestation_at(
|
||||
release_root: &Path,
|
||||
official_release_id: &str,
|
||||
integrity_status: &str,
|
||||
diagnostics: Vec<String>,
|
||||
) -> Result<OfficialDistributionAttestation, String> {
|
||||
if !matches!(
|
||||
integrity_status,
|
||||
"verified" | "stale" | "invalid" | "unavailable"
|
||||
) {
|
||||
return Err(format!(
|
||||
"不支持的官方 distribution attestation 状态:{integrity_status}"
|
||||
));
|
||||
}
|
||||
ensure_safe_directory_path(release_root, "官方 distribution attestation 根目录")?;
|
||||
let anchor = verify_official_distribution_publication_at(release_root, official_release_id)?
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"官方 distribution attestation 缺少 publication anchor:{}",
|
||||
release_root.display()
|
||||
)
|
||||
})?;
|
||||
let previous_generation = read_official_distribution_attestation_at(release_root)?
|
||||
.map(|previous| previous.verification_generation)
|
||||
.unwrap_or(0);
|
||||
let verified_at = (integrity_status == "verified").then_some(unix_seconds_now());
|
||||
let attestation = OfficialDistributionAttestation {
|
||||
version: OFFICIAL_DISTRIBUTION_ATTESTATION_VERSION,
|
||||
channel: "official".to_string(),
|
||||
official_release_id: official_release_id.to_string(),
|
||||
resource_root: release_root.to_path_buf(),
|
||||
publication_identity: official_distribution_publication_identity(&anchor),
|
||||
mapping_identity: anchor.mapping_identity,
|
||||
manifest_identity: anchor.manifest_identity,
|
||||
entry_count: anchor.entry_count,
|
||||
integrity_status: integrity_status.to_string(),
|
||||
status: official_distribution_attestation_status(integrity_status).to_string(),
|
||||
status_code: official_distribution_attestation_status_code(integrity_status).to_string(),
|
||||
ready: integrity_status == "verified",
|
||||
verification_generation: previous_generation.saturating_add(1),
|
||||
verified_at,
|
||||
max_age_seconds: OFFICIAL_DISTRIBUTION_ATTESTATION_MAX_AGE_SECONDS,
|
||||
diagnostics,
|
||||
};
|
||||
let path = release_root.join(OFFICIAL_DISTRIBUTION_ATTESTATION_FILE);
|
||||
ensure_safe_file_target(release_root, &path, "官方 distribution attestation")?;
|
||||
let bytes = serde_json::to_vec_pretty(&attestation)
|
||||
.map_err(|error| format!("序列化官方 distribution attestation 失败:{error}"))?;
|
||||
write_file_atomic(
|
||||
&path,
|
||||
&bytes,
|
||||
STATE_FILE_MODE,
|
||||
"官方 distribution attestation",
|
||||
)?;
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
/// Writes the independent publication anchor after the complete official
|
||||
/// release verification has succeeded.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user