1/**************************************************************************/
2/* codesign.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "codesign.h"
32
33#include "lipo.h"
34#include "macho.h"
35#include "plist.h"
36
37#include "core/os/os.h"
38#include "editor/editor_paths.h"
39#include "editor/editor_settings.h"
40
41#include "modules/modules_enabled.gen.h" // For regex.
42
43#include <ctime>
44
45#ifdef MODULE_REGEX_ENABLED
46
47/*************************************************************************/
48/* CodeSignCodeResources */
49/*************************************************************************/
50
51String CodeSignCodeResources::hash_sha1_base64(const String &p_path) {
52 Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::READ);
53 ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
54
55 CryptoCore::SHA1Context ctx;
56 ctx.start();
57
58 unsigned char step[4096];
59 while (true) {
60 uint64_t br = fa->get_buffer(step, 4096);
61 if (br > 0) {
62 ctx.update(step, br);
63 }
64 if (br < 4096) {
65 break;
66 }
67 }
68
69 unsigned char hash[0x14];
70 ctx.finish(hash);
71
72 return CryptoCore::b64_encode_str(hash, 0x14);
73}
74
75String CodeSignCodeResources::hash_sha256_base64(const String &p_path) {
76 Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::READ);
77 ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
78
79 CryptoCore::SHA256Context ctx;
80 ctx.start();
81
82 unsigned char step[4096];
83 while (true) {
84 uint64_t br = fa->get_buffer(step, 4096);
85 if (br > 0) {
86 ctx.update(step, br);
87 }
88 if (br < 4096) {
89 break;
90 }
91 }
92
93 unsigned char hash[0x20];
94 ctx.finish(hash);
95
96 return CryptoCore::b64_encode_str(hash, 0x20);
97}
98
99void CodeSignCodeResources::add_rule1(const String &p_rule, const String &p_key, int p_weight, bool p_store) {
100 rules1.push_back(CRRule(p_rule, p_key, p_weight, p_store));
101}
102
103void CodeSignCodeResources::add_rule2(const String &p_rule, const String &p_key, int p_weight, bool p_store) {
104 rules2.push_back(CRRule(p_rule, p_key, p_weight, p_store));
105}
106
107CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules1(const String &p_path) const {
108 CRMatch found = CRMatch::CR_MATCH_NO;
109 int weight = 0;
110 for (int i = 0; i < rules1.size(); i++) {
111 RegEx regex = RegEx(rules1[i].file_pattern);
112 if (regex.search(p_path).is_valid()) {
113 if (rules1[i].key == "omit") {
114 return CRMatch::CR_MATCH_NO;
115 } else if (rules1[i].key == "nested") {
116 if (weight <= rules1[i].weight) {
117 found = CRMatch::CR_MATCH_NESTED;
118 weight = rules1[i].weight;
119 }
120 } else if (rules1[i].key == "optional") {
121 if (weight <= rules1[i].weight) {
122 found = CRMatch::CR_MATCH_OPTIONAL;
123 weight = rules1[i].weight;
124 }
125 } else {
126 if (weight <= rules1[i].weight) {
127 found = CRMatch::CR_MATCH_YES;
128 weight = rules1[i].weight;
129 }
130 }
131 }
132 }
133 return found;
134}
135
136CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules2(const String &p_path) const {
137 CRMatch found = CRMatch::CR_MATCH_NO;
138 int weight = 0;
139 for (int i = 0; i < rules2.size(); i++) {
140 RegEx regex = RegEx(rules2[i].file_pattern);
141 if (regex.search(p_path).is_valid()) {
142 if (rules2[i].key == "omit") {
143 return CRMatch::CR_MATCH_NO;
144 } else if (rules2[i].key == "nested") {
145 if (weight <= rules2[i].weight) {
146 found = CRMatch::CR_MATCH_NESTED;
147 weight = rules2[i].weight;
148 }
149 } else if (rules2[i].key == "optional") {
150 if (weight <= rules2[i].weight) {
151 found = CRMatch::CR_MATCH_OPTIONAL;
152 weight = rules2[i].weight;
153 }
154 } else {
155 if (weight <= rules2[i].weight) {
156 found = CRMatch::CR_MATCH_YES;
157 weight = rules2[i].weight;
158 }
159 }
160 }
161 }
162 return found;
163}
164
165bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path) {
166 CRMatch found = match_rules1(p_path);
167 if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) {
168 return true; // No match.
169 }
170
171 CRFile f;
172 f.name = p_path;
173 f.optional = (found == CRMatch::CR_MATCH_OPTIONAL);
174 f.nested = false;
175 f.hash = hash_sha1_base64(p_root.path_join(p_path));
176 print_verbose(vformat("CodeSign/CodeResources: File(V1) %s hash1:%s", f.name, f.hash));
177
178 files1.push_back(f);
179 return true;
180}
181
182bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path) {
183 CRMatch found = match_rules2(p_path);
184 if (found == CRMatch::CR_MATCH_NESTED) {
185 return add_nested_file(p_root, p_path, p_root.path_join(p_path));
186 }
187 if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) {
188 return true; // No match.
189 }
190
191 CRFile f;
192 f.name = p_path;
193 f.optional = (found == CRMatch::CR_MATCH_OPTIONAL);
194 f.nested = false;
195 f.hash = hash_sha1_base64(p_root.path_join(p_path));
196 f.hash2 = hash_sha256_base64(p_root.path_join(p_path));
197
198 print_verbose(vformat("CodeSign/CodeResources: File(V2) %s hash1:%s hash2:%s", f.name, f.hash, f.hash2));
199
200 files2.push_back(f);
201 return true;
202}
203
204bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &p_path, const String &p_exepath) {
205#define CLEANUP() \
206 if (files_to_add.size() > 1) { \
207 for (int j = 0; j < files_to_add.size(); j++) { \
208 da->remove(files_to_add[j]); \
209 } \
210 }
211
212 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
213 ERR_FAIL_COND_V(da.is_null(), false);
214
215 Vector<String> files_to_add;
216 if (LipO::is_lipo(p_exepath)) {
217 String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join("_lipo");
218 Error err = da->make_dir_recursive(tmp_path_name);
219 ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name));
220 LipO lip;
221 if (lip.open_file(p_exepath)) {
222 for (int i = 0; i < lip.get_arch_count(); i++) {
223 if (!lip.extract_arch(i, tmp_path_name.path_join("_rqexe_" + itos(i)))) {
224 CLEANUP();
225 ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Failed to extract thin binary.");
226 }
227 files_to_add.push_back(tmp_path_name.path_join("_rqexe_" + itos(i)));
228 }
229 }
230 } else if (MachO::is_macho(p_exepath)) {
231 files_to_add.push_back(p_exepath);
232 }
233
234 CRFile f;
235 f.name = p_path;
236 f.optional = false;
237 f.nested = true;
238 for (int i = 0; i < files_to_add.size(); i++) {
239 MachO mh;
240 if (!mh.open_file(files_to_add[i])) {
241 CLEANUP();
242 ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid executable file.");
243 }
244 PackedByteArray hash = mh.get_cdhash_sha256(); // Use SHA-256 variant, if available.
245 if (hash.size() != 0x20) {
246 hash = mh.get_cdhash_sha1(); // Use SHA-1 instead.
247 if (hash.size() != 0x14) {
248 CLEANUP();
249 ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Unsigned nested executable file.");
250 }
251 }
252 hash.resize(0x14); // Always clamp to 0x14 size.
253 f.hash = CryptoCore::b64_encode_str(hash.ptr(), hash.size());
254
255 PackedByteArray rq_blob = mh.get_requirements();
256 String req_string;
257 if (rq_blob.size() > 8) {
258 CodeSignRequirements rq = CodeSignRequirements(rq_blob);
259 Vector<String> rqs = rq.parse_requirements();
260 for (int j = 0; j < rqs.size(); j++) {
261 if (rqs[j].begins_with("designated => ")) {
262 req_string = rqs[j].replace("designated => ", "");
263 }
264 }
265 }
266 if (req_string.is_empty()) {
267 req_string = "cdhash H\"" + String::hex_encode_buffer(hash.ptr(), hash.size()) + "\"";
268 }
269 print_verbose(vformat("CodeSign/CodeResources: Nested object %s (cputype: %d) cdhash:%s designated rq:%s", f.name, mh.get_cputype(), f.hash, req_string));
270 if (f.requirements != req_string) {
271 if (i != 0) {
272 f.requirements += " or ";
273 }
274 f.requirements += req_string;
275 }
276 }
277 files2.push_back(f);
278
279 CLEANUP();
280 return true;
281
282#undef CLEANUP
283}
284
285bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) {
286 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
287 ERR_FAIL_COND_V(da.is_null(), false);
288 Error err = da->change_dir(p_root.path_join(p_path));
289 ERR_FAIL_COND_V(err != OK, false);
290
291 bool ret = true;
292 da->list_dir_begin();
293 String n = da->get_next();
294 while (n != String()) {
295 if (n != "." && n != "..") {
296 String path = p_root.path_join(p_path).path_join(n);
297 if (path == p_main_exe_path) {
298 n = da->get_next();
299 continue; // Skip main executable.
300 }
301 if (da->current_is_dir()) {
302 CRMatch found = match_rules2(p_path.path_join(n));
303 String fmw_ver = "Current"; // Framework version (default).
304 String info_path;
305 String main_exe;
306 bool bundle = false;
307 if (da->file_exists(path.path_join("Contents/Info.plist"))) {
308 info_path = path.path_join("Contents/Info.plist");
309 main_exe = path.path_join("Contents/MacOS");
310 bundle = true;
311 } else if (da->file_exists(path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) {
312 info_path = path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver));
313 main_exe = path.path_join(vformat("Versions/%s", fmw_ver));
314 bundle = true;
315 } else if (da->file_exists(path.path_join("Info.plist"))) {
316 info_path = path.path_join("Info.plist");
317 main_exe = path;
318 bundle = true;
319 }
320 if (bundle && found == CRMatch::CR_MATCH_NESTED && !info_path.is_empty()) {
321 // Read Info.plist.
322 PList info_plist;
323 if (info_plist.load_file(info_path)) {
324 if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) {
325 main_exe = main_exe.path_join(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data()));
326 } else {
327 ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, no exe name.");
328 }
329 } else {
330 ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, can't load.");
331 }
332 ret = ret && add_nested_file(p_root, p_path.path_join(n), main_exe);
333 } else {
334 ret = ret && add_folder_recursive(p_root, p_path.path_join(n), p_main_exe_path);
335 }
336 } else {
337 ret = ret && add_file1(p_root, p_path.path_join(n));
338 ret = ret && add_file2(p_root, p_path.path_join(n));
339 }
340 }
341
342 n = da->get_next();
343 }
344
345 da->list_dir_end();
346 return ret;
347}
348
349bool CodeSignCodeResources::save_to_file(const String &p_path) {
350 PList pl;
351
352 print_verbose(vformat("CodeSign/CodeResources: Writing to file: %s", p_path));
353
354 // Write version 1 hashes.
355 Ref<PListNode> files1_dict = PListNode::new_dict();
356 pl.get_root()->push_subnode(files1_dict, "files");
357 for (int i = 0; i < files1.size(); i++) {
358 if (files1[i].optional) {
359 Ref<PListNode> file_dict = PListNode::new_dict();
360 files1_dict->push_subnode(file_dict, files1[i].name);
361
362 file_dict->push_subnode(PListNode::new_data(files1[i].hash), "hash");
363 file_dict->push_subnode(PListNode::new_bool(true), "optional");
364 } else {
365 files1_dict->push_subnode(PListNode::new_data(files1[i].hash), files1[i].name);
366 }
367 }
368
369 // Write version 2 hashes.
370 Ref<PListNode> files2_dict = PListNode::new_dict();
371 pl.get_root()->push_subnode(files2_dict, "files2");
372 for (int i = 0; i < files2.size(); i++) {
373 Ref<PListNode> file_dict = PListNode::new_dict();
374 files2_dict->push_subnode(file_dict, files2[i].name);
375
376 if (files2[i].nested) {
377 file_dict->push_subnode(PListNode::new_data(files2[i].hash), "cdhash");
378 file_dict->push_subnode(PListNode::new_string(files2[i].requirements), "requirement");
379 } else {
380 file_dict->push_subnode(PListNode::new_data(files2[i].hash), "hash");
381 file_dict->push_subnode(PListNode::new_data(files2[i].hash2), "hash2");
382 if (files2[i].optional) {
383 file_dict->push_subnode(PListNode::new_bool(true), "optional");
384 }
385 }
386 }
387
388 // Write version 1 rules.
389 Ref<PListNode> rules1_dict = PListNode::new_dict();
390 pl.get_root()->push_subnode(rules1_dict, "rules");
391 for (int i = 0; i < rules1.size(); i++) {
392 if (rules1[i].store) {
393 if (rules1[i].key.is_empty() && rules1[i].weight <= 0) {
394 rules1_dict->push_subnode(PListNode::new_bool(true), rules1[i].file_pattern);
395 } else {
396 Ref<PListNode> rule_dict = PListNode::new_dict();
397 rules1_dict->push_subnode(rule_dict, rules1[i].file_pattern);
398 if (!rules1[i].key.is_empty()) {
399 rule_dict->push_subnode(PListNode::new_bool(true), rules1[i].key);
400 }
401 if (rules1[i].weight != 1) {
402 rule_dict->push_subnode(PListNode::new_real(rules1[i].weight), "weight");
403 }
404 }
405 }
406 }
407
408 // Write version 2 rules.
409 Ref<PListNode> rules2_dict = PListNode::new_dict();
410 pl.get_root()->push_subnode(rules2_dict, "rules2");
411 for (int i = 0; i < rules2.size(); i++) {
412 if (rules2[i].store) {
413 if (rules2[i].key.is_empty() && rules2[i].weight <= 0) {
414 rules2_dict->push_subnode(PListNode::new_bool(true), rules2[i].file_pattern);
415 } else {
416 Ref<PListNode> rule_dict = PListNode::new_dict();
417 rules2_dict->push_subnode(rule_dict, rules2[i].file_pattern);
418 if (!rules2[i].key.is_empty()) {
419 rule_dict->push_subnode(PListNode::new_bool(true), rules2[i].key);
420 }
421 if (rules2[i].weight != 1) {
422 rule_dict->push_subnode(PListNode::new_real(rules2[i].weight), "weight");
423 }
424 }
425 }
426 }
427 String text = pl.save_text();
428 ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed.");
429
430 Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
431 ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
432
433 CharString cs = text.utf8();
434 fa->store_buffer((const uint8_t *)cs.ptr(), cs.length());
435 return true;
436}
437
438/*************************************************************************/
439/* CodeSignRequirements */
440/*************************************************************************/
441
442CodeSignRequirements::CodeSignRequirements() {
443 blob.append_array({ 0xFA, 0xDE, 0x0C, 0x01 }); // Requirement set magic.
444 blob.append_array({ 0x00, 0x00, 0x00, 0x0C }); // Length of requirements set (12 bytes).
445 blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Empty.
446}
447
448CodeSignRequirements::CodeSignRequirements(const PackedByteArray &p_data) {
449 blob = p_data;
450}
451
452_FORCE_INLINE_ void CodeSignRequirements::_parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
453#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
454 ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
455 r_out += "certificate ";
456 uint32_t tag_slot = _R(r_pos);
457 if (tag_slot == 0x00000000) {
458 r_out += "leaf";
459 } else if (tag_slot == 0xffffffff) {
460 r_out += "root";
461 } else {
462 r_out += itos((int32_t)tag_slot);
463 }
464 r_pos += 4;
465#undef _R
466}
467
468_FORCE_INLINE_ void CodeSignRequirements::_parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
469#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
470 ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
471 uint32_t key_size = _R(r_pos);
472 ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
473 CharString key;
474 key.resize(key_size);
475 memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size);
476 r_pos += 4 + key_size + PAD(key_size, 4);
477 r_out += "[" + String::utf8(key, key_size) + "]";
478#undef _R
479}
480
481_FORCE_INLINE_ void CodeSignRequirements::_parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
482#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
483 ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
484 uint32_t key_size = _R(r_pos);
485 ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
486 r_out += "[field.";
487 r_out += itos(blob[r_pos + 4] / 40) + ".";
488 r_out += itos(blob[r_pos + 4] % 40);
489 uint32_t spos = r_pos + 5;
490 while (spos < r_pos + 4 + key_size) {
491 r_out += ".";
492 if (blob[spos] <= 127) {
493 r_out += itos(blob[spos]);
494 spos += 1;
495 } else {
496 uint32_t x = (0x7F & blob[spos]) << 7;
497 spos += 1;
498 while (blob[spos] > 127) {
499 x = (x + (0x7F & blob[spos])) << 7;
500 spos += 1;
501 }
502 x = (x + (0x7F & blob[spos]));
503 r_out += itos(x);
504 spos += 1;
505 }
506 }
507 r_out += "]";
508 r_pos += 4 + key_size + PAD(key_size, 4);
509#undef _R
510}
511
512_FORCE_INLINE_ void CodeSignRequirements::_parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
513#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
514 ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
515 uint32_t tag_size = _R(r_pos);
516 ERR_FAIL_COND_MSG(r_pos + tag_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
517 PackedByteArray data;
518 data.resize(tag_size);
519 memcpy(data.ptrw(), blob.ptr() + r_pos + 4, tag_size);
520 r_out += "H\"" + String::hex_encode_buffer(data.ptr(), data.size()) + "\"";
521 r_pos += 4 + tag_size + PAD(tag_size, 4);
522#undef _R
523}
524
525_FORCE_INLINE_ void CodeSignRequirements::_parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
526#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
527 ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
528 uint32_t key_size = _R(r_pos);
529 ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
530 CharString key;
531 key.resize(key_size);
532 memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size);
533 r_pos += 4 + key_size + PAD(key_size, 4);
534 r_out += "\"" + String::utf8(key, key_size) + "\"";
535#undef _R
536}
537
538_FORCE_INLINE_ void CodeSignRequirements::_parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
539#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
540 ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
541 uint32_t date = _R(r_pos);
542 time_t t = 978307200 + date;
543 struct tm lt;
544#ifdef WINDOWS_ENABLED
545 gmtime_s(&lt, &t);
546#else
547 gmtime_r(&t, &lt);
548#endif
549 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));
550#undef _R
551}
552
553_FORCE_INLINE_ bool CodeSignRequirements::_parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
554#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
555 ERR_FAIL_COND_V_MSG(r_pos >= p_rq_size, false, "CodeSign/Requirements: Out of bounds.");
556 uint32_t match = _R(r_pos);
557 r_pos += 4;
558 switch (match) {
559 case 0x00000000: {
560 r_out += "exists";
561 } break;
562 case 0x00000001: {
563 r_out += "= ";
564 _parse_value(r_pos, r_out, p_rq_size);
565 } break;
566 case 0x00000002: {
567 r_out += "~ ";
568 _parse_value(r_pos, r_out, p_rq_size);
569 } break;
570 case 0x00000003: {
571 r_out += "= *";
572 _parse_value(r_pos, r_out, p_rq_size);
573 } break;
574 case 0x00000004: {
575 r_out += "= ";
576 _parse_value(r_pos, r_out, p_rq_size);
577 r_out += "*";
578 } break;
579 case 0x00000005: {
580 r_out += "< ";
581 _parse_value(r_pos, r_out, p_rq_size);
582 } break;
583 case 0x00000006: {
584 r_out += "> ";
585 _parse_value(r_pos, r_out, p_rq_size);
586 } break;
587 case 0x00000007: {
588 r_out += "<= ";
589 _parse_value(r_pos, r_out, p_rq_size);
590 } break;
591 case 0x00000008: {
592 r_out += ">= ";
593 _parse_value(r_pos, r_out, p_rq_size);
594 } break;
595 case 0x00000009: {
596 r_out += "= ";
597 _parse_date(r_pos, r_out, p_rq_size);
598 } break;
599 case 0x0000000A: {
600 r_out += "< ";
601 _parse_date(r_pos, r_out, p_rq_size);
602 } break;
603 case 0x0000000B: {
604 r_out += "> ";
605 _parse_date(r_pos, r_out, p_rq_size);
606 } break;
607 case 0x0000000C: {
608 r_out += "<= ";
609 _parse_date(r_pos, r_out, p_rq_size);
610 } break;
611 case 0x0000000D: {
612 r_out += ">= ";
613 _parse_date(r_pos, r_out, p_rq_size);
614 } break;
615 case 0x0000000E: {
616 r_out += "absent";
617 } break;
618 default: {
619 return false;
620 }
621 }
622 return true;
623#undef _R
624}
625
626Vector<String> CodeSignRequirements::parse_requirements() const {
627#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
628 Vector<String> list;
629
630 // Read requirements set header.
631 ERR_FAIL_COND_V_MSG(blob.size() < 12, list, "CodeSign/Requirements: Blob is too small.");
632 uint32_t magic = _R(0);
633 ERR_FAIL_COND_V_MSG(magic != 0xfade0c01, list, "CodeSign/Requirements: Invalid set magic.");
634 uint32_t size = _R(4);
635 ERR_FAIL_COND_V_MSG(size != (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid set size.");
636 uint32_t count = _R(8);
637
638 for (uint32_t i = 0; i < count; i++) {
639 String out;
640
641 // Read requirement header.
642 uint32_t rq_type = _R(12 + i * 8);
643 uint32_t rq_offset = _R(12 + i * 8 + 4);
644 ERR_FAIL_COND_V_MSG(rq_offset + 12 >= (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid requirement offset.");
645 switch (rq_type) {
646 case 0x00000001: {
647 out += "host => ";
648 } break;
649 case 0x00000002: {
650 out += "guest => ";
651 } break;
652 case 0x00000003: {
653 out += "designated => ";
654 } break;
655 case 0x00000004: {
656 out += "library => ";
657 } break;
658 case 0x00000005: {
659 out += "plugin => ";
660 } break;
661 default: {
662 ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement type.");
663 }
664 }
665 uint32_t rq_magic = _R(rq_offset);
666 uint32_t rq_size = _R(rq_offset + 4);
667 uint32_t rq_ver = _R(rq_offset + 8);
668 uint32_t pos = rq_offset + 12;
669 ERR_FAIL_COND_V_MSG(rq_magic != 0xfade0c00, list, "CodeSign/Requirements: Invalid requirement magic.");
670 ERR_FAIL_COND_V_MSG(rq_ver != 0x00000001, list, "CodeSign/Requirements: Invalid requirement version.");
671
672 // Read requirement tokens.
673 List<String> tokens;
674 while (pos < rq_offset + rq_size) {
675 uint32_t rq_tag = _R(pos);
676 pos += 4;
677 String token;
678 switch (rq_tag) {
679 case 0x00000000: {
680 token = "false";
681 } break;
682 case 0x00000001: {
683 token = "true";
684 } break;
685 case 0x00000002: {
686 token = "identifier ";
687 _parse_value(pos, token, rq_offset + rq_size);
688 } break;
689 case 0x00000003: {
690 token = "anchor apple";
691 } break;
692 case 0x00000004: {
693 _parse_certificate_slot(pos, token, rq_offset + rq_size);
694 token += " ";
695 _parse_hash_string(pos, token, rq_offset + rq_size);
696 } break;
697 case 0x00000005: {
698 token = "info";
699 _parse_key(pos, token, rq_offset + rq_size);
700 token += " = ";
701 _parse_value(pos, token, rq_offset + rq_size);
702 } break;
703 case 0x00000006: {
704 token = "and";
705 } break;
706 case 0x00000007: {
707 token = "or";
708 } break;
709 case 0x00000008: {
710 token = "cdhash ";
711 _parse_hash_string(pos, token, rq_offset + rq_size);
712 } break;
713 case 0x00000009: {
714 token = "!";
715 } break;
716 case 0x0000000A: {
717 token = "info";
718 _parse_key(pos, token, rq_offset + rq_size);
719 token += " ";
720 ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix.");
721 } break;
722 case 0x0000000B: {
723 _parse_certificate_slot(pos, token, rq_offset + rq_size);
724 _parse_key(pos, token, rq_offset + rq_size);
725 token += " ";
726 ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix.");
727 } break;
728 case 0x0000000C: {
729 _parse_certificate_slot(pos, token, rq_offset + rq_size);
730 token += " trusted";
731 } break;
732 case 0x0000000D: {
733 token = "anchor trusted";
734 } break;
735 case 0x0000000E: {
736 _parse_certificate_slot(pos, token, rq_offset + rq_size);
737 _parse_oid_key(pos, token, rq_offset + rq_size);
738 token += " ";
739 ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix.");
740 } break;
741 case 0x0000000F: {
742 token = "anchor apple generic";
743 } break;
744 default: {
745 ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement token.");
746 } break;
747 }
748 tokens.push_back(token);
749 }
750
751 // Polish to infix notation (w/o bracket optimization).
752 for (List<String>::Element *E = tokens.back(); E; E = E->prev()) {
753 if (E->get() == "and") {
754 ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence.");
755 String token = "(" + E->next()->get() + " and " + E->next()->next()->get() + ")";
756 tokens.erase(E->next()->next());
757 tokens.erase(E->next());
758 E->get() = token;
759 } else if (E->get() == "or") {
760 ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence.");
761 String token = "(" + E->next()->get() + " or " + E->next()->next()->get() + ")";
762 tokens.erase(E->next()->next());
763 tokens.erase(E->next());
764 E->get() = token;
765 }
766 }
767
768 if (tokens.size() == 1) {
769 list.push_back(out + tokens.front()->get());
770 } else {
771 ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid token sequence.");
772 }
773 }
774
775 return list;
776#undef _R
777}
778
779PackedByteArray CodeSignRequirements::get_hash_sha1() const {
780 PackedByteArray hash;
781 hash.resize(0x14);
782
783 CryptoCore::SHA1Context ctx;
784 ctx.start();
785 ctx.update(blob.ptr(), blob.size());
786 ctx.finish(hash.ptrw());
787
788 return hash;
789}
790
791PackedByteArray CodeSignRequirements::get_hash_sha256() const {
792 PackedByteArray hash;
793 hash.resize(0x20);
794
795 CryptoCore::SHA256Context ctx;
796 ctx.start();
797 ctx.update(blob.ptr(), blob.size());
798 ctx.finish(hash.ptrw());
799
800 return hash;
801}
802
803int CodeSignRequirements::get_size() const {
804 return blob.size();
805}
806
807void CodeSignRequirements::write_to_file(Ref<FileAccess> p_file) const {
808 ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Requirements: Invalid file handle.");
809 p_file->store_buffer(blob.ptr(), blob.size());
810}
811
812/*************************************************************************/
813/* CodeSignEntitlementsText */
814/*************************************************************************/
815
816CodeSignEntitlementsText::CodeSignEntitlementsText() {
817 blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic.
818 blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes).
819}
820
821CodeSignEntitlementsText::CodeSignEntitlementsText(const String &p_string) {
822 CharString utf8 = p_string.utf8();
823 blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic.
824 for (int i = 3; i >= 0; i--) {
825 uint8_t x = ((utf8.length() + 8) >> i * 8) & 0xFF; // Size.
826 blob.push_back(x);
827 }
828 for (int i = 0; i < utf8.length(); i++) { // Write data.
829 blob.push_back(utf8[i]);
830 }
831}
832
833PackedByteArray CodeSignEntitlementsText::get_hash_sha1() const {
834 PackedByteArray hash;
835 hash.resize(0x14);
836
837 CryptoCore::SHA1Context ctx;
838 ctx.start();
839 ctx.update(blob.ptr(), blob.size());
840 ctx.finish(hash.ptrw());
841
842 return hash;
843}
844
845PackedByteArray CodeSignEntitlementsText::get_hash_sha256() const {
846 PackedByteArray hash;
847 hash.resize(0x20);
848
849 CryptoCore::SHA256Context ctx;
850 ctx.start();
851 ctx.update(blob.ptr(), blob.size());
852 ctx.finish(hash.ptrw());
853
854 return hash;
855}
856
857int CodeSignEntitlementsText::get_size() const {
858 return blob.size();
859}
860
861void CodeSignEntitlementsText::write_to_file(Ref<FileAccess> p_file) const {
862 ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsText: Invalid file handle.");
863 p_file->store_buffer(blob.ptr(), blob.size());
864}
865
866/*************************************************************************/
867/* CodeSignEntitlementsBinary */
868/*************************************************************************/
869
870CodeSignEntitlementsBinary::CodeSignEntitlementsBinary() {
871 blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic.
872 blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes).
873}
874
875CodeSignEntitlementsBinary::CodeSignEntitlementsBinary(const String &p_string) {
876 PList pl = PList(p_string);
877
878 PackedByteArray asn1 = pl.save_asn1();
879 blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic.
880 uint32_t size = asn1.size() + 8;
881 for (int i = 3; i >= 0; i--) {
882 uint8_t x = (size >> i * 8) & 0xFF; // Size.
883 blob.push_back(x);
884 }
885 blob.append_array(asn1); // Write data.
886}
887
888PackedByteArray CodeSignEntitlementsBinary::get_hash_sha1() const {
889 PackedByteArray hash;
890 hash.resize(0x14);
891
892 CryptoCore::SHA1Context ctx;
893 ctx.start();
894 ctx.update(blob.ptr(), blob.size());
895 ctx.finish(hash.ptrw());
896
897 return hash;
898}
899
900PackedByteArray CodeSignEntitlementsBinary::get_hash_sha256() const {
901 PackedByteArray hash;
902 hash.resize(0x20);
903
904 CryptoCore::SHA256Context ctx;
905 ctx.start();
906 ctx.update(blob.ptr(), blob.size());
907 ctx.finish(hash.ptrw());
908
909 return hash;
910}
911
912int CodeSignEntitlementsBinary::get_size() const {
913 return blob.size();
914}
915
916void CodeSignEntitlementsBinary::write_to_file(Ref<FileAccess> p_file) const {
917 ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsBinary: Invalid file handle.");
918 p_file->store_buffer(blob.ptr(), blob.size());
919}
920
921/*************************************************************************/
922/* CodeSignCodeDirectory */
923/*************************************************************************/
924
925CodeSignCodeDirectory::CodeSignCodeDirectory() {
926 blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic.
927 blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Size (8 bytes).
928}
929
930CodeSignCodeDirectory::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) {
931 pages = p_code_limit / (uint64_t(1) << p_page_size);
932 remain = p_code_limit % (uint64_t(1) << p_page_size);
933 code_slots = pages + (remain > 0 ? 1 : 0);
934 special_slots = 7;
935
936 int cd_size = 8 + sizeof(CodeDirectoryHeader) + (code_slots + special_slots) * p_hash_size + p_id.size() + p_team_id.size();
937 int cd_off = 8 + sizeof(CodeDirectoryHeader);
938 blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic.
939 for (int i = 3; i >= 0; i--) {
940 uint8_t x = (cd_size >> i * 8) & 0xFF; // Size.
941 blob.push_back(x);
942 }
943 blob.resize(cd_size);
944 memset(blob.ptrw() + 8, 0x00, cd_size - 8);
945 CodeDirectoryHeader *cd = reinterpret_cast<CodeDirectoryHeader *>(blob.ptrw() + 8);
946
947 bool is_64_cl = (p_code_limit >= std::numeric_limits<uint32_t>::max());
948
949 // Version and options.
950 cd->version = BSWAP32(0x20500);
951 cd->flags = BSWAP32(SIGNATURE_ADHOC | SIGNATURE_RUNTIME);
952 cd->special_slots = BSWAP32(special_slots);
953 cd->code_slots = BSWAP32(code_slots);
954 if (is_64_cl) {
955 cd->code_limit_64 = BSWAP64(p_code_limit);
956 } else {
957 cd->code_limit = BSWAP32(p_code_limit);
958 }
959 cd->hash_size = p_hash_size;
960 cd->hash_type = p_hash_type;
961 cd->page_size = p_page_size;
962 cd->exec_seg_base = 0x00;
963 cd->exec_seg_limit = BSWAP64(p_exe_limit);
964 cd->exec_seg_flags = 0;
965 if (p_main) {
966 cd->exec_seg_flags |= EXECSEG_MAIN_BINARY;
967 }
968 cd->exec_seg_flags = BSWAP64(cd->exec_seg_flags);
969 uint32_t version = (11 << 16) + (3 << 8) + 0; // Version 11.3.0
970 cd->runtime = BSWAP32(version);
971
972 // Copy ID.
973 cd->ident_offset = BSWAP32(cd_off);
974 memcpy(blob.ptrw() + cd_off, p_id.get_data(), p_id.size());
975 cd_off += p_id.size();
976
977 // Copy Team ID.
978 if (p_team_id.length() > 0) {
979 cd->team_offset = BSWAP32(cd_off);
980 memcpy(blob.ptrw() + cd_off, p_team_id.get_data(), p_team_id.size());
981 cd_off += p_team_id.size();
982 } else {
983 cd->team_offset = 0;
984 }
985
986 // Scatter vector.
987 cd->scatter_vector_offset = 0; // Not used.
988
989 // Executable hashes offset.
990 cd->hash_offset = BSWAP32(cd_off + special_slots * cd->hash_size);
991}
992
993bool CodeSignCodeDirectory::set_hash_in_slot(const PackedByteArray &p_hash, int p_slot) {
994 ERR_FAIL_COND_V_MSG((p_slot < -special_slots) || (p_slot >= code_slots), false, vformat("CodeSign/CodeDirectory: Invalid hash slot index: %d.", p_slot));
995 CodeDirectoryHeader *cd = reinterpret_cast<CodeDirectoryHeader *>(blob.ptrw() + 8);
996 for (int i = 0; i < cd->hash_size; i++) {
997 blob.write[BSWAP32(cd->hash_offset) + p_slot * cd->hash_size + i] = p_hash[i];
998 }
999 return true;
1000}
1001
1002int32_t CodeSignCodeDirectory::get_page_count() {
1003 return pages;
1004}
1005
1006int32_t CodeSignCodeDirectory::get_page_remainder() {
1007 return remain;
1008}
1009
1010PackedByteArray CodeSignCodeDirectory::get_hash_sha1() const {
1011 PackedByteArray hash;
1012 hash.resize(0x14);
1013
1014 CryptoCore::SHA1Context ctx;
1015 ctx.start();
1016 ctx.update(blob.ptr(), blob.size());
1017 ctx.finish(hash.ptrw());
1018
1019 return hash;
1020}
1021
1022PackedByteArray CodeSignCodeDirectory::get_hash_sha256() const {
1023 PackedByteArray hash;
1024 hash.resize(0x20);
1025
1026 CryptoCore::SHA256Context ctx;
1027 ctx.start();
1028 ctx.update(blob.ptr(), blob.size());
1029 ctx.finish(hash.ptrw());
1030
1031 return hash;
1032}
1033
1034int CodeSignCodeDirectory::get_size() const {
1035 return blob.size();
1036}
1037
1038void CodeSignCodeDirectory::write_to_file(Ref<FileAccess> p_file) const {
1039 ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/CodeDirectory: Invalid file handle.");
1040 p_file->store_buffer(blob.ptr(), blob.size());
1041}
1042
1043/*************************************************************************/
1044/* CodeSignSignature */
1045/*************************************************************************/
1046
1047CodeSignSignature::CodeSignSignature() {
1048 blob.append_array({ 0xFA, 0xDE, 0x0B, 0x01 }); // Signature magic.
1049 uint32_t sign_size = 8; // Ad-hoc signature is empty.
1050 for (int i = 3; i >= 0; i--) {
1051 uint8_t x = (sign_size >> i * 8) & 0xFF; // Size.
1052 blob.push_back(x);
1053 }
1054}
1055
1056PackedByteArray CodeSignSignature::get_hash_sha1() const {
1057 PackedByteArray hash;
1058 hash.resize(0x14);
1059
1060 CryptoCore::SHA1Context ctx;
1061 ctx.start();
1062 ctx.update(blob.ptr(), blob.size());
1063 ctx.finish(hash.ptrw());
1064
1065 return hash;
1066}
1067
1068PackedByteArray CodeSignSignature::get_hash_sha256() const {
1069 PackedByteArray hash;
1070 hash.resize(0x20);
1071
1072 CryptoCore::SHA256Context ctx;
1073 ctx.start();
1074 ctx.update(blob.ptr(), blob.size());
1075 ctx.finish(hash.ptrw());
1076
1077 return hash;
1078}
1079
1080int CodeSignSignature::get_size() const {
1081 return blob.size();
1082}
1083
1084void CodeSignSignature::write_to_file(Ref<FileAccess> p_file) const {
1085 ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Signature: Invalid file handle.");
1086 p_file->store_buffer(blob.ptr(), blob.size());
1087}
1088
1089/*************************************************************************/
1090/* CodeSignSuperBlob */
1091/*************************************************************************/
1092
1093bool CodeSignSuperBlob::add_blob(const Ref<CodeSignBlob> &p_blob) {
1094 if (p_blob.is_valid()) {
1095 blobs.push_back(p_blob);
1096 return true;
1097 } else {
1098 return false;
1099 }
1100}
1101
1102int CodeSignSuperBlob::get_size() const {
1103 int size = 12 + blobs.size() * 8;
1104 for (int i = 0; i < blobs.size(); i++) {
1105 if (blobs[i].is_null()) {
1106 return 0;
1107 }
1108 size += blobs[i]->get_size();
1109 }
1110 return size;
1111}
1112
1113void CodeSignSuperBlob::write_to_file(Ref<FileAccess> p_file) const {
1114 ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/SuperBlob: Invalid file handle.");
1115 uint32_t size = get_size();
1116 uint32_t data_offset = 12 + blobs.size() * 8;
1117
1118 // Write header.
1119 p_file->store_32(BSWAP32(0xfade0cc0));
1120 p_file->store_32(BSWAP32(size));
1121 p_file->store_32(BSWAP32(blobs.size()));
1122
1123 // Write index.
1124 for (int i = 0; i < blobs.size(); i++) {
1125 if (blobs[i].is_null()) {
1126 return;
1127 }
1128 p_file->store_32(BSWAP32(blobs[i]->get_index_type()));
1129 p_file->store_32(BSWAP32(data_offset));
1130 data_offset += blobs[i]->get_size();
1131 }
1132
1133 // Write blobs.
1134 for (int i = 0; i < blobs.size(); i++) {
1135 blobs[i]->write_to_file(p_file);
1136 }
1137}
1138
1139/*************************************************************************/
1140/* CodeSign */
1141/*************************************************************************/
1142
1143PackedByteArray CodeSign::file_hash_sha1(const String &p_path) {
1144 PackedByteArray file_hash;
1145 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1146 ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
1147
1148 CryptoCore::SHA1Context ctx;
1149 ctx.start();
1150
1151 unsigned char step[4096];
1152 while (true) {
1153 uint64_t br = f->get_buffer(step, 4096);
1154 if (br > 0) {
1155 ctx.update(step, br);
1156 }
1157 if (br < 4096) {
1158 break;
1159 }
1160 }
1161
1162 file_hash.resize(0x14);
1163 ctx.finish(file_hash.ptrw());
1164 return file_hash;
1165}
1166
1167PackedByteArray CodeSign::file_hash_sha256(const String &p_path) {
1168 PackedByteArray file_hash;
1169 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
1170 ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
1171
1172 CryptoCore::SHA256Context ctx;
1173 ctx.start();
1174
1175 unsigned char step[4096];
1176 while (true) {
1177 uint64_t br = f->get_buffer(step, 4096);
1178 if (br > 0) {
1179 ctx.update(step, br);
1180 }
1181 if (br < 4096) {
1182 break;
1183 }
1184 }
1185
1186 file_hash.resize(0x20);
1187 ctx.finish(file_hash.ptrw());
1188 return file_hash;
1189}
1190
1191Error 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) {
1192#define CLEANUP() \
1193 if (files_to_sign.size() > 1) { \
1194 for (int j = 0; j < files_to_sign.size(); j++) { \
1195 da->remove(files_to_sign[j]); \
1196 } \
1197 }
1198
1199 print_verbose(vformat("CodeSign: Signing executable: %s, bundle: %s with entitlements %s", p_exe_path, p_bundle_path, p_ent_path));
1200
1201 PackedByteArray info_hash1, info_hash2;
1202 PackedByteArray res_hash1, res_hash2;
1203 String id;
1204 String main_exe = p_exe_path;
1205
1206 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1207 if (da.is_null()) {
1208 r_error_msg = TTR("Can't get filesystem access.");
1209 ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access.");
1210 }
1211
1212 // Read Info.plist.
1213 if (!p_info.is_empty()) {
1214 print_verbose("CodeSign: Reading bundle info...");
1215 PList info_plist;
1216 if (info_plist.load_file(p_info)) {
1217 info_hash1 = file_hash_sha1(p_info);
1218 info_hash2 = file_hash_sha256(p_info);
1219 if (info_hash1.is_empty() || info_hash2.is_empty()) {
1220 r_error_msg = TTR("Failed to get Info.plist hash.");
1221 ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get Info.plist hash.");
1222 }
1223
1224 if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) {
1225 main_exe = p_exe_path.path_join(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data()));
1226 } else {
1227 r_error_msg = TTR("Invalid Info.plist, no exe name.");
1228 ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no exe name.");
1229 }
1230
1231 if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleIdentifier")) {
1232 id = info_plist.get_root()->data_dict["CFBundleIdentifier"]->data_string.get_data();
1233 } else {
1234 r_error_msg = TTR("Invalid Info.plist, no bundle id.");
1235 ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no bundle id.");
1236 }
1237 } else {
1238 r_error_msg = TTR("Invalid Info.plist, can't load.");
1239 ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, can't load.");
1240 }
1241 }
1242
1243 // Extract fat binary.
1244 Vector<String> files_to_sign;
1245 if (LipO::is_lipo(main_exe)) {
1246 print_verbose(vformat("CodeSign: Executable is fat, extracting..."));
1247 String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join("_lipo");
1248 Error err = da->make_dir_recursive(tmp_path_name);
1249 if (err != OK) {
1250 r_error_msg = vformat(TTR("Failed to create \"%s\" subfolder."), tmp_path_name);
1251 ERR_FAIL_V_MSG(FAILED, vformat("CodeSign: Failed to create \"%s\" subfolder.", tmp_path_name));
1252 }
1253 LipO lip;
1254 if (lip.open_file(main_exe)) {
1255 for (int i = 0; i < lip.get_arch_count(); i++) {
1256 if (!lip.extract_arch(i, tmp_path_name.path_join("_exe_" + itos(i)))) {
1257 CLEANUP();
1258 r_error_msg = TTR("Failed to extract thin binary.");
1259 ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to extract thin binary.");
1260 }
1261 files_to_sign.push_back(tmp_path_name.path_join("_exe_" + itos(i)));
1262 }
1263 }
1264 } else if (MachO::is_macho(main_exe)) {
1265 print_verbose("CodeSign: Executable is thin...");
1266 files_to_sign.push_back(main_exe);
1267 } else {
1268 r_error_msg = TTR("Invalid binary format.");
1269 ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid binary format.");
1270 }
1271
1272 // Check if it's already signed.
1273 if (!p_force) {
1274 for (int i = 0; i < files_to_sign.size(); i++) {
1275 MachO mh;
1276 mh.open_file(files_to_sign[i]);
1277 if (mh.is_signed()) {
1278 CLEANUP();
1279 r_error_msg = TTR("Already signed!");
1280 ERR_FAIL_V_MSG(FAILED, "CodeSign: Already signed!");
1281 }
1282 }
1283 }
1284
1285 // Generate core resources.
1286 if (!p_bundle_path.is_empty()) {
1287 print_verbose("CodeSign: Generating bundle CodeResources...");
1288 CodeSignCodeResources cr;
1289
1290 if (p_ios_bundle) {
1291 cr.add_rule1("^.*");
1292 cr.add_rule1("^.*\\.lproj/", "optional", 100);
1293 cr.add_rule1("^.*\\.lproj/locversion.plist$", "omit", 1100);
1294 cr.add_rule1("^Base\\.lproj/", "", 1010);
1295 cr.add_rule1("^version.plist$");
1296
1297 cr.add_rule2(".*\\.dSYM($|/)", "", 11);
1298 cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000);
1299 cr.add_rule2("^.*");
1300 cr.add_rule2("^.*\\.lproj/", "optional", 1000);
1301 cr.add_rule2("^.*\\.lproj/locversion.plist$", "omit", 1100);
1302 cr.add_rule2("^Base\\.lproj/", "", 1010);
1303 cr.add_rule2("^Info\\.plist$", "omit", 20);
1304 cr.add_rule2("^PkgInfo$", "omit", 20);
1305 cr.add_rule2("^embedded\\.provisionprofile$", "", 10);
1306 cr.add_rule2("^version\\.plist$", "", 20);
1307
1308 cr.add_rule2("^_MASReceipt", "omit", 2000, false);
1309 cr.add_rule2("^_CodeSignature", "omit", 2000, false);
1310 cr.add_rule2("^CodeResources", "omit", 2000, false);
1311 } else {
1312 cr.add_rule1("^Resources/");
1313 cr.add_rule1("^Resources/.*\\.lproj/", "optional", 1000);
1314 cr.add_rule1("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100);
1315 cr.add_rule1("^Resources/Base\\.lproj/", "", 1010);
1316 cr.add_rule1("^version.plist$");
1317
1318 cr.add_rule2(".*\\.dSYM($|/)", "", 11);
1319 cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000);
1320 cr.add_rule2("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", "nested", 10);
1321 cr.add_rule2("^.*");
1322 cr.add_rule2("^Info\\.plist$", "omit", 20);
1323 cr.add_rule2("^PkgInfo$", "omit", 20);
1324 cr.add_rule2("^Resources/", "", 20);
1325 cr.add_rule2("^Resources/.*\\.lproj/", "optional", 1000);
1326 cr.add_rule2("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100);
1327 cr.add_rule2("^Resources/Base\\.lproj/", "", 1010);
1328 cr.add_rule2("^[^/]+$", "nested", 10);
1329 cr.add_rule2("^embedded\\.provisionprofile$", "", 10);
1330 cr.add_rule2("^version\\.plist$", "", 20);
1331 cr.add_rule2("^_MASReceipt", "omit", 2000, false);
1332 cr.add_rule2("^_CodeSignature", "omit", 2000, false);
1333 cr.add_rule2("^CodeResources", "omit", 2000, false);
1334 }
1335
1336 if (!cr.add_folder_recursive(p_bundle_path, "", main_exe)) {
1337 CLEANUP();
1338 r_error_msg = TTR("Failed to process nested resources.");
1339 ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to process nested resources.");
1340 }
1341 Error err = da->make_dir_recursive(p_bundle_path.path_join("_CodeSignature"));
1342 if (err != OK) {
1343 CLEANUP();
1344 r_error_msg = TTR("Failed to create _CodeSignature subfolder.");
1345 ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create _CodeSignature subfolder.");
1346 }
1347 cr.save_to_file(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources"));
1348 res_hash1 = file_hash_sha1(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources"));
1349 res_hash2 = file_hash_sha256(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources"));
1350 if (res_hash1.is_empty() || res_hash2.is_empty()) {
1351 CLEANUP();
1352 r_error_msg = TTR("Failed to get CodeResources hash.");
1353 ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get CodeResources hash.");
1354 }
1355 }
1356
1357 // Generate common signature structures.
1358 if (id.is_empty()) {
1359 CryptoCore::RandomGenerator rng;
1360 ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
1361 uint8_t uuid[16];
1362 Error err = rng.get_random_bytes(uuid, 16);
1363 ERR_FAIL_COND_V_MSG(err, err, "Failed to generate UUID.");
1364 id = (String("a-55554944") /*a-UUID*/ + String::hex_encode_buffer(uuid, 16));
1365 }
1366 CharString uuid_str = id.utf8();
1367 print_verbose(vformat("CodeSign: Used bundle ID: %s", id));
1368
1369 print_verbose("CodeSign: Processing entitlements...");
1370
1371 Ref<CodeSignEntitlementsText> cet;
1372 Ref<CodeSignEntitlementsBinary> ceb;
1373 if (!p_ent_path.is_empty()) {
1374 String entitlements = FileAccess::get_file_as_string(p_ent_path);
1375 if (entitlements.is_empty()) {
1376 CLEANUP();
1377 r_error_msg = TTR("Invalid entitlements file.");
1378 ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid entitlements file.");
1379 }
1380 cet = Ref<CodeSignEntitlementsText>(memnew(CodeSignEntitlementsText(entitlements)));
1381 ceb = Ref<CodeSignEntitlementsBinary>(memnew(CodeSignEntitlementsBinary(entitlements)));
1382 }
1383
1384 print_verbose("CodeSign: Generating requirements...");
1385 Ref<CodeSignRequirements> rq;
1386 String team_id = "";
1387 rq = Ref<CodeSignRequirements>(memnew(CodeSignRequirements()));
1388
1389 // Sign executables.
1390 for (int i = 0; i < files_to_sign.size(); i++) {
1391 MachO mh;
1392 if (!mh.open_file(files_to_sign[i])) {
1393 CLEANUP();
1394 r_error_msg = TTR("Invalid executable file.");
1395 ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid executable file.");
1396 }
1397 print_verbose(vformat("CodeSign: Signing executable for cputype: %d ...", mh.get_cputype()));
1398
1399 print_verbose("CodeSign: Generating CodeDirectory...");
1400 Ref<CodeSignCodeDirectory> cd1 = memnew(CodeSignCodeDirectory(0x14, 0x01, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit()));
1401 Ref<CodeSignCodeDirectory> cd2 = memnew(CodeSignCodeDirectory(0x20, 0x02, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit()));
1402 print_verbose("CodeSign: Calculating special slot hashes...");
1403 if (info_hash2.size() == 0x20) {
1404 cd2->set_hash_in_slot(info_hash2, CodeSignCodeDirectory::SLOT_INFO_PLIST);
1405 }
1406 if (info_hash1.size() == 0x14) {
1407 cd1->set_hash_in_slot(info_hash1, CodeSignCodeDirectory::SLOT_INFO_PLIST);
1408 }
1409 cd1->set_hash_in_slot(rq->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS);
1410 cd2->set_hash_in_slot(rq->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS);
1411 if (res_hash2.size() == 0x20) {
1412 cd2->set_hash_in_slot(res_hash2, CodeSignCodeDirectory::SLOT_RESOURCES);
1413 }
1414 if (res_hash1.size() == 0x14) {
1415 cd1->set_hash_in_slot(res_hash1, CodeSignCodeDirectory::SLOT_RESOURCES);
1416 }
1417 if (cet.is_valid()) {
1418 cd1->set_hash_in_slot(cet->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); //Text variant.
1419 cd2->set_hash_in_slot(cet->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS);
1420 }
1421 if (ceb.is_valid()) {
1422 cd1->set_hash_in_slot(ceb->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); //ASN.1 variant.
1423 cd2->set_hash_in_slot(ceb->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS);
1424 }
1425
1426 // Calculate signature size.
1427 int sign_size = 12; // SuperBlob header.
1428 sign_size += cd1->get_size() + 8;
1429 sign_size += cd2->get_size() + 8;
1430 sign_size += rq->get_size() + 8;
1431 if (cet.is_valid()) {
1432 sign_size += cet->get_size() + 8;
1433 }
1434 if (ceb.is_valid()) {
1435 sign_size += ceb->get_size() + 8;
1436 }
1437 sign_size += 16; // Empty signature size.
1438
1439 // Alloc/resize signature load command.
1440 print_verbose(vformat("CodeSign: Reallocating space for the signature superblob (%d)...", sign_size));
1441 if (!mh.set_signature_size(sign_size)) {
1442 CLEANUP();
1443 r_error_msg = TTR("Can't resize signature load command.");
1444 ERR_FAIL_V_MSG(FAILED, "CodeSign: Can't resize signature load command.");
1445 }
1446
1447 print_verbose("CodeSign: Calculating executable code hashes...");
1448 // Calculate executable code hashes.
1449 PackedByteArray buffer;
1450 PackedByteArray hash1, hash2;
1451 hash1.resize(0x14);
1452 hash2.resize(0x20);
1453 buffer.resize(1 << 12);
1454 mh.get_file()->seek(0);
1455 for (int32_t j = 0; j < cd2->get_page_count(); j++) {
1456 mh.get_file()->get_buffer(buffer.ptrw(), (1 << 12));
1457 CryptoCore::SHA256Context ctx2;
1458 ctx2.start();
1459 ctx2.update(buffer.ptr(), (1 << 12));
1460 ctx2.finish(hash2.ptrw());
1461 cd2->set_hash_in_slot(hash2, j);
1462
1463 CryptoCore::SHA1Context ctx1;
1464 ctx1.start();
1465 ctx1.update(buffer.ptr(), (1 << 12));
1466 ctx1.finish(hash1.ptrw());
1467 cd1->set_hash_in_slot(hash1, j);
1468 }
1469 if (cd2->get_page_remainder() > 0) {
1470 mh.get_file()->get_buffer(buffer.ptrw(), cd2->get_page_remainder());
1471 CryptoCore::SHA256Context ctx2;
1472 ctx2.start();
1473 ctx2.update(buffer.ptr(), cd2->get_page_remainder());
1474 ctx2.finish(hash2.ptrw());
1475 cd2->set_hash_in_slot(hash2, cd2->get_page_count());
1476
1477 CryptoCore::SHA1Context ctx1;
1478 ctx1.start();
1479 ctx1.update(buffer.ptr(), cd1->get_page_remainder());
1480 ctx1.finish(hash1.ptrw());
1481 cd1->set_hash_in_slot(hash1, cd1->get_page_count());
1482 }
1483
1484 print_verbose("CodeSign: Generating signature...");
1485 Ref<CodeSignSignature> cs;
1486 cs = Ref<CodeSignSignature>(memnew(CodeSignSignature()));
1487
1488 print_verbose("CodeSign: Writing signature superblob...");
1489 // Write signature data to the executable.
1490 CodeSignSuperBlob sb = CodeSignSuperBlob();
1491 sb.add_blob(cd2);
1492 sb.add_blob(cd1);
1493 sb.add_blob(rq);
1494 if (cet.is_valid()) {
1495 sb.add_blob(cet);
1496 }
1497 if (ceb.is_valid()) {
1498 sb.add_blob(ceb);
1499 }
1500 sb.add_blob(cs);
1501 mh.get_file()->seek(mh.get_signature_offset());
1502 sb.write_to_file(mh.get_file());
1503 }
1504 if (files_to_sign.size() > 1) {
1505 print_verbose("CodeSign: Rebuilding fat executable...");
1506 LipO lip;
1507 if (!lip.create_file(main_exe, files_to_sign)) {
1508 CLEANUP();
1509 r_error_msg = TTR("Failed to create fat binary.");
1510 ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create fat binary.");
1511 }
1512 CLEANUP();
1513 }
1514 FileAccess::set_unix_permissions(main_exe, 0755); // Restore unix permissions.
1515 return OK;
1516#undef CLEANUP
1517}
1518
1519Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) {
1520 Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1521 if (da.is_null()) {
1522 r_error_msg = TTR("Can't get filesystem access.");
1523 ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access.");
1524 }
1525
1526 if (da->dir_exists(p_path)) {
1527 String fmw_ver = "Current"; // Framework version (default).
1528 String info_path;
1529 String main_exe;
1530 String bundle_path;
1531 bool bundle = false;
1532 bool ios_bundle = false;
1533 if (da->file_exists(p_path.path_join("Contents/Info.plist"))) {
1534 info_path = p_path.path_join("Contents/Info.plist");
1535 main_exe = p_path.path_join("Contents/MacOS");
1536 bundle_path = p_path.path_join("Contents");
1537 bundle = true;
1538 } else if (da->file_exists(p_path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) {
1539 info_path = p_path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver));
1540 main_exe = p_path.path_join(vformat("Versions/%s", fmw_ver));
1541 bundle_path = p_path.path_join(vformat("Versions/%s", fmw_ver));
1542 bundle = true;
1543 } else if (da->file_exists(p_path.path_join("Info.plist"))) {
1544 info_path = p_path.path_join("Info.plist");
1545 main_exe = p_path;
1546 bundle_path = p_path;
1547 bundle = true;
1548 ios_bundle = true;
1549 }
1550 if (bundle) {
1551 return _codesign_file(p_use_hardened_runtime, p_force, info_path, main_exe, bundle_path, p_ent_path, ios_bundle, r_error_msg);
1552 } else {
1553 r_error_msg = TTR("Unknown bundle type.");
1554 ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown bundle type.");
1555 }
1556 } else if (da->file_exists(p_path)) {
1557 return _codesign_file(p_use_hardened_runtime, p_force, "", p_path, "", p_ent_path, false, r_error_msg);
1558 } else {
1559 r_error_msg = TTR("Unknown object type.");
1560 ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown object type.");
1561 }
1562}
1563
1564#endif // MODULE_REGEX_ENABLED
1565