Code signing in Mac with Perl scripts compiled with PAR::Packer fails
Question: Does anyone have experience getting compiled Perl binaries to code sign on
OSX? When trying to compile a Perl script in PAR, it returns an error when I
try to code sign it. I've gotten around this error by not trying to code sign
it as a binary (e.g., inside the "Resources" folder within a .app), but if I
put it in the proper MacOS directory it fails on the signature.

I've seen numerous fixes for python scripts
(<https://github.com/kamillus/py2app-pyqt-codesign-fix-os-x>), but not any for
Perl!

The error message reported by codesign -s is "main executable failed strict
validation". I've tried the --deep option as well with no success.


Answer: I've figured out how to fix this. It's a solution that I adapted based on the
Python solution written here:

<https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OSX-Code-Signing>

It's a problem caused by the Mach-O headers. Hopefully this helps someone
else. After running this python script on the PAR executable, it will
codesign.

Some modifications are still required to PAR to get this to run, however,
since PAR looks for the `\nPAR.pm\n` signature at the end of the file... which
now contains the code signature. I have a working solution for that as well,
and can share if others are interested.

    
    
    #!/usr/bin/env python
    
    from __future__ import print_function
    
    import os
    import sys
    
    from macholib._cmdline import main as _main
    from macholib.MachO import MachO
    from macholib.mach_o import *
    
    ARCH_MAP={
        ('<', '64-bit'): 'x86_64',
        ('<', '32-bit'): 'i386',
        ('>', '64-bit'): 'ppc64',
        ('>', '32-bit'): 'ppc',
    }
    
    def print_file(fp, path):
        print(path, file=fp)
        exe_data = MachO(path)
    
        for header in exe_data.headers:
            seen = set()
            if header.MH_MAGIC == MH_MAGIC_64:
                sz = '64-bit'
            else:
                sz = '32-bit'
    
            arch = CPU_TYPE_NAMES.get(header.header.cputype,
                    header.header.cputype)
    
            print('    [%s endian=%r size=%r arch=%r]' % (header.__class__.__name__,
                    header.endian, sz, arch), file=fp)
            for idx, name, other in header.walkRelocatables():
                if other not in seen:
                    seen.add(other)
                    print('\t' + other, file=fp)
        print('', file=fp)
    
        cmds = exe_data.headers[0].commands  # '0' - Exe contains only one architecture.
        file_size = exe_data.headers[0].size
    
        linkedit = cmds[3][1] # __LINKEDIT
        new_segsize = file_size - linkedit.fileoff
        linkedit.filesize = new_segsize
    
        alignment = 4096
        linkedit.vmsize = new_segsize + (alignment - (new_segsize % alignment)) # alignment
    
        data = cmds[4][1] # LC_SYMTAB
        new_strsize = file_size - data.stroff
        data.strsize = new_strsize
    
        # Write changes back.
        fp = open(exe_data.filename, 'rb+')
        exe_data.write(fp)
        fp.close()
    
        print ("Successfully modified headers.\n")
    
    def main():
        _main(print_file)
    
    if __name__ == '__main__':
        try:
            sys.exit(main())
        except KeyboardInterrupt:
            pass
    

