use {
crate::{
code_directory::CodeDirectoryBlob, code_requirement::CodeRequirements,
error::AppleCodesignError,
},
goblin::mach::{constants::SEG_LINKEDIT, load_command::CommandVariant, MachO},
scroll::{IOwrite, Pread},
std::{
borrow::Cow,
cmp::Ordering,
collections::HashMap,
convert::{TryFrom, TryInto},
io::Write,
},
};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum CodeSigningSlot {
CodeDirectory,
Info,
RequirementSet,
ResourceDir,
Application,
Entitlements,
SecuritySettings,
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::SecuritySettings => {
f.write_fmt(format_args!("SecuritySettings ({})", 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,
7 => Self::SecuritySettings,
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::SecuritySettings => 7,
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,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CodeSigningMagic {
Requirement,
RequirementSet,
CodeDirectory,
EmbeddedSignature,
EmbeddedSignatureOld,
Entitlements,
EntitlementsDer,
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,
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::DetachedSignature => 0xfade0cc1,
CodeSigningMagic::BlobWrapper => 0xfade0b01,
CodeSigningMagic::Unknown(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(u32)]
pub enum RequirementType {
Host,
Guest,
Designated,
Library,
Plugin,
Unknown(u32),
}
impl From<u32> for RequirementType {
fn from(v: u32) -> Self {
match v {
1 => Self::Host,
2 => Self::Guest,
3 => Self::Designated,
4 => Self::Library,
5 => Self::Plugin,
_ => Self::Unknown(v),
}
}
}
impl From<RequirementType> for u32 {
fn from(t: RequirementType) -> Self {
match t {
RequirementType::Host => 1,
RequirementType::Guest => 2,
RequirementType::Designated => 3,
RequirementType::Library => 4,
RequirementType::Plugin => 5,
RequirementType::Unknown(v) => v,
}
}
}
impl std::fmt::Display for RequirementType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Host => f.write_str("host(1)"),
Self::Guest => f.write_str("guest(2)"),
Self::Designated => f.write_str("designated(3)"),
Self::Library => f.write_str("library(4)"),
Self::Plugin => f.write_str("plugin(5)"),
Self::Unknown(v) => f.write_fmt(format_args!("unknown({})", v)),
}
}
}
#[repr(C)]
#[derive(Clone, Pread)]
struct BlobIndex {
typ: u32,
offset: u32,
}
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)
}
}
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()
}
}
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())
}
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> {
self.blobs.iter().find(|e| e.slot == slot)
}
pub fn find_slot_parsed(
&self,
slot: CodeSigningSlot,
) -> Result<Option<ParsedBlob<'_>>, 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<'_>>>, 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 entitlements(&self) -> Result<Option<Box<EntitlementsBlob<'_>>>, 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 code_requirements(
&self,
) -> Result<Option<Box<RequirementSetBlob<'_>>>, 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 signature_data(&self) -> Result<Option<&'_ [u8]>, AppleCodesignError> {
if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::Signature)? {
if let BlobData::BlobWrapper(blob) = parsed.blob {
Ok(Some(blob.data))
} else {
Err(AppleCodesignError::BadMagic("signature blob"))
}
} else {
Ok(None)
}
}
}
#[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 digest_with(&self, hash: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
hash.digest(&self.data)
}
}
#[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(&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 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(&self.to_blob_bytes()?)
}
}
#[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>>),
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::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::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::DetachedSignature(b) => b.to_blob_bytes(),
Self::BlobWrapper(b) => b.to_blob_bytes(),
Self::Other(b) => b.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(Clone, Copy, Debug)]
pub enum DigestType {
None,
Sha1,
Sha256,
Sha256Truncated,
Sha384,
Sha512,
Unknown(u8),
}
impl Default for DigestType {
fn default() -> Self {
Self::Sha256
}
}
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,
}
}
}
impl TryFrom<&str> for DigestType {
type Error = AppleCodesignError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"none" => Ok(Self::None),
"sha1" => Ok(Self::Sha1),
"sha256" => Ok(Self::Sha256),
"sha256-truncated" => Ok(Self::Sha256Truncated),
"sha384" => Ok(Self::Sha384),
"sha512" => Ok(Self::Sha512),
_ => Err(AppleCodesignError::DigestUnknownAlgorithm),
}
}
}
impl DigestType {
pub fn hash_len(&self) -> Result<usize, AppleCodesignError> {
Ok(self.digest(&[])?.len())
}
pub fn as_hasher(&self) -> Result<ring::digest::Context, AppleCodesignError> {
match self {
Self::None => Err(AppleCodesignError::DigestUnknownAlgorithm),
Self::Sha1 => Ok(ring::digest::Context::new(
&ring::digest::SHA1_FOR_LEGACY_USE_ONLY,
)),
Self::Sha256 | Self::Sha256Truncated => {
Ok(ring::digest::Context::new(&ring::digest::SHA256))
}
Self::Sha384 => Ok(ring::digest::Context::new(&ring::digest::SHA384)),
Self::Sha512 => Ok(ring::digest::Context::new(&ring::digest::SHA512)),
Self::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm),
}
}
pub fn digest(&self, data: &[u8]) -> Result<Vec<u8>, AppleCodesignError> {
let mut hasher = self.as_hasher()?;
hasher.update(data);
let mut hash = hasher.finish().as_ref().to_vec();
if matches!(self, Self::Sha256Truncated) {
hash.truncate(20);
}
Ok(hash)
}
}
pub struct Digest<'a> {
pub data: Cow<'a, [u8]>,
}
impl<'a> Digest<'a> {
pub fn is_null(&self) -> bool {
self.data.iter().all(|b| *b == 0)
}
pub fn to_vec(&self) -> Vec<u8> {
self.data.to_vec()
}
pub fn to_owned(&self) -> Digest<'static> {
Digest {
data: Cow::Owned(self.data.clone().into_owned()),
}
}
}
impl<'a> std::fmt::Debug for Digest<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&hex::encode(&self.data))
}
}
#[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(),
}
}
}
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 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: &'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")?,
})
}
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(data: &'a [u8]) -> BlobWrapperBlob<'a> {
Self { data }
}
}
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)))
}
}
#[repr(C)]
pub struct Scatter {
count: u32,
base: u32,
target_offset: u64,
spare: u64,
}
pub struct MachOSignatureData<'a> {
pub segments_count: usize,
pub linkedit_segment_index: usize,
pub linkedit_segment_start_offset: usize,
pub linkedit_segment_end_offset: usize,
pub linkedit_signature_start_offset: usize,
pub linkedit_signature_end_offset: usize,
pub signature_start_offset: usize,
pub signature_end_offset: usize,
pub linkedit_segment_data: &'a [u8],
pub signature_data: &'a [u8],
}
pub fn find_signature_data<'a>(
obj: &'a MachO,
) -> Result<Option<MachOSignatureData<'a>>, AppleCodesignError> {
if let Some(linkedit_data_command) = obj.load_commands.iter().find_map(|load_command| {
if let CommandVariant::CodeSignature(command) = &load_command.command {
Some(command)
} else {
None
}
}) {
let (linkedit_segment_index, linkedit) = obj
.segments
.iter()
.enumerate()
.find(|(_, segment)| {
if let Ok(name) = segment.name() {
name == SEG_LINKEDIT
} else {
false
}
})
.ok_or(AppleCodesignError::MissingLinkedit)?;
let linkedit_segment_start_offset = linkedit.fileoff as usize;
let linkedit_segment_end_offset = linkedit_segment_start_offset + linkedit.data.len();
let linkedit_signature_start_offset = linkedit_data_command.dataoff as usize;
let linkedit_signature_end_offset =
linkedit_signature_start_offset + linkedit_data_command.datasize as usize;
let signature_start_offset =
linkedit_data_command.dataoff as usize - linkedit.fileoff as usize;
let signature_end_offset = signature_start_offset + linkedit_data_command.datasize as usize;
let signature_data = &linkedit.data[signature_start_offset..signature_end_offset];
Ok(Some(MachOSignatureData {
segments_count: obj.segments.len(),
linkedit_segment_index,
linkedit_segment_start_offset,
linkedit_segment_end_offset,
linkedit_signature_start_offset,
linkedit_signature_end_offset,
signature_start_offset,
signature_end_offset,
linkedit_segment_data: linkedit.data,
signature_data,
}))
} else {
Ok(None)
}
}
pub fn parse_signature_data(data: &[u8]) -> Result<EmbeddedSignature<'_>, AppleCodesignError> {
let magic: u32 = data.pread_with(0, scroll::BE)?;
if magic == u32::from(CodeSigningMagic::EmbeddedSignature) {
EmbeddedSignature::from_bytes(data)
} else {
Err(AppleCodesignError::BadMagic("embedded signature superblob"))
}
}
#[cfg(test)]
mod tests {
use {
super::*,
cryptographic_message_syntax::SignedData,
std::{
io::Read,
path::{Path, PathBuf},
},
};
const MACHO_UNIVERSAL_MAGIC: [u8; 4] = [0xca, 0xfe, 0xba, 0xbe];
const MACHO_64BIT_MAGIC: [u8; 4] = [0xfe, 0xed, 0xfa, 0xcf];
fn find_likely_macho_files(path: &Path) -> Vec<PathBuf> {
let mut res = Vec::new();
let dir = std::fs::read_dir(path).unwrap();
for entry in dir {
let entry = entry.unwrap();
if let Ok(mut fh) = std::fs::File::open(&entry.path()) {
let mut magic = [0; 4];
if let Ok(size) = fh.read(&mut magic) {
if size == 4 && (magic == MACHO_UNIVERSAL_MAGIC || magic == MACHO_64BIT_MAGIC) {
res.push(entry.path());
}
}
}
}
res
}
fn find_apple_embedded_signature<'a>(
macho: &'a goblin::mach::MachO,
) -> Option<EmbeddedSignature<'a>> {
if let Ok(Some(codesign_data)) = find_signature_data(macho) {
if let Ok(signature) = parse_signature_data(codesign_data.signature_data) {
Some(signature)
} else {
None
}
} else {
None
}
}
fn validate_macho(path: &Path, macho: &MachO) {
if let Some(signature) = find_apple_embedded_signature(macho) {
for blob in &signature.blobs {
match blob.clone().into_parsed_blob() {
Ok(parsed) => {
match parsed.blob.to_blob_bytes() {
Ok(serialized) => {
if serialized != blob.data {
println!("blob serialization roundtrip failure on {}: index {}, magic {:?}",
path.display(),
blob.index,
blob.magic,
);
}
}
Err(e) => {
println!(
"blob serialization failure on {}; index {}, magic {:?}: {:?}",
path.display(),
blob.index,
blob.magic,
e
);
}
}
}
Err(e) => {
println!(
"blob parse failure on {}; index {}, magic {:?}: {:?}",
path.display(),
blob.index,
blob.magic,
e
);
}
}
}
if let Ok(Some(cms)) = signature.signature_data() {
match SignedData::parse_ber(&cms) {
Ok(signed_data) => {
for signer in signed_data.signers() {
if let Err(e) = signer.verify_signature_with_signed_data(&signed_data) {
println!(
"signature verification failed for {}: {}",
path.display(),
e
);
}
if let Ok(()) =
signer.verify_message_digest_with_signed_data(&signed_data)
{
println!(
"message digest verification unexpectedly correct for {}",
path.display()
);
}
}
}
Err(e) => {
println!("error performing CMS parse of {}: {:?}", path.display(), e);
}
}
}
}
}
fn validate_macho_in_dir(dir: &Path) {
for path in find_likely_macho_files(dir).into_iter() {
if let Ok(file_data) = std::fs::read(&path) {
if let Ok(mach) = goblin::mach::Mach::parse(&file_data) {
match mach {
goblin::mach::Mach::Binary(macho) => {
validate_macho(&path, &macho);
}
goblin::mach::Mach::Fat(multiarch) => {
for i in 0..multiarch.narches {
if let Ok(macho) = multiarch.get(i) {
validate_macho(&path, &macho);
}
}
}
}
}
}
}
}
#[test]
fn parse_applications_macho_signatures() {
if let Ok(dir) = std::fs::read_dir("/Applications") {
for entry in dir {
let entry = entry.unwrap();
let search_dir = entry.path().join("Contents").join("MacOS");
if search_dir.exists() {
validate_macho_in_dir(&search_dir);
}
}
}
for dir in &["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin"] {
let dir = PathBuf::from(dir);
if dir.exists() {
validate_macho_in_dir(&dir);
}
}
}
}