/**************************************************************************/ /* codesign.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "codesign.h" #include "lipo.h" #include "macho.h" #include "core/io/plist.h" #include "core/os/os.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "modules/modules_enabled.gen.h" // For regex. #include #ifdef MODULE_REGEX_ENABLED /*************************************************************************/ /* CodeSignCodeResources */ /*************************************************************************/ String CodeSignCodeResources::hash_sha1_base64(const String &p_path) { Ref fa = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); CryptoCore::SHA1Context ctx; ctx.start(); unsigned char step[4096]; while (true) { uint64_t br = fa->get_buffer(step, 4096); if (br > 0) { ctx.update(step, br); } if (br < 4096) { break; } } unsigned char hash[0x14]; ctx.finish(hash); return CryptoCore::b64_encode_str(hash, 0x14); } String CodeSignCodeResources::hash_sha256_base64(const String &p_path) { Ref fa = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); CryptoCore::SHA256Context ctx; ctx.start(); unsigned char step[4096]; while (true) { uint64_t br = fa->get_buffer(step, 4096); if (br > 0) { ctx.update(step, br); } if (br < 4096) { break; } } unsigned char hash[0x20]; ctx.finish(hash); return CryptoCore::b64_encode_str(hash, 0x20); } void CodeSignCodeResources::add_rule1(const String &p_rule, const String &p_key, int p_weight, bool p_store) { rules1.push_back(CRRule(p_rule, p_key, p_weight, p_store)); } void CodeSignCodeResources::add_rule2(const String &p_rule, const String &p_key, int p_weight, bool p_store) { rules2.push_back(CRRule(p_rule, p_key, p_weight, p_store)); } CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules1(const String &p_path) const { CRMatch found = CRMatch::CR_MATCH_NO; int weight = 0; for (int i = 0; i < rules1.size(); i++) { RegEx regex = RegEx(rules1[i].file_pattern); if (regex.search(p_path).is_valid()) { if (rules1[i].key == "omit") { return CRMatch::CR_MATCH_NO; } else if (rules1[i].key == "nested") { if (weight <= rules1[i].weight) { found = CRMatch::CR_MATCH_NESTED; weight = rules1[i].weight; } } else if (rules1[i].key == "optional") { if (weight <= rules1[i].weight) { found = CRMatch::CR_MATCH_OPTIONAL; weight = rules1[i].weight; } } else { if (weight <= rules1[i].weight) { found = CRMatch::CR_MATCH_YES; weight = rules1[i].weight; } } } } return found; } CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules2(const String &p_path) const { CRMatch found = CRMatch::CR_MATCH_NO; int weight = 0; for (int i = 0; i < rules2.size(); i++) { RegEx regex = RegEx(rules2[i].file_pattern); if (regex.search(p_path).is_valid()) { if (rules2[i].key == "omit") { return CRMatch::CR_MATCH_NO; } else if (rules2[i].key == "nested") { if (weight <= rules2[i].weight) { found = CRMatch::CR_MATCH_NESTED; weight = rules2[i].weight; } } else if (rules2[i].key == "optional") { if (weight <= rules2[i].weight) { found = CRMatch::CR_MATCH_OPTIONAL; weight = rules2[i].weight; } } else { if (weight <= rules2[i].weight) { found = CRMatch::CR_MATCH_YES; weight = rules2[i].weight; } } } } return found; } bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path) { CRMatch found = match_rules1(p_path); if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { return true; // No match. } CRFile f; f.name = p_path; f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); f.nested = false; f.hash = hash_sha1_base64(p_root.path_join(p_path)); print_verbose(vformat("CodeSign/CodeResources: File(V1) %s hash1:%s", f.name, f.hash)); files1.push_back(f); return true; } bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path) { CRMatch found = match_rules2(p_path); if (found == CRMatch::CR_MATCH_NESTED) { return add_nested_file(p_root, p_path, p_root.path_join(p_path)); } if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { return true; // No match. } CRFile f; f.name = p_path; f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); f.nested = false; f.hash = hash_sha1_base64(p_root.path_join(p_path)); f.hash2 = hash_sha256_base64(p_root.path_join(p_path)); print_verbose(vformat("CodeSign/CodeResources: File(V2) %s hash1:%s hash2:%s", f.name, f.hash, f.hash2)); files2.push_back(f); return true; } bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &p_path, const String &p_exepath) { #define CLEANUP() \ if (files_to_add.size() > 1) { \ for (int j = 0; j < files_to_add.size(); j++) { \ da->remove(files_to_add[j]); \ } \ } Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(da.is_null(), false); Vector files_to_add; if (LipO::is_lipo(p_exepath)) { String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join("_lipo"); Error err = da->make_dir_recursive(tmp_path_name); ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name)); LipO lip; if (lip.open_file(p_exepath)) { for (int i = 0; i < lip.get_arch_count(); i++) { if (!lip.extract_arch(i, tmp_path_name.path_join("_rqexe_" + itos(i)))) { CLEANUP(); ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Failed to extract thin binary."); } files_to_add.push_back(tmp_path_name.path_join("_rqexe_" + itos(i))); } } } else if (MachO::is_macho(p_exepath)) { files_to_add.push_back(p_exepath); } CRFile f; f.name = p_path; f.optional = false; f.nested = true; for (int i = 0; i < files_to_add.size(); i++) { MachO mh; if (!mh.open_file(files_to_add[i])) { CLEANUP(); ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid executable file."); } PackedByteArray hash = mh.get_cdhash_sha256(); // Use SHA-256 variant, if available. if (hash.size() != 0x20) { hash = mh.get_cdhash_sha1(); // Use SHA-1 instead. if (hash.size() != 0x14) { CLEANUP(); ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Unsigned nested executable file."); } } hash.resize(0x14); // Always clamp to 0x14 size. f.hash = CryptoCore::b64_encode_str(hash.ptr(), hash.size()); PackedByteArray rq_blob = mh.get_requirements(); String req_string; if (rq_blob.size() > 8) { CodeSignRequirements rq = CodeSignRequirements(rq_blob); Vector rqs = rq.parse_requirements(); for (int j = 0; j < rqs.size(); j++) { if (rqs[j].begins_with("designated => ")) { req_string = rqs[j].replace("designated => ", ""); } } } if (req_string.is_empty()) { req_string = "cdhash H\"" + String::hex_encode_buffer(hash.ptr(), hash.size()) + "\""; } print_verbose(vformat("CodeSign/CodeResources: Nested object %s (cputype: %d) cdhash:%s designated rq:%s", f.name, mh.get_cputype(), f.hash, req_string)); if (f.requirements != req_string) { if (i != 0) { f.requirements += " or "; } f.requirements += req_string; } } files2.push_back(f); CLEANUP(); return true; #undef CLEANUP } bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) { Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(da.is_null(), false); Error err = da->change_dir(p_root.path_join(p_path)); ERR_FAIL_COND_V(err != OK, false); bool ret = true; da->list_dir_begin(); String n = da->get_next(); while (n != String()) { if (n != "." && n != "..") { String path = p_root.path_join(p_path).path_join(n); if (path == p_main_exe_path) { n = da->get_next(); continue; // Skip main executable. } if (da->current_is_dir()) { CRMatch found = match_rules2(p_path.path_join(n)); String fmw_ver = "Current"; // Framework version (default). String info_path; String main_exe; bool bundle = false; if (da->file_exists(path.path_join("Contents/Info.plist"))) { info_path = path.path_join("Contents/Info.plist"); main_exe = path.path_join("Contents/MacOS"); bundle = true; } else if (da->file_exists(path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { info_path = path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); main_exe = path.path_join(vformat("Versions/%s", fmw_ver)); bundle = true; } else if (da->file_exists(path.path_join("Info.plist"))) { info_path = path.path_join("Info.plist"); main_exe = path; bundle = true; } if (bundle && found == CRMatch::CR_MATCH_NESTED && !info_path.is_empty()) { // Read Info.plist. PList info_plist; if (info_plist.load_file(info_path)) { if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { main_exe = main_exe.path_join(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); } else { ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, no exe name."); } } else { ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, can't load."); } ret = ret && add_nested_file(p_root, p_path.path_join(n), main_exe); } else { ret = ret && add_folder_recursive(p_root, p_path.path_join(n), p_main_exe_path); } } else { ret = ret && add_file1(p_root, p_path.path_join(n)); ret = ret && add_file2(p_root, p_path.path_join(n)); } } n = da->get_next(); } da->list_dir_end(); return ret; } bool CodeSignCodeResources::save_to_file(const String &p_path) { PList pl; print_verbose(vformat("CodeSign/CodeResources: Writing to file: %s", p_path)); // Write version 1 hashes. Ref files1_dict = PListNode::new_dict(); pl.get_root()->push_subnode(files1_dict, "files"); for (int i = 0; i < files1.size(); i++) { if (files1[i].optional) { Ref file_dict = PListNode::new_dict(); files1_dict->push_subnode(file_dict, files1[i].name); file_dict->push_subnode(PListNode::new_data(files1[i].hash), "hash"); file_dict->push_subnode(PListNode::new_bool(true), "optional"); } else { files1_dict->push_subnode(PListNode::new_data(files1[i].hash), files1[i].name); } } // Write version 2 hashes. Ref files2_dict = PListNode::new_dict(); pl.get_root()->push_subnode(files2_dict, "files2"); for (int i = 0; i < files2.size(); i++) { Ref file_dict = PListNode::new_dict(); files2_dict->push_subnode(file_dict, files2[i].name); if (files2[i].nested) { file_dict->push_subnode(PListNode::new_data(files2[i].hash), "cdhash"); file_dict->push_subnode(PListNode::new_string(files2[i].requirements), "requirement"); } else { file_dict->push_subnode(PListNode::new_data(files2[i].hash), "hash"); file_dict->push_subnode(PListNode::new_data(files2[i].hash2), "hash2"); if (files2[i].optional) { file_dict->push_subnode(PListNode::new_bool(true), "optional"); } } } // Write version 1 rules. Ref rules1_dict = PListNode::new_dict(); pl.get_root()->push_subnode(rules1_dict, "rules"); for (int i = 0; i < rules1.size(); i++) { if (rules1[i].store) { if (rules1[i].key.is_empty() && rules1[i].weight <= 0) { rules1_dict->push_subnode(PListNode::new_bool(true), rules1[i].file_pattern); } else { Ref rule_dict = PListNode::new_dict(); rules1_dict->push_subnode(rule_dict, rules1[i].file_pattern); if (!rules1[i].key.is_empty()) { rule_dict->push_subnode(PListNode::new_bool(true), rules1[i].key); } if (rules1[i].weight != 1) { rule_dict->push_subnode(PListNode::new_real(rules1[i].weight), "weight"); } } } } // Write version 2 rules. Ref rules2_dict = PListNode::new_dict(); pl.get_root()->push_subnode(rules2_dict, "rules2"); for (int i = 0; i < rules2.size(); i++) { if (rules2[i].store) { if (rules2[i].key.is_empty() && rules2[i].weight <= 0) { rules2_dict->push_subnode(PListNode::new_bool(true), rules2[i].file_pattern); } else { Ref rule_dict = PListNode::new_dict(); rules2_dict->push_subnode(rule_dict, rules2[i].file_pattern); if (!rules2[i].key.is_empty()) { rule_dict->push_subnode(PListNode::new_bool(true), rules2[i].key); } if (rules2[i].weight != 1) { rule_dict->push_subnode(PListNode::new_real(rules2[i].weight), "weight"); } } } } String text = pl.save_text(); ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed."); Ref fa = FileAccess::open(p_path, FileAccess::WRITE); ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path)); CharString cs = text.utf8(); fa->store_buffer((const uint8_t *)cs.ptr(), cs.length()); return true; } /*************************************************************************/ /* CodeSignRequirements */ /*************************************************************************/ CodeSignRequirements::CodeSignRequirements() { blob.append_array({ 0xFA, 0xDE, 0x0C, 0x01 }); // Requirement set magic. blob.append_array({ 0x00, 0x00, 0x00, 0x0C }); // Length of requirements set (12 bytes). blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Empty. } CodeSignRequirements::CodeSignRequirements(const PackedByteArray &p_data) { blob = p_data; } _FORCE_INLINE_ void CodeSignRequirements::_parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); r_out += "certificate "; uint32_t tag_slot = _R(r_pos); if (tag_slot == 0x00000000) { r_out += "leaf"; } else if (tag_slot == 0xffffffff) { r_out += "root"; } else { r_out += itos((int32_t)tag_slot); } r_pos += 4; #undef _R } _FORCE_INLINE_ void CodeSignRequirements::_parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); uint32_t key_size = _R(r_pos); ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); CharString key; key.resize(key_size); memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size); r_pos += 4 + key_size + PAD(key_size, 4); r_out += "[" + String::utf8(key, key_size) + "]"; #undef _R } _FORCE_INLINE_ void CodeSignRequirements::_parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); uint32_t key_size = _R(r_pos); ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); r_out += "[field."; r_out += itos(blob[r_pos + 4] / 40) + "."; r_out += itos(blob[r_pos + 4] % 40); uint32_t spos = r_pos + 5; while (spos < r_pos + 4 + key_size) { r_out += "."; if (blob[spos] <= 127) { r_out += itos(blob[spos]); spos += 1; } else { uint32_t x = (0x7F & blob[spos]) << 7; spos += 1; while (blob[spos] > 127) { x = (x + (0x7F & blob[spos])) << 7; spos += 1; } x = (x + (0x7F & blob[spos])); r_out += itos(x); spos += 1; } } r_out += "]"; r_pos += 4 + key_size + PAD(key_size, 4); #undef _R } _FORCE_INLINE_ void CodeSignRequirements::_parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); uint32_t tag_size = _R(r_pos); ERR_FAIL_COND_MSG(r_pos + tag_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); PackedByteArray data; data.resize(tag_size); memcpy(data.ptrw(), blob.ptr() + r_pos + 4, tag_size); r_out += "H\"" + String::hex_encode_buffer(data.ptr(), data.size()) + "\""; r_pos += 4 + tag_size + PAD(tag_size, 4); #undef _R } _FORCE_INLINE_ void CodeSignRequirements::_parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); uint32_t key_size = _R(r_pos); ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds."); CharString key; key.resize(key_size); memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size); r_pos += 4 + key_size + PAD(key_size, 4); r_out += "\"" + String::utf8(key, key_size) + "\""; #undef _R } _FORCE_INLINE_ void CodeSignRequirements::_parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds."); uint32_t date = _R(r_pos); time_t t = 978307200 + date; struct tm lt; #ifdef WINDOWS_ENABLED gmtime_s(<, &t); #else gmtime_r(&t, <); #endif r_out += vformat("<%04d-%02d-%02d ", (int)(1900 + lt.tm_year), (int)(lt.tm_mon + 1), (int)(lt.tm_mday)) + vformat("%02d:%02d:%02d +0000>", (int)(lt.tm_hour), (int)(lt.tm_min), (int)(lt.tm_sec)); #undef _R } _FORCE_INLINE_ bool CodeSignRequirements::_parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) ERR_FAIL_COND_V_MSG(r_pos >= p_rq_size, false, "CodeSign/Requirements: Out of bounds."); uint32_t match = _R(r_pos); r_pos += 4; switch (match) { case 0x00000000: { r_out += "exists"; } break; case 0x00000001: { r_out += "= "; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000002: { r_out += "~ "; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000003: { r_out += "= *"; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000004: { r_out += "= "; _parse_value(r_pos, r_out, p_rq_size); r_out += "*"; } break; case 0x00000005: { r_out += "< "; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000006: { r_out += "> "; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000007: { r_out += "<= "; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000008: { r_out += ">= "; _parse_value(r_pos, r_out, p_rq_size); } break; case 0x00000009: { r_out += "= "; _parse_date(r_pos, r_out, p_rq_size); } break; case 0x0000000A: { r_out += "< "; _parse_date(r_pos, r_out, p_rq_size); } break; case 0x0000000B: { r_out += "> "; _parse_date(r_pos, r_out, p_rq_size); } break; case 0x0000000C: { r_out += "<= "; _parse_date(r_pos, r_out, p_rq_size); } break; case 0x0000000D: { r_out += ">= "; _parse_date(r_pos, r_out, p_rq_size); } break; case 0x0000000E: { r_out += "absent"; } break; default: { return false; } } return true; #undef _R } Vector CodeSignRequirements::parse_requirements() const { #define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x)) Vector list; // Read requirements set header. ERR_FAIL_COND_V_MSG(blob.size() < 12, list, "CodeSign/Requirements: Blob is too small."); uint32_t magic = _R(0); ERR_FAIL_COND_V_MSG(magic != 0xfade0c01, list, "CodeSign/Requirements: Invalid set magic."); uint32_t size = _R(4); ERR_FAIL_COND_V_MSG(size != (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid set size."); uint32_t count = _R(8); for (uint32_t i = 0; i < count; i++) { String out; // Read requirement header. uint32_t rq_type = _R(12 + i * 8); uint32_t rq_offset = _R(12 + i * 8 + 4); ERR_FAIL_COND_V_MSG(rq_offset + 12 >= (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid requirement offset."); switch (rq_type) { case 0x00000001: { out += "host => "; } break; case 0x00000002: { out += "guest => "; } break; case 0x00000003: { out += "designated => "; } break; case 0x00000004: { out += "library => "; } break; case 0x00000005: { out += "plugin => "; } break; default: { ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement type."); } } uint32_t rq_magic = _R(rq_offset); uint32_t rq_size = _R(rq_offset + 4); uint32_t rq_ver = _R(rq_offset + 8); uint32_t pos = rq_offset + 12; ERR_FAIL_COND_V_MSG(rq_magic != 0xfade0c00, list, "CodeSign/Requirements: Invalid requirement magic."); ERR_FAIL_COND_V_MSG(rq_ver != 0x00000001, list, "CodeSign/Requirements: Invalid requirement version."); // Read requirement tokens. List tokens; while (pos < rq_offset + rq_size) { uint32_t rq_tag = _R(pos); pos += 4; String token; switch (rq_tag) { case 0x00000000: { token = "false"; } break; case 0x00000001: { token = "true"; } break; case 0x00000002: { token = "identifier "; _parse_value(pos, token, rq_offset + rq_size); } break; case 0x00000003: { token = "anchor apple"; } break; case 0x00000004: { _parse_certificate_slot(pos, token, rq_offset + rq_size); token += " "; _parse_hash_string(pos, token, rq_offset + rq_size); } break; case 0x00000005: { token = "info"; _parse_key(pos, token, rq_offset + rq_size); token += " = "; _parse_value(pos, token, rq_offset + rq_size); } break; case 0x00000006: { token = "and"; } break; case 0x00000007: { token = "or"; } break; case 0x00000008: { token = "cdhash "; _parse_hash_string(pos, token, rq_offset + rq_size); } break; case 0x00000009: { token = "!"; } break; case 0x0000000A: { token = "info"; _parse_key(pos, token, rq_offset + rq_size); token += " "; ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); } break; case 0x0000000B: { _parse_certificate_slot(pos, token, rq_offset + rq_size); _parse_key(pos, token, rq_offset + rq_size); token += " "; ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); } break; case 0x0000000C: { _parse_certificate_slot(pos, token, rq_offset + rq_size); token += " trusted"; } break; case 0x0000000D: { token = "anchor trusted"; } break; case 0x0000000E: { _parse_certificate_slot(pos, token, rq_offset + rq_size); _parse_oid_key(pos, token, rq_offset + rq_size); token += " "; ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix."); } break; case 0x0000000F: { token = "anchor apple generic"; } break; default: { ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement token."); } break; } tokens.push_back(token); } // Polish to infix notation (w/o bracket optimization). for (List::Element *E = tokens.back(); E; E = E->prev()) { if (E->get() == "and") { ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence."); String token = "(" + E->next()->get() + " and " + E->next()->next()->get() + ")"; tokens.erase(E->next()->next()); tokens.erase(E->next()); E->get() = token; } else if (E->get() == "or") { ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence."); String token = "(" + E->next()->get() + " or " + E->next()->next()->get() + ")"; tokens.erase(E->next()->next()); tokens.erase(E->next()); E->get() = token; } } if (tokens.size() == 1) { list.push_back(out + tokens.front()->get()); } else { ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid token sequence."); } } return list; #undef _R } PackedByteArray CodeSignRequirements::get_hash_sha1() const { PackedByteArray hash; hash.resize(0x14); CryptoCore::SHA1Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } PackedByteArray CodeSignRequirements::get_hash_sha256() const { PackedByteArray hash; hash.resize(0x20); CryptoCore::SHA256Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } int CodeSignRequirements::get_size() const { return blob.size(); } void CodeSignRequirements::write_to_file(Ref p_file) const { ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Requirements: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } /*************************************************************************/ /* CodeSignEntitlementsText */ /*************************************************************************/ CodeSignEntitlementsText::CodeSignEntitlementsText() { blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic. blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes). } CodeSignEntitlementsText::CodeSignEntitlementsText(const String &p_string) { CharString utf8 = p_string.utf8(); blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic. for (int i = 3; i >= 0; i--) { uint8_t x = ((utf8.length() + 8) >> i * 8) & 0xFF; // Size. blob.push_back(x); } for (int i = 0; i < utf8.length(); i++) { // Write data. blob.push_back(utf8[i]); } } PackedByteArray CodeSignEntitlementsText::get_hash_sha1() const { PackedByteArray hash; hash.resize(0x14); CryptoCore::SHA1Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } PackedByteArray CodeSignEntitlementsText::get_hash_sha256() const { PackedByteArray hash; hash.resize(0x20); CryptoCore::SHA256Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } int CodeSignEntitlementsText::get_size() const { return blob.size(); } void CodeSignEntitlementsText::write_to_file(Ref p_file) const { ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsText: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } /*************************************************************************/ /* CodeSignEntitlementsBinary */ /*************************************************************************/ CodeSignEntitlementsBinary::CodeSignEntitlementsBinary() { blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic. blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes). } CodeSignEntitlementsBinary::CodeSignEntitlementsBinary(const String &p_string) { PList pl = PList(p_string); PackedByteArray asn1 = pl.save_asn1(); blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic. uint32_t size = asn1.size() + 8; for (int i = 3; i >= 0; i--) { uint8_t x = (size >> i * 8) & 0xFF; // Size. blob.push_back(x); } blob.append_array(asn1); // Write data. } PackedByteArray CodeSignEntitlementsBinary::get_hash_sha1() const { PackedByteArray hash; hash.resize(0x14); CryptoCore::SHA1Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } PackedByteArray CodeSignEntitlementsBinary::get_hash_sha256() const { PackedByteArray hash; hash.resize(0x20); CryptoCore::SHA256Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } int CodeSignEntitlementsBinary::get_size() const { return blob.size(); } void CodeSignEntitlementsBinary::write_to_file(Ref p_file) const { ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsBinary: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } /*************************************************************************/ /* CodeSignCodeDirectory */ /*************************************************************************/ CodeSignCodeDirectory::CodeSignCodeDirectory() { blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic. blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Size (8 bytes). } CodeSignCodeDirectory::CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit) { pages = p_code_limit / (uint64_t(1) << p_page_size); remain = p_code_limit % (uint64_t(1) << p_page_size); code_slots = pages + (remain > 0 ? 1 : 0); special_slots = 7; int cd_size = 8 + sizeof(CodeDirectoryHeader) + (code_slots + special_slots) * p_hash_size + p_id.size() + p_team_id.size(); int cd_off = 8 + sizeof(CodeDirectoryHeader); blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic. for (int i = 3; i >= 0; i--) { uint8_t x = (cd_size >> i * 8) & 0xFF; // Size. blob.push_back(x); } blob.resize(cd_size); memset(blob.ptrw() + 8, 0x00, cd_size - 8); CodeDirectoryHeader *cd = reinterpret_cast(blob.ptrw() + 8); bool is_64_cl = (p_code_limit >= std::numeric_limits::max()); // Version and options. cd->version = BSWAP32(0x20500); cd->flags = BSWAP32(SIGNATURE_ADHOC | SIGNATURE_RUNTIME); cd->special_slots = BSWAP32(special_slots); cd->code_slots = BSWAP32(code_slots); if (is_64_cl) { cd->code_limit_64 = BSWAP64(p_code_limit); } else { cd->code_limit = BSWAP32(p_code_limit); } cd->hash_size = p_hash_size; cd->hash_type = p_hash_type; cd->page_size = p_page_size; cd->exec_seg_base = 0x00; cd->exec_seg_limit = BSWAP64(p_exe_limit); cd->exec_seg_flags = 0; if (p_main) { cd->exec_seg_flags |= EXECSEG_MAIN_BINARY; } cd->exec_seg_flags = BSWAP64(cd->exec_seg_flags); uint32_t version = (11 << 16) + (3 << 8) + 0; // Version 11.3.0 cd->runtime = BSWAP32(version); // Copy ID. cd->ident_offset = BSWAP32(cd_off); memcpy(blob.ptrw() + cd_off, p_id.get_data(), p_id.size()); cd_off += p_id.size(); // Copy Team ID. if (p_team_id.length() > 0) { cd->team_offset = BSWAP32(cd_off); memcpy(blob.ptrw() + cd_off, p_team_id.get_data(), p_team_id.size()); cd_off += p_team_id.size(); } else { cd->team_offset = 0; } // Scatter vector. cd->scatter_vector_offset = 0; // Not used. // Executable hashes offset. cd->hash_offset = BSWAP32(cd_off + special_slots * cd->hash_size); } bool CodeSignCodeDirectory::set_hash_in_slot(const PackedByteArray &p_hash, int p_slot) { ERR_FAIL_COND_V_MSG((p_slot < -special_slots) || (p_slot >= code_slots), false, vformat("CodeSign/CodeDirectory: Invalid hash slot index: %d.", p_slot)); CodeDirectoryHeader *cd = reinterpret_cast(blob.ptrw() + 8); for (int i = 0; i < cd->hash_size; i++) { blob.write[BSWAP32(cd->hash_offset) + p_slot * cd->hash_size + i] = p_hash[i]; } return true; } int32_t CodeSignCodeDirectory::get_page_count() { return pages; } int32_t CodeSignCodeDirectory::get_page_remainder() { return remain; } PackedByteArray CodeSignCodeDirectory::get_hash_sha1() const { PackedByteArray hash; hash.resize(0x14); CryptoCore::SHA1Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } PackedByteArray CodeSignCodeDirectory::get_hash_sha256() const { PackedByteArray hash; hash.resize(0x20); CryptoCore::SHA256Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } int CodeSignCodeDirectory::get_size() const { return blob.size(); } void CodeSignCodeDirectory::write_to_file(Ref p_file) const { ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/CodeDirectory: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } /*************************************************************************/ /* CodeSignSignature */ /*************************************************************************/ CodeSignSignature::CodeSignSignature() { blob.append_array({ 0xFA, 0xDE, 0x0B, 0x01 }); // Signature magic. uint32_t sign_size = 8; // Ad-hoc signature is empty. for (int i = 3; i >= 0; i--) { uint8_t x = (sign_size >> i * 8) & 0xFF; // Size. blob.push_back(x); } } PackedByteArray CodeSignSignature::get_hash_sha1() const { PackedByteArray hash; hash.resize(0x14); CryptoCore::SHA1Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } PackedByteArray CodeSignSignature::get_hash_sha256() const { PackedByteArray hash; hash.resize(0x20); CryptoCore::SHA256Context ctx; ctx.start(); ctx.update(blob.ptr(), blob.size()); ctx.finish(hash.ptrw()); return hash; } int CodeSignSignature::get_size() const { return blob.size(); } void CodeSignSignature::write_to_file(Ref p_file) const { ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Signature: Invalid file handle."); p_file->store_buffer(blob.ptr(), blob.size()); } /*************************************************************************/ /* CodeSignSuperBlob */ /*************************************************************************/ bool CodeSignSuperBlob::add_blob(const Ref &p_blob) { if (p_blob.is_valid()) { blobs.push_back(p_blob); return true; } else { return false; } } int CodeSignSuperBlob::get_size() const { int size = 12 + blobs.size() * 8; for (int i = 0; i < blobs.size(); i++) { if (blobs[i].is_null()) { return 0; } size += blobs[i]->get_size(); } return size; } void CodeSignSuperBlob::write_to_file(Ref p_file) const { ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/SuperBlob: Invalid file handle."); uint32_t size = get_size(); uint32_t data_offset = 12 + blobs.size() * 8; // Write header. p_file->store_32(BSWAP32(0xfade0cc0)); p_file->store_32(BSWAP32(size)); p_file->store_32(BSWAP32(blobs.size())); // Write index. for (int i = 0; i < blobs.size(); i++) { if (blobs[i].is_null()) { return; } p_file->store_32(BSWAP32(blobs[i]->get_index_type())); p_file->store_32(BSWAP32(data_offset)); data_offset += blobs[i]->get_size(); } // Write blobs. for (int i = 0; i < blobs.size(); i++) { blobs[i]->write_to_file(p_file); } } /*************************************************************************/ /* CodeSign */ /*************************************************************************/ PackedByteArray CodeSign::file_hash_sha1(const String &p_path) { PackedByteArray file_hash; Ref f = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); CryptoCore::SHA1Context ctx; ctx.start(); unsigned char step[4096]; while (true) { uint64_t br = f->get_buffer(step, 4096); if (br > 0) { ctx.update(step, br); } if (br < 4096) { break; } } file_hash.resize(0x14); ctx.finish(file_hash.ptrw()); return file_hash; } PackedByteArray CodeSign::file_hash_sha256(const String &p_path) { PackedByteArray file_hash; Ref f = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path)); CryptoCore::SHA256Context ctx; ctx.start(); unsigned char step[4096]; while (true) { uint64_t br = f->get_buffer(step, 4096); if (br > 0) { ctx.update(step, br); } if (br < 4096) { break; } } file_hash.resize(0x20); ctx.finish(file_hash.ptrw()); return file_hash; } Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg) { #define CLEANUP() \ if (files_to_sign.size() > 1) { \ for (int j = 0; j < files_to_sign.size(); j++) { \ da->remove(files_to_sign[j]); \ } \ } print_verbose(vformat("CodeSign: Signing executable: %s, bundle: %s with entitlements %s", p_exe_path, p_bundle_path, p_ent_path)); PackedByteArray info_hash1, info_hash2; PackedByteArray res_hash1, res_hash2; String id; String main_exe = p_exe_path; Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (da.is_null()) { r_error_msg = TTR("Can't get filesystem access."); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); } // Read Info.plist. if (!p_info.is_empty()) { print_verbose("CodeSign: Reading bundle info..."); PList info_plist; if (info_plist.load_file(p_info)) { info_hash1 = file_hash_sha1(p_info); info_hash2 = file_hash_sha256(p_info); if (info_hash1.is_empty() || info_hash2.is_empty()) { r_error_msg = TTR("Failed to get Info.plist hash."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get Info.plist hash."); } if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { main_exe = p_exe_path.path_join(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); } else { r_error_msg = TTR("Invalid Info.plist, no exe name."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no exe name."); } if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleIdentifier")) { id = info_plist.get_root()->data_dict["CFBundleIdentifier"]->data_string.get_data(); } else { r_error_msg = TTR("Invalid Info.plist, no bundle id."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no bundle id."); } } else { r_error_msg = TTR("Invalid Info.plist, can't load."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, can't load."); } } // Extract fat binary. Vector files_to_sign; if (LipO::is_lipo(main_exe)) { print_verbose(vformat("CodeSign: Executable is fat, extracting...")); String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join("_lipo"); Error err = da->make_dir_recursive(tmp_path_name); if (err != OK) { r_error_msg = vformat(TTR("Failed to create \"%s\" subfolder."), tmp_path_name); ERR_FAIL_V_MSG(FAILED, vformat("CodeSign: Failed to create \"%s\" subfolder.", tmp_path_name)); } LipO lip; if (lip.open_file(main_exe)) { for (int i = 0; i < lip.get_arch_count(); i++) { if (!lip.extract_arch(i, tmp_path_name.path_join("_exe_" + itos(i)))) { CLEANUP(); r_error_msg = TTR("Failed to extract thin binary."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to extract thin binary."); } files_to_sign.push_back(tmp_path_name.path_join("_exe_" + itos(i))); } } } else if (MachO::is_macho(main_exe)) { print_verbose("CodeSign: Executable is thin..."); files_to_sign.push_back(main_exe); } else { r_error_msg = TTR("Invalid binary format."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid binary format."); } // Check if it's already signed. if (!p_force) { for (int i = 0; i < files_to_sign.size(); i++) { MachO mh; mh.open_file(files_to_sign[i]); if (mh.is_signed()) { CLEANUP(); r_error_msg = TTR("Already signed!"); ERR_FAIL_V_MSG(FAILED, "CodeSign: Already signed!"); } } } // Generate core resources. if (!p_bundle_path.is_empty()) { print_verbose("CodeSign: Generating bundle CodeResources..."); CodeSignCodeResources cr; if (p_ios_bundle) { cr.add_rule1("^.*"); cr.add_rule1("^.*\\.lproj/", "optional", 100); cr.add_rule1("^.*\\.lproj/locversion.plist$", "omit", 1100); cr.add_rule1("^Base\\.lproj/", "", 1010); cr.add_rule1("^version.plist$"); cr.add_rule2(".*\\.dSYM($|/)", "", 11); cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000); cr.add_rule2("^.*"); cr.add_rule2("^.*\\.lproj/", "optional", 1000); cr.add_rule2("^.*\\.lproj/locversion.plist$", "omit", 1100); cr.add_rule2("^Base\\.lproj/", "", 1010); cr.add_rule2("^Info\\.plist$", "omit", 20); cr.add_rule2("^PkgInfo$", "omit", 20); cr.add_rule2("^embedded\\.provisionprofile$", "", 10); cr.add_rule2("^version\\.plist$", "", 20); cr.add_rule2("^_MASReceipt", "omit", 2000, false); cr.add_rule2("^_CodeSignature", "omit", 2000, false); cr.add_rule2("^CodeResources", "omit", 2000, false); } else { cr.add_rule1("^Resources/"); cr.add_rule1("^Resources/.*\\.lproj/", "optional", 1000); cr.add_rule1("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100); cr.add_rule1("^Resources/Base\\.lproj/", "", 1010); cr.add_rule1("^version.plist$"); cr.add_rule2(".*\\.dSYM($|/)", "", 11); cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000); cr.add_rule2("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", "nested", 10); cr.add_rule2("^.*"); cr.add_rule2("^Info\\.plist$", "omit", 20); cr.add_rule2("^PkgInfo$", "omit", 20); cr.add_rule2("^Resources/", "", 20); cr.add_rule2("^Resources/.*\\.lproj/", "optional", 1000); cr.add_rule2("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100); cr.add_rule2("^Resources/Base\\.lproj/", "", 1010); cr.add_rule2("^[^/]+$", "nested", 10); cr.add_rule2("^embedded\\.provisionprofile$", "", 10); cr.add_rule2("^version\\.plist$", "", 20); cr.add_rule2("^_MASReceipt", "omit", 2000, false); cr.add_rule2("^_CodeSignature", "omit", 2000, false); cr.add_rule2("^CodeResources", "omit", 2000, false); } if (!cr.add_folder_recursive(p_bundle_path, "", main_exe)) { CLEANUP(); r_error_msg = TTR("Failed to process nested resources."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to process nested resources."); } Error err = da->make_dir_recursive(p_bundle_path.path_join("_CodeSignature")); if (err != OK) { CLEANUP(); r_error_msg = TTR("Failed to create _CodeSignature subfolder."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create _CodeSignature subfolder."); } cr.save_to_file(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources")); res_hash1 = file_hash_sha1(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources")); res_hash2 = file_hash_sha256(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources")); if (res_hash1.is_empty() || res_hash2.is_empty()) { CLEANUP(); r_error_msg = TTR("Failed to get CodeResources hash."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get CodeResources hash."); } } // Generate common signature structures. if (id.is_empty()) { CryptoCore::RandomGenerator rng; ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); uint8_t uuid[16]; Error err = rng.get_random_bytes(uuid, 16); ERR_FAIL_COND_V_MSG(err, err, "Failed to generate UUID."); id = (String("a-55554944") /*a-UUID*/ + String::hex_encode_buffer(uuid, 16)); } CharString uuid_str = id.utf8(); print_verbose(vformat("CodeSign: Used bundle ID: %s", id)); print_verbose("CodeSign: Processing entitlements..."); Ref cet; Ref ceb; if (!p_ent_path.is_empty()) { String entitlements = FileAccess::get_file_as_string(p_ent_path); if (entitlements.is_empty()) { CLEANUP(); r_error_msg = TTR("Invalid entitlements file."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid entitlements file."); } cet = Ref(memnew(CodeSignEntitlementsText(entitlements))); ceb = Ref(memnew(CodeSignEntitlementsBinary(entitlements))); } print_verbose("CodeSign: Generating requirements..."); Ref rq; String team_id = ""; rq = Ref(memnew(CodeSignRequirements())); // Sign executables. for (int i = 0; i < files_to_sign.size(); i++) { MachO mh; if (!mh.open_file(files_to_sign[i])) { CLEANUP(); r_error_msg = TTR("Invalid executable file."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid executable file."); } print_verbose(vformat("CodeSign: Signing executable for cputype: %d ...", mh.get_cputype())); print_verbose("CodeSign: Generating CodeDirectory..."); Ref cd1 = memnew(CodeSignCodeDirectory(0x14, 0x01, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit())); Ref cd2 = memnew(CodeSignCodeDirectory(0x20, 0x02, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit())); print_verbose("CodeSign: Calculating special slot hashes..."); if (info_hash2.size() == 0x20) { cd2->set_hash_in_slot(info_hash2, CodeSignCodeDirectory::SLOT_INFO_PLIST); } if (info_hash1.size() == 0x14) { cd1->set_hash_in_slot(info_hash1, CodeSignCodeDirectory::SLOT_INFO_PLIST); } cd1->set_hash_in_slot(rq->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS); cd2->set_hash_in_slot(rq->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS); if (res_hash2.size() == 0x20) { cd2->set_hash_in_slot(res_hash2, CodeSignCodeDirectory::SLOT_RESOURCES); } if (res_hash1.size() == 0x14) { cd1->set_hash_in_slot(res_hash1, CodeSignCodeDirectory::SLOT_RESOURCES); } if (cet.is_valid()) { cd1->set_hash_in_slot(cet->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); //Text variant. cd2->set_hash_in_slot(cet->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); } if (ceb.is_valid()) { cd1->set_hash_in_slot(ceb->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); //ASN.1 variant. cd2->set_hash_in_slot(ceb->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); } // Calculate signature size. int sign_size = 12; // SuperBlob header. sign_size += cd1->get_size() + 8; sign_size += cd2->get_size() + 8; sign_size += rq->get_size() + 8; if (cet.is_valid()) { sign_size += cet->get_size() + 8; } if (ceb.is_valid()) { sign_size += ceb->get_size() + 8; } sign_size += 16; // Empty signature size. // Alloc/resize signature load command. print_verbose(vformat("CodeSign: Reallocating space for the signature superblob (%d)...", sign_size)); if (!mh.set_signature_size(sign_size)) { CLEANUP(); r_error_msg = TTR("Can't resize signature load command."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Can't resize signature load command."); } print_verbose("CodeSign: Calculating executable code hashes..."); // Calculate executable code hashes. PackedByteArray buffer; PackedByteArray hash1, hash2; hash1.resize(0x14); hash2.resize(0x20); buffer.resize(1 << 12); mh.get_file()->seek(0); for (int32_t j = 0; j < cd2->get_page_count(); j++) { mh.get_file()->get_buffer(buffer.ptrw(), (1 << 12)); CryptoCore::SHA256Context ctx2; ctx2.start(); ctx2.update(buffer.ptr(), (1 << 12)); ctx2.finish(hash2.ptrw()); cd2->set_hash_in_slot(hash2, j); CryptoCore::SHA1Context ctx1; ctx1.start(); ctx1.update(buffer.ptr(), (1 << 12)); ctx1.finish(hash1.ptrw()); cd1->set_hash_in_slot(hash1, j); } if (cd2->get_page_remainder() > 0) { mh.get_file()->get_buffer(buffer.ptrw(), cd2->get_page_remainder()); CryptoCore::SHA256Context ctx2; ctx2.start(); ctx2.update(buffer.ptr(), cd2->get_page_remainder()); ctx2.finish(hash2.ptrw()); cd2->set_hash_in_slot(hash2, cd2->get_page_count()); CryptoCore::SHA1Context ctx1; ctx1.start(); ctx1.update(buffer.ptr(), cd1->get_page_remainder()); ctx1.finish(hash1.ptrw()); cd1->set_hash_in_slot(hash1, cd1->get_page_count()); } print_verbose("CodeSign: Generating signature..."); Ref cs; cs = Ref(memnew(CodeSignSignature())); print_verbose("CodeSign: Writing signature superblob..."); // Write signature data to the executable. CodeSignSuperBlob sb = CodeSignSuperBlob(); sb.add_blob(cd2); sb.add_blob(cd1); sb.add_blob(rq); if (cet.is_valid()) { sb.add_blob(cet); } if (ceb.is_valid()) { sb.add_blob(ceb); } sb.add_blob(cs); mh.get_file()->seek(mh.get_signature_offset()); sb.write_to_file(mh.get_file()); } if (files_to_sign.size() > 1) { print_verbose("CodeSign: Rebuilding fat executable..."); LipO lip; if (!lip.create_file(main_exe, files_to_sign)) { CLEANUP(); r_error_msg = TTR("Failed to create fat binary."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create fat binary."); } CLEANUP(); } FileAccess::set_unix_permissions(main_exe, 0755); // Restore unix permissions. return OK; #undef CLEANUP } Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) { Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (da.is_null()) { r_error_msg = TTR("Can't get filesystem access."); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); } if (da->dir_exists(p_path)) { String fmw_ver = "Current"; // Framework version (default). String info_path; String main_exe; String bundle_path; bool bundle = false; bool ios_bundle = false; if (da->file_exists(p_path.path_join("Contents/Info.plist"))) { info_path = p_path.path_join("Contents/Info.plist"); main_exe = p_path.path_join("Contents/MacOS"); bundle_path = p_path.path_join("Contents"); bundle = true; } else if (da->file_exists(p_path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { info_path = p_path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); main_exe = p_path.path_join(vformat("Versions/%s", fmw_ver)); bundle_path = p_path.path_join(vformat("Versions/%s", fmw_ver)); bundle = true; } else if (da->file_exists(p_path.path_join("Info.plist"))) { info_path = p_path.path_join("Info.plist"); main_exe = p_path; bundle_path = p_path; bundle = true; ios_bundle = true; } if (bundle) { return _codesign_file(p_use_hardened_runtime, p_force, info_path, main_exe, bundle_path, p_ent_path, ios_bundle, r_error_msg); } else { r_error_msg = TTR("Unknown bundle type."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown bundle type."); } } else if (da->file_exists(p_path)) { return _codesign_file(p_use_hardened_runtime, p_force, "", p_path, "", p_ent_path, false, r_error_msg); } else { r_error_msg = TTR("Unknown object type."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown object type."); } } #endif // MODULE_REGEX_ENABLED