Codesigning and Notarization of Python Application Created via py2app

Hello!

I'm relatively new (started a week ago) to creating MacOS applications. I had built an application in Python for Windows devices, and now I'm looking to distribute the beta to some friends who use Mac devices. I don't intend to put the app on the App Store, so I think that means I won't need to sandbox it.

I've figured out how to adapt all of the functionality of the app to work on MacOS. I'm able to get the app to run successfully after using py2app and setting the required permissions in my .plist file. However, I'm trying to sign and notarize the functioning application and I'm hitting some challenges.

I've tried a few combinations of things, but to no avail and I'm hoping someone can help me.

I start by running the following to build my .app bundle:

python setup.py py2app
from setuptools import setup
import os

APP = ['App Name.py']
DATA_FILES = [
    ('static', ['path/to/icons', 'path/to/styles']),
    ('static/fonts/Inter', ['path/to/font']), 
]
OPTIONS =  {
    'argv_emulation': True,
    'iconfile': 'App Name.icns',
    'packages': ['chardet', 'charset_normalizer', 'soundfile', 'sounddevice', '_sounddevice_data'],
    'plist': {
        'CFBundleIdentifier': 'com.companyname.appname',
        'CFBundleName': 'App Name',
        'CFBundleVersion': '1.0.0',
        'CFBundleShortVersionString': '1.0.0',
        'CFBundleExecutable': 'App Name',
        'CFBundleIconFile': 'App Name.icns',
        'NSMicrophoneUsageDescription': 'We need access to your microphone to provide transcripts of what you say.',
        'com.apple.security.cs.allow-unsigned-executable-memory': True,
        'com.apple.security.cs.disable-library-validation': True,
        'com.apple.security.cs.allow-jit': True,
    },
}

setup(
    app=APP,
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)

os.system('find "dist/App Name.app" -iname "*.so" -or -iname "*.dylib" | while read libfile; do codesign -s "DEVELOPER CERTIFICATE" --timestamp -o runtime --entitlements Info.plist "${libfile}"; done;')

Note that I have some codesigning happening at the bottom based on what I'd seen in some of the other forum posts. After running this, the standalone app works as expected on my computer. I've tried a few things from here, including:

  1. Creating a .dmg of the .app and submitting that for notarization - the response from the notary service just says "invalid" and I'm not sure how to get more details on why the request was invalid.
  2. Codesigning the .app - in this case, the codesign action appears to work when I run it in the terminal. When I double click on the .app bundle after codesigning, the app encounters a fatal error when launching (no errors are reported when launching this from the terminal and no crash logs are created either).
  3. Creating a .dmg of the .app, codesigning the .dmg, and submitting that for notarization - this resulted in an "invalid" response from the notary service (same as #1), but it wasn't clear why my request had failed.

My codesign script looks like this. I've tried this with and without the entitlements record. I've also tried this with and without the --deep flag which seems to be a thing that other people have tried. For the Info.plist, I copied over the one that was automatically generated by py2app during the creation of the .app bundle, which looked to have all of the permissions my app needed.

codesign \
    -vvv \
    --strict \
    --force \
    --timestamp \
    --options runtime \
    --entitlements "Info.plist" \
    --sign "Developer ID Application: MY NAME (LETTERS_AND_NUMBERS)" \
    "dist/App Name.app"

My notarization request looks like this.

xcrun notarytool --vvv submit --wait --keychain-profile "profilename" "dist/App Name.dmg"
xcrun stapler staple "dist/App Name.dmg"

I had previously successfully codesigned and notarized a simple "hello world" app, so I'm fairly sure my credentials are correct for both my codesign and notarytool.

Replies

Here is my Info.plist file (sanitized) for context:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>English</string>
	<key>CFBundleDisplayName</key>
	<string>App Name</string>
	<key>CFBundleDocumentTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeOSTypes</key>
			<array>
				<string>****</string>
				<string>fold</string>
				<string>disk</string>
			</array>
			<key>CFBundleTypeRole</key>
			<string>Viewer</string>
		</dict>
	</array>
	<key>CFBundleExecutable</key>
	<string>App Name</string>
	<key>CFBundleIconFile</key>
	<string>App Name.icns</string>
	<key>CFBundleIdentifier</key>
	<string>com.companyname.appname</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>App Name</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1.0.0</string>
	<key>LSHasLocalizedDisplayName</key>
	<false/>
	<key>NSAppleScriptEnabled</key>
	<false/>
	<key>NSHumanReadableCopyright</key>
	<string>Copyright not specified</string>
	<key>NSMainNibFile</key>
	<string>MainMenu</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>We need access to your microphone to provide transcripts of what you say.</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
	<key>PyMainFileNames</key>
	<array>
		<string>__boot__</string>
	</array>
	<key>PyOptions</key>
	<dict>
		<key>alias</key>
		<true/>
		<key>argv_emulation</key>
		<true/>
		<key>emulate_shell_environment</key>
		<false/>
		<key>no_chdir</key>
		<false/>
		<key>prefer_ppc</key>
		<false/>
		<key>site_packages</key>
		<false/>
		<key>use_faulthandler</key>
		<false/>
		<key>use_pythonpath</key>
		<false/>
		<key>verbose</key>
		<false/>
	</dict>
	<key>PyResourcePackages</key>
	<array/>
	<key>PyRuntimeLocations</key>
	<array>
		<string>@executable_path/../Frameworks/Python.framework/Versions/3.11/Python</string>
		<string>/Library/Frameworks/Python.framework/Versions/3.11/Python</string>
	</array>
	<key>PythonInfoDict</key>
	<dict>
		<key>PythonExecutable</key>
		<string>/Users/alekhagopian/Documents/GitHub/app-name/env/bin/python</string>
		<key>PythonLongVersion</key>
		<string>3.11.0 (v3.11.0:deaf509e8f, Oct 24 2022, 14:43:23) [Clang 13.0.0 (clang-1300.0.29.30)]</string>
		<key>PythonShortVersion</key>
		<string>3.1</string>
		<key>py2app</key>
		<dict>
			<key>alias</key>
			<true/>
			<key>template</key>
			<string>app</string>
			<key>version</key>
			<string>0.28.6</string>
		</dict>
	</dict>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
</dict>
</plist>

I was able to resolve the underlying issues. From another forum post I learned that there was a command for getting the details of why your notarization has failed. The command structure is:

crun notarytool log YOUR-SUBMISSION-ID-HERE

With the actual feedback about why my app was deemed "invalid" by the notary service, I was able to iterate to a solution.