X-Git-Url: https://git.saurik.com/ldid.git/blobdiff_plain/2da83dea4ef1fa6c8e8cc0f5f2fbe2dca6873e36..e0db9af2b1636cc6b67ea602003053c4aa995cd7:/ldid.cpp diff --git a/ldid.cpp b/ldid.cpp index f5fd065..0769e7e 100644 --- a/ldid.cpp +++ b/ldid.cpp @@ -44,6 +44,7 @@ #ifndef LDID_NOSMIME #include +#include #include #include #include @@ -85,6 +86,8 @@ #ifndef LDID_NOPLIST #include +#elif __APPLE__ +#include #endif #include "ldid.hpp" @@ -243,6 +246,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 @@ -479,7 +483,32 @@ struct encryption_info_command { #define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 0xb0 #define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 0xc0 -static auto dummy([](double) {}); +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)); @@ -487,6 +516,10 @@ static std::streamsize read(std::streambuf &stream, void *data, size_t size) { return writ; } +static inline void put(std::streambuf &stream, uint8_t value) { + _assert(stream.sputc(value) != EOF); +} + static inline void get(std::streambuf &stream, void *data, size_t size) { _assert(read(stream, data, size) == size); } @@ -495,16 +528,20 @@ static inline void put(std::streambuf &stream, const void *data, size_t size) { _assert(stream.sputn(static_cast(data), size) == size); } -static inline void put(std::streambuf &stream, const void *data, size_t size, const ldid::Functor &percent) { - percent(0); +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; - percent(double(total) / size); + 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) @@ -531,6 +568,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) | @@ -674,6 +861,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 ); } @@ -814,6 +1002,7 @@ class FatHeader : #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) @@ -823,6 +1012,8 @@ class FatHeader : #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) @@ -832,6 +1023,26 @@ class FatHeader : #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; uint32_t offset; @@ -858,15 +1069,41 @@ 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; + 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 }; @@ -921,10 +1158,12 @@ extern "C" uint32_t hash(uint8_t *k, uint32_t length, uint32_t initval); struct Algorithm { size_t size_; uint8_t type_; + int nid_; - Algorithm(size_t size, uint8_t type) : + Algorithm(size_t size, uint8_t type, int nid) : size_(size), - type_(type) + type_(type), + nid_(nid) { } @@ -941,7 +1180,7 @@ struct AlgorithmSHA1 : Algorithm { AlgorithmSHA1() : - Algorithm(LDID_SHA1_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160) + Algorithm(LDID_SHA1_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160, NID_sha1) { } @@ -971,7 +1210,7 @@ struct AlgorithmSHA256 : Algorithm { AlgorithmSHA256() : - Algorithm(LDID_SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256) + Algorithm(LDID_SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256, NID_sha256) { } @@ -997,34 +1236,48 @@ struct AlgorithmSHA256 : } }; +static bool do_sha1(true); +static bool do_sha256(true); + static const std::vector &GetAlgorithms() { static AlgorithmSHA1 sha1; static AlgorithmSHA256 sha256; - static Algorithm *array[] = { - &sha1, - &sha256, - }; + static std::vector algorithms; + if (algorithms.empty()) { + if (do_sha1) + algorithms.push_back(&sha1); + if (do_sha256) + algorithms.push_back(&sha256); + } - static std::vector algorithms(array, array + sizeof(array) / sizeof(array[0])); 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) { } }; @@ -1132,35 +1385,44 @@ class Map { namespace ldid { +#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()) - _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)); - - if (entitlements.empty()) - entitlements.assign(reinterpret_cast(blob + 1), writ); - else - _assert(entitlements.compare(0, entitlements.size(), reinterpret_cast(blob + 1), writ) == 0); - } - } + 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 Functor &percent) { +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); @@ -1191,12 +1453,15 @@ 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(mach_header, size)); + Baton baton; + size_t alloc(allocate(mach_header, baton, size)); auto *fat_arch(mach_header.GetFatArch()); uint32_t align; @@ -1219,13 +1484,35 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co 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; + } + 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); } @@ -1239,14 +1526,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); @@ -1254,11 +1554,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()) { @@ -1269,22 +1573,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, 1 << allocation.align_); + + 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, 1 << allocation.align_); + + 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; } @@ -1340,13 +1666,13 @@ 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), percent); + 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(mach_header, output, allocation.limit_, overlap, top, percent)); + 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 @@ -1502,12 +1828,79 @@ class Stuff { } }; +class Octet { + private: + ASN1_OCTET_STRING *value_; + + public: + Octet() : + value_(ASN1_OCTET_STRING_new()) + { + _assert(value_ != NULL); + } + + Octet(const std::string &value) : + Octet() + { + _assert(ASN1_STRING_set(value_, value.data(), value.size())); + } + + Octet(const uint8_t *data, size_t size) : + Octet() + { + _assert(ASN1_STRING_set(value_, data, size)); + } + + Octet(const Octet &value) = delete; + + ~Octet() { + if (value_ != NULL) + ASN1_OCTET_STRING_free(value_); + } + + void release() { + value_ = NULL; + } + + operator ASN1_OCTET_STRING *() const { + return value_; + } +}; + +typedef struct { + ASN1_OBJECT *algorithm; + ASN1_OCTET_STRING *value; +} APPLE_CDHASH; + +DECLARE_ASN1_FUNCTIONS(APPLE_CDHASH) + +ASN1_NDEF_SEQUENCE(APPLE_CDHASH) = { + ASN1_SIMPLE(APPLE_CDHASH, algorithm, ASN1_OBJECT), + ASN1_SIMPLE(APPLE_CDHASH, value, ASN1_OCTET_STRING), +} ASN1_NDEF_SEQUENCE_END(APPLE_CDHASH) + +IMPLEMENT_ASN1_FUNCTIONS(APPLE_CDHASH) + +typedef struct { + ASN1_OBJECT *object; + STACK_OF(APPLE_CDHASH) *cdhashes; +} APPLE_CDATTR; + +DECLARE_ASN1_FUNCTIONS(APPLE_CDATTR) + +ASN1_NDEF_SEQUENCE(APPLE_CDATTR) = { + ASN1_SIMPLE(APPLE_CDATTR, object, ASN1_OBJECT), + ASN1_SET_OF(APPLE_CDATTR, cdhashes, APPLE_CDHASH), +} ASN1_NDEF_SEQUENCE_END(APPLE_CDATTR) + +IMPLEMENT_ASN1_FUNCTIONS(APPLE_CDATTR) + class Signature { private: PKCS7 *value_; public: - Signature(const Stuff &stuff, const Buffer &data, const std::string &xml) { + Signature(const Stuff &stuff, const Buffer &data, const std::string &xml, const ldid::Hash &hash) { value_ = PKCS7_new(); _assert(value_ != NULL); @@ -1518,24 +1911,80 @@ class Signature { for (unsigned i(0), e(sk_X509_num(certs)); i != e; i++) _assert(PKCS7_add_certificate(value_, sk_X509_value(certs, e - i - 1))); - auto info(PKCS7_sign_add_signer(value_, stuff, stuff, NULL, PKCS7_NOSMIMECAP)); + STACK_OF(X509_ATTRIBUTE) *attributes(sk_X509_ATTRIBUTE_new_null()); + _assert(attributes != NULL); + _scope({ sk_X509_ATTRIBUTE_pop_free(attributes, X509_ATTRIBUTE_free); }); + + + // 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)); - PKCS7_set_detached(value_, 1); + { + auto attribute(X509_ATTRIBUTE_create(NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data))); + _assert(attribute != NULL); + _assert(sk_X509_ATTRIBUTE_push(attributes, attribute) != 0); + } - ASN1_OCTET_STRING *string(ASN1_OCTET_STRING_new()); - _assert(string != NULL); - try { - _assert(ASN1_STRING_set(string, xml.data(), xml.size())); + { + auto cdattr(APPLE_CDATTR_new()); + _assert(cdattr != NULL); + _scope({ APPLE_CDATTR_free(cdattr); }); - 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; + static auto nid(OBJ_create("1.2.840.113635.100.9.2", "apple-2", "Apple 2")); + cdattr->object = OBJ_nid2obj(nid); + + for (Algorithm *pointer : GetAlgorithms()) { + Algorithm &algorithm(*pointer); + APPLE_CDHASH *cdhash(APPLE_CDHASH_new()); + _assert(cdhash != NULL); + _assert(sk_push((_STACK *) cdattr->cdhashes, cdhash) != 0); + cdhash->algorithm = OBJ_nid2obj(algorithm.nid_); + Octet string(algorithm[hash], algorithm.size_); + cdhash->value = string; + string.release(); + } + + // in e20b57270dece66ce2c68aeb5d14dd6d9f3c5d68 OpenSSL removed a "hack" + // in the process, they introduced a useful bug in X509_ATTRIBUTE_set1_data + // however, I don't want to rely on that or detect the bypass before it + // so, instead, I create my own compatible attribute and re-serialize it :/ + + ASN1_STRING *seq(ASN1_STRING_new()); + _assert(seq != NULL); + _scope({ ASN1_STRING_free(seq); }); + seq->length = ASN1_item_i2d((ASN1_VALUE *) cdattr, &seq->data, ASN1_ITEM_rptr(APPLE_CDATTR)); + + X509_ATTRIBUTE *attribute(NULL); + const unsigned char *data(seq->data); + _assert(d2i_X509_ATTRIBUTE(&attribute, &data, seq->length) != 0); + _assert(attribute != NULL); + _assert(sk_X509_ATTRIBUTE_push(attributes, attribute) != 0); + } + + { + // XXX: move the "cdhashes" plist code to here and remove xml argument + + Octet string(xml); + static auto nid(OBJ_create("1.2.840.113635.100.9.1", "apple-1", "Apple 1")); + auto attribute(X509_ATTRIBUTE_create(nid, V_ASN1_OCTET_STRING, string)); + _assert(attribute != NULL); + string.release(); + _assert(sk_X509_ATTRIBUTE_push(attributes, attribute) != 0); } - _assert(PKCS7_final(value_, data, PKCS7_BINARY)); + _assert(PKCS7_set_signed_attributes(info, attributes) != 0); + PKCS7_set_detached(value_, 1); + + // 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() { @@ -1561,11 +2010,6 @@ class NullBuffer : } }; -class Digest { - public: - uint8_t sha1_[LDID_SHA1_DIGEST_LENGTH]; -}; - class HashBuffer : public std::streambuf { @@ -1681,6 +2125,7 @@ static void Commit(const std::string &path, const std::string &temp) { namespace ldid { +#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); @@ -1692,6 +2137,7 @@ static void get(std::string &value, X509_NAME *name, int nid) { _assert(asn != NULL); value.assign(reinterpret_cast(ASN1_STRING_data(asn)), ASN1_STRING_length(asn)); } +#endif static void req(std::streambuf &buffer, uint32_t value) { value = Swap(value); @@ -1713,7 +2159,7 @@ static void req(std::streambuf &buffer, uint8_t (&&data)[Size_]) { 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, const std::string &requirements, const std::string &key, const Slots &slots, const Functor &percent) { +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; @@ -1764,7 +2210,7 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st // XXX: this is just a "sufficiently large number" size_t certificate(0x3000); - Allocate(idata, isize, output, fun([&](const MachHeader &mach_header, size_t size) -> size_t { + 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_); @@ -1783,11 +2229,60 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st alloc += sizeof(struct BlobIndex); alloc += backing.str().size(); - if (!entitlements.empty()) { +#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(); + } + + 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); @@ -1812,17 +2307,57 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st #endif return alloc; - }), fun([&](const MachHeader &mach_header, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Functor &percent) -> 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) { insert(blobs, CSSLOT_REQUIREMENTS, backing); } - if (!entitlements.empty()) { + uint64_t execs(0); + if (mach_header.Swap(mach_header->filetype) == MH_EXECUTE) + execs |= kSecCodeExecSegMainBinary; + + if (!baton.entitlements_.empty()) { std::stringbuf data; - put(data, entitlements.data(), entitlements.size()); + put(data, baton.entitlements_.data(), baton.entitlements_.size()); insert(blobs, CSSLOT_ENTITLEMENTS, CSMAGIC_EMBEDDED_ENTITLEMENTS, 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 (!baton.derformat_.empty()) { + std::stringbuf data; + put(data, baton.derformat_.data(), baton.derformat_.size()); + insert(blobs, CSSLOT_DERFORMAT, CSMAGIC_EMBEDDED_DERFORMAT, data); } Slots posts(slots); @@ -1849,19 +2384,22 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st uint32_t normal((limit + PageSize_ - 1) / PageSize_); CodeDirectory directory; - directory.version = Swap(uint32_t(0x00020200)); - directory.flags = Swap(uint32_t(0)); + 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 = algorithm.size_; directory.hashType = algorithm.type_; - directory.spare1 = 0x00; + 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(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)); @@ -1898,15 +2436,15 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st _foreach (slot, posts) memcpy(hashes - slot.first * algorithm.size_, algorithm[slot.second], algorithm.size_); - percent(0); + progress(0); if (normal != 1) for (size_t i = 0; i != normal - 1; ++i) { algorithm(hashes + i * algorithm.size_, (PageSize_ * i < overlap.size() ? overlap.data() : top) + PageSize_ * i, PageSize_); - percent(double(i) / normal); + progress(double(i) / normal); } if (normal != 0) algorithm(hashes + (normal - 1) * algorithm.size_, top + PageSize_ * (normal - 1), ((limit - 1) % PageSize_) + 1); - percent(1); + progress(1); put(data, storage.data(), storage.size()); @@ -1918,11 +2456,23 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st #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 + + ldid::Hash hash; unsigned total(0); for (Algorithm *pointer : GetAlgorithms()) { @@ -1932,17 +2482,31 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st 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); - plist_array_append_item(cdhashes, plist_new_data(hash.data(), hash.size())); + std::vector cdhash(algorithm[hash], algorithm[hash] + algorithm.size_); + cdhash.resize(20); + +#ifdef LDID_NOPLIST + auto value(CFDataCreate(kCFAllocatorDefault, reinterpret_cast(cdhash.data()), cdhash.size())); + _scope({ CFRelease(value); }); + CFArrayAppendValue(cdhashes, value); +#else + plist_array_append_item(cdhashes, plist_new_data(cdhash.data(), cdhash.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]); @@ -1950,7 +2514,7 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st Stuff stuff(key); Buffer bio(sign); - Signature signature(stuff, sign, std::string(xml, size)); + Signature signature(stuff, sign, std::string(xml, size), hash); Buffer result(signature); std::string value(result); put(data, value.data(), value.size()); @@ -1961,27 +2525,28 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st #endif return put(output, CSMAGIC_EMBEDDED_SIGNATURE, blobs); - }), percent); + }), progress); return hash; } #ifndef LDID_NOTOOLS -static void Unsign(void *idata, size_t isize, std::streambuf &output, const Functor &percent) { - Allocate(idata, isize, output, fun([](const MachHeader &mach_header, size_t size) -> size_t { +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([](const MachHeader &mach_header, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Functor &percent) -> 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; - }), percent); + }), progress); } std::string DiskFolder::Path(const std::string &path) const { - return path_ + "/" + path; + 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() { @@ -2057,8 +2622,7 @@ void DiskFolder::Find(const std::string &root, const std::string &base, const Fu void DiskFolder::Save(const std::string &path, bool edit, const void *flag, const Functor &code) { if (!edit) { - // XXX: use nullbuf - std::stringbuf save; + NullBuffer save; code(save); } else { std::filebuf save; @@ -2075,7 +2639,7 @@ bool DiskFolder::Look(const std::string &path) const { 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)); - _assert_(result == &data, "DiskFolder::Open(%s)", path.c_str()); + _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); @@ -2091,22 +2655,27 @@ 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); + return parent_.Save(Path(path), edit, flag, code); } bool SubFolder::Look(const std::string &path) const { - return parent_.Look(path_ + path); + return parent_.Look(Path(path)); } void SubFolder::Open(const std::string &path, const Functor &code) const { - return parent_.Open(path_ + path, code); + 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); + return parent_.Find(Path(path), code, link); } std::string UnionFolder::Map(const std::string &path) const { @@ -2176,8 +2745,8 @@ void UnionFolder::Find(const std::string &path, const Functor &percent) { - percent(0); +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 * 4]; @@ -2186,12 +2755,14 @@ static void copy(std::streambuf &source, std::streambuf &target, size_t length, break; _assert(target.sputn(data, writ) == writ); total += writ; - percent(double(total) / length); + progress(double(total) / length); } } #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); @@ -2203,7 +2774,7 @@ static plist_t plist(const std::string &data) { static void plist_d(std::streambuf &buffer, size_t length, const Functor &code) { std::stringbuf data; - copy(buffer, data, length, ldid::fun(dummy)); + copy(buffer, data, length, dummy_); auto node(plist(data.str())); _scope({ plist_free(node); }); _assert(plist_get_node_type(node) == PLIST_DICT); @@ -2305,30 +2876,51 @@ struct RuleCode { }; #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, const std::string &requirements, const std::string &key, const Slots &slots, size_t length, const Functor &percent) { +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, percent); + 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, requirements, key, slots, percent); + return Sign(data.data(), data.size(), proxy, identifier, entitlements, merge, requirements, key, slots, flags, platform, progress); } -Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std::map &remote, const std::string &requirements, const Functor &alter, const Functor &progress, const Functor &percent) { +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; bool mac(false); std::string info("Info.plist"); - if (!folder.Look(info) && folder.Look("Resources/" + info)) { + + SubFolder folder(parent, [&]() { + if (parent.Look(info)) + return ""; mac = true; - info = "Resources/" + info; - } + 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) { @@ -2337,16 +2929,16 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std })); })); - if (!mac && folder.Look("MacOS/" + executable)) { + if (mac && info == "Info.plist") executable = "MacOS/" + executable; - mac = true; - } + + 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, percent); + copy(buffer, temp, length, progress); // XXX: this is a stupid hack pad(temp, 0x10 - (length & 0xf)); auto data(temp.str()); @@ -2364,31 +2956,30 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std const std::string resources(mac ? "Resources/" : ""); if (true) { - rules1.insert(Rule{1, NoMode, "^" + resources}); + 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, "^Base\\.lproj/"}); + 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, "^" + resources}); + if (mac) rules2.insert(Rule{20, NoMode, "^" + resources}); rules2.insert(Rule{2000, OmitMode, "^(.*/)?\\.DS_Store$"}); - 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, "^" + resources + ".*\\.lproj/"}); rules2.insert(Rule{1100, OmitMode, "^" + resources + ".*\\.lproj/locversion.plist$"}); - rules2.insert(Rule{1010, NoMode, "^Base\\.lproj/"}); + 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{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; - std::string failure(mac ? "Contents/|Versions/[^/]*/Resources/" : ""); Expression nested("^(Frameworks/[^/]*\\.framework|PlugIns/[^/]*\\.appex(()|/[^/]*.app))/(" + failure + ")Info\\.plist$"); std::map bundles; @@ -2396,13 +2987,18 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std folder.Find("", fun([&](const std::string &name) { if (!nested(name)) return; - auto bundle(root + Split(name).dir); - bundle.resize(bundle.size() - resources.size()); + auto bundle(Split(name).dir); + if (mac) { + _assert(!bundle.empty()); + bundle = Split(bundle.substr(0, bundle.size() - 1)).dir; + } SubFolder subfolder(folder, bundle); - bundles[nested[1]] = Sign(bundle, subfolder, key, local, "", Starts(name, "PlugIns/") ? alter : - static_cast &>(fun([&](const std::string &, const std::string &entitlements) -> std::string { return entitlements; })) - , progress, percent); + 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) { })); @@ -2422,15 +3018,13 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std return false; }); - std::map links; - folder.Find("", fun([&](const std::string &name) { if (exclude(name)) return; - if (local.find(name) != local.end()) + if (local.files.find(name) != local.files.end()) return; - auto &hash(local[name]); + auto &hash(local.files[name]); folder.Open(name, fun([&](std::streambuf &data, size_t length, const void *flag) { progress(root + name); @@ -2457,7 +3051,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std 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, "", "", key, slots, length, percent); + Sign(header.bytes, size, data, hash, save, identifier, "", false, "", key, slots, length, 0, false, Progression(progress, root + name)); })); return; } @@ -2465,14 +3059,14 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std folder.Save(name, false, flag, fun([&](std::streambuf &save) { HashProxy proxy(hash, save); put(proxy, header.bytes, size); - copy(data, proxy, length - size, percent); + copy(data, proxy, length - size, progress); })); })); }), fun([&](const std::string &name, const Functor &read) { if (exclude(name)) return; - links[name] = read(); + local.links[name] = read(); })); auto plist(plist_new_dict()); @@ -2487,7 +3081,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std bool old(&version.second == &rules1); - for (const auto &hash : local) + for (const auto &hash : local.files) for (const auto &rule : version.second) if (rule(hash.first)) { if (!old && mac && excludes.find(hash.first) != excludes.end()); @@ -2506,19 +3100,20 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std break; } - for (const auto &link : 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); - } + 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; - } + break; + } if (!old && mac) for (const auto &bundle : bundles) { @@ -2569,7 +3164,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std } folder.Save(signature, true, NULL, fun([&](std::streambuf &save) { - HashProxy proxy(local[signature], save); + HashProxy proxy(local.files[signature], save); char *xml(NULL); uint32_t size; plist_to_xml(plist, &xml, &size); @@ -2578,33 +3173,47 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std })); Bundle bundle; - bundle.path = executable; + bundle.path = folder.Path(executable); 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); - bundle.hash = Sign(NULL, 0, buffer, local[executable], save, identifier, entitlements, requirements, key, slots, length, percent); + 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 &entry : local) - remote[root + entry.first] = entry.second; - return bundle; } -Bundle Sign(const std::string &root, Folder &folder, const std::string &key, const std::string &requirements, const Functor &alter, const Functor &progress, const Functor &percent) { - std::map local; - return Sign(root, folder, key, local, requirements, alter, progress, percent); +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 #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, "usage: %s -S[entitlements.xml] \n", argv0); + fprintf(stderr, " %s -e MobileSafari\n", argv0); + fprintf(stderr, " %s -S cat\n", argv0); + fprintf(stderr, " %s -Stfp.xml gdb\n", argv0); +} + #ifndef LDID_NOTOOLS int main(int argc, char *argv[]) { #ifndef LDID_NOSMIME @@ -2621,6 +3230,10 @@ int main(int argc, char *argv[]) { bool flag_r(false); bool flag_e(false); bool flag_q(false); + bool flag_k(false); + + bool flag_H(false); + bool flag_h(false); #ifndef LDID_NOFLAGT bool flag_T(false); @@ -2630,12 +3243,18 @@ int main(int argc, char *argv[]) { 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)); @@ -2654,11 +3273,8 @@ int main(int argc, char *argv[]) { 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) @@ -2688,12 +3304,36 @@ int main(int argc, char *argv[]) { 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; @@ -2712,6 +3352,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); @@ -2728,6 +3396,14 @@ int main(int argc, char *argv[]) { } break; + case 'M': + flag_M = true; + break; + + case 'k': + flag_k = true; + break; + case 'K': if (argv[argi][2] != '\0') key.open(argv[argi] + 2, O_RDONLY, PROT_READ, MAP_PRIVATE); @@ -2755,17 +3431,22 @@ int main(int argc, char *argv[]) { } 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); @@ -2774,12 +3455,10 @@ int main(int argc, char *argv[]) { _syscall(stat(path.c_str(), &info)); if (S_ISDIR(info.st_mode)) { + _assert(flag_S); #ifndef LDID_NOPLIST - _assert(!flag_r); - ldid::DiskFolder folder(path); - path += "/" + Sign("", folder, key, requirements, ldid::fun([&](const std::string &, const std::string &) -> std::string { return entitlements; }) - , ldid::fun([&](const std::string &) {}), ldid::fun(dummy) - ).path; + 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 @@ -2791,10 +3470,10 @@ int main(int argc, char *argv[]) { auto temp(Temporary(output, split)); if (flag_r) - ldid::Unsign(input.data(), input.size(), output, ldid::fun(dummy)); + 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, requirements, key, slots, ldid::fun(dummy)); + ldid::Sign(input.data(), input.size(), output, identifier, entitlements, flag_M, requirements, key, slots, flags, platform, dummy_); } Commit(path, temp); @@ -2865,6 +3544,10 @@ int main(int argc, char *argv[]) { #endif } + if (flag_d && encryption != NULL) { + printf("cryptid=%d\n", mach_header.Swap(encryption->cryptid)); + } + if (flag_D) { _assert(encryption != NULL); encryption->cryptid = mach_header.Swap(0); @@ -2900,11 +3583,13 @@ int main(int argc, char *argv[]) { if (Swap(super->index[index].type) == CSSLOT_REQUIREMENTS) { uint32_t begin = Swap(super->index[index].offset); struct Blob *requirement = reinterpret_cast(blob + begin); + // XXX: this is obviously wrong. but like, -Q is also wrong?! + // maybe I can fix all of this just by fixing both -q and -Q? fwrite(requirement, 1, Swap(requirement->length), stdout); } } - if (flag_s) { + if (flag_k) { _assert(signature != NULL); uint32_t data = mach_header.Swap(signature->dataoff); @@ -2914,19 +3599,122 @@ int main(int argc, char *argv[]) { struct SuperBlob *super = reinterpret_cast(blob); for (size_t index(0); index != Swap(super->count); ++index) - if (Swap(super->index[index].type) == CSSLOT_CODEDIRECTORY) { + if (Swap(super->index[index].type) == CSSLOT_SIGNATURESLOT) { uint32_t begin = Swap(super->index[index].offset); + struct Blob *signature = reinterpret_cast(blob + begin); + fwrite(signature + 1, 1, Swap(signature->length) - sizeof(*signature), stdout); + } + } + + if (flag_s) { + _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); + + 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 (*hashes)[LDID_SHA1_DIGEST_LENGTH] = reinterpret_cast(blob + begin + Swap(directory->hashOffset)); + uint8_t *hashes = reinterpret_cast(blob + begin + Swap(directory->hashOffset)); uint32_t pages = Swap(directory->nCodeSlots); - if (pages != 1) + if (pages != 0) { for (size_t i = 0; i != pages - 1; ++i) - LDID_SHA1(top + PageSize_ * i, PageSize_, hashes[i]); - if (pages != 0) - LDID_SHA1(top + PageSize_ * (pages - 1), ((data - 1) % PageSize_) + 1, hashes[pages - 1]); + algorithm(hashes + i * algorithm.size_, top + PageSize_ * i, PageSize_); + algorithm(hashes + (pages - 1) * algorithm.size_, top + PageSize_ * (pages - 1), ((data - 1) % PageSize_) + 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()); } }