Skip to content

Instantly share code, notes, and snippets.

@kode54
Created January 20, 2022 06:13
Show Gist options
  • Save kode54/44902da999955d2dd74a5a918f848d70 to your computer and use it in GitHub Desktop.
Save kode54/44902da999955d2dd74a5a918f848d70 to your computer and use it in GitHub Desktop.
Recursively code sign on macOS. Useful for signing complex binaries outside of Xcode.
#!/usr/bin/env python2
'''
Code-signs all nested binaries inside an app bundle (excluding the app itself).
'''
import os, sys, re
import subprocess as sp
CODE_SIGN_OPTS = ['--verbose', '--force', '--deep', '--options=runtime', '--sign']
ROOT_SIGN_OPTS = ['--verbose', '--force', '--options=runtime', '--sign'];
def is_definitely_binary(path):
return 'Mach-O' in sp.check_output(['file', '--brief', path])
def get_signing_path(path):
m = re.match('.*/(.*)\.framework/Versions/./(.*)', path)
if m and (m.lastindex==2) and m.group(1)==m.group(2):
#This is the main binary of a framework. Sign the framework version instead.
return [path, path[:-(len(m.group(1))+12)]]
m = re.match('.*/(.*)\.app/Contents/MacOS/(.*)', path)
if m and (m.lastindex==2) and m.group(1)==m.group(2):
# This is the main binary of the app bundle. Exclude it.
path = None
return path
def get_signable_binaries(path):
all_files = [os.path.join(root, fn) for root, dirs, names in os.walk(path) for fn in names]
bins = filter(is_definitely_binary, all_files)
return sorted(filter(None, map(get_signing_path, bins)), reverse=True)
def code_sign_nested(identity, path, dryrun):
signables = get_signable_binaries(path)
if len(signables)==0:
print "No signable binaries found."
return False
cmd = sp.check_output if not dryrun else lambda x: ' '.join(x)
try:
for bin in signables:
if type(bin) == list:
_tmp_path = bin[0]+'_'
print cmd(['rm', '-rf', os.path.join(bin[1], '_CodeSignature')])
print cmd(['cp', bin[0], _tmp_path])
print cmd(['rm', bin[0]])
print cmd(['mv', _tmp_path, bin[0]])
print cmd(['codesign', '--remove-signature', bin[0]])
print cmd(['codesign']+CODE_SIGN_OPTS+[identity, bin[0]])
print cmd(['codesign']+CODE_SIGN_OPTS+[identity, bin[1]])
else:
print cmd(['codesign', '--remove-signature']+[bin])
print cmd(['codesign']+CODE_SIGN_OPTS+[identity, bin])
print cmd(['codesign']+ROOT_SIGN_OPTS+[identity, path])
except sp.CalledProcessError:
print 'Code signing failed.'
exit(1)
print '%s successfully complete.'%('Code signing' if not dryrun else 'Dry run')
def main():
if (len(sys.argv)!=4) or (sys.argv[1] not in ('sign', 'list')):
print 'Usage: %s sign/list signing_identity app_path'%os.path.basename(__file__)
exit(1)
cs_identity, app_path = sys.argv[2:]
code_sign_nested(cs_identity, app_path, dryrun=(sys.argv[1]=='list'))
if __name__=='__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment