Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
198bf39
Branches
Tags
main
apple-crates
fairplay
src
metadata.rs
Add FairPlay crate reimplementing fairplay-decrypt and fix APS
Dadoum
commited
198bf39
at 2026-10-25 21:12:14
metadata.rs
Blame
History
Raw
use std::ops::Range; use crate::Error; /// Borrowed SINF fields, for callers that already parse the container. #[derive(Clone, Copy)] pub struct SinfFields<'a> { pub name: &'a [u8], pub iviv: &'a [u8], pub private: &'a [u8], } impl<'a> SinfFields<'a> { /// Parse one outer `sinf` atom containing exactly one `schi` container. /// Required fields must each occur once. Unknown sibling atoms are allowed. pub fn parse(sinf: &'a [u8]) -> Result<Self, Error> { if sinf.len() < 8 || &sinf[4..8] != b"sinf" || be_u32(sinf) as usize != sinf.len() { return Err(Error::InvalidSinf("expected one outer sinf atom")); } let schi = unique_atom(&sinf[8..], b"schi")?; Ok(Self { name: unique_atom(schi, b"name")?, iviv: unique_atom(schi, b"iviv")?, private: unique_atom(schi, b"priv")?, }) } } fn be_u32(bytes: &[u8]) -> u32 { u32::from_be_bytes(bytes[..4].try_into().expect("validated four-byte field")) } fn unique_atom<'a>(mut atoms: &'a [u8], tag: &[u8; 4]) -> Result<&'a [u8], Error> { let mut found = None; while !atoms.is_empty() { if atoms.len() < 8 { return Err(Error::InvalidSinf("truncated atom header")); } let size = be_u32(atoms) as usize; if size < 8 || size > atoms.len() { return Err(Error::InvalidSinf( "atom size exceeds container or is shorter than header", )); } if &atoms[4..8] == tag && found.replace(&atoms[8..size]).is_some() { return Err(Error::InvalidSinf("duplicate required atom")); } atoms = &atoms[size..]; } found.ok_or(Error::InvalidSinf("missing required atom")) } /// One borrowed 32-byte SUPF record: a wrapped page key followed by a CBC IV. #[derive(Clone, Copy)] pub struct PageRecord<'a>(&'a [u8; 32]); impl<'a> PageRecord<'a> { pub fn from_bytes(bytes: &'a [u8; 32]) -> Self { Self(bytes) } pub fn wrapped_key(self) -> &'a [u8; 16] { self.0[..16].try_into().expect("fixed-size record") } pub fn iv(self) -> &'a [u8; 16] { self.0[16..].try_into().expect("fixed-size record") } } /// A validated, borrowed sequence of 32-byte page records, without allocations. #[derive(Clone, Copy)] pub struct PageRecords<'a>(&'a [u8]); impl<'a> PageRecords<'a> { /// Accept raw records already selected by the caller's SUPF parser. pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> { if !bytes.len().is_multiple_of(32) { return Err(Error::InvalidSupf("partial page record")); } Ok(Self(bytes)) } /// Select an architecture by its exact Mach CPU type and subtype. /// /// Handles SUPF versions 3 and 5 and tags 507, 510 and 612. No executable /// parsing is performed. For the supported arm64 path use CPU `0x0100000c` /// and subtype `0`. The declared page count must fit in its architecture. pub fn from_supf(supf: &'a [u8], cpu_type: u32, cpu_subtype: u32) -> Result<Self, Error> { if supf.len() < 12 || !matches!(supf[0], 3 | 5) || !matches!(&supf[1..4], b"507" | b"510" | b"612") { return Err(Error::InvalidSupf("unsupported version or tag")); } let header_size = if supf[0] == 3 { 32 } else { 12 }; let count = be_u32(&supf[8..]) as usize; if !(1..=16).contains(&count) { return Err(Error::InvalidSupf("invalid architecture count")); } let mut pos: usize = 12; for _ in 0..count { let header_end = pos .checked_add(4 + header_size) .filter(|end| *end <= supf.len()) .ok_or(Error::InvalidSupf("truncated architecture header"))?; let length = be_u32(&supf[pos..]) as usize; let end = pos .checked_add(4) .and_then(|start| start.checked_add(length)) .filter(|end| *end <= supf.len() && length >= header_size) .ok_or(Error::InvalidSupf("invalid architecture length"))?; let cpu = be_u32(&supf[pos + 4..]); let subtype = be_u32(&supf[pos + 8..]); if cpu == cpu_type && subtype == cpu_subtype { let pages = be_u32(&supf[pos + header_size..]) as usize; let records_end = pages .checked_mul(32) .and_then(|size| header_end.checked_add(size)) .filter(|records_end| *records_end <= end) .ok_or(Error::InvalidSupf("incomplete architecture page records"))?; return Ok(Self(&supf[header_end..records_end])); } pos = end; } Err(Error::ArchitectureNotFound { cpu_type, cpu_subtype, }) } pub fn len(self) -> usize { self.0.len() / 32 } pub fn is_empty(self) -> bool { self.0.is_empty() } pub fn get(self, index: usize) -> Option<PageRecord<'a>> { let start = index.checked_mul(32)?; let bytes = self.0.get(start..start.checked_add(32)?)?; Some(PageRecord(bytes.try_into().expect("complete record"))) } /// Select records for a subregion, preserving their original order. /// The caller must advance the absolute page index by the same start value. pub fn slice(self, range: Range<usize>) -> Option<Self> { self.0 .get(range.start.checked_mul(32)?..range.end.checked_mul(32)?) .map(Self) } }