1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> 4 5import os 6import plistlib 7from glob import glob 8 9from bypy.macos_sign import ( 10 codesign, create_entitlements_file, make_certificate_useable, notarize_app, 11 verify_signature 12) 13from bypy.utils import current_dir 14 15entitlements = { 16 # MAP_JIT is used by libpcre which is bundled with Qt 17 'com.apple.security.cs.allow-jit': True, 18 19 # v8 and therefore WebEngine need this as they dont use MAP_JIT 20 'com.apple.security.cs.allow-unsigned-executable-memory': True, 21 22 # calibre itself does not use DYLD env vars, but dont know about its 23 # dependencies. 24 'com.apple.security.cs.allow-dyld-environment-variables': True, 25 26 # Allow loading of unsigned plugins or frameworks 27 # 'com.apple.security.cs.disable-library-validation': True, 28} 29 30 31def files_in(folder): 32 for record in os.walk(folder): 33 for f in record[-1]: 34 yield os.path.join(record[0], f) 35 36 37def expand_dirs(items, exclude=lambda x: x.endswith('.so')): 38 items = set(items) 39 dirs = set(x for x in items if os.path.isdir(x)) 40 items.difference_update(dirs) 41 for x in dirs: 42 items.update({y for y in files_in(x) if not exclude(y)}) 43 return items 44 45 46def get_executable(info_path): 47 with open(info_path, 'rb') as f: 48 return plistlib.load(f)['CFBundleExecutable'] 49 50 51def find_sub_apps(contents_dir='.'): 52 for app in glob(os.path.join(contents_dir, '*.app')): 53 cdir = os.path.join(app, 'Contents') 54 for sapp in find_sub_apps(cdir): 55 yield sapp 56 yield app 57 58 59def sign_MacOS(contents_dir='.'): 60 # Sign everything in MacOS except the main executable 61 # which will be signed automatically by codesign when 62 # signing the app bundles 63 with current_dir(os.path.join(contents_dir, 'MacOS')): 64 exe = get_executable('../Info.plist') 65 items = {x for x in os.listdir('.') if x != exe and not os.path.islink(x)} 66 if items: 67 codesign(items) 68 69 70def do_sign_app(appdir): 71 appdir = os.path.abspath(appdir) 72 with current_dir(os.path.join(appdir, 'Contents')): 73 sign_MacOS() 74 # Sign the sub application bundles 75 sub_apps = list(find_sub_apps()) 76 sub_apps.append('Frameworks/QtWebEngineCore.framework/Versions/Current/Helpers/QtWebEngineProcess.app') 77 for sa in sub_apps: 78 sign_MacOS(os.path.join(sa, 'Contents')) 79 codesign(sub_apps) 80 81 # Sign all .so files 82 so_files = {x for x in files_in('.') if x.endswith('.so')} 83 codesign(so_files) 84 85 # Sign everything in PlugIns 86 with current_dir('PlugIns'): 87 items = set(os.listdir('.')) 88 codesign(expand_dirs(items)) 89 90 # Sign everything else in Frameworks 91 with current_dir('Frameworks'): 92 fw = set(glob('*.framework')) 93 codesign(fw) 94 items = set(os.listdir('.')) - fw 95 codesign(expand_dirs(items)) 96 97 # Now sign the main app 98 codesign(appdir) 99 verify_signature(appdir) 100 return 0 101 102 103def sign_app(appdir, notarize): 104 create_entitlements_file(entitlements) 105 with make_certificate_useable(): 106 do_sign_app(appdir) 107 if notarize: 108 notarize_app(appdir) 109