X-Git-Url: https://git.saurik.com/ldid.git/blobdiff_plain/71465d5fd1846c801f628be21c57b52031df1655..f1df48331fb84ccca058563bad51529107533f1c:/ldid.cpp?ds=sidebyside diff --git a/ldid.cpp b/ldid.cpp index 25f3027..4ad9b2f 100644 --- a/ldid.cpp +++ b/ldid.cpp @@ -94,23 +94,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 +131,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) @@ -863,6 +867,53 @@ struct CodeDirectory { //uint64_t codeLimit64; } _packed; +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 @@ -1403,8 +1454,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 +1497,35 @@ 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))); + + auto info(PKCS7_sign_add_signer(value_, stuff, stuff, NULL, PKCS7_NOSMIMECAP)); + _assert(info != NULL); + + 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; + } + + _assert(PKCS7_final(value_, data, PKCS7_BINARY)); } ~Signature() { @@ -1590,28 +1671,86 @@ 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) { +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)); +} + +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, const std::string &requirements, const std::string &key, const Slots &slots, const Functor &percent) { 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); @@ -1632,10 +1771,7 @@ 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()) { special = std::max(special, CSSLOT_ENTITLEMENTS); @@ -1657,27 +1793,20 @@ 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 { 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()) { @@ -1779,13 +1908,39 @@ Hash Sign(const void *idata, size_t isize, std::streambuf &output, const std::st #ifndef LDID_NOSMIME if (!key.empty()) { + auto plist(plist_new_dict()); + _scope({ plist_free(plist); }); + + auto cdhashes(plist_new_array()); + plist_dict_set_item(plist, "cdhashes", cdhashes); + + 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); + + plist_array_append_item(cdhashes, plist_new_data(hash.data(), hash.size())); + } + + char *xml(NULL); + uint32_t size; + plist_to_xml(plist, &xml, &size); + _scope({ free(xml); }); + 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()); @@ -2140,7 +2295,7 @@ 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, const std::string &requirements, const std::string &key, const Slots &slots, size_t length, const Functor &percent) { // XXX: this is a miserable fail std::stringbuf temp; put(temp, prefix, size); @@ -2150,10 +2305,10 @@ static Hash Sign(const uint8_t *prefix, size_t size, std::streambuf &buffer, Has 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, requirements, key, slots, percent); } -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 Functor &progress, const Functor &percent) { std::string executable; std::string identifier; @@ -2200,10 +2355,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$"}); } @@ -2211,14 +2365,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$"}); @@ -2423,7 +2576,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, requirements, key, slots, length, percent); })); })); @@ -2433,9 +2586,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 Functor &progress, const Functor &percent) { std::map local; - return Sign(root, folder, key, local, requirement, alter, progress, percent); + return Sign(root, folder, key, local, requirements, alter, progress, percent); } #endif @@ -2484,7 +2637,7 @@ int main(int argc, char *argv[]) { #endif Map entitlements; - Map requirement; + Map requirements; Map key; ldid::Slots slots; @@ -2527,7 +2680,7 @@ int main(int argc, char *argv[]) { 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; @@ -2614,7 +2767,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; }) + path += "/" + Sign("", folder, key, requirements, ldid::fun([&](const std::string &, const std::string &) -> std::string { return entitlements; }) , ldid::fun([&](const std::string &) {}), ldid::fun(dummy) ).path; #else @@ -2631,7 +2784,7 @@ int main(int argc, char *argv[]) { ldid::Unsign(input.data(), input.size(), output, ldid::fun(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, requirements, key, slots, ldid::fun(dummy)); } Commit(path, temp);