X-Git-Url: https://git.cameronkatri.com/ldid.git/blobdiff_plain/ec3af45e12e60355d13d2caed08070b4ccb64e3b..685ead2605ceca538b4488792def136e74c85236:/ldid.cpp diff --git a/ldid.cpp b/ldid.cpp index 38b9343..24daf47 100644 --- a/ldid.cpp +++ b/ldid.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2007-2015 Jay Freeman (saurik) */ +/* SPDX-License-Identifier: AGPL-3.0-only */ /* GNU Affero General Public License, Version 3 {{{ */ /* * This program is free software: you can redistribute it and/or modify @@ -19,6 +20,7 @@ **/ /* }}} */ +#include #include #include #include @@ -42,55 +44,24 @@ #include #include -#ifndef LDID_NOSMIME +#include +# if OPENSSL_VERSION_MAJOR >= 3 +# include +# endif #include #include #include #include -#endif +#include -#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 - -#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 +#include -#ifndef LDID_NOPLIST #include -#elif __APPLE__ -#include -#endif #include "ldid.hpp" +#include "machine.h" + #define _assert___(line) \ #line #define _assert__(line) \ @@ -129,7 +100,8 @@ for (auto success : (long[]) {EINTR, __VA_ARGS__}) \ if (error == success) \ return (decltype(expr)) -success; \ - _assert_(false, "errno=%u", error); \ + fprintf(stderr, "ldid: %s: %s\n", __func__, strerror(error)); \ + exit(1); \ } }() #define _trace() \ @@ -141,6 +113,9 @@ #define _packed \ __attribute__((packed)) +std::string password; +std::vector cleanup; + template struct Iterator_ { typedef typename Type_::const_iterator Result; @@ -187,27 +162,6 @@ 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_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) - struct fat_header { uint32_t magic; uint32_t nfat_arch; @@ -245,6 +199,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 @@ -514,6 +469,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); } @@ -532,6 +491,10 @@ static inline void put(std::streambuf &stream, const void *data, size_t size, co } } +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) @@ -558,6 +521,157 @@ 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(); +} + +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: { + fprintf(stderr, "ldid: Invalid plist entry type\n"); + exit(1); + } break; + + case PLIST_DATE: { + fprintf(stderr, "ldid: Invalid plist entry type\n"); + exit(1); + } 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: { + fprintf(stderr, "ldid: Unsupported plist type %d", type); + exit(1); + } break; + } +} + static inline uint16_t Swap_(uint16_t value) { return ((value >> 8) & 0x00ff) | @@ -690,7 +804,8 @@ class MachHeader : break; default: - _assert(false); + fprintf(stderr, "ldid: Unknown header magic\nAre you sure that is a Mach-O?\n"); + exit(1); } void *post = mach_header_ + 1; @@ -698,11 +813,13 @@ class MachHeader : post = (uint32_t *) post + 1; load_command_ = (struct load_command *) post; - _assert( - Swap(mach_header_->filetype) == MH_EXECUTE || - Swap(mach_header_->filetype) == MH_DYLIB || - Swap(mach_header_->filetype) == MH_BUNDLE - ); + if (Swap(mach_header_->filetype) != MH_EXECUTE && + Swap(mach_header_->filetype) != MH_DYLIB && + Swap(mach_header_->filetype) != MH_DYLINKER && + Swap(mach_header_->filetype) != MH_BUNDLE) { + fprintf(stderr, "ldid: Unsupported Mach-O type\n"); + exit(1); + } } bool Bits64() const { @@ -841,6 +958,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) @@ -850,6 +968,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) @@ -859,6 +979,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; @@ -890,8 +1030,22 @@ struct CodeDirectory { 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 { @@ -903,6 +1057,7 @@ enum CodeSignatureFlags { kSecCodeSignatureRestrict = 0x0800, kSecCodeSignatureEnforcement = 0x1000, kSecCodeSignatureLibraryValidation = 0x2000, + kSecCodeSignatureRuntime = 0x10000, }; enum Kind : uint32_t { @@ -952,9 +1107,6 @@ enum MatchOperation { #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_; @@ -979,7 +1131,7 @@ struct AlgorithmSHA1 : Algorithm { AlgorithmSHA1() : - Algorithm(LDID_SHA1_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160) + Algorithm(SHA_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160) { } @@ -988,7 +1140,7 @@ struct AlgorithmSHA1 : } void operator ()(uint8_t *hash, const void *data, size_t size) const { - LDID_SHA1(static_cast(data), size, hash); + SHA1(static_cast(data), size, hash); } void operator ()(ldid::Hash &hash, const void *data, size_t size) const { @@ -996,7 +1148,7 @@ struct AlgorithmSHA1 : } void operator ()(std::vector &hash, const void *data, size_t size) const { - hash.resize(LDID_SHA1_DIGEST_LENGTH); + hash.resize(SHA_DIGEST_LENGTH); return operator ()(reinterpret_cast(hash.data()), data, size); } @@ -1009,7 +1161,7 @@ struct AlgorithmSHA256 : Algorithm { AlgorithmSHA256() : - Algorithm(LDID_SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256) + Algorithm(SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256) { } @@ -1018,7 +1170,7 @@ struct AlgorithmSHA256 : } void operator ()(uint8_t *hash, const void *data, size_t size) const { - LDID_SHA256(static_cast(data), size, hash); + SHA256(static_cast(data), size, hash); } void operator ()(ldid::Hash &hash, const void *data, size_t size) const { @@ -1026,7 +1178,7 @@ struct AlgorithmSHA256 : } void operator ()(std::vector &hash, const void *data, size_t size) const { - hash.resize(LDID_SHA256_DIGEST_LENGTH); + hash.resize(SHA256_DIGEST_LENGTH); return operator ()(reinterpret_cast(hash.data()), data, size); } @@ -1055,13 +1207,14 @@ static const std::vector &GetAlgorithms() { 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_; @@ -1097,8 +1250,11 @@ class File { } void open(const char *path, int flags) { - _assert(file_ == -1); - file_ = _syscall(::open(path, flags)); + file_ = ::open(path, flags); + if (file_ == -1) { + fprintf(stderr, "ldid: %s: %s\n", path, strerror(errno)); + exit(1); + } } int file() const { @@ -1112,14 +1268,6 @@ class Map { void *data_; size_t size_; - void clear() { - if (data_ == NULL) - return; - _syscall(munmap(data_, size_)); - data_ = NULL; - size_ = 0; - } - public: Map() : data_(NULL), @@ -1167,6 +1315,14 @@ class Map { open(path, O_RDONLY, PROT_READ, MAP_PRIVATE); } + void clear() { + if (data_ == NULL) + return; + _syscall(munmap(data_, size_)); + data_ = NULL; + size_ = 0; + } + void *data() const { return data_; } @@ -1179,13 +1335,11 @@ class Map { return std::string(static_cast(data_), size_); } }; -#endif +#endif // LDID_NOTOOLS 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()) @@ -1214,13 +1368,16 @@ std::string Analyze(const void *data, size_t size) { if (entitlements.empty()) entitlements.assign(data, size); else - _assert(entitlements.compare(0, entitlements.size(), data, size) == 0); + if (entitlements.compare(0, entitlements.size(), data, size) != 0) { + fprintf(stderr, "ldid: Entitlements do not match\n"); + exit(1); + } })); return entitlements; } -static void Allocate(const void *idata, size_t isize, std::streambuf &output, const Functor &allocate, const Functor &save, const Progress &progress) { +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); @@ -1234,8 +1391,7 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co _foreach (load_command, mach_header.GetLoadCommands()) { uint32_t cmd(mach_header.Swap(load_command->cmd)); - if (false); - else if (cmd == LC_CODE_SIGNATURE) + if (cmd == LC_CODE_SIGNATURE) signature = reinterpret_cast(load_command); else if (cmd == LC_SYMTAB) symtab = reinterpret_cast(load_command); @@ -1275,6 +1431,7 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co break; case CPU_TYPE_ARM: case CPU_TYPE_ARM64: + case CPU_TYPE_ARM64_32: align = 0xe; break; default: @@ -1302,6 +1459,9 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co case CPU_TYPE_ARM64: arch = "arm64"; break; + case CPU_TYPE_ARM64_32: + arch = "arm64_32"; + break; } offset = Align(offset, 1 << align); @@ -1324,14 +1484,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); @@ -1345,6 +1518,9 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co 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()) { @@ -1355,22 +1531,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; } @@ -1432,7 +1630,7 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co pad(output, allocation.limit_ - allocation.size_); position += allocation.limit_ - allocation.size_; - size_t saved(save(mach_header, allocation.baton_, output, allocation.limit_, overlap, top, progress)); + 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 @@ -1488,7 +1686,6 @@ static size_t put(std::streambuf &output, uint32_t magic, const Blobs &blobs) { return offset; } -#ifndef LDID_NOSMIME class Buffer { private: BIO *bio_; @@ -1518,7 +1715,10 @@ class Buffer { Buffer(PKCS7 *pkcs) : Buffer() { - _assert(i2d_PKCS7_bio(bio_, pkcs) != 0); + if (i2d_PKCS7_bio(bio_, pkcs) == 0){ + fprintf(stderr, "ldid: An error occured while getting the PKCS12 file: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } } ~Buffer() { @@ -1548,15 +1748,32 @@ class Stuff { value_(d2i_PKCS12_bio(bio, NULL)), ca_(NULL) { - _assert(value_ != NULL); - _assert(PKCS12_parse(value_, "", &key_, &cert_, &ca_) != 0); + if (value_ == NULL){ + fprintf(stderr, "ldid: An error occured while getting the PKCS12 file: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + + if (!PKCS12_verify_mac(value_, "", 0) && password.empty()) { + char passbuf[2048]; + UI_UTIL_read_pw_string(passbuf, 2048, "Enter password: ", 0); + password = passbuf; + } - _assert(key_ != NULL); - _assert(cert_ != NULL); + if (PKCS12_parse(value_, password.c_str(), &key_, &cert_, &ca_) <= 0){ + fprintf(stderr, "ldid: An error occured while parsing: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + if (key_ == NULL || cert_ == NULL){ + fprintf(stderr, "ldid: An error occured while parsing: %s\nYour p12 cert might not be valid\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } if (ca_ == NULL) ca_ = sk_X509_new_null(); - _assert(ca_ != NULL); + if (ca_ == NULL){ + fprintf(stderr, "ldid: An error occured while parsing: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } } Stuff(const std::string &data) : @@ -1588,50 +1805,99 @@ class Stuff { } }; +// xina fix; +struct SEQUENCE_hash_sha1 { + uint8_t SEQUENCE[2] = {0x30, 0x1d}; // size + uint8_t OBJECT_IDENTIFIER[7] = {0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A}; // OBJECT IDENTIFIER 1.3.14.3.2.26 sha1 (OIW) + uint8_t hash_size[2] = {0x04, 0x14}; + char hash[20]; +}; + +struct SEQUENCE_hash_sha256 { + uint8_t SEQUENCE[2] = {0x30, 0x2d}; // size + uint8_t OBJECT_IDENTIFIER[11] = {0x06 ,0x09 ,0x60, 0x86, 0x48, 0x01 ,0x65, 0x03, 0x04, 0x02, 0x01}; // 2.16.840.1.101.3.4.2.1 sha-256 (NIST Algorithm) + uint8_t hash_size[2] = {0x04, 0x20}; // hash size + char hash[32]; +}; + 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 std::vector& alternateCDSHA1, const std::vector& alternateCDSHA256) { value_ = PKCS7_new(); - _assert(value_ != NULL); + if (value_ == NULL){ + fprintf(stderr, "ldid: An error occured while getting creating PKCS7 file: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } - _assert(PKCS7_set_type(value_, NID_pkcs7_signed)); - _assert(PKCS7_content_new(value_, NID_pkcs7_data)); + if (PKCS7_set_type(value_, NID_pkcs7_signed) == 0 || + PKCS7_content_new(value_, NID_pkcs7_data) == 0) { + fprintf(stderr, "ldid: An error occured while getting creating PKCS7 file: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } 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))); + for (unsigned i(0), e(sk_X509_num(certs)); i != e; i++) { + if (PKCS7_add_certificate(value_, sk_X509_value(certs, e - i - 1)) == 0) { + fprintf(stderr, "ldid: An error occured while signing: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + } + + auto info(PKCS7_sign_add_signer(value_, stuff, stuff, NULL, PKCS7_NOSMIMECAP)); + if (info == NULL){ + fprintf(stderr, "ldid: An error occured while signing: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(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))); + X509_ATTRIBUTE *attribute = X509_ATTRIBUTE_new(); + ASN1_OBJECT *obj2 = OBJ_txt2obj("1.2.840.113635.100.9.2", 1); + X509_ATTRIBUTE_set1_object(attribute, obj2); + // xina fix; + SEQUENCE_hash_sha1 seq1; + memcpy((void *)seq1.hash,(void *)alternateCDSHA1.data() ,alternateCDSHA1.size()); + X509_ATTRIBUTE_set1_data(attribute, V_ASN1_SEQUENCE,&seq1, sizeof(seq1)); + // xina fix; + SEQUENCE_hash_sha256 seq256; + memcpy((void *)seq256.hash,(void *)alternateCDSHA256.data() ,alternateCDSHA256.size()); + X509_ATTRIBUTE_set1_data(attribute, V_ASN1_SEQUENCE,&seq256, sizeof(seq256)); + + STACK_OF(X509_ATTRIBUTE) *sk = PKCS7_get_signed_attributes(info); + if (!sk_X509_ATTRIBUTE_push(sk, attribute)) { + fprintf(stderr, "ldid: sk_X509_ATTRIBUTE_push failed: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } PKCS7_set_detached(value_, 1); ASN1_OCTET_STRING *string(ASN1_OCTET_STRING_new()); - _assert(string != NULL); + if (string == NULL) { + fprintf(stderr, "ldid: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + try { - _assert(ASN1_STRING_set(string, xml.data(), xml.size())); + if (ASN1_STRING_set(string, xml.data(), xml.size()) == 0) { + fprintf(stderr, "ldid: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } static auto nid(OBJ_create("1.2.840.113635.100.9.1", "", "")); - _assert(PKCS7_add_signed_attribute(info, nid, V_ASN1_OCTET_STRING, string)); + if (PKCS7_add_signed_attribute(info, nid, V_ASN1_OCTET_STRING, string) == 0) { + fprintf(stderr, "ldid: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } } 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)); + if (PKCS7_final(value_, data, PKCS7_BINARY) == 0) { + fprintf(stderr, "ldid: Failed to sign: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } } ~Signature() { @@ -1642,7 +1908,6 @@ class Signature { return value_; } }; -#endif class NullBuffer : public std::streambuf @@ -1657,36 +1922,37 @@ class NullBuffer : } }; -class Digest { - public: - uint8_t sha1_[LDID_SHA1_DIGEST_LENGTH]; -}; - class HashBuffer : public std::streambuf { private: ldid::Hash &hash_; - LDID_SHA1_CTX sha1_; - LDID_SHA256_CTX sha256_; + EVP_MD_CTX *sha1_; + EVP_MD_CTX *sha256_; public: HashBuffer(ldid::Hash &hash) : hash_(hash) { - LDID_SHA1_Init(&sha1_); - LDID_SHA256_Init(&sha256_); + sha1_ = EVP_MD_CTX_new(); + sha256_ = EVP_MD_CTX_new(); + + EVP_DigestInit_ex(sha1_, EVP_get_digestbyname("sha1"), nullptr); + EVP_DigestInit_ex(sha256_, EVP_get_digestbyname("sha256"), nullptr); } ~HashBuffer() { - LDID_SHA1_Final(reinterpret_cast(hash_.sha1_), &sha1_); - LDID_SHA256_Final(reinterpret_cast(hash_.sha256_), &sha256_); + EVP_DigestFinal_ex(sha1_, reinterpret_cast(hash_.sha1_), nullptr); + EVP_DigestFinal_ex(sha256_, reinterpret_cast(hash_.sha256_), nullptr); + + EVP_MD_CTX_free(sha1_); + EVP_MD_CTX_free(sha256_); } virtual std::streamsize xsputn(const char_type *data, std::streamsize size) { - LDID_SHA1_Update(&sha1_, data, size); - LDID_SHA256_Update(&sha256_, data, size); + EVP_DigestUpdate(sha1_, data, size); + EVP_DigestUpdate(sha256_, data, size); return size; } @@ -1759,6 +2025,7 @@ static std::string Temporary(std::filebuf &file, const Split &split) { std::string temp(split.dir + ".ldid." + split.base); mkdir_p(split.dir); _assert_(file.open(temp.c_str(), std::ios::out | std::ios::trunc | std::ios::binary) == &file, "open(): %s", temp.c_str()); + cleanup.push_back(temp); return temp; } @@ -1772,24 +2039,35 @@ static void Commit(const std::string &path, const std::string &temp) { } _syscall(rename(temp.c_str(), path.c_str())); + cleanup.erase(std::remove(cleanup.begin(), cleanup.end(), temp), cleanup.end()); } -#endif +#endif // LDID_NOTOOLS 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); + if (index < 0) { + fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } auto next(X509_NAME_get_index_by_NID(name, nid, index)); - _assert(next == -1); + if (next != -1) { + fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } auto entry(X509_NAME_get_entry(name, index)); - _assert(entry != NULL); + if (entry == NULL) { + fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } auto asn(X509_NAME_ENTRY_get_data(entry)); - _assert(asn != NULL); - value.assign(reinterpret_cast(ASN1_STRING_data(asn)), ASN1_STRING_length(asn)); + if (asn == NULL) { + fprintf(stderr, "ldid: An error occursed while parsing the certificate: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + value.assign(reinterpret_cast(ASN1_STRING_get0_data(asn)), ASN1_STRING_length(asn)); } -#endif static void req(std::streambuf &buffer, uint32_t value) { value = Swap(value); @@ -1818,15 +2096,16 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st 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); + if (name == NULL){ + fprintf(stderr, "ldid: Your certificate might not be valid: %s\n", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } get(team, name, NID_organizationalUnitName); get(common, name, NID_commonName); } -#endif std::stringbuf backing; @@ -1881,47 +2160,48 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st alloc += sizeof(struct BlobIndex); alloc += backing.str().size(); - if (!merge) - baton.entitlements_ = entitlements; - else { -#ifndef LDID_NOPLIST + if (merge) Analyze(mach_header, fun([&](const char *data, size_t size) { baton.entitlements_.assign(data, size); })); - if (!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); }); + if (!baton.entitlements_.empty() || !entitlements.empty()) { + auto combined(plist(baton.entitlements_)); + _scope({ plist_free(combined); }); + if (plist_get_node_type(combined) != PLIST_DICT) { + fprintf(stderr, "ldid: Existing entitlements are in wrong format\n"); + exit(1); + }; + + auto merging(plist(entitlements)); + _scope({ plist_free(merging); }); + if (plist_get_node_type(merging) != PLIST_DICT) { + fprintf(stderr, "ldid: Entitlements need a root key of dict\n"); + exit(1); + }; + + 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)); + } - 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); }); + char *xml(NULL); + uint32_t size; + plist_to_xml(combined, &xml, &size); + _scope({ free(xml); }); - baton.entitlements_.assign(xml, size); - } -#else - _assert(false); -#endif + baton.entitlements_.assign(xml, size); } if (!baton.entitlements_.empty()) { @@ -1931,6 +2211,13 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st 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); directory += sizeof(struct BlobIndex); @@ -1944,26 +2231,65 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st 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); alloc += certificate; } -#endif return alloc; - }), fun([&](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Progress &progress) -> 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); } + 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); + + auto entitlements(plist(baton.entitlements_)); + _scope({ plist_free(entitlements); }); + if (plist_get_node_type(entitlements) != PLIST_DICT) { + fprintf(stderr, "ldid: Entitlements should be a plist dicionary\n"); + exit(1); + } + + 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; + } + + 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); @@ -1990,10 +2316,10 @@ 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.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_; @@ -2001,8 +2327,11 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st 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)); @@ -2057,23 +2386,15 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st ++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 + + std::vector alternateCDSHA256; + std::vector alternateCDSHA1; unsigned total(0); for (Algorithm *pointer : GetAlgorithms()) { @@ -2085,28 +2406,19 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st std::vector hash; algorithm(hash, blob.data(), blob.size()); + if (algorithm.type_ == CS_HASHTYPE_SHA256_256) + alternateCDSHA256 = hash; + else if (algorithm.type_ == CS_HASHTYPE_SHA160_160) + alternateCDSHA1 = hash; 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]); @@ -2114,7 +2426,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), alternateCDSHA1, alternateCDSHA256); Buffer result(signature); std::string value(result); put(data, value.data(), value.size()); @@ -2122,7 +2434,6 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st const auto &save(insert(blobs, CSSLOT_SIGNATURESLOT, CSMAGIC_BLOBWRAPPER, data)); _assert(save.size() <= certificate); } -#endif return put(output, CSMAGIC_EMBEDDED_SIGNATURE, blobs); }), progress); @@ -2134,18 +2445,19 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st 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, const Baton &baton, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Progress &progress) -> 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) 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() { @@ -2189,8 +2501,7 @@ void DiskFolder::Find(const std::string &root, const std::string &base, const Fu #ifdef __WIN32__ struct stat info; _syscall(stat((path + name).c_str(), &info)); - if (false); - else if (S_ISDIR(info.st_mode)) + if (S_ISDIR(info.st_mode)) directory = true; else if (S_ISREG(info.st_mode)) directory = false; @@ -2221,8 +2532,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; @@ -2239,7 +2549,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); @@ -2249,28 +2559,33 @@ void DiskFolder::Open(const std::string &path, const Functor &code, const Functor &)> &link) const { Find(path, "", code, link); } -#endif +#endif // LDID_NOTOOLS 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 { @@ -2354,14 +2669,18 @@ static void copy(std::streambuf &source, std::streambuf &target, size_t 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); else plist_from_xml(data.data(), data.size(), &plist); - _assert(plist != NULL); + if (plist == NULL) { + fprintf(stderr, "ldid: Failed to parse plist\n"); + exit(1); + } return plist; } @@ -2370,19 +2689,25 @@ static void plist_d(std::streambuf &buffer, size_t length, const Functor\n"); + exit(1); + } code(node); } static std::string plist_s(plist_t node) { - _assert(node != NULL); - _assert(plist_get_node_type(node) == PLIST_STRING); + if (node == NULL) + return NULL; + if (plist_get_node_type(node) != PLIST_STRING) { + fprintf(stderr, "ldid: Unexpected plist type. Expected \n"); + exit(1); + } char *data; plist_get_string_val(node, &data); _scope({ free(data); }); return data; } -#endif enum Mode { NoMode, @@ -2428,7 +2753,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), @@ -2468,7 +2793,6 @@ 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, 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; @@ -2482,29 +2806,60 @@ static Hash Sign(const uint8_t *prefix, size_t size, std::streambuf &buffer, Has 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 Progress &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; 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 (parent.Look("Contents/" + info)) + return "Contents/"; + else if (parent.Look("Resources/" + info)) { + info = "Resources/" + info; + return ""; + } else { + fprintf(stderr, "ldid: Could not find Info.plist\n"); + exit(1); + } + }()); 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")); + plist_t nodebuf(plist_dict_get_item(node, "CFBundleExecutable")); + if (nodebuf == NULL) { + fprintf(stderr, "ldid: Cannot find key CFBundleExecutable\n"); + exit(1); + } + executable = plist_s(nodebuf); + nodebuf = plist_dict_get_item(node, "CFBundleIdentifier"); + if (nodebuf == NULL) { + fprintf(stderr, "ldid: Cannot find key CFBundleIdentifier\n"); + exit(1); + } + identifier = plist_s(nodebuf); })); })); - if (!mac && folder.Look("MacOS/" + executable)) { + if (mac && info == "Info.plist") executable = "MacOS/" + executable; - mac = true; - } progress(root + "*"); @@ -2530,31 +2885,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; @@ -2562,13 +2916,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 : + 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) { })); @@ -2588,15 +2947,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); @@ -2638,7 +2995,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std if (exclude(name)) return; - links[name] = read(); + local.links[name] = read(); })); auto plist(plist_new_dict()); @@ -2653,7 +3010,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()); @@ -2672,19 +3029,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) { @@ -2735,7 +3093,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); @@ -2744,38 +3102,68 @@ 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, false, requirements, key, slots, length, 0, false, Progression(progress, root + executable)); + 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 Progress &progress) { - std::map local; + 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, "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[sha1 | sha256]] [-h] [-Iname]\n"); + fprintf(stderr, " [-Kkey.p12 [-Upassword]] [-M] [-P] [-Qrequirements.xml] [-q]\n"); + fprintf(stderr, " [-r | -Sfile.xml | -s] [-u] [-arch arch_type] file ...\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"); +} + +void cleanupfunc(void) { + for (const auto &temp : cleanup) + remove(temp.c_str()); +} + #ifndef LDID_NOTOOLS int main(int argc, char *argv[]) { -#ifndef LDID_NOSMIME + std::atexit(cleanupfunc); OpenSSL_add_all_algorithms(); -#endif +# if OPENSSL_VERSION_MAJOR >= 3 + OSSL_PROVIDER *legacy = OSSL_PROVIDER_load(NULL, "legacy"); + OSSL_PROVIDER *deflt = OSSL_PROVIDER_load(NULL, "default"); +# endif union { uint16_t word; @@ -2789,15 +3177,14 @@ int main(int argc, char *argv[]) { 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); @@ -2814,10 +3201,6 @@ int main(int argc, char *argv[]) { const char *flag_I(NULL); -#ifndef LDID_NOFLAGT - bool timeh(false); - uint32_t timev(0); -#endif Map entitlements; Map requirements; @@ -2827,20 +3210,41 @@ 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) if (argv[argi][0] != '-') files.push_back(argv[argi]); - else switch (argv[argi][1]) { + else if (strcmp(argv[argi], "-arch") == 0) { + bool foundarch = false; + flag_A = true; + argi++; + if (argi == argc) { + fprintf(stderr, "ldid: -arch must be followed by an architecture string\n"); + exit(1); + } + for (int i = 0; archs[i].name != NULL; i++) { + if (strcmp(archs[i].name, argv[argi]) == 0) { + flag_CPUType = archs[i].cputype; + flag_CPUSubtype = archs[i].cpusubtype; + foundarch = true; + } + if (foundarch) + break; + } + + if (!foundarch) { + fprintf(stderr, "error: unknown architecture specification flag: -arch %s\n", argv[argi]); + exit(1); + } + } else switch (argv[argi][1]) { case 'r': - _assert(!flag_s); - _assert(!flag_S); + if (flag_s || flag_S) { + fprintf(stderr, "ldid: Can only specify one of -r, -S, -s\n"); + exit(1); + } flag_r = true; break; @@ -2849,11 +3253,17 @@ int main(int argc, char *argv[]) { case 'E': { const char *string = argv[argi] + 2; const char *colon = strchr(string, ':'); - _assert(colon != NULL); + if (colon == NULL) { + usage(argv[0]); + exit(1); + } Map file(colon + 1, O_RDONLY, PROT_READ, MAP_PRIVATE); char *arge; unsigned number(strtoul(string, &arge, 0)); - _assert(arge == colon); + if (arge != colon || (number == 0 && errno == EINVAL)) { + usage(argv[0]); + exit(1); + } auto &slot(slots[number]); for (Algorithm *algorithm : GetAlgorithms()) (*algorithm)(slot, file.data(), file.size()); @@ -2869,47 +3279,60 @@ int main(int argc, char *argv[]) { 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) + if (strcmp(hash, "sha1") == 0) do_sha1 = true; else if (strcmp(hash, "sha256") == 0) do_sha256 = true; - else _assert(false); + else { + fprintf(stderr, "ldid: only sha1 and sha256 are supported at this time\n"); + exit(1); + } } 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; case 'A': - _assert(!flag_A); + if (flag_A) { + fprintf(stderr, "ldid: -A can only be specified once\n"); + exit(1); + } flag_A = true; if (argv[argi][2] != '\0') { const char *cpu = argv[argi] + 2; const char *colon = strchr(cpu, ':'); - _assert(colon != NULL); + if (colon == NULL) { + usage(argv[0]); + exit(1); + } char *arge; flag_CPUType = strtoul(cpu, &arge, 0); - _assert(arge == colon); + if (arge != colon || (flag_CPUType == 0 && errno == EINVAL)) { + usage(argv[0]); + exit(1); + } flag_CPUSubtype = strtoul(colon + 1, &arge, 0); - _assert(arge == argv[argi] + strlen(argv[argi])); + if (arge != argv[argi] + strlen(argv[argi]) || (flag_CPUSubtype == 0 && errno == EINVAL)) { + usage(argv[0]); + exit(1); + } } break; case 'C': { const char *name = argv[argi] + 2; - if (false); - else if (strcmp(name, "host") == 0) + if (strcmp(name, "host") == 0) flags |= kSecCodeSignatureHost; else if (strcmp(name, "adhoc") == 0) flags |= kSecCodeSignatureAdhoc; @@ -2925,7 +3348,12 @@ int main(int argc, char *argv[]) { flags |= kSecCodeSignatureEnforcement; else if (strcmp(name, "library-validation") == 0) flags |= kSecCodeSignatureLibraryValidation; - else _assert(false); + else if (strcmp(name, "runtime") == 0) + flags |= kSecCodeSignatureRuntime; + else { + fprintf(stderr, "ldid: -C: Unsupported option\n"); + exit(1); + } } break; case 'P': @@ -2933,14 +3361,20 @@ int main(int argc, char *argv[]) { break; case 's': - _assert(!flag_r); - _assert(!flag_S); + if (flag_r || flag_S) { + fprintf(stderr, "ldid: Can only specify one of -r, -S, -s\n"); + exit(1); + } flag_s = true; + entitlements.clear(); + flag_M = true; break; case 'S': - _assert(!flag_r); - _assert(!flag_s); + if (flag_r || flag_s) { + fprintf(stderr, "ldid: Can only specify one of -r, -S, -s\n"); + exit(1); + } flag_S = true; if (argv[argi][2] != '\0') { const char *xml = argv[argi] + 2; @@ -2952,23 +3386,16 @@ int main(int argc, char *argv[]) { flag_M = true; break; + case 'U': + password = argv[argi] + 2; + break; + case 'K': 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] == '-') - timeh = true; - else { - char *arge; - timev = strtoul(argv[argi] + 2, &arge, 0); - _assert(arge == argv[argi] + strlen(argv[argi])); - } - } break; -#endif + case 'T': break; case 'u': { flag_u = true; @@ -2979,32 +3406,36 @@ 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_I != NULL && !flag_S) { + fprintf(stderr, "ldid: -I requires -S\n"); + exit(1); } + if (files.empty()) + return 0; + size_t filei(0), filee(0); _foreach (file, files) try { std::string path(file); struct stat info; - _syscall(stat(path.c_str(), &info)); + if (stat(path.c_str(), &info) == -1) { + fprintf(stderr, "ldid: %s: %s\n", path.c_str(), strerror(errno)); + exit(1); + } if (S_ISDIR(info.st_mode)) { -#ifndef LDID_NOPLIST - _assert(!flag_r); - ldid::DiskFolder folder(path); + if (!flag_S) { + fprintf(stderr, "ldid: Only -S can be used on directories\n"); + exit(1); + } + 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); @@ -3022,15 +3453,7 @@ int main(int argc, char *argv[]) { Commit(path, temp); } - bool modify(false); -#ifndef LDID_NOFLAGT - if (flag_T) - modify = true; -#endif - if (flag_s) - modify = true; - - Map mapping(path, modify); + Map mapping(path, flag_D ? true : false); FatHeader fat_header(mapping.data(), mapping.size()); _foreach (mach_header, fat_header.GetMachHeaders()) { @@ -3050,8 +3473,7 @@ int main(int argc, char *argv[]) { _foreach (load_command, mach_header.GetLoadCommands()) { uint32_t cmd(mach_header.Swap(load_command->cmd)); - if (false); - else if (cmd == LC_CODE_SIGNATURE) + if (cmd == LC_CODE_SIGNATURE) signature = reinterpret_cast(load_command); else if (cmd == LC_ENCRYPTION_INFO || cmd == LC_ENCRYPTION_INFO_64) encryption = reinterpret_cast(load_command); @@ -3067,34 +3489,26 @@ int main(int argc, char *argv[]) { } } } -#ifndef LDID_NOFLAGT - else if (cmd == LC_ID_DYLIB) { - volatile struct dylib_command *dylib_command(reinterpret_cast(load_command)); - - if (flag_T) { - uint32_t timed; - - if (!timeh) - timed = timev; - else { - dylib_command->dylib.timestamp = 0; - timed = hash(reinterpret_cast(mach_header.GetBase()), mach_header.GetSize(), timev); - } + } - 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) { - _assert(encryption != NULL); + if (encryption == NULL) { + fprintf(stderr, "ldid: -D requires an encrypted binary\n"); + exit(1); + } encryption->cryptid = mach_header.Swap(0); } - if (flag_e) { - _assert(signature != NULL); + if ((flag_e || flag_q || flag_h) && signature == NULL) { + fprintf(stderr, "ldid: -e, -q, and -h requre a signed binary\n"); + exit(1); + } + if (flag_e) { uint32_t data = mach_header.Swap(signature->dataoff); uint8_t *top = reinterpret_cast(mach_header.GetBase()); @@ -3110,8 +3524,6 @@ 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()); @@ -3126,8 +3538,12 @@ int main(int argc, char *argv[]) { } } - if (flag_s) { - _assert(signature != NULL); + if (flag_h) { + char *buf = _syscall(realpath(file.c_str(), NULL)); + printf("Executable=%s\n", buf); + free(buf); + + auto algorithms(GetAlgorithms()); uint32_t data = mach_header.Swap(signature->dataoff); @@ -3135,20 +3551,74 @@ int main(int argc, char *argv[]) { 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_CODEDIRECTORY) { + struct Candidate { + CodeDirectory *directory_; + size_t size_; + Algorithm &algorithm_; + std::string hash_; + uint32_t offset; + }; + + 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), begin}}); + } + } - uint8_t (*hashes)[LDID_SHA1_DIGEST_LENGTH] = reinterpret_cast(blob + begin + Swap(directory->hashOffset)); - uint32_t pages = Swap(directory->nCodeSlots); + _assert(!candidates.empty()); + auto best(candidates.end()); + --best; + + const auto directory(best->second.directory_); + const auto flags(Swap(directory->flags)); + + printf("Identifier=%s\n", blob + best->second.offset + Swap(directory->identOffset)); + + 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); - if (pages != 1) - 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]); - } + printf("CDHash=%s\n", best->second.hash_.c_str()); } } @@ -3158,6 +3628,11 @@ int main(int argc, char *argv[]) { ++filei; } +# if OPENSSL_VERSION_MAJOR >= 3 + OSSL_PROVIDER_unload(legacy); + OSSL_PROVIDER_unload(deflt); +# endif + return filee; } -#endif +#endif // LDID_NOTOOLS