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