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 | |
51 | String 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 | |
75 | String 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 | |
99 | void 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 | |
103 | void 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 | |
107 | CodeSignCodeResources::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 | |
136 | CodeSignCodeResources::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 | |
165 | bool 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 | |
182 | bool 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 | |
204 | bool 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 | |
285 | bool 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 | |
349 | bool 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 | |
442 | CodeSignRequirements::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 | |
448 | CodeSignRequirements::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(<, &t); |
546 | #else |
547 | gmtime_r(&t, <); |
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 | |
626 | Vector<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 | |
779 | PackedByteArray 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 | |
791 | PackedByteArray 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 | |
803 | int CodeSignRequirements::get_size() const { |
804 | return blob.size(); |
805 | } |
806 | |
807 | void 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 | |
816 | CodeSignEntitlementsText::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 | |
821 | CodeSignEntitlementsText::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 | |
833 | PackedByteArray 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 | |
845 | PackedByteArray 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 | |
857 | int CodeSignEntitlementsText::get_size() const { |
858 | return blob.size(); |
859 | } |
860 | |
861 | void 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 | |
870 | CodeSignEntitlementsBinary::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 | |
875 | CodeSignEntitlementsBinary::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 | |
888 | PackedByteArray 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 | |
900 | PackedByteArray 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 | |
912 | int CodeSignEntitlementsBinary::get_size() const { |
913 | return blob.size(); |
914 | } |
915 | |
916 | void 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 | |
925 | CodeSignCodeDirectory::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 | |
930 | 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) { |
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 | |
993 | bool 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 | |
1002 | int32_t CodeSignCodeDirectory::get_page_count() { |
1003 | return pages; |
1004 | } |
1005 | |
1006 | int32_t CodeSignCodeDirectory::get_page_remainder() { |
1007 | return remain; |
1008 | } |
1009 | |
1010 | PackedByteArray 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 | |
1022 | PackedByteArray 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 | |
1034 | int CodeSignCodeDirectory::get_size() const { |
1035 | return blob.size(); |
1036 | } |
1037 | |
1038 | void 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 | |
1047 | CodeSignSignature::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 | |
1056 | PackedByteArray 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 | |
1068 | PackedByteArray 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 | |
1080 | int CodeSignSignature::get_size() const { |
1081 | return blob.size(); |
1082 | } |
1083 | |
1084 | void 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 | |
1093 | bool 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 | |
1102 | int 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 | |
1113 | void 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 | |
1143 | PackedByteArray 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 | |
1167 | PackedByteArray 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 | |
1191 | 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) { |
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 | |
1519 | Error 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 | |