+ }
+}
+
+std::string Analyze(const void *data, size_t size) {
+ std::string entitlements;
+
+ FatHeader fat_header(const_cast<void *>(data), size);
+ _foreach (mach_header, fat_header.GetMachHeaders())
+ Analyze(mach_header, fun([&](const char *data, size_t size) {
+ if (entitlements.empty())
+ entitlements.assign(data, size);
+ else
+ _assert(entitlements.compare(0, entitlements.size(), data, size) == 0);
+ }));
+
+ return entitlements;
+}
+
+static void Allocate(const void *idata, size_t isize, std::streambuf &output, const Functor<size_t (const MachHeader &, Baton &, size_t)> &allocate, const Functor<size_t (const MachHeader &, const Baton &, std::streambuf &output, size_t, size_t, size_t, const std::string &, const char *, const Progress &)> &save, const Progress &progress) {
+ FatHeader source(const_cast<void *>(idata), isize);
+
+ size_t offset(0);
+ if (source.IsFat())
+ offset += sizeof(fat_header) + sizeof(fat_arch) * source.Swap(source->nfat_arch);
+
+ std::vector<CodesignAllocation> allocations;
+ _foreach (mach_header, source.GetMachHeaders()) {
+ struct linkedit_data_command *signature(NULL);
+ struct symtab_command *symtab(NULL);
+
+ _foreach (load_command, mach_header.GetLoadCommands()) {
+ uint32_t cmd(mach_header.Swap(load_command->cmd));
+ if (false);
+ else if (cmd == LC_CODE_SIGNATURE)
+ signature = reinterpret_cast<struct linkedit_data_command *>(load_command);
+ else if (cmd == LC_SYMTAB)
+ symtab = reinterpret_cast<struct symtab_command *>(load_command);
+ }
+
+ size_t size;
+ if (signature == NULL)
+ size = mach_header.GetSize();
+ else {
+ size = mach_header.Swap(signature->dataoff);
+ _assert(size <= mach_header.GetSize());
+ }
+
+ if (symtab != NULL) {
+ auto end(mach_header.Swap(symtab->stroff) + mach_header.Swap(symtab->strsize));
+ if (symtab->stroff != 0 || symtab->strsize != 0) {
+ _assert(end <= size);
+ _assert(end >= size - 0x10);
+ size = end;
+ }
+ }
+
+ Baton baton;
+ size_t alloc(allocate(mach_header, baton, size));
+
+ auto *fat_arch(mach_header.GetFatArch());
+ uint32_t align;
+
+ if (fat_arch != NULL)
+ align = source.Swap(fat_arch->align);
+ else switch (mach_header.GetCPUType()) {
+ case CPU_TYPE_POWERPC:
+ case CPU_TYPE_POWERPC64:
+ case CPU_TYPE_X86:
+ case CPU_TYPE_X86_64:
+ align = 0xc;
+ break;
+ case CPU_TYPE_ARM:
+ case CPU_TYPE_ARM64:
+ case CPU_TYPE_ARM64_32:
+ align = 0xe;
+ break;
+ default:
+ align = 0x0;
+ break;
+ }
+
+ const char *arch(NULL);
+ switch (mach_header.GetCPUType()) {
+ case CPU_TYPE_POWERPC:
+ arch = "ppc";
+ break;
+ case CPU_TYPE_POWERPC64:
+ arch = "ppc64";
+ break;
+ case CPU_TYPE_X86:
+ arch = "i386";
+ break;
+ case CPU_TYPE_X86_64:
+ arch = "x86_64";
+ break;
+ case CPU_TYPE_ARM:
+ arch = "arm";
+ break;
+ case CPU_TYPE_ARM64:
+ arch = "arm64";
+ break;
+ case CPU_TYPE_ARM64_32:
+ arch = "arm64_32";
+ break;
+ }
+
+ offset = Align(offset, 1 << align);
+
+ uint32_t limit(size);
+ if (alloc != 0)
+ limit = Align(limit, 0x10);
+
+ allocations.push_back(CodesignAllocation(mach_header, offset, size, limit, alloc, align, arch, baton));
+ offset += size + alloc;
+ offset = Align(offset, 0x10);
+ }
+
+ size_t position(0);
+
+ if (source.IsFat()) {
+ fat_header fat_header;
+ fat_header.magic = Swap(FAT_MAGIC);
+ fat_header.nfat_arch = Swap(uint32_t(allocations.size()));
+ 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(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);
+ }
+ }
+
+ _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<std::string> commands;
+
+ _foreach (load_command, mach_header.GetLoadCommands()) {
+ std::string copy(reinterpret_cast<const char *>(load_command), load_command->cmdsize);
+
+ switch (mach_header.Swap(load_command->cmd)) {
+ case LC_CODE_SIGNATURE:
+ continue;
+ break;
+
+ // XXX: this is getting ridiculous: provide a better abstraction
+
+ case LC_SEGMENT: {
+ auto segment_command(reinterpret_cast<struct segment_command *>(©[0]));
+
+ 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<struct segment_command_64 *>(©[0]));
+
+ 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;
+ }
+
+ commands.push_back(copy);
+ }
+
+ if (allocation.alloc_ != 0) {
+ linkedit_data_command signature;
+ signature.cmd = mach_header.Swap(LC_CODE_SIGNATURE);
+ signature.cmdsize = mach_header.Swap(uint32_t(sizeof(signature)));
+ signature.dataoff = mach_header.Swap(allocation.limit_);
+ signature.datasize = mach_header.Swap(allocation.alloc_);
+ commands.push_back(std::string(reinterpret_cast<const char *>(&signature), sizeof(signature)));
+ }
+
+ size_t begin(position);
+
+ uint32_t after(0);
+ _foreach(command, commands)
+ after += command.size();
+
+ std::stringbuf altern;
+
+ struct mach_header header(*mach_header);
+ header.ncmds = mach_header.Swap(uint32_t(commands.size()));
+ header.sizeofcmds = mach_header.Swap(after);
+ put(output, &header, sizeof(header));
+ put(altern, &header, sizeof(header));
+ position += sizeof(header);
+
+ if (mach_header.Bits64()) {
+ auto pad(mach_header.Swap(uint32_t(0)));
+ put(output, &pad, sizeof(pad));
+ put(altern, &pad, sizeof(pad));
+ position += sizeof(pad);
+ }
+
+ _foreach(command, commands) {
+ put(output, command.data(), command.size());
+ put(altern, command.data(), command.size());
+ position += command.size();
+ }
+
+ uint32_t before(mach_header.Swap(mach_header->sizeofcmds));
+ if (before > after) {
+ pad(output, before - after);
+ pad(altern, before - after);
+ position += before - after;
+ }
+
+ auto top(reinterpret_cast<char *>(mach_header.GetBase()));
+
+ 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), progress);
+ position = begin + allocation.size_;
+
+ pad(output, allocation.limit_ - allocation.size_);
+ position += allocation.limit_ - allocation.size_;
+
+ size_t saved(save(mach_header, allocation.baton_, output, allocation.limit_, left, right, overlap, top, progress));
+ if (allocation.alloc_ > saved)
+ pad(output, allocation.alloc_ - saved);
+ else
+ _assert(allocation.alloc_ == saved);
+ position += allocation.alloc_;
+ }
+}
+
+}
+
+typedef std::map<uint32_t, std::string> Blobs;
+
+static void insert(Blobs &blobs, uint32_t slot, const std::stringbuf &buffer) {
+ auto value(buffer.str());
+ std::swap(blobs[slot], value);
+}
+
+static const std::string &insert(Blobs &blobs, uint32_t slot, uint32_t magic, const std::stringbuf &buffer) {
+ auto value(buffer.str());
+ Blob blob;
+ blob.magic = Swap(magic);
+ blob.length = Swap(uint32_t(sizeof(blob) + value.size()));
+ value.insert(0, reinterpret_cast<char *>(&blob), sizeof(blob));
+ auto &save(blobs[slot]);
+ std::swap(save, value);
+ return save;
+}
+
+static size_t put(std::streambuf &output, uint32_t magic, const Blobs &blobs) {
+ size_t total(0);
+ _foreach (blob, blobs)
+ total += blob.second.size();
+
+ struct SuperBlob super;
+ super.blob.magic = Swap(magic);
+ super.blob.length = Swap(uint32_t(sizeof(SuperBlob) + blobs.size() * sizeof(BlobIndex) + total));
+ super.count = Swap(uint32_t(blobs.size()));
+ put(output, &super, sizeof(super));
+
+ size_t offset(sizeof(SuperBlob) + sizeof(BlobIndex) * blobs.size());
+
+ _foreach (blob, blobs) {
+ BlobIndex index;
+ index.type = Swap(blob.first);
+ index.offset = Swap(uint32_t(offset));
+ put(output, &index, sizeof(index));
+ offset += blob.second.size();
+ }
+
+ _foreach (blob, blobs)
+ put(output, blob.second.data(), blob.second.size());
+
+ return offset;
+}
+
+#ifndef LDID_NOSMIME
+class Buffer {
+ private:
+ BIO *bio_;
+
+ public:
+ Buffer(BIO *bio) :
+ bio_(bio)
+ {
+ _assert(bio_ != NULL);
+ }
+
+ Buffer() :
+ bio_(BIO_new(BIO_s_mem()))
+ {
+ }
+
+ Buffer(const char *data, size_t size) :
+ Buffer(BIO_new_mem_buf(const_cast<char *>(data), size))
+ {
+ }
+
+ Buffer(const std::string &data) :
+ Buffer(data.data(), data.size())
+ {
+ }
+
+ Buffer(PKCS7 *pkcs) :
+ Buffer()
+ {
+ 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(CMS_ContentInfo *cms) :
+ Buffer()
+ {
+ _assert(i2d_CMS_bio(bio_, cms) != 0);
+ }
+
+
+ ~Buffer() {
+ BIO_free_all(bio_);
+ }
+
+ operator BIO *() const {
+ return bio_;
+ }
+
+ explicit operator std::string() const {
+ char *data;
+ auto size(BIO_get_mem_data(bio_, &data));
+ return std::string(data, size);
+ }
+};
+
+class Stuff {
+ private:
+ PKCS12 *value_;
+ EVP_PKEY *key_;
+ X509 *cert_;
+ STACK_OF(X509) *ca_;
+
+ public:
+ Stuff(BIO *bio) :
+ value_(d2i_PKCS12_bio(bio, NULL)),
+ ca_(NULL)
+ {
+ 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;
+ }
+
+ 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();
+ 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) :
+ Stuff(Buffer(data))
+ {
+ }
+
+ ~Stuff() {
+ sk_X509_pop_free(ca_, X509_free);
+ X509_free(cert_);
+ EVP_PKEY_free(key_);
+ PKCS12_free(value_);
+ }
+
+ operator PKCS12 *() const {
+ return value_;
+ }
+
+ operator EVP_PKEY *() const {
+ return key_;
+ }
+
+ operator X509 *() const {
+ return cert_;
+ }
+
+ operator STACK_OF(X509) *() const {
+ return ca_;
+ }
+};
+
+class Signature {
+ private:
+ CMS_ContentInfo *value_;
+
+ public:
+ Signature(const Stuff &stuff, const Buffer &data, const std::string &xml,const std::vector<char>& alternateCDSHA256) {
+ //
+ int flags = CMS_PARTIAL | CMS_DETACHED | CMS_NOSMIMECAP | CMS_BINARY;
+ //--------------------------------------------
+ auto issuer_name(X509_get_issuer_name(stuff));
+ _assert(issuer_name != NULL);
+ std::string issuer;
+ auto index(X509_NAME_get_index_by_NID(issuer_name, NID_commonName, -1));
+ _assert(index >= 0);
+ auto next(X509_NAME_get_index_by_NID(issuer_name, NID_commonName, index));
+ _assert(next == -1);
+ auto entry(X509_NAME_get_entry(issuer_name, index));
+ _assert(entry != NULL);
+ auto asn(X509_NAME_ENTRY_get_data(entry));
+ _assert(asn != NULL);
+ issuer.assign(reinterpret_cast<const char *>(ASN1_STRING_get0_data(asn)), ASN1_STRING_length(asn));
+
+ CMS_ContentInfo *stream = CMS_sign(NULL, NULL, stuff, NULL, flags);
+
+ CMS_SignerInfo *info = CMS_add1_signer(stream, stuff, stuff, EVP_sha256(), flags);
+
+
+ // Hash Agility
+ ASN1_OBJECT *obj = OBJ_txt2obj("1.2.840.113635.100.9.1", 1);
+ CMS_signed_add1_attr_by_OBJ(info, obj, 0x4, xml.c_str(), (int)xml.size());
+
+ // CDHashes (iOS 15.1+)
+ std::string sha256;
+ for (size_t i = 0; i < alternateCDSHA256.size(); i++)
+ {
+ char buf[16] = {0};
+ sprintf(buf, "%02X", (uint8_t)alternateCDSHA256[i]);
+ sha256 += buf;
+ }
+
+ 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);
+
+ ASN1_TYPE *type256 = GenerateASN1Type(sha256);
+ if (type256 != NULL)
+ {
+ X509_ATTRIBUTE_set1_data(attribute, V_ASN1_SEQUENCE, type256->value.asn1_string->data, type256->value.asn1_string->length);
+ }
+
+ CMS_signed_add1_attr(info, attribute);
+
+ CMS_final(stream, data, NULL, flags);
+
+
+ value_ = stream;
+ _assert(value_ != NULL);
+ }
+
+ ~Signature() {
+ CMS_ContentInfo_free(value_);
+ }
+ operator CMS_ContentInfo *() const {
+ return value_;
+ }
+};
+#endif
+
+class NullBuffer :
+ public std::streambuf
+{
+ public:
+ virtual std::streamsize xsputn(const char_type *data, std::streamsize size) {
+ return size;
+ }
+
+ virtual int_type overflow(int_type next) {
+ return next;
+ }
+};
+
+class HashBuffer :
+ public std::streambuf
+{
+ private:
+ ldid::Hash &hash_;
+
+ LDID_SHA1_CTX sha1_;
+ LDID_SHA256_CTX sha256_;
+
+ public:
+ HashBuffer(ldid::Hash &hash) :
+ hash_(hash)
+ {
+ LDID_SHA1_Init(&sha1_);
+ LDID_SHA256_Init(&sha256_);
+ }
+
+ ~HashBuffer() {
+ LDID_SHA1_Final(reinterpret_cast<uint8_t *>(hash_.sha1_), &sha1_);
+ LDID_SHA256_Final(reinterpret_cast<uint8_t *>(hash_.sha256_), &sha256_);
+ }
+
+ virtual std::streamsize xsputn(const char_type *data, std::streamsize size) {
+ LDID_SHA1_Update(&sha1_, data, size);
+ LDID_SHA256_Update(&sha256_, data, size);
+ return size;
+ }
+
+ virtual int_type overflow(int_type next) {
+ if (next == traits_type::eof())
+ return sync();
+ char value(next);
+ xsputn(&value, 1);
+ return next;
+ }
+};
+
+class HashProxy :
+ public HashBuffer
+{
+ private:
+ std::streambuf &buffer_;
+
+ public:
+ HashProxy(ldid::Hash &hash, std::streambuf &buffer) :
+ HashBuffer(hash),
+ buffer_(buffer)
+ {
+ }
+
+ virtual std::streamsize xsputn(const char_type *data, std::streamsize size) {
+ _assert(HashBuffer::xsputn(data, size) == size);
+ return buffer_.sputn(data, size);
+ }
+};
+
+#ifndef LDID_NOTOOLS
+static bool Starts(const std::string &lhs, const std::string &rhs) {
+ return lhs.size() >= rhs.size() && lhs.compare(0, rhs.size(), rhs) == 0;
+}
+
+class Split {
+ public:
+ std::string dir;
+ std::string base;
+
+ Split(const std::string &path) {
+ size_t slash(path.rfind('/'));
+ if (slash == std::string::npos)
+ base = path;
+ else {
+ dir = path.substr(0, slash + 1);
+ base = path.substr(slash + 1);
+ }
+ }
+};
+
+static void mkdir_p(const std::string &path) {
+ if (path.empty())
+ return;
+#ifdef __WIN32__
+ if (_syscall(mkdir(path.c_str()), EEXIST) == -EEXIST)
+ return;
+#else
+ if (_syscall(mkdir(path.c_str(), 0755), EEXIST) == -EEXIST)
+ return;
+#endif
+ auto slash(path.rfind('/', path.size() - 1));
+ if (slash == std::string::npos)
+ return;
+ mkdir_p(path.substr(0, slash));
+}
+
+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());
+ return temp;
+}
+
+static void Commit(const std::string &path, const std::string &temp) {
+ struct stat info;
+ if (_syscall(stat(path.c_str(), &info), ENOENT) == 0) {
+#ifndef __WIN32__
+ _syscall(chown(temp.c_str(), info.st_uid, info.st_gid));
+#endif
+ _syscall(chmod(temp.c_str(), info.st_mode));
+ }
+
+ _syscall(rename(temp.c_str(), path.c_str()));
+}
+#endif
+
+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);
+ auto next(X509_NAME_get_index_by_NID(name, nid, index));
+ _assert(next == -1);
+ auto entry(X509_NAME_get_entry(name, index));
+ _assert(entry != NULL);
+ auto asn(X509_NAME_ENTRY_get_data(entry));
+ _assert(asn != NULL);
+ value.assign(reinterpret_cast<const char *>(ASN1_STRING_get0_data(asn)), ASN1_STRING_length(asn));
+}
+#endif
+
+static void req(std::streambuf &buffer, uint32_t value) {
+ value = Swap(value);
+ put(buffer, &value, sizeof(value));
+}
+
+static void req(std::streambuf &buffer, const std::string &value) {
+ req(buffer, value.size());
+ put(buffer, value.data(), value.size());
+ static uint8_t zeros[] = {0,0,0,0};
+ put(buffer, zeros, 3 - (value.size() + 3) % 4);
+}
+
+template <size_t Size_>
+static void req(std::streambuf &buffer, uint8_t (&&data)[Size_]) {
+ req(buffer, Size_);
+ put(buffer, data, Size_);
+ static uint8_t zeros[] = {0,0,0,0};
+ put(buffer, zeros, 3 - (Size_ + 3) % 4);
+}
+
+Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, uint32_t flags, bool platform, const Progress &progress) {
+ Hash hash;
+
+
+ std::string team;
+ std::string common;
+
+#ifndef LDID_NOSMIME
+ if (!key.empty()) {
+ Stuff stuff(key);
+ auto name(X509_get_subject_name(stuff));
+ 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;
+
+ if (!requirements.empty()) {
+ put(backing, requirements.data(), requirements.size());
+ } else {
+ Blobs blobs;
+
+ std::stringbuf requirement;
+ req(requirement, exprForm);
+ req(requirement, opAnd);
+ req(requirement, opIdent);
+ req(requirement, identifier);
+ req(requirement, opAnd);
+ req(requirement, opAppleGenericAnchor);
+ req(requirement, opAnd);
+ req(requirement, opCertField);
+ req(requirement, 0);
+ req(requirement, "subject.CN");
+ req(requirement, matchEqual);
+ req(requirement, common);
+ req(requirement, opCertGeneric);
+ req(requirement, 1);
+ req(requirement, (uint8_t []) {APPLE_EXTENSION_OID, 2, 1});
+ req(requirement, matchExists);
+ insert(blobs, 3, CSMAGIC_REQUIREMENT, requirement);
+
+ put(backing, CSMAGIC_REQUIREMENTS, blobs);
+ }
+
+
+ // XXX: this is just a "sufficiently large number"
+ size_t certificate(0x3000);
+
+ Allocate(idata, isize, output, fun([&](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t {
+ size_t alloc(sizeof(struct SuperBlob));
+
+ uint32_t normal((size + PageSize_ - 1) / PageSize_);
+
+ uint32_t special(0);
+
+ _foreach (slot, slots)
+ special = std::max(special, slot.first);
+
+ mach_header.ForSection(fun([&](const char *segment, const char *section, void *data, size_t size) {
+ if (strcmp(segment, "__TEXT") == 0 && section != NULL && strcmp(section, "__info_plist") == 0)
+ special = std::max(special, CSSLOT_INFOSLOT);
+ }));
+
+ special = std::max(special, CSSLOT_REQUIREMENTS);
+ alloc += sizeof(struct BlobIndex);
+ alloc += backing.str().size();
+
+#ifdef LDID_NOPLIST
+ baton.entitlements_ = entitlements;
+#else
+ if (merge)
+ Analyze(mach_header, fun([&](const char *data, size_t size) {
+ baton.entitlements_.assign(data, size);
+ }));
+
+ if (!baton.entitlements_.empty() || !entitlements.empty()) {
+ auto combined(plist(baton.entitlements_));
+ _scope({ plist_free(combined); });
+ _assert(plist_get_node_type(combined) == PLIST_DICT);
+
+ auto merging(plist(entitlements));
+ _scope({ plist_free(merging); });
+ _assert(plist_get_node_type(merging) == PLIST_DICT);
+
+ plist_dict_iter iterator(NULL);
+ plist_dict_new_iter(merging, &iterator);
+ _scope({ free(iterator); });
+
+ for (;;) {
+ char *key(NULL);
+ plist_t value(NULL);
+ plist_dict_next_item(merging, iterator, &key, &value);
+ if (key == NULL)
+ break;
+ _scope({ free(key); });
+ plist_dict_set_item(combined, key, plist_copy(value));
+ }
+
+ baton.derformat_ = der(combined);
+
+ char *xml(NULL);
+ uint32_t size;
+ plist_to_xml(combined, &xml, &size);
+ _scope({ free(xml); });
+
+ baton.entitlements_.assign(xml, size);
+ }
+#endif
+
+ if (!baton.entitlements_.empty()) {
+ special = std::max(special, CSSLOT_ENTITLEMENTS);
+ alloc += sizeof(struct BlobIndex);
+ alloc += sizeof(struct Blob);
+ alloc += 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);
+ directory += sizeof(struct Blob);
+ directory += sizeof(struct CodeDirectory);
+ directory += identifier.size() + 1;
+
+ if (!team.empty())
+ directory += team.size() + 1;
+
+ for (Algorithm *algorithm : GetAlgorithms())
+ alloc = Align(alloc + directory + (special + normal) * algorithm->size_, 16);
+
+#ifndef LDID_NOSMIME
+ if (!key.empty()) {
+ alloc += sizeof(struct BlobIndex);
+ alloc += sizeof(struct Blob);
+ alloc += certificate;
+ }
+#endif
+
+ return alloc;
+ }), 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);
+
+#ifndef LDID_NOPLIST
+ 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;
+#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);
+
+ mach_header.ForSection(fun([&](const char *segment, const char *section, void *data, size_t size) {
+ if (strcmp(segment, "__TEXT") == 0 && section != NULL && strcmp(section, "__info_plist") == 0) {
+ auto &slot(posts[CSSLOT_INFOSLOT]);
+ for (Algorithm *algorithm : GetAlgorithms())
+ (*algorithm)(slot, data, size);
+ }
+ }));
+
+ unsigned total(0);
+ for (Algorithm *pointer : GetAlgorithms()) {
+ Algorithm &algorithm(*pointer);
+
+ std::stringbuf data;
+
+ uint32_t special(0);
+ _foreach (blob, blobs)
+ special = std::max(special, blob.first);
+ _foreach (slot, posts)
+ special = std::max(special, slot.first);
+ uint32_t normal((limit + PageSize_ - 1) / PageSize_);
+
+ CodeDirectory directory;
+ directory.version = Swap(uint32_t(0x00020400));
+ directory.flags = Swap(uint32_t(flags));
+ directory.nSpecialSlots = Swap(special);
+ directory.codeLimit = Swap(uint32_t(limit > UINT32_MAX ? UINT32_MAX : limit));
+ directory.nCodeSlots = Swap(normal);
+ directory.hashSize = algorithm.size_;
+ directory.hashType = algorithm.type_;
+ directory.platform = platform ? 0x01 : 0x00;
+ directory.pageSize = PageShift_;
+ directory.spare2 = Swap(uint32_t(0));
+ directory.scatterOffset = Swap(uint32_t(0));
+ directory.spare3 = Swap(uint32_t(0));
+ directory.codeLimit64 = Swap(uint64_t(limit > UINT32_MAX ? limit : 0));
+ directory.execSegBase = Swap(uint64_t(left));
+ directory.execSegLimit = Swap(uint64_t(right - left));
+ directory.execSegFlags = Swap(execs);
+
+ uint32_t offset(sizeof(Blob) + sizeof(CodeDirectory));
+
+ directory.identOffset = Swap(uint32_t(offset));
+ offset += identifier.size() + 1;
+
+ if (team.empty())
+ directory.teamIDOffset = Swap(uint32_t(0));
+ else {
+ directory.teamIDOffset = Swap(uint32_t(offset));
+ offset += team.size() + 1;
+ }
+
+ offset += special * algorithm.size_;
+ directory.hashOffset = Swap(uint32_t(offset));
+ offset += normal * algorithm.size_;
+
+ put(data, &directory, sizeof(directory));
+
+ put(data, identifier.c_str(), identifier.size() + 1);
+ if (!team.empty())
+ put(data, team.c_str(), team.size() + 1);
+
+ std::vector<uint8_t> storage((special + normal) * algorithm.size_);
+ auto *hashes(&storage[special * algorithm.size_]);
+
+ memset(storage.data(), 0, special * algorithm.size_);
+
+ _foreach (blob, blobs) {
+ auto local(reinterpret_cast<const Blob *>(&blob.second[0]));
+ algorithm(hashes - blob.first * algorithm.size_, local, Swap(local->length));
+ }
+
+ _foreach (slot, posts)
+ memcpy(hashes - slot.first * algorithm.size_, algorithm[slot.second], algorithm.size_);
+
+ progress(0);
+ if (normal != 1)
+ for (size_t i = 0; i != normal - 1; ++i) {
+ algorithm(hashes + i * algorithm.size_, (PageSize_ * i < overlap.size() ? overlap.data() : top) + PageSize_ * i, PageSize_);
+ progress(double(i) / normal);
+ }
+ if (normal != 0)
+ algorithm(hashes + (normal - 1) * algorithm.size_, top + PageSize_ * (normal - 1), ((limit - 1) % PageSize_) + 1);
+ progress(1);
+
+ put(data, storage.data(), storage.size());
+
+ const auto &save(insert(blobs, total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1, CSMAGIC_CODEDIRECTORY, data));
+ algorithm(hash, save.data(), save.size());
+
+ ++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<char> alternateCDSHA256;
+
+ unsigned total(0);
+ for (Algorithm *pointer : GetAlgorithms()) {
+ Algorithm &algorithm(*pointer);
+ (void) algorithm;
+
+ const auto &blob(blobs[total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1]);
+ ++total;
+
+ std::vector<char> hash;
+ algorithm(hash, blob.data(), blob.size());
+ hash.resize(20);
+
+ if (algorithm.type_ == CS_HASHTYPE_SHA256_256){
+ alternateCDSHA256 = hash;
+ }
+
+
+#ifdef LDID_NOPLIST
+ auto value(CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(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<const char *>(CFDataGetBytePtr(created)));
+ auto size(CFDataGetLength(created));
+#else
+ char *xml(NULL);
+ uint32_t size;
+ plist_to_xml(plist, &xml, &size);
+ _scope({ free(xml); });
+#endif
+
+ std::stringbuf data;
+ const std::string &sign(blobs[CSSLOT_CODEDIRECTORY]);
+
+ Stuff stuff(key);
+ Buffer bio(sign);
+
+ Signature signature(stuff, sign, std::string(xml, size), alternateCDSHA256);
+ Buffer result(signature);
+ std::string value(result);
+ put(data, value.data(), value.size());
+
+ const auto &save(insert(blobs, CSSLOT_SIGNATURESLOT, CSMAGIC_BLOBWRAPPER, data));
+ _assert(save.size() <= certificate);
+ }
+#endif
+
+ return put(output, CSMAGIC_EMBEDDED_SIGNATURE, blobs);
+ }), progress);
+
+ return hash;
+}
+
+#ifndef LDID_NOTOOLS
+static void Unsign(void *idata, size_t isize, std::streambuf &output, const Progress &progress) {
+ Allocate(idata, isize, output, fun([](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t {
+ return 0;
+ }), fun([](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;
+}
+
+DiskFolder::DiskFolder(const std::string &path) :
+ path_(path)
+{
+ _assert_(path_.size() != 0 && path_[path_.size() - 1] == '/', "missing / on %s", path_.c_str());
+}
+
+DiskFolder::~DiskFolder() {
+ if (!std::uncaught_exception())
+ for (const auto &commit : commit_)
+ Commit(commit.first, commit.second);
+}
+
+#ifndef __WIN32__
+std::string readlink(const std::string &path) {
+ for (size_t size(1024); ; size *= 2) {
+ std::string data;
+ data.resize(size);
+
+ int writ(_syscall(::readlink(path.c_str(), &data[0], data.size())));
+ if (size_t(writ) >= size)
+ continue;
+
+ data.resize(writ);
+ return data;
+ }
+}
+#endif
+
+void DiskFolder::Find(const std::string &root, const std::string &base, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ std::string path(Path(root) + base);
+
+ DIR *dir(opendir(path.c_str()));
+ _assert(dir != NULL);
+ _scope({ _syscall(closedir(dir)); });
+
+ while (auto child = readdir(dir)) {
+ std::string name(child->d_name);
+ if (name == "." || name == "..")
+ continue;
+ if (Starts(name, ".ldid."))
+ continue;
+
+ bool directory;
+
+#ifdef __WIN32__
+ struct stat info;
+ _syscall(stat((path + name).c_str(), &info));
+ if (false);
+ else if (S_ISDIR(info.st_mode))
+ directory = true;
+ else if (S_ISREG(info.st_mode))
+ directory = false;
+ else
+ _assert_(false, "st_mode=%x", info.st_mode);
+#else
+ switch (child->d_type) {
+ case DT_DIR:
+ directory = true;
+ break;
+ case DT_REG:
+ directory = false;
+ break;
+ case DT_LNK:
+ link(base + name, fun([&]() { return readlink(path + name); }));
+ continue;
+ default:
+ _assert_(false, "d_type=%u", child->d_type);
+ }
+#endif
+
+ if (directory)
+ Find(root, base + name + "/", code, link);
+ else
+ code(base + name);
+ }
+}
+
+void DiskFolder::Save(const std::string &path, bool edit, const void *flag, const Functor<void (std::streambuf &)> &code) {
+ if (!edit) {
+ NullBuffer save;
+ code(save);
+ } else {
+ std::filebuf save;
+ auto from(Path(path));
+ commit_[from] = Temporary(save, from);
+ code(save);
+ }
+}
+
+bool DiskFolder::Look(const std::string &path) const {
+ return _syscall(access(Path(path).c_str(), R_OK), ENOENT) == 0;
+}
+
+void DiskFolder::Open(const std::string &path, const Functor<void (std::streambuf &, size_t, const void *)> &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(path).c_str());
+
+ auto length(data.pubseekoff(0, std::ios::end, std::ios::in));
+ data.pubseekpos(0, std::ios::in);
+ code(data, length, NULL);
+}
+
+void DiskFolder::Find(const std::string &path, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ Find(path, "", code, link);
+}
+#endif
+
+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<void (std::streambuf &)> &code) {
+ return parent_.Save(Path(path), edit, flag, code);
+}
+
+bool SubFolder::Look(const std::string &path) const {
+ return parent_.Look(Path(path));
+}
+
+void SubFolder::Open(const std::string &path, const Functor<void (std::streambuf &, size_t, const void *)> &code) const {
+ return parent_.Open(Path(path), code);
+}
+
+void SubFolder::Find(const std::string &path, const Functor<void (const std::string &)> &code, const Functor<void (const std::string &, const Functor<std::string ()> &)> &link) const {
+ return parent_.Find(Path(path), code, link);
+}
+
+std::string UnionFolder::Map(const std::string &path) const {
+ auto remap(remaps_.find(path));
+ if (remap == remaps_.end())
+ return path;
+ return remap->second;
+}
+
+void UnionFolder::Map(const std::string &path, const Functor<void (const std::string &)> &code, const std::string &file, const Functor<void (const Functor<void (std::streambuf &, size_t, const void *)> &)> &save) const {
+ if (file.size() >= path.size() && file.substr(0, path.size()) == path)
+ code(file.substr(path.size()));
+}
+
+UnionFolder::UnionFolder(Folder &parent) :
+ parent_(parent)
+{
+}