Apple Code Signing Py2app Applications
Revision 0.21
HUMBERTO A. SANCHEZ II
S AT U R D AY, O C T O B E R 2 3 , 2 0 2 1 1 2 : 2 3 : 2 3
El Gato Malo, LLC
This page intentionally blank
ii
Table of Contents
1. Introduction 1
1.1. Document Management...........................................................................................................................1
1.2. Acronyms..................................................................................................................................................1
1.3. Software Versions....................................................................................................................................1
1.4. Format of py2app created OS X application package.............................................................................2
2. Generating Signing Certificates 3
2.1. Request from certificate authority..........................................................................................................3
2.2. Certificate Request..................................................................................................................................5
2.3. Code Signing Certificate Request............................................................................................................6
2.4. Imported Certificate.................................................................................................................................8
3. Code Signing Tasks 9
3.1. Sign Frameworks......................................................................................................................................9
3.2. Sign the embedded Python39.zip file.....................................................................................................10
3.3. Sign Libraries..........................................................................................................................................11
3.4. Sign the outermost binaries...................................................................................................................11
4. Notarization 12
4.1. Generate Notarization Tool APP ID........................................................................................................12
4.2. Save APP ID in your Keychain................................................................................................................12
4.3. Notarize It!!.............................................................................................................................................13
4.4. Notarization Steps..................................................................................................................................13
4.5. Notarization Log.....................................................................................................................................13
4.6. Stapling the Application.........................................................................................................................13
5. Stapling 14
6. Another Major Heading 15
6.1. Example Figure.......................................................................................................................................15
7. Appendices 16
7.1. Post 0.2 publication anomalies..............................................................................................................16
7.2. Resolution...............................................................................................................................................16
iii
List of Figures
Figure 1: Generated Application.....................................................................................................2
Figure 2: Request Certificate from Certificate Authority..................................................................3
Figure 3: Certificate Assistant Request Dialog................................................................................4
Figure 4: Create a Certificate..........................................................................................................5
Figure 5: Select Correct Type of Certificate....................................................................................6
Figure 6: Choose Certificate Request File......................................................................................6
Figure 7: Download Certificate........................................................................................................7
Figure 8: Imported Certificate..........................................................................................................8
Figure 9: Sample Code Monster...................................................................................................15
iv
Revision History
Revision Date Contributor Comments
0.1 09/25/2021 hasii Initial Revision
0.2 10/06/2021 hasii Released on WordPress as
PDF via scribd
0.21 10/23/2021 hasii Additional notes on anomalies I
encountered
v
Apple Code Signing Py2app Applications
1. Introduction
If you use Apple’s Xcode developer environment developing and publishing code signed
applications appears to be largely handled by that tool chain. To Apple’s credit they have various
technical papers scattered around the developer help pages that with serious study you can
glean the required information to code sign your non Apple/Swift applications. Be aware you will
have to pay a developer fee for this privilege. The piper must be paid. Resistance is futile.
1.1. Document Management
This Libre Office document resides at the following URL: https://hsanchezii.wordpress.com
This document’s status is: Work-in-progress.
1.2. Acronyms
1.3. Software Versions
• Python – 3.9.1
• wxPython – 4.1.1
• todoist-python – 8.1.3
• PyGithub – 1.54.1
• py2app – 0.26
1
Apple Code Signing Py2app Applications
1.4. Format of py2app created OS X application package
Figure 1 illustrates the format of the generated application and highlights the portions we need to
sign.
2
Apple Code Signing Py2app Applications
2. Generating Signing Certificates
This is probably the most obfuscated step in the whole process and that is funny because it is not
even a programming step. Additionally, I think it is the least documented step.
2.1. Request from certificate authority
This seems like an odd way to request something from an authority. You generate something on
you local machine then pass it to a service on Apple servers. Run the Keychain Access utility as
noted in Figure 2.
Fill in the dialog as noted in Figure 3.
3
Apple Code Signing Py2app Applications
4
Apple Code Signing Py2app Applications
2.2. Certificate Request
Once you are signed into your developer account proceed to this web page. As of the date of this
document the URL is https://developer.apple.com/account/resources/certificates/list
On the Certificates, page create a new one.
It is important as a Python developer who intends to release his/her application outside of the
Apple App Store to pick the correct type of certificate. Also, this document assumes that the
signed, notarized, and stapled application is just going to be “.zipped” up and placed on a web
page or a Github release page for distribution.
5
Apple Code Signing Py2app Applications
2.3. Code Signing Certificate Request
Figure 5 correctly shows that we need code signing certificate for distribution outside of the Mac
App Store. Press Continue after selecting this step.
In this step (Figure 6), you choose the file that you created in Figure 3.
6
Apple Code Signing Py2app Applications
Then as depicted in Figure 7, download the certificate to your development machine.
Figure 7: Download Certificate
7
Apple Code Signing Py2app Applications
2.4. Imported Certificate
Once you import the certificate via the Keychain Access utility you will see your certificate as
noted in Figure 8. Notice the name of the certificate. That name is referred to as an environment
variable named IDENTITY in subsequent bash script snippets later in this document.
8
Apple Code Signing Py2app Applications
3. Code Signing Tasks
Essentially, these boil down to these signing rules:
• Sign every framework in your app bundle
• Sign every plugin in your app bundle
• Sign your app file
However, things are not always that simple.
For all the following bash snippets the environment variable OPTIONS is set as follows.
export OPTIONS="--verbose --timestamp --options=runtime "
3.1. Sign Frameworks
For my type of Py2App applications I am running Python 3.9 and wxPython as a GUI framework.
So I used the following of bash scripting snippet to sign my frameworks. The value of IDENTITY
is as described in section 2.4.
export IDENTITY="Your Identity Goes Here"
export OPTIONS="--verbose --timestamp --options=runtime "
export FRAMEWORK="${APP}/Contents/Frameworks/Python.framework/Versions/3.9/Python"
codesign --sign "${IDENTITY}" ${OPTIONS} ${FRAMEWORK}
9
Apple Code Signing Py2app Applications
3.2. Sign the embedded Python39.zip file
I discovered during the iterative and investigation phase of the signing process an embedded zip
file that the code signing verification process insisted needed to be signed. Additionally, even
after signing the notarization process complained of “a sealed resource is missing or invalid.” I
eventually solved this by first signing the embedded zip file and then all the other libraries. I am
assuming that is what Apple means when it says to “sign from the inside out.”
Signing the embedded zip file involved:
1. Copy it to a temporary location
2. Unzipping it
3. Sign the individual libraries
4. Re-zipping the libraries
5. Copying it back to the original location by overlaying the original
The following bash snippet illustrates steps 1 and 2.
export APP="PyGitIssueClone.app"
export ZIP_NAME="python39.zip"
export ORIGINAL_ZIP_DIR="${APP}/Contents/Resources/lib"
export PYTHON_ZIP="${ORIGINAL_ZIP_DIR}/${ZIP_NAME}"
export TEMP_DIR="/tmp"
export UNZIP_DIR="python39"
echo "Get copy of unsigned zip file"
cp -p ${PYTHON_ZIP} ${TEMP_DIR}
echo "Unzip it"
/usr/bin/ditto -x -k "${TEMP_DIR}/${ZIP_NAME}" "${TEMP_DIR}/${UNZIP_DIR}"
The following bash snippet is step 3. The value of IDENTITY is as described in section 2.4.
find "${PYTHON_UNZIP_DIR}/PIL/.dylibs" -iname '*.dylib' |
while read libfile; do
codesign --sign "${IDENTITY}" "${libfile}" ${OPTIONS}
# codesign --sign "${IDENTITY}" "${libfile}" ${OPTIONS}
done;
The following bash snippet illustrates steps 4 and 5.
echo "Remove old temp copy zip file"
rm -vrf "${TEMP_DIR}/${ZIP_NAME}"
echo "recreate zip file"
/usr/bin/ditto -c -k "${TEMP_DIR}/${UNZIP_DIR}" "${TEMP_DIR}/${ZIP_NAME}"
echo "Move signed zip back"
cp -p "${TEMP_DIR}/${ZIP_NAME}" ${ORIGINAL_ZIP_DIR}
10
Apple Code Signing Py2app Applications
3.3. Sign Libraries
Signing all the other libraries was relatively straight-forward. Essentially, I found all files in the
application that had a suffix of “.so” or “.dylib” and signed those. The value of IDENTITY is as
described in section 2.4.
echo "Sign libraries"
find "PyGitIssueClone.app" -iname '*.so' -or -iname '*.dylib'
while read libfile; do
codesign --sign "${IDENTITY}" ${OPTIONS} "${libfile}" >> ${LOGFILE} 2>&1 ;
done;
3.4. Sign the outermost binaries
Finally, we sign the two outermost binaries as depicted in the following bash snippet.
codesign --sign "${IDENTITY}" ${OPTIONS} "${APP}/Contents/MacOS/python"
codesign --sign "${IDENTITY}" ${OPTIONS} "${APP}/Contents/MacOS/PyGitIssueClone"
For my applications I distribute only zipped files and only as part of the application Github release
artifacts. As such, I do not cover signing disk image files (.dmg) or installer packages.
11
Apple Code Signing Py2app Applications
4. Notarization
You should notarize your application to avoid additional warnings from the OS X gatekeeper
when your end user first tries to run your application
4.1. Generate Notarization Tool APP ID
In order to run the notarization tool you need an Application ID (APP ID in Apple parlance)
associated with your Apple ID.
1. Log in to your Apple ID account page
2. In the Security section, click Generate Password below App-Specific Passwords
3. Follow the instructions
4. Save the application ID for later use
If you later have to view which APP IDs you created navigate back to the above Security Section
and click the View History link. Unfortunately, all you can do from there is view the names you
associated with them and revoke them.
4.2. Save APP ID in your Keychain
In order to not have to keep typing in the APP ID or accidentally checking it into Github or some
other source control system it is best to store this in your local keychain.
xcrun altool --store-password-in-keychain-item \
TheNameYouWantToUseInYourScripts \
-u <your apple id> \
-p <the app id you generated>
For example for me with my real APP ID redacted it is:
xcrun altool --store-password-in-keychain-item \
APP_PASSWORD \
-u
[email protected] -p abcdefgh996677
12
Apple Code Signing Py2app Applications
4.3. Notarize It!!
4.4. Notarization Steps
Notarize the application is a two step process.
1. Create a suitable zip file
2. Run the notarization command line
The following bash snippet assumes you run it from the directory where Py2App created it.
export EXPORT_PATH="`pwd`"
export PRODUCT_NAME="Pyut"
export APP_PATH="$EXPORT_PATH/$PRODUCT_NAME.app"
export ZIP_PATH="$EXPORT_PATH/$PRODUCT_NAME.zip"
# Create a ZIP archive suitable for notarization
/usr/bin/ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
xcrun notarytool submit "${PRODUCT_NAME}.zip" --keychain-profile "APP_PASSWORD" --wait
4.5. Notarization Log
When the notary tool completes it generates and displays a large UUID that you can use to query
the notarization task results. This is especially useful if notarization fails. The following
invocation of the notary tool queries for the task results and stores them in the file
developer_log.json.
xcrun notarytool log <UUID> --keychain-profile "APP_PASSWORD" developer_log.json
4.6. Stapling the Application
The following bash snippet illustrates how to staple the original application. This stapled
application is the one you should rezip and distribute on your Github release page or your web
site.
export APP=PyGitIssueClone.app
xcrun stapler staple dist/${APP}
13
Apple Code Signing Py2app Applications
5. Stapling
14
Apple Code Signing Py2app Applications
6. Another Major Heading
6.1. Example Figure
These instructions work for LibreOffice version 7.1.2.2
To add a figure do:
1. Insert→ Figure
2. Right Click on Figure, Anchor → To Paragraph
3. Right Click on Figure, Wrap → None
4. Right Click on Figure, Insert Caption
5. Optional. If you want to add fancy borders around the figure, Right Click on figure,
Properties → Borders; Adjust to taste
To add a Table of Figures move the cursor to an appropriate part of the document. Preferably, at
the top of a forced new page start. Then do the following:
1. Insert Table of Contents and Index → Table of Contents, Index, or Bibliography
2. Title: Fill in to taste
3. Select: Table of Figures
15
Apple Code Signing Py2app Applications
7. Appendices
7.1. Post 0.2 publication anomalies
I have two py2app applications that I frequently publish and want code-signed. They are
gittodoistclone and PyUt. Short after the initial publication of this article I needed to update the
former because the todoist team was posting a link to it on the integrated apps web page.
At that point, I wanted to sign and publish my updated app. Since Murphy was closely monitoring
me signing was failing because the CLI said that the libraries in the embedded python.zip (see
3.2.) file were already signed. This caused errors in signing the main application and of course
notarization failed.
What I discovered was the zip file created by py2app for PyUt and the one for gittodoistclone
were different. After a couple of days sleuthing and various failed work-arounds I was
flummoxed.
7.2. Resolution
At that point, I discovered that the codesign CLI had a –force flag which has the following
effect:
causes codesign to replace any existing signature on the path(s) given.
Without this option, existing signatures will not be replaced, and the
signing operation fails
I used the flag to sign the zip file and the rest of the application. Voila, codesign worked again.
16