//! Private carrier metadata, separate from the stable v1 payload format.
//!
//! The build and release verifier establish SHA-512 equality first. Runtime
//! verification uses BLAKE3 over every byte, bound to that complete v1 header.
//! Neither digest authenticates an untrusted carrier or replaces code signing.
use std::io::{Read, Seek};
use sha2::{Digest, Sha512};
use crate::{
format::{Header, HEADER_LEN},
Error, ErrorKind, Result,
};
pub const INTEGRITY_MAGIC: &[u8; 8] = b"SWCNB3V1";
pub const INTEGRITY_LEN: usize = 8 + HEADER_LEN + 32;
#[derive(Clone, Debug)]
pub struct RuntimeIntegrity {
header: [u8; HEADER_LEN],
digest: [u8; 32],
}
impl RuntimeIntegrity {
/// Derive runtime metadata only from bytes verified against the SHA-512
/// payload header. This runs during building, never on a cache hit.
pub fn from_raw(header: &Header, raw: &mut (impl Read + Seek)) -> Result<Self> {
header.verify(raw)?;
raw.rewind()
.map_err(|e| Error::io(ErrorKind::Integrity, "rewind verified raw addon", e))?;
let mut hash = blake3::Hasher::new();
let mut buffer = [0; 64 * 1024];
loop {
let len = raw
.read(&mut buffer)
.map_err(|e| Error::io(ErrorKind::Integrity, "hash verified raw addon", e))?;
if len == 0 {
break;
}
hash.update(&buffer[..len]);
}
Ok(Self {
header: header.encode(),
digest: *hash.finalize().as_bytes(),
})
}
pub fn encode(&self) -> [u8; INTEGRITY_LEN] {
let mut bytes = [0; INTEGRITY_LEN];
bytes[..8].copy_from_slice(INTEGRITY_MAGIC);
bytes[8..8 + HEADER_LEN].copy_from_slice(&self.header);
bytes[8 + HEADER_LEN..].copy_from_slice(&self.digest);
bytes
}
pub fn parse(bytes: &[u8]) -> Result<Self> {
if bytes.len() != INTEGRITY_LEN || &bytes[..8] != INTEGRITY_MAGIC {
return Err(Error::new(
ErrorKind::Integrity,
"invalid native runtime integrity metadata",
));
}
Ok(Self {
header: bytes[8..8 + HEADER_LEN].try_into().unwrap(),
digest: bytes[8 + HEADER_LEN..].try_into().unwrap(),
})
}
pub(crate) fn verifier(&self, header: &Header) -> Result<Verifier> {
if self.header != header.encode() {
return Err(Error::new(
ErrorKind::Integrity,
"runtime integrity metadata does not match payload header",
));
}
Ok(Verifier::Blake3(Box::default(), self.digest))
}
}
/// Select a digest once per verification, with no persistent validation cache.
pub(crate) enum Verifier {
Sha512(Box<Sha512>, [u8; 64]),
Blake3(Box<blake3::Hasher>, [u8; 32]),
}
impl Verifier {
pub(crate) fn sha512(header: &Header) -> Self {
Self::Sha512(Box::default(), header.digest)
}
pub(crate) fn buffer_len(&self, _raw_len: u64) -> usize {
// Enter the worker pool once for ordinary addon sizes. Repeated 1 MiB
// dispatches stall Rosetta verification on constrained CI hosts. Bound
// scratch memory even for the largest accepted payloads; the buffer is
// freed after verification. Small images and SHA-512 keep streaming.
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
if matches!(self, Self::Blake3(..)) && _raw_len >= 1024 * 1024 {
return _raw_len.min(32 * 1024 * 1024) as usize;
}
64 * 1024
}
pub(crate) fn update(&mut self, bytes: &[u8]) {
match self {
Self::Sha512(hash, _) => hash.update(bytes),
Self::Blake3(hash, _) => {
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
if bytes.len() >= 128 * 1024 {
if let Some(pool) = verification_pool() {
pool.install(|| hash.update_rayon(bytes));
return;
}
}
hash.update(bytes);
}
}
}
pub(crate) fn finish(self) -> Result<()> {
let algorithm = match self {
Self::Sha512(hash, expected) => {
if hash.finalize()[..] == expected {
return Ok(());
}
"SHA-512"
}
Self::Blake3(hash, expected) => {
if hash.finalize().as_bytes() == &expected {
return Ok(());
}
"BLAKE3"
}
};
Err(Error::new(
ErrorKind::Integrity,
format!("raw addon {algorithm} mismatch"),
))
}
}
/// Bound Rosetta verification work without using the application's global
/// Rayon pool. Only worker threads are reused: every verification still reads
/// and hashes every file byte. Resource-constrained hosts hash sequentially.
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
fn verification_pool() -> Option<&'static rayon_core::ThreadPool> {
static POOL: std::sync::OnceLock<Option<rayon_core::ThreadPool>> = std::sync::OnceLock::new();
POOL.get_or_init(|| {
let threads = std::thread::available_parallelism().ok()?.get().min(4);
if threads < 2 {
return None;
}
rayon_core::ThreadPoolBuilder::new()
.num_threads(threads)
.thread_name(|index| format!("swc-native-verify-{index}"))
.build()
.ok()
})
.as_ref()
}