#ifndef LDID_NOSMIME
#include <openssl/err.h>
+#include <openssl/asn1t.h>
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs12.h>
return data.str();
}
+#ifndef LDID_NOPLIST
static std::string der(plist_t data) {
switch (const auto type = plist_get_node_type(data)) {
case PLIST_BOOLEAN: {
} break;
}
}
+#endif
static inline uint16_t Swap_(uint16_t value) {
return
struct Algorithm {
size_t size_;
uint8_t type_;
+ int nid_;
- Algorithm(size_t size, uint8_t type) :
+ Algorithm(size_t size, uint8_t type, int nid) :
size_(size),
- type_(type)
+ type_(type),
+ nid_(nid)
{
}
Algorithm
{
AlgorithmSHA1() :
- Algorithm(LDID_SHA1_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160)
+ Algorithm(LDID_SHA1_DIGEST_LENGTH, CS_HASHTYPE_SHA160_160, NID_sha1)
{
}
Algorithm
{
AlgorithmSHA256() :
- Algorithm(LDID_SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256)
+ Algorithm(LDID_SHA256_DIGEST_LENGTH, CS_HASHTYPE_SHA256_256, NID_sha256)
{
}
}
};
+class Octet {
+ private:
+ ASN1_OCTET_STRING *value_;
+
+ public:
+ Octet() :
+ value_(ASN1_OCTET_STRING_new())
+ {
+ _assert(value_ != NULL);
+ }
+
+ Octet(const std::string &value) :
+ Octet()
+ {
+ _assert(ASN1_STRING_set(value_, value.data(), value.size()));
+ }
+
+ Octet(const uint8_t *data, size_t size) :
+ Octet()
+ {
+ _assert(ASN1_STRING_set(value_, data, size));
+ }
+
+ Octet(const Octet &value) = delete;
+
+ ~Octet() {
+ if (value_ != NULL)
+ ASN1_OCTET_STRING_free(value_);
+ }
+
+ void release() {
+ value_ = NULL;
+ }
+
+ operator ASN1_OCTET_STRING *() const {
+ return value_;
+ }
+};
+
+typedef struct {
+ ASN1_OBJECT *algorithm;
+ ASN1_OCTET_STRING *value;
+} APPLE_CDHASH;
+
+DECLARE_ASN1_FUNCTIONS(APPLE_CDHASH)
+
+ASN1_NDEF_SEQUENCE(APPLE_CDHASH) = {
+ ASN1_SIMPLE(APPLE_CDHASH, algorithm, ASN1_OBJECT),
+ ASN1_SIMPLE(APPLE_CDHASH, value, ASN1_OCTET_STRING),
+} ASN1_NDEF_SEQUENCE_END(APPLE_CDHASH)
+
+IMPLEMENT_ASN1_FUNCTIONS(APPLE_CDHASH)
+
+typedef struct {
+ ASN1_OBJECT *object;
+ STACK_OF(APPLE_CDHASH) *cdhashes;
+} APPLE_CDATTR;
+
+DECLARE_ASN1_FUNCTIONS(APPLE_CDATTR)
+
+ASN1_NDEF_SEQUENCE(APPLE_CDATTR) = {
+ ASN1_SIMPLE(APPLE_CDATTR, object, ASN1_OBJECT),
+ ASN1_SET_OF(APPLE_CDATTR, cdhashes, APPLE_CDHASH),
+} ASN1_NDEF_SEQUENCE_END(APPLE_CDATTR)
+
+IMPLEMENT_ASN1_FUNCTIONS(APPLE_CDATTR)
+
class Signature {
private:
PKCS7 *value_;
public:
- Signature(const Stuff &stuff, const Buffer &data, const std::string &xml) {
+ Signature(const Stuff &stuff, const Buffer &data, const std::string &xml, const ldid::Hash &hash) {
value_ = PKCS7_new();
_assert(value_ != NULL);
for (unsigned i(0), e(sk_X509_num(certs)); i != e; i++)
_assert(PKCS7_add_certificate(value_, sk_X509_value(certs, e - i - 1)));
+ STACK_OF(X509_ATTRIBUTE) *attributes(sk_X509_ATTRIBUTE_new_null());
+ _assert(attributes != NULL);
+ _scope({ sk_X509_ATTRIBUTE_pop_free(attributes, X509_ATTRIBUTE_free); });
+
+
// XXX: this is the same as PKCS7_sign_add_signer(value_, stuff, stuff, NULL, PKCS7_NOSMIMECAP)
_assert(X509_check_private_key(stuff, stuff));
auto info(PKCS7_add_signature(value_, stuff, stuff, EVP_sha1()));
_assert(info != NULL);
_assert(PKCS7_add_certificate(value_, stuff));
- _assert(PKCS7_add_signed_attribute(info, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data)));
- PKCS7_set_detached(value_, 1);
+ {
+ auto attribute(X509_ATTRIBUTE_create(NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data)));
+ _assert(attribute != NULL);
+ _assert(sk_X509_ATTRIBUTE_push(attributes, attribute) != 0);
+ }
+
+ {
+ auto cdattr(APPLE_CDATTR_new());
+ _assert(cdattr != NULL);
+ _scope({ APPLE_CDATTR_free(cdattr); });
- 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.2", "apple-2", "Apple 2"));
+ cdattr->object = OBJ_nid2obj(nid);
- 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;
+ for (Algorithm *pointer : GetAlgorithms()) {
+ Algorithm &algorithm(*pointer);
+ APPLE_CDHASH *cdhash(APPLE_CDHASH_new());
+ _assert(cdhash != NULL);
+ _assert(sk_push((_STACK *) cdattr->cdhashes, cdhash) != 0);
+ cdhash->algorithm = OBJ_nid2obj(algorithm.nid_);
+ Octet string(algorithm[hash], algorithm.size_);
+ cdhash->value = string;
+ string.release();
+ }
+
+ // in e20b57270dece66ce2c68aeb5d14dd6d9f3c5d68 OpenSSL removed a "hack"
+ // in the process, they introduced a useful bug in X509_ATTRIBUTE_set1_data
+ // however, I don't want to rely on that or detect the bypass before it
+ // so, instead, I create my own compatible attribute and re-serialize it :/
+
+ ASN1_STRING *seq(ASN1_STRING_new());
+ _assert(seq != NULL);
+ _scope({ ASN1_STRING_free(seq); });
+ seq->length = ASN1_item_i2d((ASN1_VALUE *) cdattr, &seq->data, ASN1_ITEM_rptr(APPLE_CDATTR));
+
+ X509_ATTRIBUTE *attribute(NULL);
+ const unsigned char *data(seq->data);
+ _assert(d2i_X509_ATTRIBUTE(&attribute, &data, seq->length) != 0);
+ _assert(attribute != NULL);
+ _assert(sk_X509_ATTRIBUTE_push(attributes, attribute) != 0);
+ }
+
+ {
+ // XXX: move the "cdhashes" plist code to here and remove xml argument
+
+ Octet string(xml);
+ static auto nid(OBJ_create("1.2.840.113635.100.9.1", "apple-1", "Apple 1"));
+ auto attribute(X509_ATTRIBUTE_create(nid, V_ASN1_OCTET_STRING, string));
+ _assert(attribute != NULL);
+ string.release();
+ _assert(sk_X509_ATTRIBUTE_push(attributes, attribute) != 0);
}
+ _assert(PKCS7_set_signed_attributes(info, attributes) != 0);
+ PKCS7_set_detached(value_, 1);
+
// XXX: this is the same as PKCS7_final(value_, data, PKCS7_BINARY)
BIO *bio(PKCS7_dataInit(value_, NULL));
_assert(bio != NULL);
plist_dict_set_item(plist, "cdhashes", cdhashes);
#endif
+ ldid::Hash hash;
+
unsigned total(0);
for (Algorithm *pointer : GetAlgorithms()) {
Algorithm &algorithm(*pointer);
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);
+
+ std::vector<char> cdhash(algorithm[hash], algorithm[hash] + algorithm.size_);
+ cdhash.resize(20);
#ifdef LDID_NOPLIST
- auto value(CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(hash.data()), hash.size()));
+ auto value(CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(cdhash.data()), cdhash.size()));
_scope({ CFRelease(value); });
CFArrayAppendValue(cdhashes, value);
#else
- plist_array_append_item(cdhashes, plist_new_data(hash.data(), hash.size()));
+ plist_array_append_item(cdhashes, plist_new_data(cdhash.data(), cdhash.size()));
#endif
}
Stuff stuff(key);
Buffer bio(sign);
- Signature signature(stuff, sign, std::string(xml, size));
+ Signature signature(stuff, sign, std::string(xml, size), hash);
Buffer result(signature);
std::string value(result);
put(data, value.data(), value.size());
void DiskFolder::Save(const std::string &path, bool edit, const void *flag, const Functor<void (std::streambuf &)> &code) {
if (!edit) {
- // XXX: use nullbuf
- std::stringbuf save;
+ NullBuffer save;
code(save);
} else {
std::filebuf save;
}
};
-Bundle Sign(const std::string &root, Folder &parent, const std::string &key, State &remote, const std::string &requirements, const Functor<std::string (const std::string &, const std::string &)> &alter, const Progress &progress) {
+Bundle Sign(const std::string &root, Folder &parent, const std::string &key, State &local, const std::string &requirements, const Functor<std::string (const std::string &, const std::string &)> &alter, const Progress &progress) {
std::string executable;
std::string identifier;
rules2.insert(Rule{20, NoMode, "^version\\.plist$"});
}
- State local;
-
std::string failure(mac ? "Contents/|Versions/[^/]*/Resources/" : "");
Expression nested("^(Frameworks/[^/]*\\.framework|PlugIns/[^/]*\\.appex(()|/[^/]*.app))/(" + failure + ")Info\\.plist$");
std::map<std::string, Bundle> bundles;
folder.Find("", fun([&](const std::string &name) {
if (!nested(name))
return;
- auto bundle(root + Split(name).dir);
+ 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<const Functor<std::string (const std::string &, const std::string &)> &>(fun([&](const std::string &, const std::string &) -> std::string { return entitlements; }))
, progress);
+ local.Merge(bundle, remote);
}), fun([&](const std::string &name, const Functor<std::string ()> &read) {
}));
}));
}));
- remote.Merge(root, local);
return bundle;
}
bool flag_r(false);
bool flag_e(false);
bool flag_q(false);
+ bool flag_k(false);
bool flag_H(false);
bool flag_h(false);
flag_M = true;
break;
+ case 'k':
+ flag_k = true;
+ break;
+
case 'K':
if (argv[argi][2] != '\0')
key.open(argv[argi] + 2, O_RDONLY, PROT_READ, MAP_PRIVATE);
if (Swap(super->index[index].type) == CSSLOT_REQUIREMENTS) {
uint32_t begin = Swap(super->index[index].offset);
struct Blob *requirement = reinterpret_cast<struct Blob *>(blob + begin);
+ // XXX: this is obviously wrong. but like, -Q is also wrong?!
+ // maybe I can fix all of this just by fixing both -q and -Q?
fwrite(requirement, 1, Swap(requirement->length), stdout);
}
}
- if (flag_s) {
+ if (flag_k) {
_assert(signature != NULL);
uint32_t data = mach_header.Swap(signature->dataoff);
struct SuperBlob *super = reinterpret_cast<struct SuperBlob *>(blob);
for (size_t index(0); index != Swap(super->count); ++index)
- if (Swap(super->index[index].type) == CSSLOT_CODEDIRECTORY) {
+ if (Swap(super->index[index].type) == CSSLOT_SIGNATURESLOT) {
+ uint32_t begin = Swap(super->index[index].offset);
+ struct Blob *signature = reinterpret_cast<struct Blob *>(blob + begin);
+ fwrite(signature + 1, 1, Swap(signature->length) - sizeof(*signature), stdout);
+ }
+ }
+
+ if (flag_s) {
+ _assert(signature != NULL);
+
+ auto algorithms(GetAlgorithms());
+
+ uint32_t data = mach_header.Swap(signature->dataoff);
+
+ uint8_t *top = reinterpret_cast<uint8_t *>(mach_header.GetBase());
+ uint8_t *blob = top + data;
+ struct SuperBlob *super = reinterpret_cast<struct SuperBlob *>(blob);
+
+ for (size_t index(0); index != Swap(super->count); ++index) {
+ auto type(Swap(super->index[index].type));
+ if ((type == CSSLOT_CODEDIRECTORY || type >= CSSLOT_ALTERNATE) && type != CSSLOT_SIGNATURESLOT) {
uint32_t begin = Swap(super->index[index].offset);
+ uint32_t end = index + 1 == Swap(super->count) ? Swap(super->blob.length) : Swap(super->index[index + 1].offset);
struct CodeDirectory *directory = reinterpret_cast<struct CodeDirectory *>(blob + begin + sizeof(Blob));
+ auto type(directory->hashType);
+ _assert(type > 0 && type <= algorithms.size());
+ auto &algorithm(*algorithms[type - 1]);
- uint8_t (*hashes)[LDID_SHA1_DIGEST_LENGTH] = reinterpret_cast<uint8_t (*)[LDID_SHA1_DIGEST_LENGTH]>(blob + begin + Swap(directory->hashOffset));
+ uint8_t *hashes = reinterpret_cast<uint8_t *>(blob + begin + Swap(directory->hashOffset));
uint32_t pages = Swap(directory->nCodeSlots);
- if (pages != 1)
+ if (pages != 0) {
for (size_t i = 0; i != pages - 1; ++i)
- LDID_SHA1(top + PageSize_ * i, PageSize_, hashes[i]);
- if (pages != 0)
- LDID_SHA1(top + PageSize_ * (pages - 1), ((data - 1) % PageSize_) + 1, hashes[pages - 1]);
+ algorithm(hashes + i * algorithm.size_, top + PageSize_ * i, PageSize_);
+ algorithm(hashes + (pages - 1) * algorithm.size_, top + PageSize_ * (pages - 1), ((data - 1) % PageSize_) + 1);
+ }
}
+ }
}
if (flag_h) {