use {
crate::{
code_directory::CodeDirectoryBlob,
code_requirement::{CodeRequirements, RequirementType},
cryptography::DigestType,
environment_constraints::EncodedEnvironmentConstraints,
AppleCodesignError, Result,
},
cryptographic_message_syntax::SignedData,
scroll::{IOwrite, Pread},
std::{borrow::Cow, cmp::Ordering, collections::HashMap, io::Write},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CodeSigningMagic {
Requirement,
RequirementSet,
CodeDirectory,
EmbeddedSignature,
EmbeddedSignatureOld,
Entitlements,
EntitlementsDer,
EnvironmentContraintsDer,
DetachedSignature,
BlobWrapper,
Unknown(u32),
}
impl From<u32> for CodeSigningMagic {
fn from(v: u32) -> Self {
match v {
0xfade0c00 => Self::Requirement,
0xfade0c01 => Self::RequirementSet,
0xfade0c02 => Self::CodeDirectory,
0xfade0cc0 => Self::EmbeddedSignature,
0xfade0b02 => Self::EmbeddedSignatureOld,
0xfade7171 => Self::Entitlements,
0xfade7172 => Self::EntitlementsDer,
0xfade8181 => Self::EnvironmentContraintsDer,
0xfade0cc1 => Self::DetachedSignature,
0xfade0b01 => Self::BlobWrapper,
_ => Self::Unknown(v),
}
}
}
impl From<CodeSigningMagic> for u32 {
fn from(magic: CodeSigningMagic) -> u32 {
match magic {
CodeSigningMagic::Requirement => 0xfade0c00,
CodeSigningMagic::RequirementSet => 0xfade0c01,
CodeSigningMagic::CodeDirectory => 0xfade0c02,
CodeSigningMagic::EmbeddedSignature => 0xfade0cc0,
CodeSigningMagic::EmbeddedSignatureOld => 0xfade0b02,
CodeSigningMagic::Entitlements => 0xfade7171,
CodeSigningMagic::EntitlementsDer => 0xfade7172,
CodeSigningMagic::EnvironmentContraintsDer => 0xfade8181,
CodeSigningMagic::DetachedSignature => 0xfade0cc1,
CodeSigningMagic::BlobWrapper => 0xfade0b01,
CodeSigningMagic::Unknown(v) => v,
}
}
}
impl From<u8> for DigestType {
fn from(v: u8) -> Self {
match v {
0 => Self::None,
1 => Self::Sha1,
2 => Self::Sha256,
3 => Self::Sha256Truncated,
4 => Self::Sha384,
5 => Self::Sha512,
_ => Self::Unknown(v),
}
}
}
impl From<DigestType> for u8 {
fn from(v: DigestType) -> u8 {
match v {
DigestType::None => 0,
DigestType::Sha1 => 1,
DigestType::Sha256 => 2,
DigestType::Sha256Truncated => 3,
DigestType::Sha384 => 4,
DigestType::Sha512 => 5,
DigestType::Unknown(v) => v,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum CodeSigningSlot {
CodeDirectory,
Info,
RequirementSet,
ResourceDir,
Application,
Entitlements,
RepSpecific,
EntitlementsDer,
LaunchConstraintsSelf,
LaunchConstraintsParent,
LaunchConstraintsResponsibleProcess,
LibraryConstraints,
AlternateCodeDirectory0,
AlternateCodeDirectory1,
AlternateCodeDirectory2,
AlternateCodeDirectory3,
AlternateCodeDirectory4,
Signature,
Identification,
Ticket,
Unknown(u32),
}
impl std::fmt::Debug for CodeSigningSlot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CodeDirectory => {
f.write_fmt(format_args!("CodeDirectory ({})", u32::from(*self)))
}
Self::Info => f.write_fmt(format_args!("Info ({})", u32::from(*self))),
Self::RequirementSet => {
f.write_fmt(format_args!("RequirementSet ({})", u32::from(*self)))
}
Self::ResourceDir => f.write_fmt(format_args!("Resources ({})", u32::from(*self))),
Self::Application => f.write_fmt(format_args!("Application ({})", u32::from(*self))),
Self::Entitlements => f.write_fmt(format_args!("Entitlements ({})", u32::from(*self))),
Self::RepSpecific => f.write_fmt(format_args!("Rep Specific ({})", u32::from(*self))),
Self::EntitlementsDer => {
f.write_fmt(format_args!("DER Entitlements ({})", u32::from(*self)))
}
Self::LaunchConstraintsSelf => f.write_fmt(format_args!(
"DER Launch Constraints on Self ({})",
u32::from(*self)
)),
Self::LaunchConstraintsParent => f.write_fmt(format_args!(
"DER Launch Constraints on Parent ({})",
u32::from(*self)
)),
Self::LaunchConstraintsResponsibleProcess => f.write_fmt(format_args!(
"DER Launch Constraints on Responsible Process ({})",
u32::from(*self)
)),
Self::LibraryConstraints => f.write_fmt(format_args!(
"DER Launch Constraints on Loaded Libraries ({})",
u32::from(*self)
)),
Self::AlternateCodeDirectory0 => f.write_fmt(format_args!(
"CodeDirectory Alternate #0 ({})",
u32::from(*self)
)),
Self::AlternateCodeDirectory1 => f.write_fmt(format_args!(
"CodeDirectory Alternate #1 ({})",
u32::from(*self)
)),
Self::AlternateCodeDirectory2 => f.write_fmt(format_args!(
"CodeDirectory Alternate #2 ({})",
u32::from(*self)
)),
Self::AlternateCodeDirectory3 => f.write_fmt(format_args!(
"CodeDirectory Alternate #3 ({})",
u32::from(*self)
)),
Self::AlternateCodeDirectory4 => f.write_fmt(format_args!(
"CodeDirectory Alternate #4 ({})",
u32::from(*self)
)),
Self::Signature => f.write_fmt(format_args!("CMS Signature ({})", u32::from(*self))),
Self::Identification => {
f.write_fmt(format_args!("Identification ({})", u32::from(*self)))
}
Self::Ticket => f.write_fmt(format_args!("Ticket ({})", u32::from(*self))),
Self::Unknown(value) => f.write_fmt(format_args!("Unknown ({value})")),
}
}
}
impl From<u32> for CodeSigningSlot {
fn from(v: u32) -> Self {
match v {
0 => Self::CodeDirectory,
1 => Self::Info,
2 => Self::RequirementSet,
3 => Self::ResourceDir,
4 => Self::Application,
5 => Self::Entitlements,
6 => Self::RepSpecific,
7 => Self::EntitlementsDer,
8 => Self::LaunchConstraintsSelf,
9 => Self::LaunchConstraintsParent,
10 => Self::LaunchConstraintsResponsibleProcess,
11 => Self::LibraryConstraints,
0x1000 => Self::AlternateCodeDirectory0,
0x1001 => Self::AlternateCodeDirectory1,
0x1002 => Self::AlternateCodeDirectory2,
0x1003 => Self::AlternateCodeDirectory3,
0x1004 => Self::AlternateCodeDirectory4,
0x10000 => Self::Signature,
0x10001 => Self::Identification,
0x10002 => Self::Ticket,
_ => Self::Unknown(v),
}
}
}
impl From<CodeSigningSlot> for u32 {
fn from(v: CodeSigningSlot) -> Self {
match v {
CodeSigningSlot::CodeDirectory => 0,
CodeSigningSlot::Info => 1,
CodeSigningSlot::RequirementSet => 2,
CodeSigningSlot::ResourceDir => 3,
CodeSigningSlot::Application => 4,
CodeSigningSlot::Entitlements => 5,
CodeSigningSlot::RepSpecific => 6,
CodeSigningSlot::EntitlementsDer => 7,
CodeSigningSlot::LaunchConstraintsSelf => 8,
CodeSigningSlot::LaunchConstraintsParent => 9,
CodeSigningSlot::LaunchConstraintsResponsibleProcess => 10,
CodeSigningSlot::LibraryConstraints => 11,
CodeSigningSlot::AlternateCodeDirectory0 => 0x1000,
CodeSigningSlot::AlternateCodeDirectory1 => 0x1001,
CodeSigningSlot::AlternateCodeDirectory2 => 0x1002,
CodeSigningSlot::AlternateCodeDirectory3 => 0x1003,
CodeSigningSlot::AlternateCodeDirectory4 => 0x1004,
CodeSigningSlot::Signature => 0x10000,
CodeSigningSlot::Identification => 0x10001,
CodeSigningSlot::Ticket => 0x10002,
CodeSigningSlot::Unknown(v) => v,
}
}
}
impl PartialOrd for CodeSigningSlot {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CodeSigningSlot {
fn cmp(&self, other: &Self) -> Ordering {
u32::from(*self).cmp(&u32::from(*other))
}
}
impl CodeSigningSlot {
pub fn has_external_content(&self) -> bool {
matches!(self, Self::Info | Self::ResourceDir)
}
pub fn is_alternative_code_directory(&self) -> bool {
matches!(
self,
CodeSigningSlot::AlternateCodeDirectory0
| CodeSigningSlot::AlternateCodeDirectory1
| CodeSigningSlot::AlternateCodeDirectory2
| CodeSigningSlot::AlternateCodeDirectory3
| CodeSigningSlot::AlternateCodeDirectory4
)
}
pub fn is_code_directory_specials_expressible(&self) -> bool {
*self >= Self::Info && *self <= Self::LibraryConstraints
}
}
#[repr(C)]
#[derive(Clone, Pread)]
struct BlobIndex {
typ: u32,
offset: u32,
}
impl std::fmt::Debug for BlobIndex {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("BlobIndex")
.field("type", &CodeSigningSlot::from(self.typ))
.field("offset", &self.offset)
.finish()
}
}
fn read_blob_header(data: &[u8]) -> Result<(u32, usize, &[u8]), scroll::Error> {
let magic = data.pread_with(0, scroll::BE)?;
let length = data.pread_with::<u32>(4, scroll::BE)?;
Ok((magic, length as usize, &data[8..]))
}
pub(crate) fn read_and_validate_blob_header<'a>(
data: &'a [u8],
expected_magic: u32,
what: &'static str,
) -> Result<&'a [u8], AppleCodesignError> {
let (magic, _, data) = read_blob_header(data)?;
if magic != expected_magic {
Err(AppleCodesignError::BadMagic(what))
} else {
Ok(data)
}
}
pub fn create_superblob<'a>(
magic: CodeSigningMagic,
blobs: impl Iterator<Item = &'a (CodeSigningSlot, Vec<u8>)>,
) -> Result<Vec<u8>, AppleCodesignError> {
let blobs = blobs.collect::<Vec<_>>();
let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
let mut blob_data = Vec::new();
let mut total_length: u32 = 4 + 4 + 4;
total_length += 8 * blobs.len() as u32;
let mut indices = Vec::with_capacity(blobs.len());
for (slot, blob) in blobs {
blob_data.push(blob);
indices.push(BlobIndex {
typ: u32::from(*slot),
offset: total_length,
});
total_length += blob.len() as u32;
}
cursor.iowrite_with(u32::from(magic), scroll::BE)?;
cursor.iowrite_with(total_length, scroll::BE)?;
cursor.iowrite_with(indices.len() as u32, scroll::BE)?;
for index in indices {
cursor.iowrite_with(index.typ, scroll::BE)?;
cursor.iowrite_with(index.offset, scroll::BE)?;
}
for data in blob_data {
cursor.write_all(data)?;
}
Ok(cursor.into_inner())
}
#[derive(Clone)]
pub struct BlobEntry<'a> {
pub index: usize,
pub slot: CodeSigningSlot,
pub offset: usize,
pub magic: CodeSigningMagic,
pub length: usize,
pub data: &'a [u8],
}
impl<'a> std::fmt::Debug for BlobEntry<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("BlobEntry")
.field("index", &self.index)
.field("slot", &self.slot)
.field("offset", &self.offset)
.field("length", &self.length)
.field("magic", &self.magic)
.finish()
}
}
impl<'a> BlobEntry<'a> {
pub fn into_parsed_blob(self) -> Result<ParsedBlob<'a>, AppleCodesignError> {
self.try_into()
}
pub fn payload(&self) -> Result<&'a [u8], AppleCodesignError> {
Ok(read_blob_header(self.data)?.2)
}
pub fn digest_with(&self, hash: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
hash.digest_data(self.data)
}
}
pub trait Blob<'a>
where
Self: Sized,
{
fn magic() -> u32;
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError>;
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError>;
fn to_blob_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
let mut res = Vec::new();
res.iowrite_with(Self::magic(), scroll::BE)?;
let payload = self.serialize_payload()?;
res.iowrite_with(payload.len() as u32 + 8, scroll::BE)?;
res.extend(payload);
Ok(res)
}
fn digest_with(&self, hash_type: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
hash_type.digest_data(&self.to_blob_bytes()?)
}
}
pub struct RequirementBlob<'a> {
pub data: Cow<'a, [u8]>,
}
impl<'a> Blob<'a> for RequirementBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::Requirement)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
let data = read_and_validate_blob_header(data, Self::magic(), "requirement blob")?;
Ok(Self { data: data.into() })
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.data.to_vec())
}
}
impl<'a> std::fmt::Debug for RequirementBlob<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("RequirementBlob({})", hex::encode(&self.data)))
}
}
impl<'a> RequirementBlob<'a> {
pub fn to_owned(&self) -> RequirementBlob<'static> {
RequirementBlob {
data: Cow::Owned(self.data.clone().into_owned()),
}
}
pub fn parse_expressions(&self) -> Result<CodeRequirements, AppleCodesignError> {
Ok(CodeRequirements::parse_binary(&self.data)?.0)
}
}
#[derive(Debug, Default)]
pub struct RequirementSetBlob<'a> {
pub requirements: HashMap<RequirementType, RequirementBlob<'a>>,
}
impl<'a> Blob<'a> for RequirementSetBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::RequirementSet)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
read_and_validate_blob_header(data, Self::magic(), "requirement set blob")?;
let offset = &mut 8;
let count = data.gread_with::<u32>(offset, scroll::BE)?;
let mut indices = Vec::with_capacity(count as usize);
for _ in 0..count {
indices.push((
data.gread_with::<u32>(offset, scroll::BE)?,
data.gread_with::<u32>(offset, scroll::BE)?,
));
}
let mut requirements = HashMap::with_capacity(indices.len());
for (i, (flavor, offset)) in indices.iter().enumerate() {
let typ = RequirementType::from(*flavor);
let end_offset = if i == indices.len() - 1 {
data.len()
} else {
indices[i + 1].1 as usize
};
let requirement_data = &data[*offset as usize..end_offset];
requirements.insert(typ, RequirementBlob::from_blob_bytes(requirement_data)?);
}
Ok(Self { requirements })
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
let mut res = Vec::new();
let data_start_offset = 8 + 4 + (8 * self.requirements.len() as u32);
let mut written_requirements_data = 0;
res.iowrite_with(self.requirements.len() as u32, scroll::BE)?;
for (typ, requirement) in &self.requirements {
res.iowrite_with(u32::from(*typ), scroll::BE)?;
res.iowrite_with(data_start_offset + written_requirements_data, scroll::BE)?;
written_requirements_data += requirement.to_blob_bytes()?.len() as u32;
}
for requirement in self.requirements.values() {
res.write_all(&requirement.to_blob_bytes()?)?;
}
Ok(res)
}
}
impl<'a> RequirementSetBlob<'a> {
pub fn to_owned(&self) -> RequirementSetBlob<'static> {
RequirementSetBlob {
requirements: self
.requirements
.iter()
.map(|(flavor, blob)| (*flavor, blob.to_owned()))
.collect::<HashMap<_, _>>(),
}
}
pub fn set_requirements(&mut self, slot: RequirementType, blob: RequirementBlob<'a>) {
self.requirements.insert(slot, blob);
}
}
#[derive(Debug)]
pub struct EmbeddedSignatureBlob<'a> {
data: &'a [u8],
}
impl<'a> Blob<'a> for EmbeddedSignatureBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::EmbeddedSignature)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
Ok(Self {
data: read_and_validate_blob_header(data, Self::magic(), "embedded signature blob")?,
})
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.data.to_vec())
}
}
#[derive(Debug)]
pub struct EmbeddedSignatureOldBlob<'a> {
data: &'a [u8],
}
impl<'a> Blob<'a> for EmbeddedSignatureOldBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::EmbeddedSignatureOld)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
Ok(Self {
data: read_and_validate_blob_header(
data,
Self::magic(),
"old embedded signature blob",
)?,
})
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.data.to_vec())
}
}
#[derive(Debug)]
pub struct EntitlementsBlob<'a> {
plist: Cow<'a, str>,
}
impl<'a> Blob<'a> for EntitlementsBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::Entitlements)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
let data = read_and_validate_blob_header(data, Self::magic(), "entitlements blob")?;
let s = std::str::from_utf8(data).map_err(AppleCodesignError::EntitlementsBadUtf8)?;
Ok(Self { plist: s.into() })
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.plist.as_bytes().to_vec())
}
}
impl<'a> EntitlementsBlob<'a> {
pub fn from_string(s: &(impl ToString + ?Sized)) -> Self {
Self {
plist: s.to_string().into(),
}
}
pub fn as_str(&self) -> &str {
&self.plist
}
}
impl<'a> std::fmt::Display for EntitlementsBlob<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.plist)
}
}
#[derive(Debug)]
pub struct EntitlementsDerBlob<'a> {
der: Cow<'a, [u8]>,
}
impl<'a> Blob<'a> for EntitlementsDerBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::EntitlementsDer)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
let der = read_and_validate_blob_header(data, Self::magic(), "DER entitlements blob")?;
Ok(Self { der: der.into() })
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.der.to_vec())
}
}
impl<'a> EntitlementsDerBlob<'a> {
pub fn from_plist(v: &plist::Value) -> Result<Self, AppleCodesignError> {
let der = crate::plist_der::der_encode_plist(v)?;
Ok(Self { der: der.into() })
}
pub fn parse_der(&self) -> Result<plist::Value, AppleCodesignError> {
crate::plist_der::der_decode_plist(self.der.as_ref())
}
pub fn plist_xml(&self) -> Result<Vec<u8>, AppleCodesignError> {
let mut buffer = vec![];
self.parse_der()?.to_writer_xml(&mut buffer)?;
Ok(buffer)
}
}
#[derive(Debug)]
pub struct ConstraintsDerBlob<'a> {
der: Cow<'a, [u8]>,
}
impl<'a> Blob<'a> for ConstraintsDerBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::EnvironmentContraintsDer)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self> {
let der = read_and_validate_blob_header(
data,
Self::magic(),
"DER encoded environment constraints blob",
)?;
Ok(Self { der: der.into() })
}
fn serialize_payload(&self) -> Result<Vec<u8>> {
Ok(self.der.to_vec())
}
}
impl<'a> ConstraintsDerBlob<'a> {
pub fn from_encoded_constraints(v: &EncodedEnvironmentConstraints) -> Result<Self> {
let der = v.der_encode()?;
Ok(Self { der: der.into() })
}
pub fn parse_der_plist(&self) -> Result<plist::Value> {
crate::plist_der::der_decode_plist(self.der.as_ref())
}
pub fn parse_encoded_constraints(&self) -> Result<EncodedEnvironmentConstraints> {
EncodedEnvironmentConstraints::from_der(self.der.as_ref())
}
pub fn plist_xml(&self) -> Result<Vec<u8>> {
let mut buffer = vec![];
self.parse_der_plist()?.to_writer_xml(&mut buffer)?;
Ok(buffer)
}
}
#[derive(Debug)]
pub struct DetachedSignatureBlob<'a> {
data: &'a [u8],
}
impl<'a> Blob<'a> for DetachedSignatureBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::DetachedSignature)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
Ok(Self {
data: read_and_validate_blob_header(data, Self::magic(), "detached signature blob")?,
})
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.data.to_vec())
}
}
pub struct BlobWrapperBlob<'a> {
data: Cow<'a, [u8]>,
}
impl<'a> Blob<'a> for BlobWrapperBlob<'a> {
fn magic() -> u32 {
u32::from(CodeSigningMagic::BlobWrapper)
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
Ok(Self {
data: read_and_validate_blob_header(data, Self::magic(), "blob wrapper blob")?.into(),
})
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.data.to_vec())
}
}
impl<'a> std::fmt::Debug for BlobWrapperBlob<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", hex::encode(&self.data)))
}
}
impl<'a> BlobWrapperBlob<'a> {
pub fn from_data_borrowed(data: &'a [u8]) -> BlobWrapperBlob<'a> {
Self { data: data.into() }
}
}
impl BlobWrapperBlob<'static> {
pub fn from_data_owned(data: Vec<u8>) -> BlobWrapperBlob<'static> {
Self { data: data.into() }
}
}
pub struct OtherBlob<'a> {
pub magic: u32,
pub data: &'a [u8],
}
impl<'a> Blob<'a> for OtherBlob<'a> {
fn magic() -> u32 {
u32::MAX
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
let (magic, _, data) = read_blob_header(data)?;
Ok(Self { magic, data })
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
Ok(self.data.to_vec())
}
fn to_blob_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
let mut res = Vec::with_capacity(self.data.len() + 8);
res.iowrite_with(self.magic, scroll::BE)?;
res.iowrite_with(self.data.len() as u32 + 8, scroll::BE)?;
res.write_all(self.data)?;
Ok(res)
}
}
impl<'a> std::fmt::Debug for OtherBlob<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", hex::encode(self.data)))
}
}
#[derive(Debug)]
pub enum BlobData<'a> {
Requirement(Box<RequirementBlob<'a>>),
RequirementSet(Box<RequirementSetBlob<'a>>),
CodeDirectory(Box<CodeDirectoryBlob<'a>>),
EmbeddedSignature(Box<EmbeddedSignatureBlob<'a>>),
EmbeddedSignatureOld(Box<EmbeddedSignatureOldBlob<'a>>),
Entitlements(Box<EntitlementsBlob<'a>>),
EntitlementsDer(Box<EntitlementsDerBlob<'a>>),
ConstraintsDer(Box<ConstraintsDerBlob<'a>>),
DetachedSignature(Box<DetachedSignatureBlob<'a>>),
BlobWrapper(Box<BlobWrapperBlob<'a>>),
Other(Box<OtherBlob<'a>>),
}
impl<'a> Blob<'a> for BlobData<'a> {
fn magic() -> u32 {
u32::MAX
}
fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
let (magic, length, _) = read_blob_header(data)?;
let data = &data[0..length];
let magic = CodeSigningMagic::from(magic);
Ok(match magic {
CodeSigningMagic::Requirement => {
Self::Requirement(Box::new(RequirementBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::RequirementSet => {
Self::RequirementSet(Box::new(RequirementSetBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::CodeDirectory => {
Self::CodeDirectory(Box::new(CodeDirectoryBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::EmbeddedSignature => {
Self::EmbeddedSignature(Box::new(EmbeddedSignatureBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::EmbeddedSignatureOld => Self::EmbeddedSignatureOld(Box::new(
EmbeddedSignatureOldBlob::from_blob_bytes(data)?,
)),
CodeSigningMagic::Entitlements => {
Self::Entitlements(Box::new(EntitlementsBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::EntitlementsDer => {
Self::EntitlementsDer(Box::new(EntitlementsDerBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::EnvironmentContraintsDer => {
Self::ConstraintsDer(Box::new(ConstraintsDerBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::DetachedSignature => {
Self::DetachedSignature(Box::new(DetachedSignatureBlob::from_blob_bytes(data)?))
}
CodeSigningMagic::BlobWrapper => {
Self::BlobWrapper(Box::new(BlobWrapperBlob::from_blob_bytes(data)?))
}
_ => Self::Other(Box::new(OtherBlob::from_blob_bytes(data)?)),
})
}
fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
match self {
Self::Requirement(b) => b.serialize_payload(),
Self::RequirementSet(b) => b.serialize_payload(),
Self::CodeDirectory(b) => b.serialize_payload(),
Self::EmbeddedSignature(b) => b.serialize_payload(),
Self::EmbeddedSignatureOld(b) => b.serialize_payload(),
Self::Entitlements(b) => b.serialize_payload(),
Self::EntitlementsDer(b) => b.serialize_payload(),
Self::ConstraintsDer(b) => b.serialize_payload(),
Self::DetachedSignature(b) => b.serialize_payload(),
Self::BlobWrapper(b) => b.serialize_payload(),
Self::Other(b) => b.serialize_payload(),
}
}
fn to_blob_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
match self {
Self::Requirement(b) => b.to_blob_bytes(),
Self::RequirementSet(b) => b.to_blob_bytes(),
Self::CodeDirectory(b) => b.to_blob_bytes(),
Self::EmbeddedSignature(b) => b.to_blob_bytes(),
Self::EmbeddedSignatureOld(b) => b.to_blob_bytes(),
Self::Entitlements(b) => b.to_blob_bytes(),
Self::EntitlementsDer(b) => b.to_blob_bytes(),
Self::ConstraintsDer(b) => b.to_blob_bytes(),
Self::DetachedSignature(b) => b.to_blob_bytes(),
Self::BlobWrapper(b) => b.to_blob_bytes(),
Self::Other(b) => b.to_blob_bytes(),
}
}
}
impl<'a> From<RequirementBlob<'a>> for BlobData<'a> {
fn from(b: RequirementBlob<'a>) -> Self {
Self::Requirement(Box::new(b))
}
}
impl<'a> From<RequirementSetBlob<'a>> for BlobData<'a> {
fn from(b: RequirementSetBlob<'a>) -> Self {
Self::RequirementSet(Box::new(b))
}
}
impl<'a> From<CodeDirectoryBlob<'a>> for BlobData<'a> {
fn from(b: CodeDirectoryBlob<'a>) -> Self {
Self::CodeDirectory(Box::new(b))
}
}
impl<'a> From<EmbeddedSignatureBlob<'a>> for BlobData<'a> {
fn from(b: EmbeddedSignatureBlob<'a>) -> Self {
Self::EmbeddedSignature(Box::new(b))
}
}
impl<'a> From<EmbeddedSignatureOldBlob<'a>> for BlobData<'a> {
fn from(b: EmbeddedSignatureOldBlob<'a>) -> Self {
Self::EmbeddedSignatureOld(Box::new(b))
}
}
impl<'a> From<EntitlementsBlob<'a>> for BlobData<'a> {
fn from(b: EntitlementsBlob<'a>) -> Self {
Self::Entitlements(Box::new(b))
}
}
impl<'a> From<EntitlementsDerBlob<'a>> for BlobData<'a> {
fn from(b: EntitlementsDerBlob<'a>) -> Self {
Self::EntitlementsDer(Box::new(b))
}
}
impl<'a> From<ConstraintsDerBlob<'a>> for BlobData<'a> {
fn from(b: ConstraintsDerBlob<'a>) -> Self {
Self::ConstraintsDer(Box::new(b))
}
}
impl<'a> From<DetachedSignatureBlob<'a>> for BlobData<'a> {
fn from(b: DetachedSignatureBlob<'a>) -> Self {
Self::DetachedSignature(Box::new(b))
}
}
impl<'a> From<BlobWrapperBlob<'a>> for BlobData<'a> {
fn from(b: BlobWrapperBlob<'a>) -> Self {
Self::BlobWrapper(Box::new(b))
}
}
impl<'a> From<OtherBlob<'a>> for BlobData<'a> {
fn from(b: OtherBlob<'a>) -> Self {
Self::Other(Box::new(b))
}
}
#[derive(Debug)]
pub struct ParsedBlob<'a> {
pub blob_entry: BlobEntry<'a>,
pub blob: BlobData<'a>,
}
impl<'a> ParsedBlob<'a> {
pub fn digest_with(&self, hash: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
hash.digest_data(self.blob_entry.data)
}
}
impl<'a> TryFrom<BlobEntry<'a>> for ParsedBlob<'a> {
type Error = AppleCodesignError;
fn try_from(blob_entry: BlobEntry<'a>) -> Result<Self, Self::Error> {
let blob = BlobData::from_blob_bytes(blob_entry.data)?;
Ok(Self { blob_entry, blob })
}
}
pub struct EmbeddedSignature<'a> {
pub magic: CodeSigningMagic,
pub length: u32,
pub count: u32,
pub data: &'a [u8],
pub blobs: Vec<BlobEntry<'a>>,
}
impl<'a> std::fmt::Debug for EmbeddedSignature<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("SuperBlob")
.field("magic", &self.magic)
.field("length", &self.length)
.field("count", &self.count)
.field("blobs", &self.blobs)
.finish()
}
}
impl<'a> EmbeddedSignature<'a> {
pub fn from_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
let offset = &mut 0;
let magic = data.gread_with::<u32>(offset, scroll::BE)?.into();
if magic != CodeSigningMagic::EmbeddedSignature {
return Err(AppleCodesignError::BadMagic(
"embedded signature super blob",
));
}
let length = data.gread_with(offset, scroll::BE)?;
let count = data.gread_with(offset, scroll::BE)?;
let mut blob_indices = Vec::with_capacity(count as usize);
for _ in 0..count {
blob_indices.push(data.gread_with::<BlobIndex>(offset, scroll::BE)?);
}
let mut blobs = Vec::with_capacity(blob_indices.len());
for (i, index) in blob_indices.iter().enumerate() {
let end_offset = if i == blob_indices.len() - 1 {
data.len()
} else {
blob_indices[i + 1].offset as usize
};
let full_slice = &data[index.offset as usize..end_offset];
let (magic, blob_length, _) = read_blob_header(full_slice)?;
let blob_data = match blob_length.cmp(&full_slice.len()) {
Ordering::Greater => {
return Err(AppleCodesignError::SuperblobMalformed);
}
Ordering::Equal => full_slice,
Ordering::Less => &full_slice[0..blob_length],
};
blobs.push(BlobEntry {
index: i,
slot: index.typ.into(),
offset: index.offset as usize,
magic: magic.into(),
length: blob_length,
data: blob_data,
});
}
Ok(Self {
magic,
length,
count,
data,
blobs,
})
}
pub fn find_slot(&self, slot: CodeSigningSlot) -> Option<&BlobEntry<'a>> {
self.blobs.iter().find(|e| e.slot == slot)
}
pub fn find_slot_parsed(
&self,
slot: CodeSigningSlot,
) -> Result<Option<ParsedBlob<'a>>, AppleCodesignError> {
if let Some(entry) = self.find_slot(slot) {
Ok(Some(entry.clone().into_parsed_blob()?))
} else {
Ok(None)
}
}
pub fn code_directory(&self) -> Result<Option<Box<CodeDirectoryBlob<'a>>>, AppleCodesignError> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::CodeDirectory)? {
if let BlobData::CodeDirectory(cd) = parsed.blob {
Ok(Some(cd))
} else {
Err(AppleCodesignError::BadMagic("code directory blob"))
}
} else {
Ok(None)
}
}
pub fn alternate_code_directories(
&self,
) -> Result<Vec<(CodeSigningSlot, Box<CodeDirectoryBlob<'a>>)>, AppleCodesignError> {
let slots = [
CodeSigningSlot::AlternateCodeDirectory0,
CodeSigningSlot::AlternateCodeDirectory1,
CodeSigningSlot::AlternateCodeDirectory2,
CodeSigningSlot::AlternateCodeDirectory3,
CodeSigningSlot::AlternateCodeDirectory4,
];
let mut res = vec![];
for slot in slots {
if let Some(parsed) = self.find_slot_parsed(slot)? {
if let BlobData::CodeDirectory(cd) = parsed.blob {
res.push((slot, cd));
} else {
return Err(AppleCodesignError::BadMagic(
"wrong blob magic in alternative code directory slot",
));
}
}
}
Ok(res)
}
pub fn all_code_directories(
&self,
) -> Result<Vec<(CodeSigningSlot, Box<CodeDirectoryBlob<'a>>)>, AppleCodesignError> {
let mut res = vec![];
if let Some(cd) = self.code_directory()? {
res.push((CodeSigningSlot::CodeDirectory, cd));
}
res.extend(self.alternate_code_directories()?);
Ok(res)
}
pub fn code_directory_for_digest(
&self,
digest: DigestType,
) -> Result<Option<Box<CodeDirectoryBlob<'a>>>, AppleCodesignError> {
for (_, cd) in self.all_code_directories()? {
if cd.digest_type == digest {
return Ok(Some(cd));
}
}
Ok(None)
}
pub fn preferred_code_directory(
&self,
) -> Result<Box<CodeDirectoryBlob<'a>>, AppleCodesignError> {
if let Some(cd) = self.code_directory_for_digest(DigestType::Sha256)? {
Ok(cd)
} else if let Some(cd) = self.code_directory_for_digest(DigestType::Sha1)? {
Ok(cd)
} else if let Some(cd) = self.code_directory()? {
Ok(cd)
} else {
Err(AppleCodesignError::BinaryNoCodeDirectory)
}
}
pub fn entitlements(&self) -> Result<Option<Box<EntitlementsBlob<'a>>>, AppleCodesignError> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::Entitlements)? {
if let BlobData::Entitlements(entitlements) = parsed.blob {
Ok(Some(entitlements))
} else {
Err(AppleCodesignError::BadMagic("entitlements blob"))
}
} else {
Ok(None)
}
}
pub fn entitlements_der(
&self,
) -> Result<Option<Box<EntitlementsDerBlob<'a>>>, AppleCodesignError> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::EntitlementsDer)? {
if let BlobData::EntitlementsDer(entitlements) = parsed.blob {
Ok(Some(entitlements))
} else {
Err(AppleCodesignError::BadMagic("DER entitlements blob"))
}
} else {
Ok(None)
}
}
pub fn code_requirements(
&self,
) -> Result<Option<Box<RequirementSetBlob<'a>>>, AppleCodesignError> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::RequirementSet)? {
if let BlobData::RequirementSet(reqs) = parsed.blob {
Ok(Some(reqs))
} else {
Err(AppleCodesignError::BadMagic("requirements blob"))
}
} else {
Ok(None)
}
}
pub fn launch_constraints_self(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsSelf)? {
if let BlobData::ConstraintsDer(blob) = parsed.blob {
Ok(Some(blob))
} else {
Err(AppleCodesignError::BadMagic("self launch constraints blob"))
}
} else {
Ok(None)
}
}
pub fn launch_constraints_parent(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsParent)? {
if let BlobData::ConstraintsDer(blob) = parsed.blob {
Ok(Some(blob))
} else {
Err(AppleCodesignError::BadMagic(
"parent launch constraints blob",
))
}
} else {
Ok(None)
}
}
pub fn launch_constraints_responsible(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
if let Some(parsed) =
self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsResponsibleProcess)?
{
if let BlobData::ConstraintsDer(blob) = parsed.blob {
Ok(Some(blob))
} else {
Err(AppleCodesignError::BadMagic(
"responsible process launch constraints blob",
))
}
} else {
Ok(None)
}
}
pub fn library_constraints(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LibraryConstraints)? {
if let BlobData::ConstraintsDer(blob) = parsed.blob {
Ok(Some(blob))
} else {
Err(AppleCodesignError::BadMagic("library constraints blob"))
}
} else {
Ok(None)
}
}
pub fn signature_data(&self) -> Result<Option<&'a [u8]>, AppleCodesignError> {
if let Some(parsed) = self.find_slot(CodeSigningSlot::Signature) {
ParsedBlob::try_from(parsed.clone())?;
Ok(Some(parsed.payload()?))
} else {
Ok(None)
}
}
pub fn signed_data(&self) -> Result<Option<SignedData>, AppleCodesignError> {
if let Some(data) = self.signature_data()? {
if data.is_empty() {
Ok(None)
} else {
let signed_data = SignedData::parse_ber(data)?;
Ok(Some(signed_data))
}
} else {
Ok(None)
}
}
}