Nightmare on Apple Street
apple 08-05-2025 ~8 minutes (1519 words)
*Clicks fingers, clears throat*
Okay, I have procrastinated on this post for, checks notes, 3 months now. It’s time. Time to let go of all the bad memories and the pain.
See, all I wanted to do was to create a terminal application that you can just download and run on Linux, macOS, and Windows. I also got a bit ambitious and wanted to create this app on, *gasp*, Linux! And you know, it worked on Windows. Yeah, that Windows that every developer loves to hate but secretly uses one way or another. But macOS? No no no no no, tsk tsk tsk, not so fast little boy. You need to sign & notarize your stuff, and you need to do it The Apple Way™.
The Apple Way™
The very first thing Apple wants is your money an Apple Developer account which will set you back $991. Every year that is.
Oh, and you cannot just create a developer account. You see, you need One Apple Account™.
If this is going to be a personal developer account, you just need your name, email, phone number, and your address2. If you are trying to enroll your organization, god help you: you need your D-U-N-S number.3
Now that we have warmed up, it is time for you to create an identifier for your application.
They recommend using a reverse-domain like name: com.my-company.my-app
. I know, you just want to self-distribute a simple binary.
Yes, you still need the unique identifier. No, you cannot use asdf
or foobar
.
Okay head over to identifier creation page and get it over with please. I’ll await
.
I think you leave the capabilities empty.
Switching to the Highway
After this step we need to add a certificate to our account. Now, if you have XCode, there’s a built in UI for this. But remember, we don’t have access to macOS where XCode can only survive in. Hence we will go rogue and will create a certificate signing request (CSR) using the command line. We need a “private key” to create a CSR so we’ll be creating that via the CLI too. Might seem complicated but it is just answering a bunch of questions and shuffling some files around.
For this, we’ll be needing 2 tools:
Let’s start with the private key:
openssl genrsa -out private.pem 2048
Now that we have generated our private key in private.pem
, we can create the CSR using rcodesign
:
rcodesign generate-certificate-signing-request --pem-file private.pem --csr-pem-file csr.pem
Okay, we have the signing request in csr.pem
. Now we head to the page where you can add a certificate
and follow the steps below:
-
From the gazillion options, select Developer ID Application.
-
Select
G2 Sub-CA (Xcode 11.4.1 or later)
for Profile Type. -
Upload the
csr.pem
file we just created. -
Now we should arrive at a page saying “Download Your Certificate”
-
Save this file as
pass.cer
next to the other ones and keep them safe. -
Download Apple’s root certificate and convert to PEM format (Apple Worldwide Developer Relations Certification Authority)
wget http://developer.apple.com/certificationauthority/AppleWWDRCA.cer # Convert that to PEM format openssl x509 -inform der -in AppleWWDRCA.cer -out AppleWWDRCA.pem # Convert pass.cer to PEM format openssl x509 -inform der -in pass.cer -out pass.pem
-
Note down your cert expiration date. You’ll need to do this entire dance again some days before this date:
openssl x509 -in pass.pem -noout -enddate
-
Now we are going to combine everything into a p12 file. Make sure to replace
Company Name
in the command line arguments below with your company name or your name.openssl pkcs12 -export -clcerts -inkey pass.pem -in pass.pem -certfile AppleWWDRCA.pem -name "Company Name" -out codesign.p12 -passout pass:
We made our
codesign.p12
file not password protected so you can use it in your CI/CD pipeline without having to enter a password. If you’d rather have it password protected, run the command above without-passout pass:
at the end. -
Before finishing, we need to note down your Team ID:
openssl pkcs12 -in codesign.p12 -nodes | grep OU
At this point, you only need the final codesign.p12
file.
Apple’s Sacred Stamp of Approval
To be admitted to Apple’s sacred notarization service, you need to get an App Store Connect API key. If you enjoy a good read from Apple go read their documentation. For the twitchy ones, like myself:
-
Head to API Key Creation page
-
Click on the + next to Active at the top of the table.
-
Enter a name like
Code Signing
-
Put
Developer
for the Access field -
Hit Generate
-
Notice the Download button in the last column for the key you just created (bottom row)
-
Download and save the key with the name
apikey.p8
next tocodesign.p12
-
Note the Key ID somewhere
-
Note the Issuer ID somewhere. This is a separate section above the key table.
-
Now let’s combine all these 3 into a single JSON file so we don’t have to manage them separately:
rcodesign encode-app-store-connect-api-key -o codesign_key.json <issuer-id> <key-id> apikey.p8
At this point you only need the codesign_key.json
file. This will be used for notarization.
The Entitled Apps
To be able to get your app notarized, it needs to have “entitlements”. This is essentially letting Apple know ahead of time, which sensitive APIs your application will be using. Then Apple’s servers will issue a “ticket” for this specific version of your app and when someone tries to run it, it will be checked and restrained to these limitations.
Since I don’t have cybernetic powers, I cannot (yet) deduce which entitlements your app needs over a blog post. That said I can at least make a recommendation. Since I did this for fossilized Node.js applications, I just copied what Node.js used for itself.
You can use this or create your own by picking and choosing from the vast array of entitlements that Apple offers. There’s also more excellent prose for those to understand deeper and follow the Apple cult even closer.
At the end of this section, I’ll just assume you have an entitlements.plist
file that is
properly formatted5 next to the binary
you want to sign and notarize.
Sign here please6
Now that we got everything we need for signing and notarization, we can get to actual business. Signing is quite straightforward but getting the notarization right took a few tries. Let’s start with signing:
rcodesign sign --team-name <your_team_id> --p12-file codesign.p12 --for-notarization -e entitlements.plist <your_binary_path>
If you opted for a password-protected p12 file above, you can add --p12-password <password>
or
--p12-password-file <password_file_path>
at the end of the command above.
Knock Knock Knocking on Notary’s Door
Now that we have a signed binary, we will get it notarized. We already got the prerequisites by using the --for-notarization
and
-e entitlements.plist
parts above so we are in good hands. We still need to zip the file before though7.
zip app.zip <path_to_your_app>
rcodesign notary-submit --api-key-file codesign_key.json --wait app.zip
rm app.zip
We’re Done Here
Yup, we really are done. At this point you can start distributing the signed binary. People using a macOS should be able to use it without errors or warnings.
If they double click on it (instead of running from a terminal), they may still see a security warning as we cannot “staple” the notarization tickets to plain binaries. To be able to do this you
need to package your app as a .pkg
or .dmg
file but I wasn’t (and still am not) interested in learning more Apple stuff so you’ll need to figure that part out
yourself.
If you want to have this process on a CI/CD pipeline you need to remember a few things:
- Make sure you don’t do signing and notarization on PR branches as that means anyone who can create a PR can generate and distribute a binary with their potentially malicious changes and with your signature on it.
- I don’t think you need to password-protect your p12 file but if you are using a service like GitHub you probably cannot store files as secrets. A quick hack for
this is to store the
base64
encoded string versions of these 2 files (codesign.p12
andcodesign_key.json
) as secrets. Then youbase64
decode these into their respective files and continue business as usual. - Also, don’t forget to store the signed binary as the artifact of your build.
Resources
I’ve used the excellent docs Gregory Szorc created for his amazing apple-codesign
project.
I essentially summarized these two pages:
- https://gregoryszorc.com/docs/apple-codesign/main/apple_codesign_certificate_management.html
- https://gregoryszorc.com/docs/apple-codesign/main/apple_codesign_getting_started.html
I also found this amazing gist for creating pkpass.p12
files from the GitHub user
karnauskas and used parts of it.
Finally, I’ve used this little hack from StackOverflow for providing you with a command for creating password-less p12 files from the get go.
Thanks
I’d like to thank my colleague Daniel Szoke for his help for establishing this entire flow and
proof-reading this post. I should have written this before he also got the pain to get sentry-cli
signed but hey, better late than never, right? 😅
Footnotes
-
You need to scroll all the way to the bottom to see this very unimportant detail. ↩
-
Yep, I’m being snarky. ↩
-
First time I heard about it. I wish patience to people dealing with Apple. And no, I have no intention of learning more about this but you have that link there. ↩
-
If you don’t have
openssl
around, just search for how you can install it. Should be as easy as<package_manager> install openssl
where<package_manager>
isapt
oryum
or something akin to those. ↩ -
Being a bit picky, are we dear Apple? ↩
-
and here, and here, and here, and here… ↩
-
Don’t ask me why they cannot be bothered with on-the-fly zipping or HTTP content encoding etc., I don’t know. ↩