use {
crate::{
embedded_signature::{
read_and_validate_blob_header, CodeSigningMagic, RequirementBlob, RequirementSetBlob,
},
error::AppleCodesignError,
},
bcder::Oid,
chrono::TimeZone,
scroll::{IOwrite, Pread},
std::{
borrow::Cow,
cmp::Ordering,
fmt::{Debug, Display},
io::Write,
ops::{Deref, DerefMut},
},
};
const OPCODE_FLAG_MASK: u32 = 0xff000000;
const OPCODE_VALUE_MASK: u32 = 0x00ffffff;
#[allow(unused)]
const OPCODE_FLAG_DEFAULT_FALSE: u32 = 0x80000000;
#[allow(unused)]
const OPCODE_FLAG_SKIP: u32 = 0x40000000;
#[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 PartialOrd for RequirementType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RequirementType {
fn cmp(&self, other: &Self) -> Ordering {
u32::from(*self).cmp(&u32::from(*other))
}
}
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})")),
}
}
}
fn read_data(data: &[u8]) -> Result<(&[u8], &[u8]), AppleCodesignError> {
let length = data.pread_with::<u32>(0, scroll::BE)?;
let value = &data[4..4 + length as usize];
let offset = 4 + length as usize;
let offset = match offset % 4 {
0 => offset,
extra => offset + 4 - extra,
};
let remaining = &data[offset..];
Ok((value, remaining))
}
fn write_data(dest: &mut impl Write, data: &[u8]) -> Result<(), AppleCodesignError> {
dest.iowrite_with(data.len() as u32, scroll::BE)?;
dest.write_all(data)?;
match data.len() % 4 {
0 => {}
pad => {
for _ in 0..4 - pad {
dest.iowrite(0u8)?;
}
}
}
Ok(())
}
fn format_certificate_slot(slot: i32) -> String {
match slot {
-1 => "root".to_string(),
0 => "leaf".to_string(),
_ => format!("{slot}"),
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CodeRequirementValue<'a> {
String(Cow<'a, str>),
Bytes(Cow<'a, [u8]>),
}
impl<'a> From<&'a [u8]> for CodeRequirementValue<'a> {
fn from(value: &'a [u8]) -> Self {
let is_ascii_printable = |c: &u8| -> bool {
c.is_ascii_alphanumeric() || c.is_ascii_whitespace() || c.is_ascii_punctuation()
};
if value.iter().all(is_ascii_printable) {
Self::String(unsafe { std::str::from_utf8_unchecked(value) }.into())
} else {
Self::Bytes(value.into())
}
}
}
impl<'a> From<&'a str> for CodeRequirementValue<'a> {
fn from(s: &'a str) -> Self {
Self::String(s.into())
}
}
impl<'a> From<Cow<'a, str>> for CodeRequirementValue<'a> {
fn from(v: Cow<'a, str>) -> Self {
Self::String(v)
}
}
impl From<String> for CodeRequirementValue<'static> {
fn from(v: String) -> Self {
Self::String(Cow::Owned(v))
}
}
impl<'a> Display for CodeRequirementValue<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(s) => f.write_str(s),
Self::Bytes(data) => f.write_fmt(format_args!("{}", hex::encode(data))),
}
}
}
impl<'a> CodeRequirementValue<'a> {
fn write_encoded(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> {
match self {
Self::Bytes(data) => write_data(dest, data),
Self::String(s) => write_data(dest, s.as_bytes()),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u32)]
enum RequirementOpCode {
False = 0,
True = 1,
Identifier = 2,
AnchorApple = 3,
AnchorCertificateHash = 4,
InfoKeyValueLegacy = 5,
And = 6,
Or = 7,
CodeDirectoryHash = 8,
Not = 9,
InfoPlistExpression = 10,
CertificateField = 11,
CertificateTrusted = 12,
AnchorTrusted = 13,
CertificateGeneric = 14,
AnchorAppleGeneric = 15,
EntitlementsField = 16,
CertificatePolicy = 17,
NamedAnchor = 18,
NamedCode = 19,
Platform = 20,
Notarized = 21,
CertificateFieldDate = 22,
LegacyDeveloperId = 23,
}
impl TryFrom<u32> for RequirementOpCode {
type Error = AppleCodesignError;
fn try_from(v: u32) -> Result<Self, Self::Error> {
match v {
0 => Ok(Self::False),
1 => Ok(Self::True),
2 => Ok(Self::Identifier),
3 => Ok(Self::AnchorApple),
4 => Ok(Self::AnchorCertificateHash),
5 => Ok(Self::InfoKeyValueLegacy),
6 => Ok(Self::And),
7 => Ok(Self::Or),
8 => Ok(Self::CodeDirectoryHash),
9 => Ok(Self::Not),
10 => Ok(Self::InfoPlistExpression),
11 => Ok(Self::CertificateField),
12 => Ok(Self::CertificateTrusted),
13 => Ok(Self::AnchorTrusted),
14 => Ok(Self::CertificateGeneric),
15 => Ok(Self::AnchorAppleGeneric),
16 => Ok(Self::EntitlementsField),
17 => Ok(Self::CertificatePolicy),
18 => Ok(Self::NamedAnchor),
19 => Ok(Self::NamedCode),
20 => Ok(Self::Platform),
21 => Ok(Self::Notarized),
22 => Ok(Self::CertificateFieldDate),
23 => Ok(Self::LegacyDeveloperId),
_ => Err(AppleCodesignError::RequirementUnknownOpcode(v)),
}
}
}
impl RequirementOpCode {
pub fn parse_payload<'a>(
&self,
data: &'a [u8],
) -> Result<(CodeRequirementExpression<'a>, &'a [u8]), AppleCodesignError> {
match self {
Self::False => Ok((CodeRequirementExpression::False, data)),
Self::True => Ok((CodeRequirementExpression::True, data)),
Self::Identifier => {
let (value, data) = read_data(data)?;
let s = std::str::from_utf8(value).map_err(|_| {
AppleCodesignError::RequirementMalformed("identifier value not a UTF-8 string")
})?;
Ok((CodeRequirementExpression::Identifier(Cow::from(s)), data))
}
Self::AnchorApple => Ok((CodeRequirementExpression::AnchorApple, data)),
Self::AnchorCertificateHash => {
let slot = data.pread_with::<i32>(0, scroll::BE)?;
let digest_length = data.pread_with::<u32>(4, scroll::BE)?;
let digest = &data[8..8 + digest_length as usize];
Ok((
CodeRequirementExpression::AnchorCertificateHash(slot, digest.into()),
&data[8 + digest_length as usize..],
))
}
Self::InfoKeyValueLegacy => {
let (key, data) = read_data(data)?;
let key = std::str::from_utf8(key).map_err(|_| {
AppleCodesignError::RequirementMalformed("info key not a UTF-8 string")
})?;
let (value, data) = read_data(data)?;
let value = std::str::from_utf8(value).map_err(|_| {
AppleCodesignError::RequirementMalformed("info value not a UTF-8 string")
})?;
Ok((
CodeRequirementExpression::InfoKeyValueLegacy(key.into(), value.into()),
data,
))
}
Self::And => {
let (a, data) = CodeRequirementExpression::from_bytes(data)?;
let (b, data) = CodeRequirementExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::And(Box::new(a), Box::new(b)),
data,
))
}
Self::Or => {
let (a, data) = CodeRequirementExpression::from_bytes(data)?;
let (b, data) = CodeRequirementExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::Or(Box::new(a), Box::new(b)),
data,
))
}
Self::CodeDirectoryHash => {
let (value, data) = read_data(data)?;
Ok((
CodeRequirementExpression::CodeDirectoryHash(value.into()),
data,
))
}
Self::Not => {
let (expr, data) = CodeRequirementExpression::from_bytes(data)?;
Ok((CodeRequirementExpression::Not(Box::new(expr)), data))
}
Self::InfoPlistExpression => {
let (key, data) = read_data(data)?;
let key = std::str::from_utf8(key).map_err(|_| {
AppleCodesignError::RequirementMalformed("key is not valid UTF-8")
})?;
let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::InfoPlistKeyField(key.into(), expr),
data,
))
}
Self::CertificateField => {
let slot = data.pread_with::<i32>(0, scroll::BE)?;
let (field, data) = read_data(&data[4..])?;
let field = std::str::from_utf8(field).map_err(|_| {
AppleCodesignError::RequirementMalformed("certificate field is not valid UTF-8")
})?;
let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::CertificateField(slot, field.into(), expr),
data,
))
}
Self::CertificateTrusted => {
let slot = data.pread_with::<i32>(0, scroll::BE)?;
Ok((
CodeRequirementExpression::CertificateTrusted(slot),
&data[4..],
))
}
Self::AnchorTrusted => Ok((CodeRequirementExpression::AnchorTrusted, data)),
Self::CertificateGeneric => {
let slot = data.pread_with::<i32>(0, scroll::BE)?;
let (oid, data) = read_data(&data[4..])?;
let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::CertificateGeneric(slot, Oid(oid), expr),
data,
))
}
Self::AnchorAppleGeneric => Ok((CodeRequirementExpression::AnchorAppleGeneric, data)),
Self::EntitlementsField => {
let (key, data) = read_data(data)?;
let key = std::str::from_utf8(key).map_err(|_| {
AppleCodesignError::RequirementMalformed("entitlement key is not UTF-8")
})?;
let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::EntitlementsKey(key.into(), expr),
data,
))
}
Self::CertificatePolicy => {
let slot = data.pread_with::<i32>(0, scroll::BE)?;
let (oid, data) = read_data(&data[4..])?;
let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::CertificatePolicy(slot, Oid(oid), expr),
data,
))
}
Self::NamedAnchor => {
let (name, data) = read_data(data)?;
let name = std::str::from_utf8(name).map_err(|_| {
AppleCodesignError::RequirementMalformed("named anchor isn't UTF-8")
})?;
Ok((CodeRequirementExpression::NamedAnchor(name.into()), data))
}
Self::NamedCode => {
let (name, data) = read_data(data)?;
let name = std::str::from_utf8(name).map_err(|_| {
AppleCodesignError::RequirementMalformed("named code isn't UTF-8")
})?;
Ok((CodeRequirementExpression::NamedCode(name.into()), data))
}
Self::Platform => {
let value = data.pread_with::<u32>(0, scroll::BE)?;
Ok((CodeRequirementExpression::Platform(value), &data[4..]))
}
Self::Notarized => Ok((CodeRequirementExpression::Notarized, data)),
Self::CertificateFieldDate => {
let slot = data.pread_with::<i32>(0, scroll::BE)?;
let (oid, data) = read_data(&data[4..])?;
let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?;
Ok((
CodeRequirementExpression::CertificateFieldDate(slot, Oid(oid), expr),
data,
))
}
Self::LegacyDeveloperId => Ok((CodeRequirementExpression::LegacyDeveloperId, data)),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CodeRequirementExpression<'a> {
False,
True,
Identifier(Cow<'a, str>),
AnchorApple,
AnchorCertificateHash(i32, Cow<'a, [u8]>),
InfoKeyValueLegacy(Cow<'a, str>, Cow<'a, str>),
And(
Box<CodeRequirementExpression<'a>>,
Box<CodeRequirementExpression<'a>>,
),
Or(
Box<CodeRequirementExpression<'a>>,
Box<CodeRequirementExpression<'a>>,
),
CodeDirectoryHash(Cow<'a, [u8]>),
Not(Box<CodeRequirementExpression<'a>>),
InfoPlistKeyField(Cow<'a, str>, CodeRequirementMatchExpression<'a>),
CertificateField(i32, Cow<'a, str>, CodeRequirementMatchExpression<'a>),
CertificateTrusted(i32),
AnchorTrusted,
CertificateGeneric(i32, Oid<&'a [u8]>, CodeRequirementMatchExpression<'a>),
AnchorAppleGeneric,
EntitlementsKey(Cow<'a, str>, CodeRequirementMatchExpression<'a>),
CertificatePolicy(i32, Oid<&'a [u8]>, CodeRequirementMatchExpression<'a>),
NamedAnchor(Cow<'a, str>),
NamedCode(Cow<'a, str>),
Platform(u32),
Notarized,
CertificateFieldDate(i32, Oid<&'a [u8]>, CodeRequirementMatchExpression<'a>),
LegacyDeveloperId,
}
impl<'a> Display for CodeRequirementExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::False => f.write_str("never"),
Self::True => f.write_str("always"),
Self::Identifier(value) => f.write_fmt(format_args!("identifier \"{value}\"")),
Self::AnchorApple => f.write_str("anchor apple"),
Self::AnchorCertificateHash(slot, digest) => f.write_fmt(format_args!(
"certificate {} = H\"{}\"",
format_certificate_slot(*slot),
hex::encode(digest)
)),
Self::InfoKeyValueLegacy(key, value) => {
f.write_fmt(format_args!("info[{key}] = \"{value}\""))
}
Self::And(a, b) => f.write_fmt(format_args!("({a}) and ({b})")),
Self::Or(a, b) => f.write_fmt(format_args!("({a}) or ({b})")),
Self::CodeDirectoryHash(digest) => {
f.write_fmt(format_args!("cdhash H\"{}\"", hex::encode(digest)))
}
Self::Not(expr) => f.write_fmt(format_args!("!({expr})")),
Self::InfoPlistKeyField(key, expr) => f.write_fmt(format_args!("info [{key}] {expr}")),
Self::CertificateField(slot, field, expr) => f.write_fmt(format_args!(
"certificate {}[{}] {}",
format_certificate_slot(*slot),
field,
expr
)),
Self::CertificateTrusted(slot) => {
f.write_fmt(format_args!("certificate {slot} trusted"))
}
Self::AnchorTrusted => f.write_str("anchor trusted"),
Self::CertificateGeneric(slot, oid, expr) => f.write_fmt(format_args!(
"certificate {}[field.{}] {}",
format_certificate_slot(*slot),
oid,
expr
)),
Self::AnchorAppleGeneric => f.write_str("anchor apple generic"),
Self::EntitlementsKey(key, expr) => {
f.write_fmt(format_args!("entitlement [{key}] {expr}"))
}
Self::CertificatePolicy(slot, oid, expr) => f.write_fmt(format_args!(
"certificate {}[policy.{}] {}",
format_certificate_slot(*slot),
oid,
expr
)),
Self::NamedAnchor(name) => f.write_fmt(format_args!("anchor apple {name}")),
Self::NamedCode(name) => f.write_fmt(format_args!("({name})")),
Self::Platform(platform) => f.write_fmt(format_args!("platform = {platform}")),
Self::Notarized => f.write_str("notarized"),
Self::CertificateFieldDate(slot, oid, expr) => f.write_fmt(format_args!(
"certificate {}[timestamp.{}] {}",
format_certificate_slot(*slot),
oid,
expr
)),
Self::LegacyDeveloperId => f.write_str("legacy"),
}
}
}
impl<'a> From<&CodeRequirementExpression<'a>> for RequirementOpCode {
fn from(e: &CodeRequirementExpression) -> Self {
match e {
CodeRequirementExpression::False => RequirementOpCode::False,
CodeRequirementExpression::True => RequirementOpCode::True,
CodeRequirementExpression::Identifier(_) => RequirementOpCode::Identifier,
CodeRequirementExpression::AnchorApple => RequirementOpCode::AnchorApple,
CodeRequirementExpression::AnchorCertificateHash(_, _) => {
RequirementOpCode::AnchorCertificateHash
}
CodeRequirementExpression::InfoKeyValueLegacy(_, _) => {
RequirementOpCode::InfoKeyValueLegacy
}
CodeRequirementExpression::And(_, _) => RequirementOpCode::And,
CodeRequirementExpression::Or(_, _) => RequirementOpCode::Or,
CodeRequirementExpression::CodeDirectoryHash(_) => RequirementOpCode::CodeDirectoryHash,
CodeRequirementExpression::Not(_) => RequirementOpCode::Not,
CodeRequirementExpression::InfoPlistKeyField(_, _) => {
RequirementOpCode::InfoPlistExpression
}
CodeRequirementExpression::CertificateField(_, _, _) => {
RequirementOpCode::CertificateField
}
CodeRequirementExpression::CertificateTrusted(_) => {
RequirementOpCode::CertificateTrusted
}
CodeRequirementExpression::AnchorTrusted => RequirementOpCode::AnchorTrusted,
CodeRequirementExpression::CertificateGeneric(_, _, _) => {
RequirementOpCode::CertificateGeneric
}
CodeRequirementExpression::AnchorAppleGeneric => RequirementOpCode::AnchorAppleGeneric,
CodeRequirementExpression::EntitlementsKey(_, _) => {
RequirementOpCode::EntitlementsField
}
CodeRequirementExpression::CertificatePolicy(_, _, _) => {
RequirementOpCode::CertificatePolicy
}
CodeRequirementExpression::NamedAnchor(_) => RequirementOpCode::NamedAnchor,
CodeRequirementExpression::NamedCode(_) => RequirementOpCode::NamedCode,
CodeRequirementExpression::Platform(_) => RequirementOpCode::Platform,
CodeRequirementExpression::Notarized => RequirementOpCode::Notarized,
CodeRequirementExpression::CertificateFieldDate(_, _, _) => {
RequirementOpCode::CertificateFieldDate
}
CodeRequirementExpression::LegacyDeveloperId => RequirementOpCode::LegacyDeveloperId,
}
}
}
impl<'a> CodeRequirementExpression<'a> {
pub fn from_bytes(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> {
let opcode_raw = data.pread_with::<u32>(0, scroll::BE)?;
let _flags = opcode_raw & OPCODE_FLAG_MASK;
let opcode = opcode_raw & OPCODE_VALUE_MASK;
let data = &data[4..];
let opcode = RequirementOpCode::try_from(opcode)?;
opcode.parse_payload(data)
}
pub fn write_to(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> {
dest.iowrite_with(RequirementOpCode::from(self) as u32, scroll::BE)?;
match self {
Self::False => {}
Self::True => {}
Self::Identifier(s) => {
write_data(dest, s.as_bytes())?;
}
Self::AnchorApple => {}
Self::AnchorCertificateHash(slot, hash) => {
dest.iowrite_with(*slot, scroll::BE)?;
write_data(dest, hash)?;
}
Self::InfoKeyValueLegacy(key, value) => {
write_data(dest, key.as_bytes())?;
write_data(dest, value.as_bytes())?;
}
Self::And(a, b) => {
a.write_to(dest)?;
b.write_to(dest)?;
}
Self::Or(a, b) => {
a.write_to(dest)?;
b.write_to(dest)?;
}
Self::CodeDirectoryHash(hash) => {
write_data(dest, hash)?;
}
Self::Not(expr) => {
expr.write_to(dest)?;
}
Self::InfoPlistKeyField(key, m) => {
write_data(dest, key.as_bytes())?;
m.write_to(dest)?;
}
Self::CertificateField(slot, field, m) => {
dest.iowrite_with(*slot, scroll::BE)?;
write_data(dest, field.as_bytes())?;
m.write_to(dest)?;
}
Self::CertificateTrusted(slot) => {
dest.iowrite_with(*slot, scroll::BE)?;
}
Self::AnchorTrusted => {}
Self::CertificateGeneric(slot, oid, m) => {
dest.iowrite_with(*slot, scroll::BE)?;
write_data(dest, oid.as_ref())?;
m.write_to(dest)?;
}
Self::AnchorAppleGeneric => {}
Self::EntitlementsKey(key, m) => {
write_data(dest, key.as_bytes())?;
m.write_to(dest)?;
}
Self::CertificatePolicy(slot, oid, m) => {
dest.iowrite_with(*slot, scroll::BE)?;
write_data(dest, oid.as_ref())?;
m.write_to(dest)?;
}
Self::NamedAnchor(value) => {
write_data(dest, value.as_bytes())?;
}
Self::NamedCode(value) => {
write_data(dest, value.as_bytes())?;
}
Self::Platform(value) => {
dest.iowrite_with(*value, scroll::BE)?;
}
Self::Notarized => {}
Self::CertificateFieldDate(slot, oid, m) => {
dest.iowrite_with(*slot, scroll::BE)?;
write_data(dest, oid.as_ref())?;
m.write_to(dest)?;
}
Self::LegacyDeveloperId => {}
}
Ok(())
}
pub fn to_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
let mut res = vec![];
self.write_to(&mut res)?;
Ok(res)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u32)]
enum MatchType {
Exists = 0,
Equal = 1,
Contains = 2,
BeginsWith = 3,
EndsWith = 4,
LessThan = 5,
GreaterThan = 6,
LessThanEqual = 7,
GreaterThanEqual = 8,
On = 9,
Before = 10,
After = 11,
OnOrBefore = 12,
OnOrAfter = 13,
Absent = 14,
}
impl TryFrom<u32> for MatchType {
type Error = AppleCodesignError;
fn try_from(v: u32) -> Result<Self, Self::Error> {
match v {
0 => Ok(Self::Exists),
1 => Ok(Self::Equal),
2 => Ok(Self::Contains),
3 => Ok(Self::BeginsWith),
4 => Ok(Self::EndsWith),
5 => Ok(Self::LessThan),
6 => Ok(Self::GreaterThan),
7 => Ok(Self::LessThanEqual),
8 => Ok(Self::GreaterThanEqual),
9 => Ok(Self::On),
10 => Ok(Self::Before),
11 => Ok(Self::After),
12 => Ok(Self::OnOrBefore),
13 => Ok(Self::OnOrAfter),
14 => Ok(Self::Absent),
_ => Err(AppleCodesignError::RequirementUnknownMatchExpression(v)),
}
}
}
impl MatchType {
pub fn parse_payload<'a>(
&self,
data: &'a [u8],
) -> Result<(CodeRequirementMatchExpression<'a>, &'a [u8]), AppleCodesignError> {
match self {
Self::Exists => Ok((CodeRequirementMatchExpression::Exists, data)),
Self::Equal => {
let (value, data) = read_data(data)?;
Ok((CodeRequirementMatchExpression::Equal(value.into()), data))
}
Self::Contains => {
let (value, data) = read_data(data)?;
Ok((CodeRequirementMatchExpression::Contains(value.into()), data))
}
Self::BeginsWith => {
let (value, data) = read_data(data)?;
Ok((
CodeRequirementMatchExpression::BeginsWith(value.into()),
data,
))
}
Self::EndsWith => {
let (value, data) = read_data(data)?;
Ok((CodeRequirementMatchExpression::EndsWith(value.into()), data))
}
Self::LessThan => {
let (value, data) = read_data(data)?;
Ok((CodeRequirementMatchExpression::LessThan(value.into()), data))
}
Self::GreaterThan => {
let (value, data) = read_data(data)?;
Ok((
CodeRequirementMatchExpression::GreaterThan(value.into()),
data,
))
}
Self::LessThanEqual => {
let (value, data) = read_data(data)?;
Ok((
CodeRequirementMatchExpression::LessThanEqual(value.into()),
data,
))
}
Self::GreaterThanEqual => {
let (value, data) = read_data(data)?;
Ok((
CodeRequirementMatchExpression::GreaterThanEqual(value.into()),
data,
))
}
Self::On => {
let value = data.pread_with::<i64>(0, scroll::BE)?;
Ok((
CodeRequirementMatchExpression::On(
chrono::Utc
.timestamp_opt(value, 0)
.single()
.ok_or(AppleCodesignError::BadTime)?,
),
&data[8..],
))
}
Self::Before => {
let value = data.pread_with::<i64>(0, scroll::BE)?;
Ok((
CodeRequirementMatchExpression::Before(
chrono::Utc
.timestamp_opt(value, 0)
.single()
.ok_or(AppleCodesignError::BadTime)?,
),
&data[8..],
))
}
Self::After => {
let value = data.pread_with::<i64>(0, scroll::BE)?;
Ok((
CodeRequirementMatchExpression::After(
chrono::Utc
.timestamp_opt(value, 0)
.single()
.ok_or(AppleCodesignError::BadTime)?,
),
&data[8..],
))
}
Self::OnOrBefore => {
let value = data.pread_with::<i64>(0, scroll::BE)?;
Ok((
CodeRequirementMatchExpression::OnOrBefore(
chrono::Utc
.timestamp_opt(value, 0)
.single()
.ok_or(AppleCodesignError::BadTime)?,
),
&data[8..],
))
}
Self::OnOrAfter => {
let value = data.pread_with::<i64>(0, scroll::BE)?;
Ok((
CodeRequirementMatchExpression::OnOrAfter(
chrono::Utc
.timestamp_opt(value, 0)
.single()
.ok_or(AppleCodesignError::BadTime)?,
),
&data[8..],
))
}
Self::Absent => Ok((CodeRequirementMatchExpression::Absent, data)),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CodeRequirementMatchExpression<'a> {
Exists,
Equal(CodeRequirementValue<'a>),
Contains(CodeRequirementValue<'a>),
BeginsWith(CodeRequirementValue<'a>),
EndsWith(CodeRequirementValue<'a>),
LessThan(CodeRequirementValue<'a>),
GreaterThan(CodeRequirementValue<'a>),
LessThanEqual(CodeRequirementValue<'a>),
GreaterThanEqual(CodeRequirementValue<'a>),
On(chrono::DateTime<chrono::Utc>),
Before(chrono::DateTime<chrono::Utc>),
After(chrono::DateTime<chrono::Utc>),
OnOrBefore(chrono::DateTime<chrono::Utc>),
OnOrAfter(chrono::DateTime<chrono::Utc>),
Absent,
}
impl<'a> Display for CodeRequirementMatchExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Exists => f.write_str("/* exists */"),
Self::Equal(value) => f.write_fmt(format_args!("= \"{value}\"")),
Self::Contains(value) => f.write_fmt(format_args!("~ \"{value}\"")),
Self::BeginsWith(value) => f.write_fmt(format_args!("= \"{value}*\"")),
Self::EndsWith(value) => f.write_fmt(format_args!("= \"*{value}\"")),
Self::LessThan(value) => f.write_fmt(format_args!("< \"{value}\"")),
Self::GreaterThan(value) => f.write_fmt(format_args!("> \"{value}\"")),
Self::LessThanEqual(value) => f.write_fmt(format_args!("<= \"{value}\"")),
Self::GreaterThanEqual(value) => f.write_fmt(format_args!(">= \"{value}\"")),
Self::On(value) => f.write_fmt(format_args!("= \"{value}\"")),
Self::Before(value) => f.write_fmt(format_args!("< \"{value}\"")),
Self::After(value) => f.write_fmt(format_args!("> \"{value}\"")),
Self::OnOrBefore(value) => f.write_fmt(format_args!("<= \"{value}\"")),
Self::OnOrAfter(value) => f.write_fmt(format_args!(">= \"{value}\"")),
Self::Absent => f.write_str("absent"),
}
}
}
impl<'a> From<&CodeRequirementMatchExpression<'a>> for MatchType {
fn from(m: &CodeRequirementMatchExpression<'a>) -> Self {
match m {
CodeRequirementMatchExpression::Exists => MatchType::Exists,
CodeRequirementMatchExpression::Equal(_) => MatchType::Equal,
CodeRequirementMatchExpression::Contains(_) => MatchType::Contains,
CodeRequirementMatchExpression::BeginsWith(_) => MatchType::BeginsWith,
CodeRequirementMatchExpression::EndsWith(_) => MatchType::EndsWith,
CodeRequirementMatchExpression::LessThan(_) => MatchType::LessThan,
CodeRequirementMatchExpression::GreaterThan(_) => MatchType::GreaterThan,
CodeRequirementMatchExpression::LessThanEqual(_) => MatchType::LessThanEqual,
CodeRequirementMatchExpression::GreaterThanEqual(_) => MatchType::GreaterThanEqual,
CodeRequirementMatchExpression::On(_) => MatchType::On,
CodeRequirementMatchExpression::Before(_) => MatchType::Before,
CodeRequirementMatchExpression::After(_) => MatchType::After,
CodeRequirementMatchExpression::OnOrBefore(_) => MatchType::OnOrBefore,
CodeRequirementMatchExpression::OnOrAfter(_) => MatchType::OnOrAfter,
CodeRequirementMatchExpression::Absent => MatchType::Absent,
}
}
}
impl<'a> CodeRequirementMatchExpression<'a> {
pub fn from_bytes(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> {
let typ = data.pread_with::<u32>(0, scroll::BE)?;
let typ = MatchType::try_from(typ)?;
typ.parse_payload(&data[4..])
}
pub fn write_to(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> {
dest.iowrite_with(MatchType::from(self) as u32, scroll::BE)?;
match self {
Self::Exists => {}
Self::Equal(value) => value.write_encoded(dest)?,
Self::Contains(value) => value.write_encoded(dest)?,
Self::BeginsWith(value) => value.write_encoded(dest)?,
Self::EndsWith(value) => value.write_encoded(dest)?,
Self::LessThan(value) => value.write_encoded(dest)?,
Self::GreaterThan(value) => value.write_encoded(dest)?,
Self::LessThanEqual(value) => value.write_encoded(dest)?,
Self::GreaterThanEqual(value) => value.write_encoded(dest)?,
Self::On(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?,
Self::Before(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?,
Self::After(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?,
Self::OnOrBefore(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?,
Self::OnOrAfter(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?,
Self::Absent => {}
}
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CodeRequirements<'a>(Vec<CodeRequirementExpression<'a>>);
impl<'a> Deref for CodeRequirements<'a> {
type Target = Vec<CodeRequirementExpression<'a>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DerefMut for CodeRequirements<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a> Display for CodeRequirements<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, expr) in self.0.iter().enumerate() {
f.write_fmt(format_args!("{i}: {expr};"))?;
}
Ok(())
}
}
impl<'a> From<Vec<CodeRequirementExpression<'a>>> for CodeRequirements<'a> {
fn from(v: Vec<CodeRequirementExpression<'a>>) -> Self {
Self(v)
}
}
impl<'a> CodeRequirements<'a> {
pub fn parse_binary(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> {
let count = data.pread_with::<u32>(0, scroll::BE)?;
let mut data = &data[4..];
let mut elements = Vec::with_capacity(count as usize);
for _ in 0..count {
let res = CodeRequirementExpression::from_bytes(data)?;
elements.push(res.0);
data = res.1;
}
Ok((Self(elements), data))
}
pub fn parse_blob(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> {
let data = read_and_validate_blob_header(
data,
u32::from(CodeSigningMagic::Requirement),
"code requirement blob",
)
.map_err(|_| AppleCodesignError::RequirementMalformed("blob header"))?;
Self::parse_binary(data)
}
pub fn write_to(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> {
dest.iowrite_with(self.0.len() as u32, scroll::BE)?;
for e in &self.0 {
e.write_to(dest)?;
}
Ok(())
}
pub fn to_blob_data(&self) -> Result<Vec<u8>, AppleCodesignError> {
let mut payload = vec![];
self.write_to(&mut payload)?;
let mut dest = Vec::with_capacity(payload.len() + 8);
dest.iowrite_with(u32::from(CodeSigningMagic::Requirement), scroll::BE)?;
dest.iowrite_with(dest.capacity() as u32, scroll::BE)?;
dest.write_all(&payload)?;
Ok(dest)
}
pub fn add_to_requirement_set(
&self,
requirements_set: &mut RequirementSetBlob,
slot: RequirementType,
) -> Result<(), AppleCodesignError> {
let blob = RequirementBlob::try_from(self)?;
requirements_set.set_requirements(slot, blob);
Ok(())
}
}
impl<'a> TryFrom<&CodeRequirements<'a>> for RequirementBlob<'static> {
type Error = AppleCodesignError;
fn try_from(requirements: &CodeRequirements<'a>) -> Result<Self, Self::Error> {
let mut data = Vec::<u8>::new();
requirements.write_to(&mut data)?;
Ok(Self {
data: Cow::Owned(data),
})
}
}
#[cfg(test)]
mod test {
use super::*;
fn verify_roundtrip(reqs: &CodeRequirements, source: &[u8]) {
let mut dest = Vec::<u8>::new();
reqs.write_to(&mut dest).unwrap();
assert_eq!(dest.as_slice(), source);
}
#[test]
fn parse_false() {
let source = hex::decode("0000000100000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::False])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_true() {
let source = hex::decode("0000000100000001").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(els, CodeRequirements(vec![CodeRequirementExpression::True]));
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_identifier() {
let source = hex::decode("000000010000000200000007666f6f2e62617200").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::Identifier(
"foo.bar".into()
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_anchor_apple() {
let source = hex::decode("0000000100000003").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::AnchorApple])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_anchor_certificate_hash() {
let source =
hex::decode("0000000100000004ffffffff00000014deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::AnchorCertificateHash(
-1,
hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.unwrap()
.into()
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_and() {
let source = hex::decode("00000001000000060000000100000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::And(
Box::new(CodeRequirementExpression::True),
Box::new(CodeRequirementExpression::False)
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_or() {
let source = hex::decode("00000001000000070000000100000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::Or(
Box::new(CodeRequirementExpression::True),
Box::new(CodeRequirementExpression::False)
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_code_directory_hash() {
let source =
hex::decode("000000010000000800000014deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::CodeDirectoryHash(
hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
.unwrap()
.into()
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_not() {
let source = hex::decode("000000010000000900000001").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::Not(Box::new(
CodeRequirementExpression::True
))])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_info_plist_key_field() {
let source = hex::decode("000000010000000a000000036b65790000000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::Exists
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_certificate_field() {
let source =
hex::decode("000000010000000bffffffff0000000a7375626a6563742e434e000000000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::CertificateField(
-1,
"subject.CN".into(),
CodeRequirementMatchExpression::Exists
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_certificate_trusted() {
let source = hex::decode("000000010000000cffffffff").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::CertificateTrusted(-1)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_anchor_trusted() {
let source = hex::decode("000000010000000d").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::AnchorTrusted])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_certificate_generic() {
let source = hex::decode("000000010000000effffffff000000035504030000000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::CertificateGeneric(
-1,
Oid(&[0x55, 4, 3]),
CodeRequirementMatchExpression::Exists
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_anchor_apple_generic() {
let source = hex::decode("000000010000000f").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::AnchorAppleGeneric])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_entitlements_key() {
let source = hex::decode("0000000100000010000000036b65790000000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::EntitlementsKey(
"key".into(),
CodeRequirementMatchExpression::Exists
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_certificate_policy() {
let source = hex::decode("0000000100000011ffffffff000000035504030000000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::CertificatePolicy(
-1,
Oid(&[0x55, 4, 3]),
CodeRequirementMatchExpression::Exists
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_named_anchor() {
let source = hex::decode("000000010000001200000003666f6f00").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::NamedAnchor("foo".into())])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_named_code() {
let source = hex::decode("000000010000001300000003666f6f00").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::NamedCode("foo".into())])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_platform() {
let source = hex::decode("00000001000000140000000a").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::Platform(10)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_notarized() {
let source = hex::decode("0000000100000015").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::Notarized])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_certificate_field_date() {
let source = hex::decode("0000000100000016ffffffff000000035504030000000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::CertificateFieldDate(
-1,
Oid(&[0x55, 4, 3]),
CodeRequirementMatchExpression::Exists,
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_legacy() {
let source = hex::decode("0000000100000017").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::LegacyDeveloperId])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_blob() {
let source = hex::decode("fade0c00000000100000000100000000").unwrap();
let (els, data) = CodeRequirements::parse_blob(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::False])
);
assert!(data.is_empty());
let dest = els.to_blob_data().unwrap();
assert_eq!(source, dest);
}
#[test]
fn parse_match_exists() {
let source = hex::decode("000000010000000a000000036b65790000000000").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::Exists
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_absent() {
let source = hex::decode("000000010000000a000000036b6579000000000e").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::Absent
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_equal() {
let source =
hex::decode("000000010000000a000000036b657900000000010000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::Equal(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_contains() {
let source =
hex::decode("000000010000000a000000036b657900000000020000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::Contains(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_begins_with() {
let source =
hex::decode("000000010000000a000000036b657900000000030000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::BeginsWith(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_ends_with() {
let source =
hex::decode("000000010000000a000000036b657900000000040000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::EndsWith(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_less_than() {
let source =
hex::decode("000000010000000a000000036b657900000000050000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::LessThan(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_greater_than() {
let source =
hex::decode("000000010000000a000000036b657900000000060000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::GreaterThan(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_less_than_equal() {
let source =
hex::decode("000000010000000a000000036b657900000000070000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::LessThanEqual(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_greater_than_equal() {
let source =
hex::decode("000000010000000a000000036b657900000000080000000576616c7565000000")
.unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::GreaterThanEqual(b"value".as_ref().into())
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_on() {
let source =
hex::decode("000000010000000a000000036b6579000000000900000000605fca30").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::On(
chrono::Utc.timestamp_opt(1616890416, 0).unwrap()
),
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_before() {
let source =
hex::decode("000000010000000a000000036b6579000000000a00000000605fca30").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::Before(
chrono::Utc.timestamp_opt(1616890416, 0).unwrap()
),
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_after() {
let source =
hex::decode("000000010000000a000000036b6579000000000b00000000605fca30").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::After(
chrono::Utc.timestamp_opt(1616890416, 0).unwrap()
),
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_on_or_before() {
let source =
hex::decode("000000010000000a000000036b6579000000000c00000000605fca30").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::OnOrBefore(
chrono::Utc.timestamp_opt(1616890416, 0).unwrap()
),
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
#[test]
fn parse_match_on_or_after() {
let source =
hex::decode("000000010000000a000000036b6579000000000d00000000605fca30").unwrap();
let (els, data) = CodeRequirements::parse_binary(&source).unwrap();
assert_eq!(
els,
CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField(
"key".into(),
CodeRequirementMatchExpression::OnOrAfter(
chrono::Utc.timestamp_opt(1616890416, 0).unwrap()
),
)])
);
assert!(data.is_empty());
verify_roundtrip(&els, &source);
}
}