X-Git-Url: https://git.cameronkatri.com/ldid.git/blobdiff_plain/f74652ab3bfc5026ef954a35aeb137a18f55bf67..ec3af45e12e60355d13d2caed08070b4ccb64e3b:/ldid.cpp diff --git a/ldid.cpp b/ldid.cpp index 9027c67..38b9343 100644 --- a/ldid.cpp +++ b/ldid.cpp @@ -85,6 +85,8 @@ #ifndef LDID_NOPLIST #include +#elif __APPLE__ +#include #endif #include "ldid.hpp" @@ -94,23 +96,27 @@ #define _assert__(line) \ _assert___(line) +#ifndef $ +#define $(value) value +#endif + #ifdef __EXCEPTIONS #define _assert_(expr, format, ...) \ do if (!(expr)) { \ - fprintf(stderr, "%s(%u): _assert(): " format "\n", __FILE__, __LINE__, ## __VA_ARGS__); \ - throw __FILE__ "(" _assert__(__LINE__) "): _assert(" #expr ")"; \ + fprintf(stderr, $("%s(%u): _assert(): " format "\n"), __FILE__, __LINE__, ## __VA_ARGS__); \ + throw $(__FILE__ "(" _assert__(__LINE__) "): _assert(" #expr ")"); \ } while (false) #else // XXX: this is not acceptable #define _assert_(expr, format, ...) \ do if (!(expr)) { \ - fprintf(stderr, "%s(%u): _assert(): " format "\n", __FILE__, __LINE__, ## __VA_ARGS__); \ + fprintf(stderr, $("%s(%u): _assert(): " format "\n"), __FILE__, __LINE__, ## __VA_ARGS__); \ exit(-1); \ } while (false) #endif #define _assert(expr) \ - _assert_(expr, "%s", #expr) + _assert_(expr, "%s", $(#expr)) #define _syscall(expr, ...) [&] { for (;;) { \ auto _value(expr); \ @@ -127,7 +133,7 @@ } }() #define _trace() \ - fprintf(stderr, "_trace(%s:%u): %s\n", __FILE__, __LINE__, __FUNCTION__) + fprintf(stderr, $("_trace(%s:%u): %s\n"), __FILE__, __LINE__, $(__FUNCTION__)) #define _not(type) \ ((type) ~ (type) 0) @@ -475,7 +481,32 @@ struct encryption_info_command { #define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 0xb0 #define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 0xc0 -static auto dummy([](double) {}); +struct : ldid::Progress { + virtual void operator()(const std::string &value) const { + } + + virtual void operator()(double value) const { + } +} dummy_; + +struct Progression : ldid::Progress { + const ldid::Progress &progress_; + std::string name_; + + Progression(const ldid::Progress &progress, const std::string &name) : + progress_(progress), + name_(name) + { + } + + virtual void operator()(const std::string &value) const { + return progress_(name_ + " (" + value + ")"); + } + + virtual void operator()(double value) const { + return progress_(value); + } +}; static std::streamsize read(std::streambuf &stream, void *data, size_t size) { auto writ(stream.sgetn(static_cast(data), size)); @@ -491,13 +522,13 @@ static inline void put(std::streambuf &stream, const void *data, size_t size) { _assert(stream.sputn(static_cast(data), size) == size); } -static inline void put(std::streambuf &stream, const void *data, size_t size, const ldid::Functor &percent) { - percent(0); +static inline void put(std::streambuf &stream, const void *data, size_t size, const ldid::Progress &progress) { + progress(0); for (size_t total(0); total != size;) { auto writ(std::min(size - total, size_t(4096 * 4))); _assert(stream.sputn(static_cast(data) + total, writ) == writ); total += writ; - percent(double(total) / size); + progress(double(total) / size); } } @@ -854,15 +885,73 @@ struct CodeDirectory { uint32_t codeLimit; uint8_t hashSize; uint8_t hashType; - uint8_t spare1; + uint8_t platform; uint8_t pageSize; uint32_t spare2; uint32_t scatterOffset; uint32_t teamIDOffset; - uint32_t spare3; - uint64_t codeLimit64; + //uint32_t spare3; + //uint64_t codeLimit64; } _packed; +enum CodeSignatureFlags { + kSecCodeSignatureHost = 0x0001, + kSecCodeSignatureAdhoc = 0x0002, + kSecCodeSignatureForceHard = 0x0100, + kSecCodeSignatureForceKill = 0x0200, + kSecCodeSignatureForceExpiration = 0x0400, + kSecCodeSignatureRestrict = 0x0800, + kSecCodeSignatureEnforcement = 0x1000, + kSecCodeSignatureLibraryValidation = 0x2000, +}; + +enum Kind : uint32_t { + exprForm = 1, // prefix expr form +}; + +enum ExprOp : uint32_t { + opFalse, // unconditionally false + opTrue, // unconditionally true + opIdent, // match canonical code [string] + opAppleAnchor, // signed by Apple as Apple's product + opAnchorHash, // match anchor [cert hash] + opInfoKeyValue, // *legacy* - use opInfoKeyField [key; value] + opAnd, // binary prefix expr AND expr [expr; expr] + opOr, // binary prefix expr OR expr [expr; expr] + opCDHash, // match hash of CodeDirectory directly [cd hash] + opNot, // logical inverse [expr] + opInfoKeyField, // Info.plist key field [string; match suffix] + opCertField, // Certificate field [cert index; field name; match suffix] + opTrustedCert, // require trust settings to approve one particular cert [cert index] + opTrustedCerts, // require trust settings to approve the cert chain + opCertGeneric, // Certificate component by OID [cert index; oid; match suffix] + opAppleGenericAnchor, // signed by Apple in any capacity + opEntitlementField, // entitlement dictionary field [string; match suffix] + opCertPolicy, // Certificate policy by OID [cert index; oid; match suffix] + opNamedAnchor, // named anchor type + opNamedCode, // named subroutine + opPlatform, // platform constraint [integer] + exprOpCount // (total opcode count in use) +}; + +enum MatchOperation { + matchExists, // anything but explicit "false" - no value stored + matchEqual, // equal (CFEqual) + matchContains, // partial match (substring) + matchBeginsWith, // partial match (initial substring) + matchEndsWith, // partial match (terminal substring) + matchLessThan, // less than (string with numeric comparison) + matchGreaterThan, // greater than (string with numeric comparison) + matchLessEqual, // less or equal (string with numeric comparison) + matchGreaterEqual, // greater or equal (string with numeric comparison) +}; + +#define OID_ISO_MEMBER 42 +#define OID_US OID_ISO_MEMBER, 134, 72 +#define APPLE_OID OID_US, 0x86, 0xf7, 0x63 +#define APPLE_ADS_OID APPLE_OID, 0x64 +#define APPLE_EXTENSION_OID APPLE_ADS_OID, 6 + #ifndef LDID_NOFLAGT extern "C" uint32_t hash(uint8_t *k, uint32_t length, uint32_t initval); #endif @@ -882,6 +971,8 @@ struct Algorithm { virtual void operator ()(uint8_t *hash, const void *data, size_t size) const = 0; virtual void operator ()(ldid::Hash &hash, const void *data, size_t size) const = 0; virtual void operator ()(std::vector &hash, const void *data, size_t size) const = 0; + + virtual const char *name() = 0; }; struct AlgorithmSHA1 : @@ -908,6 +999,10 @@ struct AlgorithmSHA1 : hash.resize(LDID_SHA1_DIGEST_LENGTH); return operator ()(reinterpret_cast(hash.data()), data, size); } + + virtual const char *name() { + return "sha1"; + } }; struct AlgorithmSHA256 : @@ -934,21 +1029,34 @@ struct AlgorithmSHA256 : hash.resize(LDID_SHA256_DIGEST_LENGTH); return operator ()(reinterpret_cast(hash.data()), data, size); } + + virtual const char *name() { + return "sha256"; + } }; +static bool do_sha1(true); +static bool do_sha256(true); + static const std::vector &GetAlgorithms() { static AlgorithmSHA1 sha1; static AlgorithmSHA256 sha256; - static Algorithm *array[] = { - &sha1, - &sha256, - }; + static std::vector algorithms; + if (algorithms.empty()) { + if (do_sha1) + algorithms.push_back(&sha1); + if (do_sha256) + algorithms.push_back(&sha256); + } - static std::vector algorithms(array, array + sizeof(array) / sizeof(array[0])); return algorithms; } +struct Baton { + std::string entitlements_; +}; + struct CodesignAllocation { FatMachHeader mach_header_; uint32_t offset_; @@ -956,14 +1064,18 @@ struct CodesignAllocation { uint32_t limit_; uint32_t alloc_; uint32_t align_; + const char *arch_; + Baton baton_; - CodesignAllocation(FatMachHeader mach_header, size_t offset, size_t size, size_t limit, size_t alloc, size_t align) : + CodesignAllocation(FatMachHeader mach_header, size_t offset, size_t size, size_t limit, size_t alloc, size_t align, const char *arch, const Baton &baton) : mach_header_(mach_header), offset_(offset), size_(size), limit_(limit), alloc_(alloc), - align_(align) + align_(align), + arch_(arch), + baton_(baton) { } }; @@ -1071,35 +1183,44 @@ class Map { namespace ldid { +#ifndef LDID_NOPLIST +static plist_t plist(const std::string &data); +#endif + +void Analyze(const MachHeader &mach_header, const Functor &entitle) { + _foreach (load_command, mach_header.GetLoadCommands()) + if (mach_header.Swap(load_command->cmd) == LC_CODE_SIGNATURE) { + auto signature(reinterpret_cast(load_command)); + auto offset(mach_header.Swap(signature->dataoff)); + auto pointer(reinterpret_cast(mach_header.GetBase()) + offset); + auto super(reinterpret_cast(pointer)); + + for (size_t index(0); index != Swap(super->count); ++index) + if (Swap(super->index[index].type) == CSSLOT_ENTITLEMENTS) { + auto begin(Swap(super->index[index].offset)); + auto blob(reinterpret_cast(pointer + begin)); + auto writ(Swap(blob->length) - sizeof(*blob)); + entitle(reinterpret_cast(blob + 1), writ); + } + } +} + std::string Analyze(const void *data, size_t size) { std::string entitlements; FatHeader fat_header(const_cast(data), size); _foreach (mach_header, fat_header.GetMachHeaders()) - _foreach (load_command, mach_header.GetLoadCommands()) - if (mach_header.Swap(load_command->cmd) == LC_CODE_SIGNATURE) { - auto signature(reinterpret_cast(load_command)); - auto offset(mach_header.Swap(signature->dataoff)); - auto pointer(reinterpret_cast(mach_header.GetBase()) + offset); - auto super(reinterpret_cast(pointer)); - - for (size_t index(0); index != Swap(super->count); ++index) - if (Swap(super->index[index].type) == CSSLOT_ENTITLEMENTS) { - auto begin(Swap(super->index[index].offset)); - auto blob(reinterpret_cast(pointer + begin)); - auto writ(Swap(blob->length) - sizeof(*blob)); - - if (entitlements.empty()) - entitlements.assign(reinterpret_cast(blob + 1), writ); - else - _assert(entitlements.compare(0, entitlements.size(), reinterpret_cast(blob + 1), writ) == 0); - } - } + Analyze(mach_header, fun([&](const char *data, size_t size) { + if (entitlements.empty()) + entitlements.assign(data, size); + else + _assert(entitlements.compare(0, entitlements.size(), data, size) == 0); + })); return entitlements; } -static void Allocate(const void *idata, size_t isize, std::streambuf &output, const Functor &allocate, const Functor &)> &save, const Functor &percent) { +static void Allocate(const void *idata, size_t isize, std::streambuf &output, const Functor &allocate, const Functor &save, const Progress &progress) { FatHeader source(const_cast(idata), isize); size_t offset(0); @@ -1130,12 +1251,15 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co if (symtab != NULL) { auto end(mach_header.Swap(symtab->stroff) + mach_header.Swap(symtab->strsize)); - _assert(end <= size); - _assert(end >= size - 0x10); - size = end; + if (symtab->stroff != 0 || symtab->strsize != 0) { + _assert(end <= size); + _assert(end >= size - 0x10); + size = end; + } } - size_t alloc(allocate(mach_header, size)); + Baton baton; + size_t alloc(allocate(mach_header, baton, size)); auto *fat_arch(mach_header.GetFatArch()); uint32_t align; @@ -1158,13 +1282,35 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co break; } + const char *arch(NULL); + switch (mach_header.GetCPUType()) { + case CPU_TYPE_POWERPC: + arch = "ppc"; + break; + case CPU_TYPE_POWERPC64: + arch = "ppc64"; + break; + case CPU_TYPE_X86: + arch = "i386"; + break; + case CPU_TYPE_X86_64: + arch = "x86_64"; + break; + case CPU_TYPE_ARM: + arch = "arm"; + break; + case CPU_TYPE_ARM64: + arch = "arm64"; + break; + } + offset = Align(offset, 1 << align); uint32_t limit(size); if (alloc != 0) limit = Align(limit, 0x10); - allocations.push_back(CodesignAllocation(mach_header, offset, size, limit, alloc, align)); + allocations.push_back(CodesignAllocation(mach_header, offset, size, limit, alloc, align, arch, baton)); offset += size + alloc; offset = Align(offset, 0x10); } @@ -1193,6 +1339,7 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co } _foreach (allocation, allocations) { + progress(allocation.arch_); auto &mach_header(allocation.mach_header_); pad(output, allocation.offset_ - position); @@ -1279,13 +1426,13 @@ static void Allocate(const void *idata, size_t isize, std::streambuf &output, co std::string overlap(altern.str()); overlap.append(top + overlap.size(), Align(overlap.size(), 0x1000) - overlap.size()); - put(output, top + (position - begin), allocation.size_ - (position - begin), percent); + put(output, top + (position - begin), allocation.size_ - (position - begin), progress); position = begin + allocation.size_; pad(output, allocation.limit_ - allocation.size_); position += allocation.limit_ - allocation.size_; - size_t saved(save(mach_header, output, allocation.limit_, overlap, top, percent)); + size_t saved(save(mach_header, allocation.baton_, output, allocation.limit_, overlap, top, progress)); if (allocation.alloc_ > saved) pad(output, allocation.alloc_ - saved); else @@ -1403,8 +1550,13 @@ class Stuff { { _assert(value_ != NULL); _assert(PKCS12_parse(value_, "", &key_, &cert_, &ca_) != 0); + _assert(key_ != NULL); _assert(cert_ != NULL); + + if (ca_ == NULL) + ca_ = sk_X509_new_null(); + _assert(ca_ != NULL); } Stuff(const std::string &data) : @@ -1441,10 +1593,45 @@ class Signature { PKCS7 *value_; public: - Signature(const Stuff &stuff, const Buffer &data) : - value_(PKCS7_sign(stuff, stuff, stuff, data, PKCS7_BINARY | PKCS7_DETACHED)) - { + Signature(const Stuff &stuff, const Buffer &data, const std::string &xml) { + value_ = PKCS7_new(); _assert(value_ != NULL); + + _assert(PKCS7_set_type(value_, NID_pkcs7_signed)); + _assert(PKCS7_content_new(value_, NID_pkcs7_data)); + + STACK_OF(X509) *certs(stuff); + for (unsigned i(0), e(sk_X509_num(certs)); i != e; i++) + _assert(PKCS7_add_certificate(value_, sk_X509_value(certs, e - i - 1))); + + // XXX: this is the same as PKCS7_sign_add_signer(value_, stuff, stuff, NULL, PKCS7_NOSMIMECAP) + _assert(X509_check_private_key(stuff, stuff)); + auto info(PKCS7_add_signature(value_, stuff, stuff, EVP_sha1())); + _assert(info != NULL); + _assert(PKCS7_add_certificate(value_, stuff)); + _assert(PKCS7_add_signed_attribute(info, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data))); + + PKCS7_set_detached(value_, 1); + + ASN1_OCTET_STRING *string(ASN1_OCTET_STRING_new()); + _assert(string != NULL); + try { + _assert(ASN1_STRING_set(string, xml.data(), xml.size())); + + static auto nid(OBJ_create("1.2.840.113635.100.9.1", "", "")); + _assert(PKCS7_add_signed_attribute(info, nid, V_ASN1_OCTET_STRING, string)); + } catch (...) { + ASN1_OCTET_STRING_free(string); + throw; + } + + // XXX: this is the same as PKCS7_final(value_, data, PKCS7_BINARY) + BIO *bio(PKCS7_dataInit(value_, NULL)); + _assert(bio != NULL); + _scope({ BIO_free_all(bio); }); + SMIME_crlf_copy(data, bio, PKCS7_BINARY); + BIO_flush(bio); + _assert(PKCS7_dataFinal(value_, bio)); } ~Signature() { @@ -1590,32 +1777,92 @@ static void Commit(const std::string &path, const std::string &temp) { namespace ldid { -Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::string &identifier, const std::string &entitlements, const std::string &requirement, const std::string &key, const Slots &slots, const Functor &percent) { +#ifndef LDID_NOSMIME +static void get(std::string &value, X509_NAME *name, int nid) { + auto index(X509_NAME_get_index_by_NID(name, nid, -1)); + _assert(index >= 0); + auto next(X509_NAME_get_index_by_NID(name, nid, index)); + _assert(next == -1); + auto entry(X509_NAME_get_entry(name, index)); + _assert(entry != NULL); + auto asn(X509_NAME_ENTRY_get_data(entry)); + _assert(asn != NULL); + value.assign(reinterpret_cast(ASN1_STRING_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 +static void req(std::streambuf &buffer, uint8_t (&&data)[Size_]) { + req(buffer, Size_); + put(buffer, data, Size_); + static uint8_t zeros[] = {0,0,0,0}; + put(buffer, zeros, 3 - (Size_ + 3) % 4); +} + +Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, uint32_t flags, bool platform, const Progress &progress) { Hash hash; + std::string team; + std::string common; #ifndef LDID_NOSMIME if (!key.empty()) { Stuff stuff(key); auto name(X509_get_subject_name(stuff)); _assert(name != NULL); - auto index(X509_NAME_get_index_by_NID(name, NID_organizationalUnitName, -1)); - _assert(index >= 0); - auto next(X509_NAME_get_index_by_NID(name, NID_organizationalUnitName, 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); - team.assign(reinterpret_cast(ASN1_STRING_data(asn)), ASN1_STRING_length(asn)); + 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, size_t size) -> size_t { + Allocate(idata, isize, output, fun([&](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t { size_t alloc(sizeof(struct SuperBlob)); uint32_t normal((size + PageSize_ - 1) / PageSize_); @@ -1632,16 +1879,56 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st special = std::max(special, CSSLOT_REQUIREMENTS); alloc += sizeof(struct BlobIndex); - if (requirement.empty()) - alloc += 0xc; - else - alloc += requirement.size(); + alloc += backing.str().size(); - if (!entitlements.empty()) { + if (!merge) + baton.entitlements_ = entitlements; + else { +#ifndef LDID_NOPLIST + 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); }); + + 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)); + } + + char *xml(NULL); + uint32_t size; + plist_to_xml(combined, &xml, &size); + _scope({ free(xml); }); + + baton.entitlements_.assign(xml, size); + } +#else + _assert(false); +#endif + } + + if (!baton.entitlements_.empty()) { special = std::max(special, CSSLOT_ENTITLEMENTS); alloc += sizeof(struct BlobIndex); alloc += sizeof(struct Blob); - alloc += entitlements.size(); + alloc += baton.entitlements_.size(); } size_t directory(0); @@ -1657,32 +1944,25 @@ 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, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Functor &percent) -> size_t { + }), fun([&](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Progress &progress) -> size_t { Blobs blobs; if (true) { - std::stringbuf data; - - if (requirement.empty()) { - Blobs requirements; - put(data, CSMAGIC_REQUIREMENTS, requirements); - } else { - put(data, requirement.data(), requirement.size()); - } - - insert(blobs, CSSLOT_REQUIREMENTS, data); + insert(blobs, CSSLOT_REQUIREMENTS, backing); } - if (!entitlements.empty()) { + if (!baton.entitlements_.empty()) { std::stringbuf data; - put(data, entitlements.data(), entitlements.size()); + put(data, baton.entitlements_.data(), baton.entitlements_.size()); insert(blobs, CSSLOT_ENTITLEMENTS, CSMAGIC_EMBEDDED_ENTITLEMENTS, data); } @@ -1711,18 +1991,18 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st CodeDirectory directory; directory.version = Swap(uint32_t(0x00020200)); - directory.flags = Swap(uint32_t(0)); + directory.flags = Swap(uint32_t(flags)); directory.nSpecialSlots = Swap(special); directory.codeLimit = Swap(uint32_t(limit)); directory.nCodeSlots = Swap(normal); directory.hashSize = algorithm.size_; directory.hashType = algorithm.type_; - directory.spare1 = 0x00; + directory.platform = platform ? 0x01 : 0x00; directory.pageSize = PageShift_; directory.spare2 = Swap(uint32_t(0)); directory.scatterOffset = Swap(uint32_t(0)); - directory.spare3 = Swap(uint32_t(0)); - directory.codeLimit64 = Swap(uint64_t(0)); + //directory.spare3 = Swap(uint32_t(0)); + //directory.codeLimit64 = Swap(uint64_t(0)); uint32_t offset(sizeof(Blob) + sizeof(CodeDirectory)); @@ -1759,15 +2039,15 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st _foreach (slot, posts) memcpy(hashes - slot.first * algorithm.size_, algorithm[slot.second], algorithm.size_); - percent(0); + progress(0); if (normal != 1) for (size_t i = 0; i != normal - 1; ++i) { algorithm(hashes + i * algorithm.size_, (PageSize_ * i < overlap.size() ? overlap.data() : top) + PageSize_ * i, PageSize_); - percent(double(i) / normal); + progress(double(i) / normal); } if (normal != 0) algorithm(hashes + (normal - 1) * algorithm.size_, top + PageSize_ * (normal - 1), ((limit - 1) % PageSize_) + 1); - percent(1); + progress(1); put(data, storage.data(), storage.size()); @@ -1779,13 +2059,62 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st #ifndef LDID_NOSMIME if (!key.empty()) { +#ifdef LDID_NOPLIST + auto plist(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + _scope({ CFRelease(plist); }); + + auto cdhashes(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + _scope({ CFRelease(cdhashes); }); + + CFDictionarySetValue(plist, CFSTR("cdhashes"), cdhashes); +#else + auto plist(plist_new_dict()); + _scope({ plist_free(plist); }); + + auto cdhashes(plist_new_array()); + plist_dict_set_item(plist, "cdhashes", cdhashes); +#endif + + unsigned total(0); + for (Algorithm *pointer : GetAlgorithms()) { + Algorithm &algorithm(*pointer); + (void) algorithm; + + const auto &blob(blobs[total == 0 ? CSSLOT_CODEDIRECTORY : CSSLOT_ALTERNATE + total - 1]); + ++total; + + std::vector hash; + algorithm(hash, blob.data(), blob.size()); + hash.resize(20); + +#ifdef LDID_NOPLIST + auto value(CFDataCreate(kCFAllocatorDefault, reinterpret_cast(hash.data()), hash.size())); + _scope({ CFRelease(value); }); + CFArrayAppendValue(cdhashes, value); +#else + plist_array_append_item(cdhashes, plist_new_data(hash.data(), hash.size())); +#endif + } + +#ifdef LDID_NOPLIST + auto created(CFPropertyListCreateXMLData(kCFAllocatorDefault, plist)); + _scope({ CFRelease(created); }); + auto xml(reinterpret_cast(CFDataGetBytePtr(created))); + auto size(CFDataGetLength(created)); +#else + char *xml(NULL); + uint32_t size; + plist_to_xml(plist, &xml, &size); + _scope({ free(xml); }); +#endif + std::stringbuf data; const std::string &sign(blobs[CSSLOT_CODEDIRECTORY]); Stuff stuff(key); Buffer bio(sign); - Signature signature(stuff, sign); + Signature signature(stuff, sign, std::string(xml, size)); Buffer result(signature); std::string value(result); put(data, value.data(), value.size()); @@ -1796,18 +2125,18 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st #endif return put(output, CSMAGIC_EMBEDDED_SIGNATURE, blobs); - }), percent); + }), progress); return hash; } #ifndef LDID_NOTOOLS -static void Unsign(void *idata, size_t isize, std::streambuf &output, const Functor &percent) { - Allocate(idata, isize, output, fun([](const MachHeader &mach_header, size_t size) -> size_t { +static void Unsign(void *idata, size_t isize, std::streambuf &output, const Progress &progress) { + Allocate(idata, isize, output, fun([](const MachHeader &mach_header, Baton &baton, size_t size) -> size_t { return 0; - }), fun([](const MachHeader &mach_header, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Functor &percent) -> size_t { + }), fun([](const MachHeader &mach_header, const Baton &baton, std::streambuf &output, size_t limit, const std::string &overlap, const char *top, const Progress &progress) -> size_t { return 0; - }), percent); + }), progress); } std::string DiskFolder::Path(const std::string &path) const { @@ -1978,19 +2307,20 @@ void UnionFolder::Open(const std::string &path, const Functorsecond); - auto &data(entry.first); + auto &data(*entry.data_); auto length(data.pubseekoff(0, std::ios::end, std::ios::in)); data.pubseekpos(0, std::ios::in); - code(data, length, entry.second); + code(data, length, entry.flag_); } void UnionFolder::Find(const std::string &path, const Functor &code, const Functor &)> &link) const { for (auto &reset : resets_) Map(path, code, reset.first, fun([&](const Functor &code) { auto &entry(reset.second); - auto length(entry.first.pubseekoff(0, std::ios::end, std::ios::in)); - entry.first.pubseekpos(0, std::ios::in); - code(entry.first, length, entry.second); + auto &data(*entry.data_); + auto length(data.pubseekoff(0, std::ios::end, std::ios::in)); + data.pubseekpos(0, std::ios::in); + code(data, length, entry.flag_); })); for (auto &remap : remaps_) @@ -2010,8 +2340,8 @@ void UnionFolder::Find(const std::string &path, const Functor &percent) { - percent(0); +static void copy(std::streambuf &source, std::streambuf &target, size_t length, const Progress &progress) { + progress(0); size_t total(0); for (;;) { char data[4096 * 4]; @@ -2020,7 +2350,7 @@ static void copy(std::streambuf &source, std::streambuf &target, size_t length, break; _assert(target.sputn(data, writ) == writ); total += writ; - percent(double(total) / length); + progress(double(total) / length); } } @@ -2037,7 +2367,7 @@ static plist_t plist(const std::string &data) { static void plist_d(std::streambuf &buffer, size_t length, const Functor &code) { std::stringbuf data; - copy(buffer, data, length, ldid::fun(dummy)); + copy(buffer, data, length, dummy_); auto node(plist(data.str())); _scope({ plist_free(node); }); _assert(plist_get_node_type(node) == PLIST_DICT); @@ -2139,20 +2469,20 @@ struct RuleCode { }; #ifndef LDID_NOPLIST -static Hash Sign(const uint8_t *prefix, size_t size, std::streambuf &buffer, Hash &hash, std::streambuf &save, const std::string &identifier, const std::string &entitlements, const std::string &requirement, const std::string &key, const Slots &slots, size_t length, const Functor &percent) { +static Hash Sign(const uint8_t *prefix, size_t size, std::streambuf &buffer, Hash &hash, std::streambuf &save, const std::string &identifier, const std::string &entitlements, bool merge, const std::string &requirements, const std::string &key, const Slots &slots, size_t length, uint32_t flags, bool platform, const Progress &progress) { // XXX: this is a miserable fail std::stringbuf temp; put(temp, prefix, size); - copy(buffer, temp, length - size, percent); + copy(buffer, temp, length - size, progress); // XXX: this is a stupid hack pad(temp, 0x10 - (length & 0xf)); auto data(temp.str()); HashProxy proxy(hash, save); - return Sign(data.data(), data.size(), proxy, identifier, entitlements, requirement, key, slots, percent); + return Sign(data.data(), data.size(), proxy, identifier, entitlements, merge, requirements, key, slots, flags, platform, progress); } -Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std::map &remote, const std::string &requirement, const Functor &alter, const Functor &progress, const Functor &percent) { +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) { std::string executable; std::string identifier; @@ -2176,11 +2506,13 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std mac = true; } + progress(root + "*"); + std::string entitlements; folder.Open(executable, fun([&](std::streambuf &buffer, size_t length, const void *flag) { // XXX: this is a miserable fail std::stringbuf temp; - copy(buffer, temp, length, percent); + copy(buffer, temp, length, progress); // XXX: this is a stupid hack pad(temp, 0x10 - (length & 0xf)); auto data(temp.str()); @@ -2199,10 +2531,9 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std if (true) { rules1.insert(Rule{1, NoMode, "^" + resources}); - if (!mac) rules1.insert(Rule{10000, OmitMode, "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/|())SC_Info/[^/]+\\.(sinf|supf|supp)$"}); rules1.insert(Rule{1000, OptionalMode, "^" + resources + ".*\\.lproj/"}); rules1.insert(Rule{1100, OmitMode, "^" + resources + ".*\\.lproj/locversion.plist$"}); - if (!mac) rules1.insert(Rule{10000, OmitMode, "^Watch/[^/]+\\.app/(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/)SC_Info/[^/]+\\.(sinf|supf|supp)$"}); + rules1.insert(Rule{1010, NoMode, "^Base\\.lproj/"}); rules1.insert(Rule{1, NoMode, "^version.plist$"}); } @@ -2210,14 +2541,13 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std rules2.insert(Rule{11, NoMode, ".*\\.dSYM($|/)"}); rules2.insert(Rule{20, NoMode, "^" + resources}); rules2.insert(Rule{2000, OmitMode, "^(.*/)?\\.DS_Store$"}); - if (!mac) rules2.insert(Rule{10000, OmitMode, "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/|())SC_Info/[^/]+\\.(sinf|supf|supp)$"}); rules2.insert(Rule{10, NestedMode, "^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/"}); 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/"}); rules2.insert(Rule{20, OmitMode, "^Info\\.plist$"}); rules2.insert(Rule{20, OmitMode, "^PkgInfo$"}); - if (!mac) rules2.insert(Rule{10000, OmitMode, "^Watch/[^/]+\\.app/(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|PlugIns/[^/]+\\.appex/Frameworks/[^/]+\\.framework/)SC_Info/[^/]+\\.(sinf|supf|supp)$"}); rules2.insert(Rule{10, NestedMode, "^[^/]+$"}); rules2.insert(Rule{20, NoMode, "^embedded\\.provisionprofile$"}); rules2.insert(Rule{20, NoMode, "^version\\.plist$"}); @@ -2237,8 +2567,8 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std SubFolder subfolder(folder, bundle); bundles[nested[1]] = Sign(bundle, subfolder, key, local, "", Starts(name, "PlugIns/") ? alter : - static_cast &>(fun([&](const std::string &, const std::string &entitlements) -> std::string { return entitlements; })) - , progress, percent); + static_cast &>(fun([&](const std::string &, const std::string &) -> std::string { return entitlements; })) + , progress); }), fun([&](const std::string &name, const Functor &read) { })); @@ -2293,7 +2623,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std case MH_CIGAM: case MH_CIGAM_64: folder.Save(name, true, flag, fun([&](std::streambuf &save) { Slots slots; - Sign(header.bytes, size, data, hash, save, identifier, "", "", key, slots, length, percent); + Sign(header.bytes, size, data, hash, save, identifier, "", false, "", key, slots, length, 0, false, Progression(progress, root + name)); })); return; } @@ -2301,7 +2631,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std folder.Save(name, false, flag, fun([&](std::streambuf &save) { HashProxy proxy(hash, save); put(proxy, header.bytes, size); - copy(data, proxy, length - size, percent); + copy(data, proxy, length - size, progress); })); })); }), fun([&](const std::string &name, const Functor &read) { @@ -2422,7 +2752,7 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std Slots slots; slots[1] = local.at(info); slots[3] = local.at(signature); - bundle.hash = Sign(NULL, 0, buffer, local[executable], save, identifier, entitlements, requirement, key, slots, length, percent); + bundle.hash = Sign(NULL, 0, buffer, local[executable], save, identifier, entitlements, false, requirements, key, slots, length, 0, false, Progression(progress, root + executable)); })); })); @@ -2432,9 +2762,9 @@ Bundle Sign(const std::string &root, Folder &folder, const std::string &key, std return bundle; } -Bundle Sign(const std::string &root, Folder &folder, const std::string &key, const std::string &requirement, const Functor &alter, const Functor &progress, const Functor &percent) { +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; - return Sign(root, folder, key, local, requirement, alter, progress, percent); + return Sign(root, folder, key, local, requirements, alter, progress); } #endif @@ -2458,6 +2788,8 @@ int main(int argc, char *argv[]) { bool flag_e(false); bool flag_q(false); + bool flag_H(false); + #ifndef LDID_NOFLAGT bool flag_T(false); #endif @@ -2472,6 +2804,11 @@ int main(int argc, char *argv[]) { bool flag_u(false); + bool flag_M(false); + + uint32_t flags(0); + bool platform(false); + uint32_t flag_CPUType(_not(uint32_t)); uint32_t flag_CPUSubtype(_not(uint32_t)); @@ -2483,7 +2820,7 @@ int main(int argc, char *argv[]) { #endif Map entitlements; - Map requirement; + Map requirements; Map key; ldid::Slots slots; @@ -2524,9 +2861,30 @@ int main(int argc, char *argv[]) { case 'q': flag_q = true; break; + case 'H': { + const char *hash = argv[argi] + 2; + + if (!flag_H) { + flag_H = true; + + do_sha1 = false; + do_sha256 = false; + + fprintf(stderr, "WARNING: -H is only present for compatibility with a fork of ldid\n"); + fprintf(stderr, " you should NOT be manually specifying the hash algorithm\n"); + } + + if (false); + else if (strcmp(hash, "sha1") == 0) + do_sha1 = true; + else if (strcmp(hash, "sha256") == 0) + do_sha256 = true; + else _assert(false); + } break; + case 'Q': { const char *xml = argv[argi] + 2; - requirement.open(xml, O_RDONLY, PROT_READ, MAP_PRIVATE); + requirements.open(xml, O_RDONLY, PROT_READ, MAP_PRIVATE); } break; case 'D': flag_D = true; break; @@ -2548,6 +2906,32 @@ int main(int argc, char *argv[]) { } break; + case 'C': { + const char *name = argv[argi] + 2; + if (false); + else if (strcmp(name, "host") == 0) + flags |= kSecCodeSignatureHost; + else if (strcmp(name, "adhoc") == 0) + flags |= kSecCodeSignatureAdhoc; + else if (strcmp(name, "hard") == 0) + flags |= kSecCodeSignatureForceHard; + else if (strcmp(name, "kill") == 0) + flags |= kSecCodeSignatureForceKill; + else if (strcmp(name, "expires") == 0) + flags |= kSecCodeSignatureForceExpiration; + else if (strcmp(name, "restrict") == 0) + flags |= kSecCodeSignatureRestrict; + else if (strcmp(name, "enforcement") == 0) + flags |= kSecCodeSignatureEnforcement; + else if (strcmp(name, "library-validation") == 0) + flags |= kSecCodeSignatureLibraryValidation; + else _assert(false); + } break; + + case 'P': + platform = true; + break; + case 's': _assert(!flag_r); _assert(!flag_S); @@ -2564,6 +2948,10 @@ int main(int argc, char *argv[]) { } break; + case 'M': + flag_M = true; + break; + case 'K': if (argv[argi][2] != '\0') key.open(argv[argi] + 2, O_RDONLY, PROT_READ, MAP_PRIVATE); @@ -2613,9 +3001,7 @@ int main(int argc, char *argv[]) { #ifndef LDID_NOPLIST _assert(!flag_r); ldid::DiskFolder folder(path); - path += "/" + Sign("", folder, key, requirement, ldid::fun([&](const std::string &, const std::string &) -> std::string { return entitlements; }) - , ldid::fun([&](const std::string &) {}), ldid::fun(dummy) - ).path; + path += "/" + Sign("", folder, key, requirements, ldid::fun([&](const std::string &, const std::string &) -> std::string { return entitlements; }), dummy_).path; #else _assert(false); #endif @@ -2627,10 +3013,10 @@ int main(int argc, char *argv[]) { auto temp(Temporary(output, split)); if (flag_r) - ldid::Unsign(input.data(), input.size(), output, ldid::fun(dummy)); + ldid::Unsign(input.data(), input.size(), output, dummy_); else { std::string identifier(flag_I ?: split.base.c_str()); - ldid::Sign(input.data(), input.size(), output, identifier, entitlements, requirement, key, slots, ldid::fun(dummy)); + ldid::Sign(input.data(), input.size(), output, identifier, entitlements, flag_M, requirements, key, slots, flags, platform, dummy_); } Commit(path, temp);