X-Git-Url: https://git.cameronkatri.com/ldid.git/blobdiff_plain/261b99639c4ab7c53058a3a1a5906e566a5c5210..33c6686fda5e2d18d26f7e0510a245487f9a0c01:/ldid.cpp diff --git a/ldid.cpp b/ldid.cpp index ffc2821..7598b40 100644 --- a/ldid.cpp +++ b/ldid.cpp @@ -42,13 +42,57 @@ #include #include +#ifndef LDID_NOSMIME +#include +# if OPENSSL_VERSION_MAJOR >= 3 +# include +# endif #include #include #include #include +#include +#endif + +#ifdef __APPLE__ +#include + +#define LDID_SHA1_DIGEST_LENGTH CC_SHA1_DIGEST_LENGTH +#define LDID_SHA1 CC_SHA1 +#define LDID_SHA1_CTX CC_SHA1_CTX +#define LDID_SHA1_Init CC_SHA1_Init +#define LDID_SHA1_Update CC_SHA1_Update +#define LDID_SHA1_Final CC_SHA1_Final + +#define LDID_SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH +#define LDID_SHA256 CC_SHA256 +#define LDID_SHA256_CTX CC_SHA256_CTX +#define LDID_SHA256_Init CC_SHA256_Init +#define LDID_SHA256_Update CC_SHA256_Update +#define LDID_SHA256_Final CC_SHA256_Final +#else #include -#include +#define LDID_SHA1_DIGEST_LENGTH SHA_DIGEST_LENGTH +#define LDID_SHA1 SHA1 +#define LDID_SHA1_CTX SHA_CTX +#define LDID_SHA1_Init SHA1_Init +#define LDID_SHA1_Update SHA1_Update +#define LDID_SHA1_Final SHA1_Final + +#define LDID_SHA256_DIGEST_LENGTH SHA256_DIGEST_LENGTH +#define LDID_SHA256 SHA256 +#define LDID_SHA256_CTX SHA256_CTX +#define LDID_SHA256_Init SHA256_Init +#define LDID_SHA256_Update SHA256_Update +#define LDID_SHA256_Final SHA256_Final +#endif + +#ifndef LDID_NOPLIST +#include +#elif __APPLE__ +#include +#endif #include "ldid.hpp" @@ -57,14 +101,27 @@ #define _assert__(line) \ _assert___(line) +#ifndef $ +#define $(value) value +#endif + +#ifdef __EXCEPTIONS +#define _assert_(expr, format, ...) \ + do if (!(expr)) { \ + fprintf(stderr, $("%s(%u): _assert(): " format "\n"), __FILE__, __LINE__, ## __VA_ARGS__); \ + throw $(__FILE__ "(" _assert__(__LINE__) "): _assert(" #expr ")"); \ + } while (false) +#else +// XXX: this is not acceptable #define _assert_(expr, format, ...) \ do if (!(expr)) { \ - fprintf(stderr, "%s(%u): _assert(): " format "\n", __FILE__, __LINE__, ## __VA_ARGS__); \ - throw __FILE__ "(" _assert__(__LINE__) "): _assert(" #expr ")"; \ + fprintf(stderr, $("%s(%u): _assert(): " format "\n"), __FILE__, __LINE__, ## __VA_ARGS__); \ + exit(-1); \ } while (false) +#endif #define _assert(expr) \ - _assert_(expr, "%s", #expr) + _assert_(expr, "%s", $(#expr)) #define _syscall(expr, ...) [&] { for (;;) { \ auto _value(expr); \ @@ -73,14 +130,15 @@ int error(errno); \ if (error == EINTR) \ continue; \ - for (auto success : (long[]) {__VA_ARGS__}) \ + /* XXX: EINTR is included in this list to fix g++ */ \ + for (auto success : (long[]) {EINTR, __VA_ARGS__}) \ if (error == success) \ return (decltype(expr)) -success; \ _assert_(false, "errno=%u", error); \ } }() #define _trace() \ - fprintf(stderr, "_trace(%s:%u): %s\n", __FILE__, __LINE__, __FUNCTION__) + fprintf(stderr, $("_trace(%s:%u): %s\n"), __FILE__, __LINE__, $(__FUNCTION__)) #define _not(type) \ ((type) ~ (type) 0) @@ -88,6 +146,10 @@ #define _packed \ __attribute__((packed)) +#ifndef LDID_NOSMIME +std::string password; +#endif + template struct Iterator_ { typedef typename Type_::const_iterator Result; @@ -134,6 +196,29 @@ Scope _scope(const Function_ &function) { #define _scope(function) \ _scope_(__COUNTER__, function) +#define CPU_ARCH_MASK uint32_t(0xff000000) +#define CPU_ARCH_ABI64 uint32_t(0x01000000) +#define CPU_ARCH_ABI64_32 uint32_t(0x02000000) + +#define CPU_TYPE_ANY uint32_t(-1) +#define CPU_TYPE_VAX uint32_t( 1) +#define CPU_TYPE_MC680x0 uint32_t( 6) +#define CPU_TYPE_X86 uint32_t( 7) +#define CPU_TYPE_MC98000 uint32_t(10) +#define CPU_TYPE_HPPA uint32_t(11) +#define CPU_TYPE_ARM uint32_t(12) +#define CPU_TYPE_MC88000 uint32_t(13) +#define CPU_TYPE_SPARC uint32_t(14) +#define CPU_TYPE_I860 uint32_t(15) +#define CPU_TYPE_POWERPC uint32_t(18) + +#define CPU_TYPE_I386 CPU_TYPE_X86 + +#define CPU_TYPE_ARM64 (CPU_ARCH_ABI64 | CPU_TYPE_ARM) +#define CPU_TYPE_POWERPC64 (CPU_ARCH_ABI64 | CPU_TYPE_POWERPC) +#define CPU_TYPE_X86_64 (CPU_ARCH_ABI64 | CPU_TYPE_X86) +#define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) + struct fat_header { uint32_t magic; uint32_t nfat_arch; @@ -171,6 +256,7 @@ struct mach_header { #define MH_OBJECT 0x1 #define MH_EXECUTE 0x2 #define MH_DYLIB 0x6 +#define MH_DYLINKER 0x7 #define MH_BUNDLE 0x8 #define MH_DYLIB_STUB 0x9 @@ -196,6 +282,16 @@ struct load_command { #define LC_DYLD_INFO_ONLY uint32_t(0x22 | LC_REQ_DYLD) #define LC_ENCRYPTION_INFO_64 uint32_t(0x2c) +union Version { + struct { + uint8_t patch; + uint8_t minor; + uint16_t major; + } _packed; + + uint32_t value; +}; + struct dylib { uint32_t name; uint32_t timestamp; @@ -363,6 +459,7 @@ struct section_64 { uint32_t flags; uint32_t reserved1; uint32_t reserved2; + uint32_t reserved3; } _packed; struct linkedit_data_command { @@ -396,15 +493,75 @@ struct encryption_info_command { #define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 0xb0 #define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 0xc0 -inline void get(std::streambuf &stream, void *data, size_t size) { - _assert(stream.sgetn(static_cast(data), size) == size); +struct : ldid::Progress { + virtual void operator()(const std::string &value) const { + } + + virtual void operator()(double value) const { + } +} dummy_; + +struct Progression : ldid::Progress { + const ldid::Progress &progress_; + std::string name_; + + Progression(const ldid::Progress &progress, const std::string &name) : + progress_(progress), + name_(name) + { + } + + virtual void operator()(const std::string &value) const { + return progress_(name_ + " (" + value + ")"); + } + + virtual void operator()(double value) const { + return progress_(value); + } +}; + +static std::streamsize read(std::streambuf &stream, void *data, size_t size) { + auto writ(stream.sgetn(static_cast(data), size)); + _assert(writ >= 0); + return writ; +} + +static inline void put(std::streambuf &stream, uint8_t value) { + _assert(stream.sputc(value) != EOF); } -inline void put(std::streambuf &stream, const void *data, size_t size) { +static inline void get(std::streambuf &stream, void *data, size_t size) { + _assert(read(stream, data, size) == size); +} + +static inline void put(std::streambuf &stream, const void *data, size_t size) { _assert(stream.sputn(static_cast(data), size) == size); } -inline void pad(std::streambuf &stream, size_t size) { +static inline void put(std::streambuf &stream, const void *data, size_t size, const ldid::Progress &progress) { + progress(0); + for (size_t total(0); total != size;) { + auto writ(std::min(size - total, size_t(4096 * 4))); + _assert(stream.sputn(static_cast(data) + total, writ) == writ); + total += writ; + progress(double(total) / size); + } +} + +static inline void put(std::streambuf &stream, const std::string &data) { + return put(stream, data.data(), data.size()); +} + +static size_t most(std::streambuf &stream, void *data, size_t size) { + size_t total(size); + while (size > 0) + if (auto writ = read(stream, data, size)) + size -= writ; + else break; + return total - size; +} + +static inline void pad(std::streambuf &stream, size_t size) { char padding[size]; memset(padding, 0, size); put(stream, padding, size); @@ -421,6 +578,156 @@ Type_ Align(Type_ value, size_t align) { static const uint8_t PageShift_(0x0c); static const uint32_t PageSize_(1 << PageShift_); +static inline unsigned bytes(uint64_t value) { + return (64 - __builtin_clzll(value) + 7) / 8; +} + +static void put(std::streambuf &stream, uint64_t value, size_t length) { + length *= 8; + do put(stream, uint8_t(value >> (length -= 8))); + while (length != 0); +} + +static void der(std::streambuf &stream, uint64_t value) { + if (value < 128) + put(stream, value); + else { + unsigned length(bytes(value)); + put(stream, 0x80 | length); + put(stream, value, length); + } +} + +static std::string der(uint8_t tag, const char *value, size_t length) { + std::stringbuf data; + put(data, tag); + der(data, length); + put(data, value, length); + return data.str(); +} + +static std::string der(uint8_t tag, const char *value) { + return der(tag, value, strlen(value)); } +static std::string der(uint8_t tag, const std::string &value) { + return der(tag, value.data(), value.size()); } + +template +static void der_(std::stringbuf &data, const Type_ &values) { + size_t size(0); + for (const auto &value : values) + size += value.size(); + der(data, size); + for (const auto &value : values) + put(data, value); +} + +static std::string der(const std::vector &values) { + std::stringbuf data; + put(data, 0x30); + der_(data, values); + return data.str(); +} + +static std::string der(const std::multiset &values) { + std::stringbuf data; + put(data, 0x31); + der_(data, values); + return data.str(); +} + +static std::string der(const std::pair &value) { + const auto key(der(0x0c, value.first)); + std::stringbuf data; + put(data, 0x30); + der(data, key.size() + value.second.size()); + put(data, key); + put(data, value.second); + return data.str(); +} + +#ifndef LDID_NOPLIST +static std::string der(plist_t data) { + switch (const auto type = plist_get_node_type(data)) { + case PLIST_BOOLEAN: { + uint8_t value(0); + plist_get_bool_val(data, &value); + + std::stringbuf data; + put(data, 0x01); + der(data, 1); + put(data, value != 0 ? 1 : 0); + return data.str(); + } break; + + case PLIST_UINT: { + uint64_t value; + plist_get_uint_val(data, &value); + const auto length(bytes(value)); + + std::stringbuf data; + put(data, 0x02); + der(data, length); + put(data, value, length); + return data.str(); + } break; + + case PLIST_REAL: { + _assert(false); + } break; + + case PLIST_DATE: { + _assert(false); + } break; + + case PLIST_DATA: { + char *value; + uint64_t length; + plist_get_data_val(data, &value, &length); + _scope({ free(value); }); + return der(0x04, value, length); + } break; + + case PLIST_STRING: { + char *value; + plist_get_string_val(data, &value); + _scope({ free(value); }); + return der(0x0c, value); + } break; + + case PLIST_ARRAY: { + std::vector values; + for (auto e(plist_array_get_size(data)), i(decltype(e)(0)); i != e; ++i) + values.push_back(der(plist_array_get_item(data, i))); + return der(values); + } break; + + case PLIST_DICT: { + std::multiset values; + + plist_dict_iter iterator(NULL); + plist_dict_new_iter(data, &iterator); + _scope({ free(iterator); }); + + for (;;) { + char *key(NULL); + plist_t value(NULL); + plist_dict_next_item(data, iterator, &key, &value); + if (key == NULL) + break; + _scope({ free(key); }); + values.insert(der(std::make_pair(key, der(value)))); + } + + return der(values); + } break; + + default: { + _assert_(false, "unsupported plist type %d", type); + } break; + } +} +#endif + static inline uint16_t Swap_(uint16_t value) { return ((value >> 8) & 0x00ff) | @@ -480,9 +787,6 @@ static inline int64_t Swap(int64_t value) { return Swap(static_cast(value)); } -template -class Pointer; - class Swapped { protected: bool swapped_; @@ -567,6 +871,7 @@ class MachHeader : _assert( Swap(mach_header_->filetype) == MH_EXECUTE || Swap(mach_header_->filetype) == MH_DYLIB || + Swap(mach_header_->filetype) == MH_DYLINKER || Swap(mach_header_->filetype) == MH_BUNDLE ); } @@ -607,85 +912,30 @@ class MachHeader : return load_commands; } - std::vector GetSegments(const char *segment_name) const { - std::vector segment_commands; - - _foreach (load_command, GetLoadCommands()) { - if (Swap(load_command->cmd) == LC_SEGMENT) { - segment_command *segment_command = reinterpret_cast(load_command); - if (strncmp(segment_command->segname, segment_name, 16) == 0) - segment_commands.push_back(segment_command); - } - } - - return segment_commands; - } - - std::vector GetSegments64(const char *segment_name) const { - std::vector segment_commands; - - _foreach (load_command, GetLoadCommands()) { - if (Swap(load_command->cmd) == LC_SEGMENT_64) { - segment_command_64 *segment_command = reinterpret_cast(load_command); - if (strncmp(segment_command->segname, segment_name, 16) == 0) - segment_commands.push_back(segment_command); - } - } - - return segment_commands; - } - - std::vector
GetSections(const char *segment_name, const char *section_name) const { - std::vector
sections; - - _foreach (segment, GetSegments(segment_name)) { - section *section = (struct section *) (segment + 1); - - uint32_t sect; - for (sect = 0; sect != Swap(segment->nsects); ++sect) { - if (strncmp(section->sectname, section_name, 16) == 0) - sections.push_back(section); - ++section; - } - } - - return sections; - } + void ForSection(const ldid::Functor &code) const { + _foreach (load_command, GetLoadCommands()) + switch (Swap(load_command->cmd)) { + case LC_SEGMENT: { + auto segment(reinterpret_cast(load_command)); + code(segment->segname, NULL, GetOffset(segment->fileoff), segment->filesize); + auto section(reinterpret_cast(segment + 1)); + for (uint32_t i(0), e(Swap(segment->nsects)); i != e; ++i, ++section) + code(segment->segname, section->sectname, GetOffset(segment->fileoff + section->offset), section->size); + } break; - template - Pointer GetPointer(uint32_t address, const char *segment_name = NULL) const { - load_command *load_command = (struct load_command *) (mach_header_ + 1); - uint32_t cmd; - - for (cmd = 0; cmd != Swap(mach_header_->ncmds); ++cmd) { - if (Swap(load_command->cmd) == LC_SEGMENT) { - segment_command *segment_command = (struct segment_command *) load_command; - if (segment_name != NULL && strncmp(segment_command->segname, segment_name, 16) != 0) - goto next_command; - - section *sections = (struct section *) (segment_command + 1); - - uint32_t sect; - for (sect = 0; sect != Swap(segment_command->nsects); ++sect) { - section *section = §ions[sect]; - //printf("%s %u %p %p %u\n", segment_command->segname, sect, address, section->addr, section->size); - if (address >= Swap(section->addr) && address < Swap(section->addr) + Swap(section->size)) { - //printf("0x%.8x %s\n", address, segment_command->segname); - return Pointer(this, reinterpret_cast(address - Swap(section->addr) + Swap(section->offset) + (char *) mach_header_)); - } - } + case LC_SEGMENT_64: { + auto segment(reinterpret_cast(load_command)); + code(segment->segname, NULL, GetOffset(segment->fileoff), segment->filesize); + auto section(reinterpret_cast(segment + 1)); + for (uint32_t i(0), e(Swap(segment->nsects)); i != e; ++i, ++section) + code(segment->segname, section->sectname, GetOffset(segment->fileoff + section->offset), section->size); + } break; } - - next_command: - load_command = (struct load_command *) ((char *) load_command + Swap(load_command->cmdsize)); - } - - return Pointer(this); } template - Pointer GetOffset(uint32_t offset) { - return Pointer(this, reinterpret_cast(offset + (uint8_t *) mach_header_)); + Target_ *GetOffset(uint32_t offset) const { + return reinterpret_cast(offset + (uint8_t *) mach_header_); } }; @@ -756,44 +1006,13 @@ class FatHeader : } }; -template -class Pointer { - private: - const MachHeader *framework_; - const Target_ *pointer_; - - public: - Pointer(const MachHeader *framework = NULL, const Target_ *pointer = NULL) : - framework_(framework), - pointer_(pointer) - { - } - - operator const Target_ *() const { - return pointer_; - } - - const Target_ *operator ->() const { - return pointer_; - } - - Pointer &operator ++() { - ++pointer_; - return *this; - } - - template - Value_ Swap(Value_ value) { - return framework_->Swap(value); - } -}; - #define CSMAGIC_REQUIREMENT uint32_t(0xfade0c00) #define CSMAGIC_REQUIREMENTS uint32_t(0xfade0c01) #define CSMAGIC_CODEDIRECTORY uint32_t(0xfade0c02) #define CSMAGIC_EMBEDDED_SIGNATURE uint32_t(0xfade0cc0) #define CSMAGIC_EMBEDDED_SIGNATURE_OLD uint32_t(0xfade0b02) #define CSMAGIC_EMBEDDED_ENTITLEMENTS uint32_t(0xfade7171) +#define CSMAGIC_EMBEDDED_DERFORMAT uint32_t(0xfade7172) // name? #define CSMAGIC_DETACHED_SIGNATURE uint32_t(0xfade0cc1) #define CSMAGIC_BLOBWRAPPER uint32_t(0xfade0b01) @@ -803,10 +1022,36 @@ class Pointer { #define CSSLOT_RESOURCEDIR uint32_t(0x00003) #define CSSLOT_APPLICATION uint32_t(0x00004) #define CSSLOT_ENTITLEMENTS uint32_t(0x00005) +#define CSSLOT_REPSPECIFIC uint32_t(0x00006) // name? +#define CSSLOT_DERFORMAT uint32_t(0x00007) // name? +#define CSSLOT_ALTERNATE uint32_t(0x01000) #define CSSLOT_SIGNATURESLOT uint32_t(0x10000) -#define CS_HASHTYPE_SHA1 1 +#define CS_HASHTYPE_SHA160_160 1 +#define CS_HASHTYPE_SHA256_256 2 +#define CS_HASHTYPE_SHA256_160 3 +#define CS_HASHTYPE_SHA386_386 4 + +#if 0 +#define CS_EXECSEG_MAIN_BINARY 0x001 /* executable segment denotes main binary */ +#define CS_EXECSEG_ALLOW_UNSIGNED 0x010 /* allow unsigned pages (for debugging) */ +#define CS_EXECSEG_DEBUGGER 0x020 /* main binary is debugger */ +#define CS_EXECSEG_JIT 0x040 /* JIT enabled */ +#define CS_EXECSEG_SKIP_LV 0x080 /* skip library validation */ +#define CS_EXECSEG_CAN_LOAD_CDHASH 0x100 /* can bless cdhash for execution */ +#define CS_EXECSEG_CAN_EXEC_CDHASH 0x200 /* can execute blessed cdhash */ +#else +enum SecCodeExecSegFlags { + kSecCodeExecSegMainBinary = 0x001, + kSecCodeExecSegAllowUnsigned = 0x010, + kSecCodeExecSegDebugger = 0x020, + kSecCodeExecSegJit = 0x040, + kSecCodeExecSegSkipLibraryVal = 0x080, + kSecCodeExecSegCanLoadCdHash = 0x100, + kSecCodeExecSegCanExecCdHash = 0x100, +}; +#endif struct BlobIndex { uint32_t type; @@ -834,36 +1079,218 @@ struct CodeDirectory { uint32_t codeLimit; uint8_t hashSize; uint8_t hashType; - uint8_t spare1; + uint8_t platform; uint8_t pageSize; uint32_t spare2; + uint32_t scatterOffset; + uint32_t teamIDOffset; + uint32_t spare3; + uint64_t codeLimit64; + uint64_t execSegBase; + uint64_t execSegLimit; + uint64_t execSegFlags; +#if 0 // version = 0x20500 + uint32_t runtime; + uint32_t preEncryptOffset; +#endif +#if 0 // version = 0x20600 + uint8_t linkageHashType; + uint8_t linkageTruncated; + uint16_t spare4; + uint32_t linkageOffset; + uint32_t linkageSize; +#endif } _packed; +enum CodeSignatureFlags { + kSecCodeSignatureHost = 0x0001, + kSecCodeSignatureAdhoc = 0x0002, + kSecCodeSignatureForceHard = 0x0100, + kSecCodeSignatureForceKill = 0x0200, + kSecCodeSignatureForceExpiration = 0x0400, + kSecCodeSignatureRestrict = 0x0800, + kSecCodeSignatureEnforcement = 0x1000, + kSecCodeSignatureLibraryValidation = 0x2000, + kSecCodeSignatureRuntime = 0x10000, +}; + +enum Kind : uint32_t { + exprForm = 1, // prefix expr form +}; + +enum ExprOp : uint32_t { + opFalse, // unconditionally false + opTrue, // unconditionally true + opIdent, // match canonical code [string] + opAppleAnchor, // signed by Apple as Apple's product + opAnchorHash, // match anchor [cert hash] + opInfoKeyValue, // *legacy* - use opInfoKeyField [key; value] + opAnd, // binary prefix expr AND expr [expr; expr] + opOr, // binary prefix expr OR expr [expr; expr] + opCDHash, // match hash of CodeDirectory directly [cd hash] + opNot, // logical inverse [expr] + opInfoKeyField, // Info.plist key field [string; match suffix] + opCertField, // Certificate field [cert index; field name; match suffix] + opTrustedCert, // require trust settings to approve one particular cert [cert index] + opTrustedCerts, // require trust settings to approve the cert chain + opCertGeneric, // Certificate component by OID [cert index; oid; match suffix] + opAppleGenericAnchor, // signed by Apple in any capacity + opEntitlementField, // entitlement dictionary field [string; match suffix] + opCertPolicy, // Certificate policy by OID [cert index; oid; match suffix] + opNamedAnchor, // named anchor type + opNamedCode, // named subroutine + opPlatform, // platform constraint [integer] + exprOpCount // (total opcode count in use) +}; + +enum MatchOperation { + matchExists, // anything but explicit "false" - no value stored + matchEqual, // equal (CFEqual) + matchContains, // partial match (substring) + matchBeginsWith, // partial match (initial substring) + matchEndsWith, // partial match (terminal substring) + matchLessThan, // less than (string with numeric comparison) + matchGreaterThan, // greater than (string with numeric comparison) + matchLessEqual, // less or equal (string with numeric comparison) + matchGreaterEqual, // greater or equal (string with numeric comparison) +}; + +#define OID_ISO_MEMBER 42 +#define OID_US OID_ISO_MEMBER, 134, 72 +#define APPLE_OID OID_US, 0x86, 0xf7, 0x63 +#define APPLE_ADS_OID APPLE_OID, 0x64 +#define APPLE_EXTENSION_OID APPLE_ADS_OID, 6 + +#ifndef LDID_NOFLAGT extern "C" uint32_t hash(uint8_t *k, uint32_t length, uint32_t initval); +#endif + +struct Algorithm { + size_t size_; + uint8_t type_; + + Algorithm(size_t size, uint8_t type) : + size_(size), + type_(type) + { + } + + virtual const uint8_t *operator [](const ldid::Hash &hash) const = 0; + + virtual void operator ()(uint8_t *hash, const void *data, size_t size) const = 0; + virtual void operator ()(ldid::Hash &hash, const void *data, size_t size) const = 0; + virtual void operator ()(std::vector &hash, const void *data, size_t size) const = 0; + + virtual const char *name() = 0; +}; + +struct AlgorithmSHA1 : + Algorithm +{ + AlgorithmSHA1() : + Algorithm(LDID_SHA1_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160) + { + } + + virtual const uint8_t *operator [](const ldid::Hash &hash) const { + return hash.sha1_; + } + + void operator ()(uint8_t *hash, const void *data, size_t size) const { + LDID_SHA1(static_cast(data), size, hash); + } + + void operator ()(ldid::Hash &hash, const void *data, size_t size) const { + return operator()(hash.sha1_, data, size); + } + + void operator ()(std::vector &hash, const void *data, size_t size) const { + hash.resize(LDID_SHA1_DIGEST_LENGTH); + return operator ()(reinterpret_cast(hash.data()), data, size); + } + + virtual const char *name() { + return "sha1"; + } +}; + +struct AlgorithmSHA256 : + Algorithm +{ + AlgorithmSHA256() : + Algorithm(LDID_SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256) + { + } + + virtual const uint8_t *operator [](const ldid::Hash &hash) const { + return hash.sha256_; + } + + void operator ()(uint8_t *hash, const void *data, size_t size) const { + LDID_SHA256(static_cast(data), size, hash); + } + + void operator ()(ldid::Hash &hash, const void *data, size_t size) const { + return operator()(hash.sha256_, data, size); + } + + void operator ()(std::vector &hash, const void *data, size_t size) const { + hash.resize(LDID_SHA256_DIGEST_LENGTH); + return operator ()(reinterpret_cast(hash.data()), data, size); + } + + virtual const char *name() { + return "sha256"; + } +}; -static void sha1(uint8_t *hash, const void *data, size_t size) { - SHA1(static_cast(data), size, hash); +static bool do_sha1(true); +static bool do_sha256(true); + +static const std::vector &GetAlgorithms() { + static AlgorithmSHA1 sha1; + static AlgorithmSHA256 sha256; + + static std::vector algorithms; + if (algorithms.empty()) { + if (do_sha256) + algorithms.push_back(&sha256); + if (do_sha1) + algorithms.push_back(&sha1); + } + + return algorithms; } +struct Baton { + std::string entitlements_; + std::string derformat_; +}; + struct CodesignAllocation { FatMachHeader mach_header_; - uint32_t offset_; + uint64_t offset_; uint32_t size_; - uint32_t limit_; + uint64_t limit_; uint32_t alloc_; uint32_t align_; + const char *arch_; + Baton baton_; - CodesignAllocation(FatMachHeader mach_header, size_t offset, size_t size, size_t limit, size_t alloc, size_t align) : + CodesignAllocation(FatMachHeader mach_header, size_t offset, size_t size, size_t limit, size_t alloc, size_t align, const char *arch, const Baton &baton) : mach_header_(mach_header), offset_(offset), size_(size), limit_(limit), alloc_(alloc), - align_(align) + align_(align), + arch_(arch), + baton_(baton) { } }; +#ifndef LDID_NOTOOLS class File { private: int file_; @@ -962,10 +1389,48 @@ class Map { return std::string(static_cast(data_), size_); } }; +#endif namespace ldid { -static void Allocate(const void *idata, size_t isize, std::streambuf &output, const Functor &allocate, const Functor &save) { +#ifndef LDID_NOPLIST +static plist_t plist(const std::string &data); +#endif + +void Analyze(const MachHeader &mach_header, const Functor &entitle) { + _foreach (load_command, mach_header.GetLoadCommands()) + if (mach_header.Swap(load_command->cmd) == LC_CODE_SIGNATURE) { + auto signature(reinterpret_cast(load_command)); + auto offset(mach_header.Swap(signature->dataoff)); + auto pointer(reinterpret_cast(mach_header.GetBase()) + offset); + auto super(reinterpret_cast(pointer)); + + for (size_t index(0); index != Swap(super->count); ++index) + if (Swap(super->index[index].type) == CSSLOT_ENTITLEMENTS) { + auto begin(Swap(super->index[index].offset)); + auto blob(reinterpret_cast(pointer + begin)); + auto writ(Swap(blob->length) - sizeof(*blob)); + entitle(reinterpret_cast(blob + 1), writ); + } + } +} + +std::string Analyze(const void *data, size_t size) { + std::string entitlements; + + FatHeader fat_header(const_cast(data), size); + _foreach (mach_header, fat_header.GetMachHeaders()) + Analyze(mach_header, fun([&](const char *data, size_t size) { + if (entitlements.empty()) + entitlements.assign(data, size); + else + _assert(entitlements.compare(0, entitlements.size(), data, size) == 0); + })); + + return entitlements; +} + +static void Allocate(const void *idata, size_t isize, std::streambuf &output, const Functor &allocate, const Functor &save, const Progress &progress) { FatHeader source(const_cast(idata), isize); size_t offset(0); @@ -996,22 +1461,70 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co if (symtab != NULL) { auto end(mach_header.Swap(symtab->stroff) + mach_header.Swap(symtab->strsize)); - _assert(end <= size); - _assert(end >= size - 0x10); - size = end; + if (symtab->stroff != 0 || symtab->strsize != 0) { + _assert(end <= size); + _assert(end >= size - 0x10); + size = end; + } } - size_t alloc(allocate(size)); + Baton baton; + size_t alloc(allocate(mach_header, baton, size)); auto *fat_arch(mach_header.GetFatArch()); - uint32_t align(fat_arch == NULL ? 0 : source.Swap(fat_arch->align)); + uint32_t align; + + if (fat_arch != NULL) + align = source.Swap(fat_arch->align); + else switch (mach_header.GetCPUType()) { + case CPU_TYPE_POWERPC: + case CPU_TYPE_POWERPC64: + case CPU_TYPE_X86: + case CPU_TYPE_X86_64: + align = 0xc; + break; + case CPU_TYPE_ARM: + case CPU_TYPE_ARM64: + case CPU_TYPE_ARM64_32: + align = 0xe; + break; + default: + align = 0x0; + break; + } + + const char *arch(NULL); + switch (mach_header.GetCPUType()) { + case CPU_TYPE_POWERPC: + arch = "ppc"; + break; + case CPU_TYPE_POWERPC64: + arch = "ppc64"; + break; + case CPU_TYPE_X86: + arch = "i386"; + break; + case CPU_TYPE_X86_64: + arch = "x86_64"; + break; + case CPU_TYPE_ARM: + arch = "arm"; + break; + case CPU_TYPE_ARM64: + arch = "arm64"; + break; + case CPU_TYPE_ARM64_32: + arch = "arm64_32"; + break; + } + offset = Align(offset, 1 << align); uint32_t limit(size); if (alloc != 0) limit = Align(limit, 0x10); - allocations.push_back(CodesignAllocation(mach_header, offset, size, limit, alloc, align)); + allocations.push_back(CodesignAllocation(mach_header, offset, size, limit, alloc, align, arch, baton)); offset += size + alloc; offset = Align(offset, 0x10); } @@ -1025,14 +1538,27 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co put(output, &fat_header, sizeof(fat_header)); position += sizeof(fat_header); + // XXX: support fat_arch_64 (not in my toolchain) + // probably use C++14 generic lambda (not in my toolchain) + + _assert_(![&]() { + _foreach (allocation, allocations) { + const auto offset(allocation.offset_); + const auto size(allocation.limit_ + allocation.alloc_); + if (uint32_t(offset) != offset || uint32_t(size) != size) + return true; + } + return false; + }(), "FAT slice >=4GiB not currently supported"); + _foreach (allocation, allocations) { auto &mach_header(allocation.mach_header_); fat_arch fat_arch; fat_arch.cputype = Swap(mach_header->cputype); fat_arch.cpusubtype = Swap(mach_header->cpusubtype); - fat_arch.offset = Swap(allocation.offset_); - fat_arch.size = Swap(allocation.limit_ + allocation.alloc_); + fat_arch.offset = Swap(uint32_t(allocation.offset_)); + fat_arch.size = Swap(uint32_t(allocation.limit_ + allocation.alloc_)); fat_arch.align = Swap(allocation.align_); put(output, &fat_arch, sizeof(fat_arch)); position += sizeof(fat_arch); @@ -1040,11 +1566,15 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co } _foreach (allocation, allocations) { + progress(allocation.arch_); auto &mach_header(allocation.mach_header_); pad(output, allocation.offset_ - position); position = allocation.offset_; + size_t left(-1); + size_t right(0); + std::vector commands; _foreach (load_command, mach_header.GetLoadCommands()) { @@ -1055,22 +1585,44 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co continue; break; + // XXX: this is getting ridiculous: provide a better abstraction + case LC_SEGMENT: { auto segment_command(reinterpret_cast(©[0])); - if (strncmp(segment_command->segname, "__LINKEDIT", 16) != 0) - break; - size_t size(mach_header.Swap(allocation.limit_ + allocation.alloc_ - mach_header.Swap(segment_command->fileoff))); - segment_command->filesize = size; - segment_command->vmsize = Align(size, PageSize_); + + if ((segment_command->initprot & 04) != 0) { + auto begin(mach_header.Swap(segment_command->fileoff)); + auto end(begin + mach_header.Swap(segment_command->filesize)); + if (left > begin) + left = begin; + if (right < end) + right = end; + } + + if (strncmp(segment_command->segname, "__LINKEDIT", 16) == 0) { + size_t size(mach_header.Swap(allocation.limit_ + allocation.alloc_ - mach_header.Swap(segment_command->fileoff))); + segment_command->filesize = size; + segment_command->vmsize = Align(size, 1 << allocation.align_); + } } break; case LC_SEGMENT_64: { auto segment_command(reinterpret_cast(©[0])); - if (strncmp(segment_command->segname, "__LINKEDIT", 16) != 0) - break; - size_t size(mach_header.Swap(allocation.limit_ + allocation.alloc_ - mach_header.Swap(segment_command->fileoff))); - segment_command->filesize = size; - segment_command->vmsize = Align(size, PageSize_); + + if ((segment_command->initprot & 04) != 0) { + auto begin(mach_header.Swap(segment_command->fileoff)); + auto end(begin + mach_header.Swap(segment_command->filesize)); + if (left > begin) + left = begin; + if (right < end) + right = end; + } + + if (strncmp(segment_command->segname, "__LINKEDIT", 16) == 0) { + size_t size(mach_header.Swap(allocation.limit_ + allocation.alloc_ - mach_header.Swap(segment_command->fileoff))); + segment_command->filesize = size; + segment_command->vmsize = Align(size, 1 << allocation.align_); + } } break; } @@ -1126,15 +1678,17 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co std::string overlap(altern.str()); overlap.append(top + overlap.size(), Align(overlap.size(), 0x1000) - overlap.size()); - put(output, top + (position - begin), allocation.size_ - (position - begin)); + put(output, top + (position - begin), allocation.size_ - (position - begin), progress); position = begin + allocation.size_; pad(output, allocation.limit_ - allocation.size_); position += allocation.limit_ - allocation.size_; - size_t saved(save(output, allocation.limit_, overlap, top)); + size_t saved(save(mach_header, allocation.baton_, output, allocation.limit_, left, right, overlap, top, progress)); if (allocation.alloc_ > saved) pad(output, allocation.alloc_ - saved); + else + _assert(allocation.alloc_ == saved); position += allocation.alloc_; } } @@ -1148,13 +1702,15 @@ static void insert(Blobs &blobs, uint32_t slot, const std::stringbuf &buffer) { std::swap(blobs[slot], value); } -static void insert(Blobs &blobs, uint32_t slot, uint32_t magic, const std::stringbuf &buffer) { +static const std::string &insert(Blobs &blobs, uint32_t slot, uint32_t magic, const std::stringbuf &buffer) { auto value(buffer.str()); Blob blob; blob.magic = Swap(magic); blob.length = Swap(uint32_t(sizeof(blob) + value.size())); value.insert(0, reinterpret_cast(&blob), sizeof(blob)); - std::swap(blobs[slot], value); + auto &save(blobs[slot]); + std::swap(save, value); + return save; } static size_t put(std::streambuf &output, uint32_t magic, const Blobs &blobs) { @@ -1184,6 +1740,7 @@ static size_t put(std::streambuf &output, uint32_t magic, const Blobs &blobs) { return offset; } +#ifndef LDID_NOSMIME class Buffer { private: BIO *bio_; @@ -1244,9 +1801,20 @@ class Stuff { ca_(NULL) { _assert(value_ != NULL); - _assert(PKCS12_parse(value_, "", &key_, &cert_, &ca_) != 0); + + if (!PKCS12_verify_mac(value_, "", 0) && password.empty()) { + char passbuf[2048]; + UI_UTIL_read_pw_string(passbuf, 2048, "Enter password: ", 0); + password = passbuf; + } + + _assert(PKCS12_parse(value_, password.c_str(), &key_, &cert_, &ca_) != 0); _assert(key_ != NULL); _assert(cert_ != NULL); + + if (ca_ == NULL) + ca_ = sk_X509_new_null(); + _assert(ca_ != NULL); } Stuff(const std::string &data) : @@ -1283,10 +1851,45 @@ class Signature { PKCS7 *value_; public: - Signature(const Stuff &stuff, const Buffer &data) : - value_(PKCS7_sign(stuff, stuff, stuff, data, PKCS7_BINARY | PKCS7_DETACHED)) - { + Signature(const Stuff &stuff, const Buffer &data, const std::string &xml) { + value_ = PKCS7_new(); _assert(value_ != NULL); + + _assert(PKCS7_set_type(value_, NID_pkcs7_signed)); + _assert(PKCS7_content_new(value_, NID_pkcs7_data)); + + STACK_OF(X509) *certs(stuff); + for (unsigned i(0), e(sk_X509_num(certs)); i != e; i++) + _assert(PKCS7_add_certificate(value_, sk_X509_value(certs, e - i - 1))); + + // XXX: this is the same as PKCS7_sign_add_signer(value_, stuff, stuff, NULL, PKCS7_NOSMIMECAP) + _assert(X509_check_private_key(stuff, stuff)); + auto info(PKCS7_add_signature(value_, stuff, stuff, EVP_sha1())); + _assert(info != NULL); + _assert(PKCS7_add_certificate(value_, stuff)); + _assert(PKCS7_add_signed_attribute(info, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data))); + + PKCS7_set_detached(value_, 1); + + ASN1_OCTET_STRING *string(ASN1_OCTET_STRING_new()); + _assert(string != NULL); + try { + _assert(ASN1_STRING_set(string, xml.data(), xml.size())); + + static auto nid(OBJ_create("1.2.840.113635.100.9.1", "", "")); + _assert(PKCS7_add_signed_attribute(info, nid, V_ASN1_OCTET_STRING, string)); + } catch (...) { + ASN1_OCTET_STRING_free(string); + throw; + } + + // XXX: this is the same as PKCS7_final(value_, data, PKCS7_BINARY) + BIO *bio(PKCS7_dataInit(value_, NULL)); + _assert(bio != NULL); + _scope({ BIO_free_all(bio); }); + SMIME_crlf_copy(data, bio, PKCS7_BINARY); + BIO_flush(bio); + _assert(PKCS7_dataFinal(value_, bio)); } ~Signature() { @@ -1297,6 +1900,7 @@ class Signature { return value_; } }; +#endif class NullBuffer : public std::streambuf @@ -1315,23 +1919,27 @@ class HashBuffer : public std::streambuf { private: - std::vector &hash_; - SHA_CTX context_; + ldid::Hash &hash_; + + LDID_SHA1_CTX sha1_; + LDID_SHA256_CTX sha256_; public: - HashBuffer(std::vector &hash) : + HashBuffer(ldid::Hash &hash) : hash_(hash) { - SHA1_Init(&context_); + LDID_SHA1_Init(&sha1_); + LDID_SHA256_Init(&sha256_); } ~HashBuffer() { - hash_.resize(SHA_DIGEST_LENGTH); - SHA1_Final(reinterpret_cast(hash_.data()), &context_); + LDID_SHA1_Final(reinterpret_cast(hash_.sha1_), &sha1_); + LDID_SHA256_Final(reinterpret_cast(hash_.sha256_), &sha256_); } virtual std::streamsize xsputn(const char_type *data, std::streamsize size) { - SHA1_Update(&context_, data, size); + LDID_SHA1_Update(&sha1_, data, size); + LDID_SHA256_Update(&sha256_, data, size); return size; } @@ -1351,7 +1959,7 @@ class HashProxy : std::streambuf &buffer_; public: - HashProxy(std::vector &hash, std::streambuf &buffer) : + HashProxy(ldid::Hash &hash, std::streambuf &buffer) : HashBuffer(hash), buffer_(buffer) { @@ -1363,6 +1971,7 @@ class HashProxy : } }; +#ifndef LDID_NOTOOLS static bool Starts(const std::string &lhs, const std::string &rhs) { return lhs.size() >= rhs.size() && lhs.compare(0, rhs.size(), rhs) == 0; } @@ -1386,8 +1995,13 @@ class Split { static void mkdir_p(const std::string &path) { if (path.empty()) return; +#ifdef __WIN32__ + if (_syscall(mkdir(path.c_str()), EEXIST) == -EEXIST) + return; +#else if (_syscall(mkdir(path.c_str(), 0755), EEXIST) == -EEXIST) return; +#endif auto slash(path.rfind('/', path.size() - 1)); if (slash == std::string::npos) return; @@ -1412,150 +2026,429 @@ static void Commit(const std::string &path, const std::string &temp) { _syscall(rename(temp.c_str(), path.c_str())); } +#endif namespace ldid { -void Sign(const void *idata, size_t isize, std::streambuf &output, const std::string &identifier, const std::string &entitlements, const std::string &key, const Slots &slots) { - Allocate(idata, isize, output, fun([&](size_t size) -> size_t { - size_t alloc(sizeof(struct SuperBlob)); +#ifndef LDID_NOSMIME +static void get(std::string &value, X509_NAME *name, int nid) { + auto index(X509_NAME_get_index_by_NID(name, nid, -1)); + _assert(index >= 0); + auto next(X509_NAME_get_index_by_NID(name, nid, index)); + _assert(next == -1); + auto entry(X509_NAME_get_entry(name, index)); + _assert(entry != NULL); + auto asn(X509_NAME_ENTRY_get_data(entry)); + _assert(asn != NULL); + value.assign(reinterpret_cast(ASN1_STRING_get0_data(asn)), ASN1_STRING_length(asn)); +} +#endif - uint32_t special(0); +static void req(std::streambuf &buffer, uint32_t value) { + value = Swap(value); + put(buffer, &value, sizeof(value)); +} - special = std::max(special, CSSLOT_REQUIREMENTS); - alloc += sizeof(struct BlobIndex); - alloc += 0xc; +static void req(std::streambuf &buffer, const std::string &value) { + req(buffer, value.size()); + put(buffer, value.data(), value.size()); + static uint8_t zeros[] = {0,0,0,0}; + put(buffer, zeros, 3 - (value.size() + 3) % 4); +} - if (!entitlements.empty()) { - special = std::max(special, CSSLOT_ENTITLEMENTS); - alloc += sizeof(struct BlobIndex); +template +static void req(std::streambuf &buffer, uint8_t (&&data)[Size_]) { + req(buffer, Size_); + put(buffer, data, Size_); + static uint8_t zeros[] = {0,0,0,0}; + put(buffer, zeros, 3 - (Size_ + 3) % 4); +} + +Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, uint32_t flags, bool platform, const Progress &progress) { + Hash hash; + + + std::string team; + std::string common; + +#ifndef LDID_NOSMIME + if (!key.empty()) { + Stuff stuff(key); + auto name(X509_get_subject_name(stuff)); + _assert(name != NULL); + get(team, name, NID_organizationalUnitName); + get(common, name, NID_commonName); + } +#endif + + + std::stringbuf backing; + + if (!requirements.empty()) { + put(backing, requirements.data(), requirements.size()); + } else { + Blobs blobs; + + std::stringbuf requirement; + req(requirement, exprForm); + req(requirement, opAnd); + req(requirement, opIdent); + req(requirement, identifier); + req(requirement, opAnd); + req(requirement, opAppleGenericAnchor); + req(requirement, opAnd); + req(requirement, opCertField); + req(requirement, 0); + req(requirement, "subject.CN"); + req(requirement, matchEqual); + req(requirement, common); + req(requirement, opCertGeneric); + req(requirement, 1); + req(requirement, (uint8_t []) {APPLE_EXTENSION_OID, 2, 1}); + req(requirement, matchExists); + insert(blobs, 3, CSMAGIC_REQUIREMENT, requirement); + + put(backing, CSMAGIC_REQUIREMENTS, blobs); + } + + + // XXX: this is just a "sufficiently large number" + size_t certificate(0x3000); + + Allocate(idata, isize, output, fun([&](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t { + size_t alloc(sizeof(struct SuperBlob)); + + uint32_t normal((size + PageSize_ - 1) / PageSize_); + + uint32_t special(0); + + _foreach (slot, slots) + special = std::max(special, slot.first); + + mach_header.ForSection(fun([&](const char *segment, const char *section, void *data, size_t size) { + if (strcmp(segment, "__TEXT") == 0 && section != NULL && strcmp(section, "__info_plist") == 0) + special = std::max(special, CSSLOT_INFOSLOT); + })); + + special = std::max(special, CSSLOT_REQUIREMENTS); + alloc += sizeof(struct BlobIndex); + alloc += backing.str().size(); + +#ifdef LDID_NOPLIST + baton.entitlements_ = entitlements; +#else + if (merge) + Analyze(mach_header, fun([&](const char *data, size_t size) { + baton.entitlements_.assign(data, size); + })); + + if (!baton.entitlements_.empty() || !entitlements.empty()) { + auto combined(plist(baton.entitlements_)); + _scope({ plist_free(combined); }); + _assert(plist_get_node_type(combined) == PLIST_DICT); + + auto merging(plist(entitlements)); + _scope({ plist_free(merging); }); + _assert(plist_get_node_type(merging) == PLIST_DICT); + + plist_dict_iter iterator(NULL); + plist_dict_new_iter(merging, &iterator); + _scope({ free(iterator); }); + + for (;;) { + char *key(NULL); + plist_t value(NULL); + plist_dict_next_item(merging, iterator, &key, &value); + if (key == NULL) + break; + _scope({ free(key); }); + plist_dict_set_item(combined, key, plist_copy(value)); + } + + baton.derformat_ = der(combined); + + char *xml(NULL); + uint32_t size; + plist_to_xml(combined, &xml, &size); + _scope({ free(xml); }); + + baton.entitlements_.assign(xml, size); + } +#endif + + if (!baton.entitlements_.empty()) { + special = std::max(special, CSSLOT_ENTITLEMENTS); + alloc += sizeof(struct BlobIndex); alloc += sizeof(struct Blob); - alloc += entitlements.size(); + alloc += baton.entitlements_.size(); } - special = std::max(special, CSSLOT_CODEDIRECTORY); - alloc += sizeof(struct BlobIndex); - alloc += sizeof(struct Blob); - alloc += sizeof(struct CodeDirectory); - alloc += identifier.size() + 1; + if (!baton.derformat_.empty()) { + special = std::max(special, CSSLOT_DERFORMAT); + alloc += sizeof(struct BlobIndex); + alloc += sizeof(struct Blob); + alloc += baton.derformat_.size(); + } + + size_t directory(0); + directory += sizeof(struct BlobIndex); + directory += sizeof(struct Blob); + directory += sizeof(struct CodeDirectory); + directory += identifier.size() + 1; + + if (!team.empty()) + directory += team.size() + 1; + + for (Algorithm *algorithm : GetAlgorithms()) + alloc = Align(alloc + directory + (special + normal) * algorithm->size_, 16); + +#ifndef LDID_NOSMIME if (!key.empty()) { alloc += sizeof(struct BlobIndex); alloc += sizeof(struct Blob); - // XXX: this is just a "sufficiently large number" - alloc += 0x3000; + alloc += certificate; } +#endif - _foreach (slot, slots) - special = std::max(special, slot.first); - - uint32_t normal((size + PageSize_ - 1) / PageSize_); - alloc = Align(alloc + (special + normal) * SHA_DIGEST_LENGTH, 16); return alloc; - }), fun([&](std::streambuf &output, size_t limit, const std::string &overlap, const char *top) -> size_t { + }), fun([&](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, size_t left, size_t right, const std::string &overlap, const char *top, const Progress &progress) -> size_t { Blobs blobs; if (true) { - std::stringbuf data; + insert(blobs, CSSLOT_REQUIREMENTS, backing); + } - Blobs requirements; - put(data, CSMAGIC_REQUIREMENTS, requirements); + uint64_t execs(0); + if (mach_header.Swap(mach_header->filetype) == MH_EXECUTE) + execs |= kSecCodeExecSegMainBinary; + + if (!baton.entitlements_.empty()) { + std::stringbuf data; + put(data, baton.entitlements_.data(), baton.entitlements_.size()); + insert(blobs, CSSLOT_ENTITLEMENTS, CSMAGIC_EMBEDDED_ENTITLEMENTS, data); - insert(blobs, CSSLOT_REQUIREMENTS, data); +#ifndef LDID_NOPLIST + auto entitlements(plist(baton.entitlements_)); + _scope({ plist_free(entitlements); }); + _assert(plist_get_node_type(entitlements) == PLIST_DICT); + + const auto entitled([&](const char *key) { + auto item(plist_dict_get_item(entitlements, key)); + if (plist_get_node_type(item) != PLIST_BOOLEAN) + return false; + uint8_t value(0); + plist_get_bool_val(item, &value); + return value != 0; + }); + + if (entitled("get-task-allow")) + execs |= kSecCodeExecSegAllowUnsigned; + if (entitled("run-unsigned-code")) + execs |= kSecCodeExecSegAllowUnsigned; + if (entitled("com.apple.private.cs.debugger")) + execs |= kSecCodeExecSegDebugger; + if (entitled("dynamic-codesigning")) + execs |= kSecCodeExecSegJit; + if (entitled("com.apple.private.skip-library-validation")) + execs |= kSecCodeExecSegSkipLibraryVal; + if (entitled("com.apple.private.amfi.can-load-cdhash")) + execs |= kSecCodeExecSegCanLoadCdHash; + if (entitled("com.apple.private.amfi.can-execute-cdhash")) + execs |= kSecCodeExecSegCanExecCdHash; +#endif } - if (!entitlements.empty()) { + if (!baton.derformat_.empty()) { std::stringbuf data; - put(data, entitlements.data(), entitlements.size()); - insert(blobs, CSSLOT_ENTITLEMENTS, CSMAGIC_EMBEDDED_ENTITLEMENTS, data); + put(data, baton.derformat_.data(), baton.derformat_.size()); + insert(blobs, CSSLOT_DERFORMAT, CSMAGIC_EMBEDDED_DERFORMAT, data); } - if (true) { + Slots posts(slots); + + mach_header.ForSection(fun([&](const char *segment, const char *section, void *data, size_t size) { + if (strcmp(segment, "__TEXT") == 0 && section != NULL && strcmp(section, "__info_plist") == 0) { + auto &slot(posts[CSSLOT_INFOSLOT]); + for (Algorithm *algorithm : GetAlgorithms()) + (*algorithm)(slot, data, size); + } + })); + + unsigned total(0); + for (Algorithm *pointer : GetAlgorithms()) { + Algorithm &algorithm(*pointer); + std::stringbuf data; uint32_t special(0); _foreach (blob, blobs) special = std::max(special, blob.first); - _foreach (slot, slots) + _foreach (slot, posts) special = std::max(special, slot.first); uint32_t normal((limit + PageSize_ - 1) / PageSize_); CodeDirectory directory; - directory.version = Swap(uint32_t(0x00020001)); - directory.flags = Swap(uint32_t(0)); - directory.hashOffset = Swap(uint32_t(sizeof(Blob) + sizeof(CodeDirectory) + identifier.size() + 1 + SHA_DIGEST_LENGTH * special)); - directory.identOffset = Swap(uint32_t(sizeof(Blob) + sizeof(CodeDirectory))); + directory.version = Swap(uint32_t(0x00020400)); + directory.flags = Swap(uint32_t(flags)); directory.nSpecialSlots = Swap(special); - directory.codeLimit = Swap(uint32_t(limit)); + directory.codeLimit = Swap(uint32_t(limit > UINT32_MAX ? UINT32_MAX : limit)); directory.nCodeSlots = Swap(normal); - directory.hashSize = SHA_DIGEST_LENGTH; - directory.hashType = CS_HASHTYPE_SHA1; - directory.spare1 = 0x00; + directory.hashSize = algorithm.size_; + directory.hashType = algorithm.type_; + directory.platform = platform ? 0x01 : 0x00; directory.pageSize = PageShift_; directory.spare2 = Swap(uint32_t(0)); + directory.scatterOffset = Swap(uint32_t(0)); + directory.spare3 = Swap(uint32_t(0)); + directory.codeLimit64 = Swap(uint64_t(limit > UINT32_MAX ? limit : 0)); + directory.execSegBase = Swap(uint64_t(left)); + directory.execSegLimit = Swap(uint64_t(right - left)); + directory.execSegFlags = Swap(execs); + + uint32_t offset(sizeof(Blob) + sizeof(CodeDirectory)); + + directory.identOffset = Swap(uint32_t(offset)); + offset += identifier.size() + 1; + + if (team.empty()) + directory.teamIDOffset = Swap(uint32_t(0)); + else { + directory.teamIDOffset = Swap(uint32_t(offset)); + offset += team.size() + 1; + } + + offset += special * algorithm.size_; + directory.hashOffset = Swap(uint32_t(offset)); + offset += normal * algorithm.size_; + put(data, &directory, sizeof(directory)); put(data, identifier.c_str(), identifier.size() + 1); + if (!team.empty()) + put(data, team.c_str(), team.size() + 1); - uint8_t storage[special + normal][SHA_DIGEST_LENGTH]; - uint8_t (*hashes)[SHA_DIGEST_LENGTH] = storage + special; + std::vector storage((special + normal) * algorithm.size_); + auto *hashes(&storage[special * algorithm.size_]); - memset(storage, 0, sizeof(*storage) * special); + memset(storage.data(), 0, special * algorithm.size_); _foreach (blob, blobs) { auto local(reinterpret_cast(&blob.second[0])); - sha1((uint8_t *) (hashes - blob.first), local, Swap(local->length)); + algorithm(hashes - blob.first * algorithm.size_, local, Swap(local->length)); } - _foreach (slot, slots) { - _assert(sizeof(*hashes) == slot.second.size()); - memcpy(hashes - slot.first, slot.second.data(), slot.second.size()); - } + _foreach (slot, posts) + memcpy(hashes - slot.first * algorithm.size_, algorithm[slot.second], algorithm.size_); + progress(0); if (normal != 1) - for (size_t i = 0; i != normal - 1; ++i) - sha1(hashes[i], (PageSize_ * i < overlap.size() ? overlap.data() : top) + PageSize_ * i, PageSize_); + for (size_t i = 0; i != normal - 1; ++i) { + algorithm(hashes + i * algorithm.size_, (PageSize_ * i < overlap.size() ? overlap.data() : top) + PageSize_ * i, PageSize_); + progress(double(i) / normal); + } if (normal != 0) - sha1(hashes[normal - 1], top + PageSize_ * (normal - 1), ((limit - 1) % PageSize_) + 1); + algorithm(hashes + (normal - 1) * algorithm.size_, top + PageSize_ * (normal - 1), ((limit - 1) % PageSize_) + 1); + progress(1); + + put(data, storage.data(), storage.size()); - put(data, storage, sizeof(storage)); + const auto &save(insert(blobs, total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1, CSMAGIC_CODEDIRECTORY, data)); + algorithm(hash, save.data(), save.size()); - insert(blobs, CSSLOT_CODEDIRECTORY, CSMAGIC_CODEDIRECTORY, data); + ++total; } +#ifndef LDID_NOSMIME if (!key.empty()) { +#ifdef LDID_NOPLIST + auto plist(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + _scope({ CFRelease(plist); }); + + auto cdhashes(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + _scope({ CFRelease(cdhashes); }); + + CFDictionarySetValue(plist, CFSTR("cdhashes"), cdhashes); +#else + auto plist(plist_new_dict()); + _scope({ plist_free(plist); }); + + auto cdhashes(plist_new_array()); + plist_dict_set_item(plist, "cdhashes", cdhashes); +#endif + + unsigned total(0); + for (Algorithm *pointer : GetAlgorithms()) { + Algorithm &algorithm(*pointer); + (void) algorithm; + + const auto &blob(blobs[total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1]); + ++total; + + std::vector hash; + algorithm(hash, blob.data(), blob.size()); + hash.resize(20); + +#ifdef LDID_NOPLIST + auto value(CFDataCreate(kCFAllocatorDefault, reinterpret_cast(hash.data()), hash.size())); + _scope({ CFRelease(value); }); + CFArrayAppendValue(cdhashes, value); +#else + plist_array_append_item(cdhashes, plist_new_data(hash.data(), hash.size())); +#endif + } + +#ifdef LDID_NOPLIST + auto created(CFPropertyListCreateXMLData(kCFAllocatorDefault, plist)); + _scope({ CFRelease(created); }); + auto xml(reinterpret_cast(CFDataGetBytePtr(created))); + auto size(CFDataGetLength(created)); +#else + char *xml(NULL); + uint32_t size; + plist_to_xml(plist, &xml, &size); + _scope({ free(xml); }); +#endif + std::stringbuf data; const std::string &sign(blobs[CSSLOT_CODEDIRECTORY]); Stuff stuff(key); Buffer bio(sign); - Signature signature(stuff, sign); + Signature signature(stuff, sign, std::string(xml, size)); Buffer result(signature); std::string value(result); put(data, value.data(), value.size()); - insert(blobs, CSSLOT_SIGNATURESLOT, CSMAGIC_BLOBWRAPPER, data); + const auto &save(insert(blobs, CSSLOT_SIGNATURESLOT, CSMAGIC_BLOBWRAPPER, data)); + _assert(save.size() <= certificate); } +#endif return put(output, CSMAGIC_EMBEDDED_SIGNATURE, blobs); - })); + }), progress); + + return hash; } -static void Unsign(void *idata, size_t isize, std::streambuf &output) { - Allocate(idata, isize, output, fun([](size_t size) -> size_t { +#ifndef LDID_NOTOOLS +static void Unsign(void *idata, size_t isize, std::streambuf &output, const Progress &progress) { + Allocate(idata, isize, output, fun([](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t { return 0; - }), fun([](std::streambuf &output, size_t limit, const std::string &overlap, const char *top) -> size_t { + }), fun([](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, size_t left, size_t right, const std::string &overlap, const char *top, const Progress &progress) -> size_t { return 0; - })); + }), progress); } -std::string DiskFolder::Path(const std::string &path) { - return path_ + "/" + path; +std::string DiskFolder::Path(const std::string &path) const { + return path_ + path; } DiskFolder::DiskFolder(const std::string &path) : path_(path) { + _assert_(path_.size() != 0 && path_[path_.size() - 1] == '/', "missing / on %s", path_.c_str()); } DiskFolder::~DiskFolder() { @@ -1564,7 +2457,23 @@ DiskFolder::~DiskFolder() { Commit(commit.first, commit.second); } -void DiskFolder::Find(const std::string &root, const std::string &base, const Functor &)> &)>&code) { +#ifndef __WIN32__ +std::string readlink(const std::string &path) { + for (size_t size(1024); ; size *= 2) { + std::string data; + data.resize(size); + + int writ(_syscall(::readlink(path.c_str(), &data[0], data.size()))); + if (size_t(writ) >= size) + continue; + + data.resize(writ); + return data; + } +} +#endif + +void DiskFolder::Find(const std::string &root, const std::string &base, const Functor &code, const Functor &)> &link) const { std::string path(Path(root) + base); DIR *dir(opendir(path.c_str())); @@ -1578,102 +2487,211 @@ void DiskFolder::Find(const std::string &root, const std::string &base, const Fu if (Starts(name, ".ldid.")) continue; + bool directory; + +#ifdef __WIN32__ + struct stat info; + _syscall(stat((path + name).c_str(), &info)); + if (false); + else if (S_ISDIR(info.st_mode)) + directory = true; + else if (S_ISREG(info.st_mode)) + directory = false; + else + _assert_(false, "st_mode=%x", info.st_mode); +#else switch (child->d_type) { case DT_DIR: - Find(root, base + name + "/", code); - break; - + directory = true; + break; case DT_REG: - code(base + name, fun([&](const Functor &code) { - std::string access(root + base + name); - _assert_(Open(access, fun([&](std::streambuf &data) { - NullBuffer save; - code(data, save); - })), "open(): %s", access.c_str()); - })); - break; - + directory = false; + break; + case DT_LNK: + link(base + name, fun([&]() { return readlink(path + name); })); + continue; default: _assert_(false, "d_type=%u", child->d_type); - break; } +#endif + + if (directory) + Find(root, base + name + "/", code, link); + else + code(base + name); } } -void DiskFolder::Save(const std::string &path, const Functor &code) { - std::filebuf save; - auto from(Path(path)); - commit_[from] = Temporary(save, from); - code(save); +void DiskFolder::Save(const std::string &path, bool edit, const void *flag, const Functor &code) { + if (!edit) { + NullBuffer save; + code(save); + } else { + std::filebuf save; + auto from(Path(path)); + commit_[from] = Temporary(save, from); + code(save); + } +} + +bool DiskFolder::Look(const std::string &path) const { + return _syscall(access(Path(path).c_str(), R_OK), ENOENT) == 0; } -bool DiskFolder::Open(const std::string &path, const Functor &code) { +void DiskFolder::Open(const std::string &path, const Functor &code) const { std::filebuf data; auto result(data.open(Path(path).c_str(), std::ios::binary | std::ios::in)); - if (result == NULL) - return false; - _assert(result == &data); - code(data); - return true; + _assert_(result == &data, "DiskFolder::Open(%s)", Path(path).c_str()); + + auto length(data.pubseekoff(0, std::ios::end, std::ios::in)); + data.pubseekpos(0, std::ios::in); + code(data, length, NULL); } -void DiskFolder::Find(const std::string &path, const Functor &)> &)>&code) { - Find(path, "", code); +void DiskFolder::Find(const std::string &path, const Functor &code, const Functor &)> &link) const { + Find(path, "", code, link); } +#endif -SubFolder::SubFolder(Folder *parent, const std::string &path) : +SubFolder::SubFolder(Folder &parent, const std::string &path) : parent_(parent), path_(path) { + _assert_(path_.size() == 0 || path_[path_.size() - 1] == '/', "missing / on %s", path_.c_str()); +} + +std::string SubFolder::Path(const std::string &path) const { + return path_ + path; +} + +void SubFolder::Save(const std::string &path, bool edit, const void *flag, const Functor &code) { + return parent_.Save(Path(path), edit, flag, code); +} + +bool SubFolder::Look(const std::string &path) const { + return parent_.Look(Path(path)); +} + +void SubFolder::Open(const std::string &path, const Functor &code) const { + return parent_.Open(Path(path), code); +} + +void SubFolder::Find(const std::string &path, const Functor &code, const Functor &)> &link) const { + return parent_.Find(Path(path), code, link); +} + +std::string UnionFolder::Map(const std::string &path) const { + auto remap(remaps_.find(path)); + if (remap == remaps_.end()) + return path; + return remap->second; +} + +void UnionFolder::Map(const std::string &path, const Functor &code, const std::string &file, const Functor &)> &save) const { + if (file.size() >= path.size() && file.substr(0, path.size()) == path) + code(file.substr(path.size())); +} + +UnionFolder::UnionFolder(Folder &parent) : + parent_(parent) +{ +} + +void UnionFolder::Save(const std::string &path, bool edit, const void *flag, const Functor &code) { + return parent_.Save(Map(path), edit, flag, code); } -void SubFolder::Save(const std::string &path, const Functor &code) { - return parent_->Save(path_ + path, code); +bool UnionFolder::Look(const std::string &path) const { + auto file(resets_.find(path)); + if (file != resets_.end()) + return true; + return parent_.Look(Map(path)); } -bool SubFolder::Open(const std::string &path, const Functor &code) { - return parent_->Open(path_ + path, code); +void UnionFolder::Open(const std::string &path, const Functor &code) const { + auto file(resets_.find(path)); + if (file == resets_.end()) + return parent_.Open(Map(path), code); + auto &entry(file->second); + + auto &data(*entry.data_); + auto length(data.pubseekoff(0, std::ios::end, std::ios::in)); + data.pubseekpos(0, std::ios::in); + code(data, length, entry.flag_); } -void SubFolder::Find(const std::string &path, const Functor &)> &)> &code) { - return parent_->Find(path_ + path, code); +void UnionFolder::Find(const std::string &path, const Functor &code, const Functor &)> &link) const { + for (auto &reset : resets_) + Map(path, code, reset.first, fun([&](const Functor &code) { + auto &entry(reset.second); + auto &data(*entry.data_); + auto length(data.pubseekoff(0, std::ios::end, std::ios::in)); + data.pubseekpos(0, std::ios::in); + code(data, length, entry.flag_); + })); + + for (auto &remap : remaps_) + Map(path, code, remap.first, fun([&](const Functor &code) { + parent_.Open(remap.second, fun([&](std::streambuf &data, size_t length, const void *flag) { + code(data, length, flag); + })); + })); + + parent_.Find(path, fun([&](const std::string &name) { + if (deletes_.find(path + name) == deletes_.end()) + code(name); + }), fun([&](const std::string &name, const Functor &read) { + if (deletes_.find(path + name) == deletes_.end()) + link(name, read); + })); } -static size_t copy(std::streambuf &source, std::streambuf &target) { +#ifndef LDID_NOTOOLS +static void copy(std::streambuf &source, std::streambuf &target, size_t length, const Progress &progress) { + progress(0); size_t total(0); for (;;) { - char data[4096]; + char data[4096 * 4]; size_t writ(source.sgetn(data, sizeof(data))); if (writ == 0) break; _assert(target.sputn(data, writ) == writ); total += writ; + progress(double(total) / length); } - return total; } -static PList::Structure *plist(const std::string &data) { - if (!Starts(data, "bplist00")) - return PList::Structure::FromXml(data); - std::vector bytes(data.data(), data.data() + data.size()); - return PList::Structure::FromBin(bytes); +#ifndef LDID_NOPLIST +static plist_t plist(const std::string &data) { + if (data.empty()) + return plist_new_dict(); + plist_t plist(NULL); + if (Starts(data, "bplist00")) + plist_from_bin(data.data(), data.size(), &plist); + else + plist_from_xml(data.data(), data.size(), &plist); + _assert(plist != NULL); + return plist; } -static void plist_d(std::streambuf &buffer, const Functor &code) { +static void plist_d(std::streambuf &buffer, size_t length, const Functor &code) { std::stringbuf data; - copy(buffer, data); - PList::Structure *structure(plist(data.str())); - _scope({ delete structure; }); - auto dictionary(dynamic_cast(structure)); - _assert(dictionary != NULL); - code(dictionary); + copy(buffer, data, length, dummy_); + auto node(plist(data.str())); + _scope({ plist_free(node); }); + _assert(plist_get_node_type(node) == PLIST_DICT); + code(node); } -static std::string plist_s(PList::Node *node) { - auto value(dynamic_cast(node)); - _assert(value != NULL); - return value->GetValue(); +static std::string plist_s(plist_t node) { + _assert(node != NULL); + _assert(plist_get_node_type(node) == PLIST_STRING); + char *data; + plist_get_string_val(node, &data); + _scope({ free(data); }); + return data; } +#endif enum Mode { NoMode, @@ -1686,23 +2704,32 @@ enum Mode { class Expression { private: regex_t regex_; + std::vector matches_; public: Expression(const std::string &code) { - _assert_(regcomp(®ex_, code.c_str(), REG_EXTENDED | REG_NOSUB) == 0, "regcomp()"); + _assert_(regcomp(®ex_, code.c_str(), REG_EXTENDED) == 0, "regcomp()"); + matches_.resize(regex_.re_nsub + 1); } ~Expression() { regfree(®ex_); } - bool operator ()(const std::string &data) const { - auto value(regexec(®ex_, data.c_str(), 0, NULL, 0)); + bool operator ()(const std::string &data) { + regmatch_t matches[matches_.size()]; + auto value(regexec(®ex_, data.c_str(), matches_.size(), matches, 0)); if (value == REG_NOMATCH) return false; _assert_(value == 0, "regexec()"); + for (size_t i(0); i != matches_.size(); ++i) + matches_[i].assign(data.data() + matches[i].rm_so, matches[i].rm_eo - matches[i].rm_so); return true; } + + const std::string &operator [](size_t index) const { + return matches_[index]; + } }; struct Rule { @@ -1710,7 +2737,7 @@ struct Rule { Mode mode_; std::string code_; - mutable std::auto_ptr regex_; + mutable std::unique_ptr regex_; Rule(unsigned weight, Mode mode, const std::string &code) : weight_(weight), @@ -1750,114 +2777,258 @@ struct RuleCode { } }; -std::string Bundle(const std::string &root, Folder &folder, const std::string &key, std::map> &remote) { +#ifndef LDID_NOPLIST +static Hash Sign(const uint8_t *prefix, size_t size, std::streambuf &buffer, Hash &hash, std::streambuf &save, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, size_t length, uint32_t flags, bool platform, const Progress &progress) { + // XXX: this is a miserable fail + std::stringbuf temp; + put(temp, prefix, size); + copy(buffer, temp, length - size, progress); + // XXX: this is a stupid hack + pad(temp, 0x10 - (length & 0xf)); + auto data(temp.str()); + + HashProxy proxy(hash, save); + return Sign(data.data(), data.size(), proxy, identifier, entitlements, merge, requirements, key, slots, flags, platform, progress); +} + +struct State { + std::map files; + std::map links; + + void Merge(const std::string &root, const State &state) { + for (const auto &entry : state.files) + files[root + entry.first] = entry.second; + for (const auto &entry : state.links) + links[root + entry.first] = entry.second; + } +}; + +Bundle Sign(const std::string &root, Folder &parent, const std::string &key, State &local, const std::string &requirements, const Functor &alter, const Progress &progress) { std::string executable; std::string identifier; - static const std::string info("Info.plist"); - - _assert_(folder.Open(info, fun([&](std::streambuf &buffer) { - plist_d(buffer, fun([&](PList::Dictionary *dictionary) { - executable = plist_s(((*dictionary)["CFBundleExecutable"])); - identifier = plist_s(((*dictionary)["CFBundleIdentifier"])); + bool mac(false); + + std::string info("Info.plist"); + + SubFolder folder(parent, [&]() { + if (parent.Look(info)) + return ""; + mac = true; + if (false); + else if (parent.Look("Contents/" + info)) + return "Contents/"; + else if (parent.Look("Resources/" + info)) { + info = "Resources/" + info; + return ""; + } else _assert_(false, "cannot find Info.plist"); + }()); + + folder.Open(info, fun([&](std::streambuf &buffer, size_t length, const void *flag) { + plist_d(buffer, length, fun([&](plist_t node) { + executable = plist_s(plist_dict_get_item(node, "CFBundleExecutable")); + identifier = plist_s(plist_dict_get_item(node, "CFBundleIdentifier")); })); - })), "open(): Info.plist"); + })); + + if (mac && info == "Info.plist") + executable = "MacOS/" + executable; + + progress(root + "*"); + + std::string entitlements; + folder.Open(executable, fun([&](std::streambuf &buffer, size_t length, const void *flag) { + // XXX: this is a miserable fail + std::stringbuf temp; + copy(buffer, temp, length, progress); + // XXX: this is a stupid hack + pad(temp, 0x10 - (length & 0xf)); + auto data(temp.str()); + entitlements = alter(root, Analyze(data.data(), data.size())); + })); + + static const std::string directory("_CodeSignature/"); + static const std::string signature(directory + "CodeResources"); std::map> versions; auto &rules1(versions[""]); auto &rules2(versions["2"]); - static const std::string signature("_CodeSignature/CodeResources"); - - folder.Open(signature, fun([&](std::streambuf &buffer) { - plist_d(buffer, fun([&](PList::Dictionary *dictionary) { - // XXX: maybe attempt to preserve existing rules - })); - })); + const std::string resources(mac ? "Resources/" : ""); if (true) { - rules1.insert(Rule{1, NoMode, "^"}); - rules1.insert(Rule{10000, OmitMode, "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/|())SC_Info/[^/]+\\.(sinf|supf|supp)$"}); - rules1.insert(Rule{1000, OptionalMode, "^.*\\.lproj/"}); - rules1.insert(Rule{1100, OmitMode, "^.*\\.lproj/locversion.plist$"}); - rules1.insert(Rule{10000, OmitMode, "^Watch/[^/]+\\.app/(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/)SC_Info/[^/]+\\.(sinf|supf|supp)$"}); + rules1.insert(Rule{1, NoMode, "^" + (resources == "" ? ".*" : resources)}); + rules1.insert(Rule{1000, OptionalMode, "^" + resources + ".*\\.lproj/"}); + rules1.insert(Rule{1100, OmitMode, "^" + resources + ".*\\.lproj/locversion.plist$"}); + rules1.insert(Rule{1010, NoMode, "^" + resources + "Base\\.lproj/"}); rules1.insert(Rule{1, NoMode, "^version.plist$"}); } if (true) { rules2.insert(Rule{11, NoMode, ".*\\.dSYM($|/)"}); - rules2.insert(Rule{20, NoMode, "^"}); + if (mac) rules2.insert(Rule{20, NoMode, "^" + resources}); rules2.insert(Rule{2000, OmitMode, "^(.*/)?\\.DS_Store$"}); - rules2.insert(Rule{10000, OmitMode, "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/|())SC_Info/[^/]+\\.(sinf|supf|supp)$"}); - rules2.insert(Rule{10, NestedMode, "^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/"}); + if (mac) rules2.insert(Rule{10, NestedMode, "^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/"}); rules2.insert(Rule{1, NoMode, "^.*"}); - rules2.insert(Rule{1000, OptionalMode, "^.*\\.lproj/"}); - rules2.insert(Rule{1100, OmitMode, "^.*\\.lproj/locversion.plist$"}); + rules2.insert(Rule{1000, OptionalMode, "^" + resources + ".*\\.lproj/"}); + rules2.insert(Rule{1100, OmitMode, "^" + resources + ".*\\.lproj/locversion.plist$"}); + if (!mac) rules2.insert(Rule{1010, NoMode, "^Base\\.lproj/"}); rules2.insert(Rule{20, OmitMode, "^Info\\.plist$"}); rules2.insert(Rule{20, OmitMode, "^PkgInfo$"}); - rules2.insert(Rule{10000, OmitMode, "^Watch/[^/]+\\.app/(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/)SC_Info/[^/]+\\.(sinf|supf|supp)$"}); - rules2.insert(Rule{10, NestedMode, "^[^/]+$"}); + if (mac) rules2.insert(Rule{10, NestedMode, "^[^/]+$"}); rules2.insert(Rule{20, NoMode, "^embedded\\.provisionprofile$"}); + if (mac) rules2.insert(Rule{1010, NoMode, "^" + resources + "Base\\.lproj/"}); rules2.insert(Rule{20, NoMode, "^version\\.plist$"}); } - std::map> local; - - static Expression nested("^PlugIns/[^/]*\\.appex/Info\\.plist$"); + std::string failure(mac ? "Contents/|Versions/[^/]*/Resources/" : ""); + Expression nested("^(Frameworks/[^/]*\\.framework|PlugIns/[^/]*\\.appex(()|/[^/]*.app))/(" + failure + ")Info\\.plist$"); + std::map bundles; - folder.Find("", fun([&](const std::string &name, const Functor &)> &code) { + folder.Find("", fun([&](const std::string &name) { if (!nested(name)) return; - auto bundle(root + Split(name).dir); - SubFolder subfolder(&folder, bundle); - Bundle(bundle, subfolder, key, local); + auto bundle(Split(name).dir); + if (mac) { + _assert(!bundle.empty()); + bundle = Split(bundle.substr(0, bundle.size() - 1)).dir; + } + SubFolder subfolder(folder, bundle); + + State remote; + bundles[nested[1]] = Sign(root + bundle, subfolder, key, remote, "", Starts(name, "PlugIns/") ? alter : + static_cast &>(fun([&](const std::string &, const std::string &) -> std::string { return entitlements; })) + , progress); + local.Merge(bundle, remote); + }), fun([&](const std::string &name, const Functor &read) { })); - folder.Find("", fun([&](const std::string &name, const Functor &)> &code) { - if (name == executable || name == signature) + std::set excludes; + + auto exclude([&](const std::string &name) { + // BundleDiskRep::adjustResources -> builder.addExclusion + if (name == executable || Starts(name, directory) || Starts(name, "_MASReceipt/") || name == "CodeResources") + return true; + + for (const auto &bundle : bundles) + if (Starts(name, bundle.first + "/")) { + excludes.insert(name); + return true; + } + + return false; + }); + + folder.Find("", fun([&](const std::string &name) { + if (exclude(name)) return; - auto &hash(local[name]); - if (!hash.empty()) + if (local.files.find(name) != local.files.end()) return; + auto &hash(local.files[name]); + + folder.Open(name, fun([&](std::streambuf &data, size_t length, const void *flag) { + progress(root + name); + + union { + struct { + uint32_t magic; + uint32_t count; + }; + + uint8_t bytes[8]; + } header; + + auto size(most(data, &header.bytes, sizeof(header.bytes))); + + if (name != "_WatchKitStub/WK" && size == sizeof(header.bytes)) + switch (Swap(header.magic)) { + case FAT_MAGIC: + // Java class file format + if (Swap(header.count) >= 40) + break; + case FAT_CIGAM: + case MH_MAGIC: case MH_MAGIC_64: + case MH_CIGAM: case MH_CIGAM_64: + folder.Save(name, true, flag, fun([&](std::streambuf &save) { + Slots slots; + Sign(header.bytes, size, data, hash, save, identifier, "", false, "", key, slots, length, 0, false, Progression(progress, root + name)); + })); + return; + } - code(fun([&](std::streambuf &data, std::streambuf &save) { - HashProxy proxy(hash, save); - copy(data, proxy); + folder.Save(name, false, flag, fun([&](std::streambuf &save) { + HashProxy proxy(hash, save); + put(proxy, header.bytes, size); + copy(data, proxy, length - size, progress); + })); })); + }), fun([&](const std::string &name, const Functor &read) { + if (exclude(name)) + return; - _assert(hash.size() == SHA_DIGEST_LENGTH); + local.links[name] = read(); })); - PList::Dictionary plist; + auto plist(plist_new_dict()); + _scope({ plist_free(plist); }); for (const auto &version : versions) { - PList::Dictionary files; + auto files(plist_new_dict()); + plist_dict_set_item(plist, ("files" + version.first).c_str(), files); for (const auto &rule : version.second) rule.Compile(); - for (const auto &hash : local) + bool old(&version.second == &rules1); + + for (const auto &hash : local.files) for (const auto &rule : version.second) if (rule(hash.first)) { - if (rule.mode_ == NoMode) - files.Set(hash.first, PList::Data(hash.second)); - else if (rule.mode_ == OptionalMode) { - PList::Dictionary entry; - entry.Set("hash", PList::Data(hash.second)); - entry.Set("optional", PList::Boolean(true)); - files.Set(hash.first, entry); + if (!old && mac && excludes.find(hash.first) != excludes.end()); + else if (old && rule.mode_ == NoMode) + plist_dict_set_item(files, hash.first.c_str(), plist_new_data(reinterpret_cast(hash.second.sha1_), sizeof(hash.second.sha1_))); + else if (rule.mode_ != OmitMode) { + auto entry(plist_new_dict()); + plist_dict_set_item(entry, "hash", plist_new_data(reinterpret_cast(hash.second.sha1_), sizeof(hash.second.sha1_))); + if (!old) + plist_dict_set_item(entry, "hash2", plist_new_data(reinterpret_cast(hash.second.sha256_), sizeof(hash.second.sha256_))); + if (rule.mode_ == OptionalMode) + plist_dict_set_item(entry, "optional", plist_new_bool(true)); + plist_dict_set_item(files, hash.first.c_str(), entry); } break; } - plist.Set("files" + version.first, files); + if (!old) + for (const auto &link : local.links) + for (const auto &rule : version.second) + if (rule(link.first)) { + if (rule.mode_ != OmitMode) { + auto entry(plist_new_dict()); + plist_dict_set_item(entry, "symlink", plist_new_string(link.second.c_str())); + if (rule.mode_ == OptionalMode) + plist_dict_set_item(entry, "optional", plist_new_bool(true)); + plist_dict_set_item(files, link.first.c_str(), entry); + } + + break; + } + + if (!old && mac) + for (const auto &bundle : bundles) { + auto entry(plist_new_dict()); + plist_dict_set_item(entry, "cdhash", plist_new_data(reinterpret_cast(bundle.second.hash.sha256_), sizeof(bundle.second.hash.sha256_))); + plist_dict_set_item(entry, "requirement", plist_new_string("anchor apple generic")); + plist_dict_set_item(files, bundle.first.c_str(), entry); + } } for (const auto &version : versions) { - PList::Dictionary rules; + auto rules(plist_new_dict()); + plist_dict_set_item(plist, ("rules" + version.first).c_str(), rules); std::multiset ordered; for (const auto &rule : version.second) @@ -1865,70 +3036,103 @@ std::string Bundle(const std::string &root, Folder &folder, const std::string &k for (const auto &rule : ordered) if (rule->weight_ == 1 && rule->mode_ == NoMode) - rules.Set(rule->code_, PList::Boolean(true)); + plist_dict_set_item(rules, rule->code_.c_str(), plist_new_bool(true)); else { - PList::Dictionary entry; + auto entry(plist_new_dict()); + plist_dict_set_item(rules, rule->code_.c_str(), entry); switch (rule->mode_) { case NoMode: break; case OmitMode: - entry.Set("omit", PList::Boolean(true)); + plist_dict_set_item(entry, "omit", plist_new_bool(true)); break; case OptionalMode: - entry.Set("optional", PList::Boolean(true)); + plist_dict_set_item(entry, "optional", plist_new_bool(true)); break; case NestedMode: - entry.Set("nested", PList::Boolean(true)); + plist_dict_set_item(entry, "nested", plist_new_bool(true)); break; case TopMode: - entry.Set("top", PList::Boolean(true)); + plist_dict_set_item(entry, "top", plist_new_bool(true)); break; } if (rule->weight_ >= 10000) - entry.Set("weight", PList::Integer(rule->weight_)); + plist_dict_set_item(entry, "weight", plist_new_uint(rule->weight_)); else if (rule->weight_ != 1) - entry.Set("weight", PList::Real(rule->weight_)); - - rules.Set(rule->code_, entry); + plist_dict_set_item(entry, "weight", plist_new_real(rule->weight_)); } - - plist.Set("rules" + version.first, rules); } - folder.Save(signature, fun([&](std::streambuf &save) { - HashProxy proxy(local[signature], save); - auto xml(plist.ToXml()); - put(proxy, xml.data(), xml.size()); + folder.Save(signature, true, NULL, fun([&](std::streambuf &save) { + HashProxy proxy(local.files[signature], save); + char *xml(NULL); + uint32_t size; + plist_to_xml(plist, &xml, &size); + _scope({ free(xml); }); + put(proxy, xml, size); })); - folder.Open(executable, fun([&](std::streambuf &buffer) { - // XXX: this is a miserable fail - std::stringbuf temp; - copy(buffer, temp); - auto data(temp.str()); + Bundle bundle; + bundle.path = folder.Path(executable); - folder.Save(executable, fun([&](std::streambuf &save) { + folder.Open(executable, fun([&](std::streambuf &buffer, size_t length, const void *flag) { + progress(root + executable); + folder.Save(executable, true, flag, fun([&](std::streambuf &save) { Slots slots; - slots[1] = local.at(info); - slots[3] = local.at(signature); - - HashProxy proxy(local[executable], save); - Sign(data.data(), data.size(), proxy, identifier, "", key, slots); + slots[1] = local.files.at(info); + slots[3] = local.files.at(signature); + bundle.hash = Sign(NULL, 0, buffer, local.files[executable], save, identifier, entitlements, false, requirements, key, slots, length, 0, false, Progression(progress, root + executable)); })); })); - for (const auto &hash : local) - remote[root + hash.first] = hash.second; + return bundle; +} + +Bundle Sign(const std::string &root, Folder &folder, const std::string &key, const std::string &requirements, const Functor &alter, const Progress &progress) { + State local; + return Sign(root, folder, key, local, requirements, alter, progress); +} +#endif - return executable; +#endif } +std::string Hex(const uint8_t *data, size_t size) { + std::string hex; + hex.reserve(size * 2); + for (size_t i(0); i != size; ++i) { + hex += "0123456789abcdef"[data[i] >> 4]; + hex += "0123456789abcdef"[data[i] & 0xf]; + } + return hex; } +static void usage(const char *argv0) { + fprintf(stderr, "Link Identity Editor %s\n\n", LDID_VERSION); + fprintf(stderr, "usage: %s [-Acputype:subtype] [-a] [-C[adhoc | enforcement | expires | hard |\n", argv0); + fprintf(stderr, " host | kill | library-validation | restrict | runtime]] [-D] [-d]\n"); + fprintf(stderr, " [-Enum:file] [-e] [-h] [-Kkey.p12 [-Upassword]] [-M] [-P] [-q]\n"); + fprintf(stderr, " [-r | -Sfile | -s] [-Ttimestamp] [-u] file ...\n\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -S[file.xml] Pseudo-sign using the entitlements in file.xml\n"); + fprintf(stderr, " -Kkey.p12 Sign using private key in key.p12\n"); + fprintf(stderr, " -Upassword Use password to unlock key.p12\n"); + fprintf(stderr, " -M Merge entitlements with any existing\n"); + fprintf(stderr, " -h Print CDHash of file\n\n"); + fprintf(stderr, "More information: 'man ldid'\n"); +} + +#ifndef LDID_NOTOOLS int main(int argc, char *argv[]) { +#ifndef LDID_NOSMIME OpenSSL_add_all_algorithms(); +# if OPENSSL_VERSION_MAJOR >= 3 + OSSL_PROVIDER *legacy = OSSL_PROVIDER_load(NULL, "legacy"); + OSSL_PROVIDER *deflt = OSSL_PROVIDER_load(NULL, "default"); +# endif +#endif union { uint16_t word; @@ -1939,37 +3143,51 @@ int main(int argc, char *argv[]) { bool flag_r(false); bool flag_e(false); + bool flag_q(false); + bool flag_H(false); + bool flag_h(false); + +#ifndef LDID_NOFLAGT bool flag_T(false); +#endif bool flag_S(false); bool flag_s(false); bool flag_D(false); + bool flag_d(false); bool flag_A(false); bool flag_a(false); + bool flag_u(false); + + bool flag_M(false); + + uint32_t flags(0); + bool platform(false); + uint32_t flag_CPUType(_not(uint32_t)); uint32_t flag_CPUSubtype(_not(uint32_t)); const char *flag_I(NULL); +#ifndef LDID_NOFLAGT bool timeh(false); uint32_t timev(0); +#endif Map entitlements; + Map requirements; Map key; ldid::Slots slots; std::vector files; if (argc == 1) { - fprintf(stderr, "usage: %s -S[entitlements.xml] \n", argv[0]); - fprintf(stderr, " %s -e MobileSafari\n", argv[0]); - fprintf(stderr, " %s -S cat\n", argv[0]); - fprintf(stderr, " %s -Stfp.xml gdb\n", argv[0]); - exit(0); + usage(argv[0]); + return 0; } for (int argi(1); argi != argc; ++argi) @@ -1985,19 +3203,50 @@ int main(int argc, char *argv[]) { case 'e': flag_e = true; break; case 'E': { - const char *slot = argv[argi] + 2; - const char *colon = strchr(slot, ':'); + const char *string = argv[argi] + 2; + const char *colon = strchr(string, ':'); _assert(colon != NULL); Map file(colon + 1, O_RDONLY, PROT_READ, MAP_PRIVATE); char *arge; - unsigned number(strtoul(slot, &arge, 0)); + unsigned number(strtoul(string, &arge, 0)); _assert(arge == colon); - std::vector &hash(slots[number]); - hash.resize(SHA_DIGEST_LENGTH); - sha1(reinterpret_cast(hash.data()), file.data(), file.size()); + auto &slot(slots[number]); + for (Algorithm *algorithm : GetAlgorithms()) + (*algorithm)(slot, file.data(), file.size()); + } break; + + case 'q': flag_q = true; break; + + case 'H': { + const char *hash = argv[argi] + 2; + + if (!flag_H) { + flag_H = true; + + do_sha1 = false; + do_sha256 = false; + + fprintf(stderr, "WARNING: -H is only present for compatibility with a fork of ldid\n"); + fprintf(stderr, " you should NOT be manually specifying the hash algorithm\n"); + } + + if (false); + else if (strcmp(hash, "sha1") == 0) + do_sha1 = true; + else if (strcmp(hash, "sha256") == 0) + do_sha256 = true; + else _assert(false); + } break; + + case 'h': flag_h = true; break; + + case 'Q': { + const char *xml = argv[argi] + 2; + requirements.open(xml, O_RDONLY, PROT_READ, MAP_PRIVATE); } break; case 'D': flag_D = true; break; + case 'd': flag_d = true; break; case 'a': flag_a = true; break; @@ -2016,6 +3265,34 @@ int main(int argc, char *argv[]) { } break; + case 'C': { + const char *name = argv[argi] + 2; + if (false); + else if (strcmp(name, "host") == 0) + flags |= kSecCodeSignatureHost; + else if (strcmp(name, "adhoc") == 0) + flags |= kSecCodeSignatureAdhoc; + else if (strcmp(name, "hard") == 0) + flags |= kSecCodeSignatureForceHard; + else if (strcmp(name, "kill") == 0) + flags |= kSecCodeSignatureForceKill; + else if (strcmp(name, "expires") == 0) + flags |= kSecCodeSignatureForceExpiration; + else if (strcmp(name, "restrict") == 0) + flags |= kSecCodeSignatureRestrict; + else if (strcmp(name, "enforcement") == 0) + flags |= kSecCodeSignatureEnforcement; + else if (strcmp(name, "library-validation") == 0) + flags |= kSecCodeSignatureLibraryValidation; + else if (strcmp(name, "runtime") == 0) + flags |= kSecCodeSignatureRuntime; + else _assert(false); + } break; + + case 'P': + platform = true; + break; + case 's': _assert(!flag_r); _assert(!flag_S); @@ -2032,10 +3309,20 @@ int main(int argc, char *argv[]) { } break; + case 'M': + flag_M = true; + break; + + case 'U': + password = argv[argi] + 2; + break; + case 'K': - key.open(argv[argi] + 2, O_RDONLY, PROT_READ, MAP_PRIVATE); + if (argv[argi][2] != '\0') + key.open(argv[argi] + 2, O_RDONLY, PROT_READ, MAP_PRIVATE); break; +#ifndef LDID_NOFLAGT case 'T': { flag_T = true; if (argv[argi][2] == '-') @@ -2046,23 +3333,33 @@ int main(int argc, char *argv[]) { _assert(arge == argv[argi] + strlen(argv[argi])); } } break; +#endif + + case 'u': { + flag_u = true; + } break; case 'I': { flag_I = argv[argi] + 2; } break; default: - goto usage; + usage(argv[0]); + return 1; break; } _assert(flag_S || key.empty()); _assert(flag_S || flag_I == NULL); - if (files.empty()) usage: { - exit(0); + if (flag_d && !flag_h) { + flag_h = true; + fprintf(stderr, "WARNING: -d also (temporarily) does the behavior of -h for compatibility with a fork of ldid\n"); } + if (files.empty()) + return 0; + size_t filei(0), filee(0); _foreach (file, files) try { std::string path(file); @@ -2071,10 +3368,13 @@ int main(int argc, char *argv[]) { _syscall(stat(path.c_str(), &info)); if (S_ISDIR(info.st_mode)) { - _assert(!flag_r); - ldid::DiskFolder folder(path); - std::map> hashes; - path += "/" + Bundle("", folder, key, hashes); + _assert(flag_S); +#ifndef LDID_NOPLIST + ldid::DiskFolder folder(path + "/"); + path += "/" + Sign("", folder, key, requirements, ldid::fun([&](const std::string &, const std::string &) -> std::string { return entitlements; }), dummy_).path; +#else + _assert(false); +#endif } else if (flag_S || flag_r) { Map input(path, O_RDONLY, PROT_READ, MAP_PRIVATE); @@ -2083,16 +3383,24 @@ int main(int argc, char *argv[]) { auto temp(Temporary(output, split)); if (flag_r) - ldid::Unsign(input.data(), input.size(), output); + ldid::Unsign(input.data(), input.size(), output, dummy_); else { std::string identifier(flag_I ?: split.base.c_str()); - ldid::Sign(input.data(), input.size(), output, identifier, entitlements, key, slots); + ldid::Sign(input.data(), input.size(), output, identifier, entitlements, flag_M, requirements, key, slots, flags, platform, dummy_); } Commit(path, temp); } - Map mapping(path, flag_T || flag_s); + bool modify(false); +#ifndef LDID_NOFLAGT + if (flag_T) + modify = true; +#endif + if (flag_s) + modify = true; + + Map mapping(path, modify); FatHeader fat_header(mapping.data(), mapping.size()); _foreach (mach_header, fat_header.GetMachHeaders()) { @@ -2117,6 +3425,19 @@ int main(int argc, char *argv[]) { signature = reinterpret_cast(load_command); else if (cmd == LC_ENCRYPTION_INFO || cmd == LC_ENCRYPTION_INFO_64) encryption = reinterpret_cast(load_command); + else if (cmd == LC_LOAD_DYLIB) { + volatile struct dylib_command *dylib_command(reinterpret_cast(load_command)); + const char *name(reinterpret_cast(load_command) + mach_header.Swap(dylib_command->dylib.name)); + + if (strcmp(name, "/System/Library/Frameworks/UIKit.framework/UIKit") == 0) { + if (flag_u) { + Version version; + version.value = mach_header.Swap(dylib_command->dylib.current_version); + printf("uikit=%u.%u.%u\n", version.major, version.minor, version.patch); + } + } + } +#ifndef LDID_NOFLAGT else if (cmd == LC_ID_DYLIB) { volatile struct dylib_command *dylib_command(reinterpret_cast(load_command)); @@ -2133,6 +3454,11 @@ int main(int argc, char *argv[]) { dylib_command->dylib.timestamp = mach_header.Swap(timed); } } +#endif + } + + if (flag_d && encryption != NULL) { + printf("cryptid=%d\n", mach_header.Swap(encryption->cryptid)); } if (flag_D) { @@ -2157,6 +3483,23 @@ int main(int argc, char *argv[]) { } } + if (flag_q) { + _assert(signature != NULL); + + uint32_t data = mach_header.Swap(signature->dataoff); + + uint8_t *top = reinterpret_cast(mach_header.GetBase()); + uint8_t *blob = top + data; + struct SuperBlob *super = reinterpret_cast(blob); + + for (size_t index(0); index != Swap(super->count); ++index) + if (Swap(super->index[index].type) == CSSLOT_REQUIREMENTS) { + uint32_t begin = Swap(super->index[index].offset); + struct Blob *requirement = reinterpret_cast(blob + begin); + fwrite(requirement, 1, Swap(requirement->length), stdout); + } + } + if (flag_s) { _assert(signature != NULL); @@ -2169,17 +3512,95 @@ int main(int argc, char *argv[]) { for (size_t index(0); index != Swap(super->count); ++index) if (Swap(super->index[index].type) == CSSLOT_CODEDIRECTORY) { uint32_t begin = Swap(super->index[index].offset); - struct CodeDirectory *directory = reinterpret_cast(blob + begin); + struct CodeDirectory *directory = reinterpret_cast(blob + begin + sizeof(Blob)); - uint8_t (*hashes)[SHA_DIGEST_LENGTH] = reinterpret_cast(blob + begin + Swap(directory->hashOffset)); + uint8_t (*hashes)[LDID_SHA1_DIGEST_LENGTH] = reinterpret_cast(blob + begin + Swap(directory->hashOffset)); uint32_t pages = Swap(directory->nCodeSlots); if (pages != 1) for (size_t i = 0; i != pages - 1; ++i) - sha1(hashes[i], top + PageSize_ * i, PageSize_); + LDID_SHA1(top + PageSize_ * i, PageSize_, hashes[i]); if (pages != 0) - sha1(hashes[pages - 1], top + PageSize_ * (pages - 1), ((data - 1) % PageSize_) + 1); + LDID_SHA1(top + PageSize_ * (pages - 1), ((data - 1) % PageSize_) + 1, hashes[pages - 1]); + } + } + + if (flag_h) { + _assert(signature != NULL); + + auto algorithms(GetAlgorithms()); + + uint32_t data = mach_header.Swap(signature->dataoff); + + uint8_t *top = reinterpret_cast(mach_header.GetBase()); + uint8_t *blob = top + data; + struct SuperBlob *super = reinterpret_cast(blob); + + struct Candidate { + CodeDirectory *directory_; + size_t size_; + Algorithm &algorithm_; + std::string hash_; + }; + + std::map candidates; + + for (size_t index(0); index != Swap(super->count); ++index) { + auto type(Swap(super->index[index].type)); + if ((type == CSSLOT_CODEDIRECTORY || type >= CSSLOT_ALTERNATE) && type != CSSLOT_SIGNATURESLOT) { + uint32_t begin = Swap(super->index[index].offset); + uint32_t end = index + 1 == Swap(super->count) ? Swap(super->blob.length) : Swap(super->index[index + 1].offset); + struct CodeDirectory *directory = reinterpret_cast(blob + begin + sizeof(Blob)); + auto type(directory->hashType); + _assert(type > 0 && type <= algorithms.size()); + auto &algorithm(*algorithms[type - 1]); + uint8_t hash[algorithm.size_]; + algorithm(hash, blob + begin, end - begin); + candidates.insert({type, {directory, end - begin, algorithm, Hex(hash, 20)}}); } + } + + _assert(!candidates.empty()); + auto best(candidates.end()); + --best; + + const auto directory(best->second.directory_); + const auto flags(Swap(directory->flags)); + + std::string names; + if (flags & kSecCodeSignatureHost) + names += ",host"; + if (flags & kSecCodeSignatureAdhoc) + names += ",adhoc"; + if (flags & kSecCodeSignatureForceHard) + names += ",hard"; + if (flags & kSecCodeSignatureForceKill) + names += ",kill"; + if (flags & kSecCodeSignatureForceExpiration) + names += ",expires"; + if (flags & kSecCodeSignatureRestrict) + names += ",restrict"; + if (flags & kSecCodeSignatureEnforcement) + names += ",enforcement"; + if (flags & kSecCodeSignatureLibraryValidation) + names += ",library-validation"; + if (flags & kSecCodeSignatureRuntime) + names += ",runtime"; + + printf("CodeDirectory v=%x size=%zd flags=0x%x(%s) hashes=%d+%d location=embedded\n", + Swap(directory->version), best->second.size_, flags, names.empty() ? "none" : names.c_str() + 1, Swap(directory->nCodeSlots), Swap(directory->nSpecialSlots)); + printf("Hash type=%s size=%d\n", best->second.algorithm_.name(), directory->hashSize); + + std::string choices; + for (const auto &candidate : candidates) { + auto choice(candidate.second.algorithm_.name()); + choices += ','; + choices += choice; + printf("CandidateCDHash %s=%s\n", choice, candidate.second.hash_.c_str()); + } + printf("Hash choices=%s\n", choices.c_str() + 1); + + printf("CDHash=%s\n", best->second.hash_.c_str()); } } @@ -2189,5 +3610,13 @@ int main(int argc, char *argv[]) { ++filei; } +#ifndef LDID_NOSMIME +# if OPENSSL_VERSION_MAJOR >= 3 + OSSL_PROVIDER_unload(legacy); + OSSL_PROVIDER_unload(deflt); +# endif +#endif + return filee; } +#endif