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