The purpose of this blog is to help web developers to jump into systems programming. So you can ask any questions; there are no dummy questions. I want this blog to be a discussion space for every programmer on this journey.

What is notarization?

Notarization is a process that identifying malicious software before it is distributed.

Notarization will perform security checks. The application is submitted to the Apple Notary Service, which will perform inspections and send back a ticket. This ticket will be stapled to the software. Then the software is ready to be distributed.

macOS development lifecyle

Once the user downloads the software, Gatekeeper will do a verification:

  • Check the ticket stapled to the software
  • Ask the Notary Service for a ticket
  • Compare both tickets and see if they match

⚠️ If the ticket is not stapled, Gatekeeper will still perform the verification. However, depending on the OS version or the user security settings, it could result in a slower experience and different behavior.

Why should I do notarization?

  • It will perform security checks on your code for you; keep in mind that it is not an application review
  • End users are more confident about downloading and using applications
  • Gatekeeper could prevent users from opening your application unless the user disables security checks

What kind of security issues does it cover?

What is the difference between code signing and notarization?

Code signing ensures that your code was not modified after its distribution, and notarization performs security checks that you, as a developer, could have introduced.
Note that the notarization process will ensure the application is appropriately code signed.

Notarize your app with notarytool

In a previous post, we dived into code signing, let’s resume the work we did there to start our journey into notarization.

First, let’s clone the repository

git clone

Please check out the codesign-only branch.

As the readme mentions, ensure you have Xcode and CMake installed on your machine.

Then, run the following:

TEAM_ID="<your developer ID>" make

You should see this in the console output:


cpack -G DragNDrop -B dist --config ./dist/CPackConfig.cmake -C Release
CPack: Create package using DragNDrop
CPack: Install projects
CPack: - Install project: MyMacOSApp [Release]
CPack: Create package
CPack: - package: /Users/tonygorez/perso/codesign-macos/dist/MyMacOSApp-0.1.1-Darwin.dmg generated.
codesign --verify --verbose=2 ./dist/Release/
./dist/Release/ valid on disk
./dist/Release/ satisfies its Designated Requirement
codesign --force --verbose=2 --sign "<TEAM_ID>" ./dist/MyMacOSApp-0.1.1-Darwin.dmg
./dist/MyMacOSApp-0.1.1-Darwin.dmg: signed  []

It means that the application build succeeds and the app is signed correctly.

👉If you don’t know to generate a certificate, please check this post about code signing.Last, check that the notarytool util is available on your machine:

xcrun notarytool help

Now, let’s notarize our application.

Store credentials

Along the way, you will probably use notarytool for different purposes: submit your application, get info on your submission, or inspect logs. You must pass a punch of arguments for each of these actions: Apple ID, organization ID, and an app-specific password.

👉As you need to create an app-specific password, please do.Passing three arguments each time you must perform an action with notarytool, could be cumbersome. Moreover, if you need to script out all these actions, you probably prefer not to pass sensitive information into your code.

It’s why we will use notarytool store-credential command to store our credentials and re-use them through a keychain profile.

Aiming to perform this store-credential command, you need three pieces of information:

  • your Apple identifier, probably the mail you use for login
  • the team ID, for example, if your certificate is: Developer ID Application: JOHN, DOE (X4MF6H9XZ6) the team ID is: X4MF6H9XZ6
  • an app-specific password

Then you can run the following command:

xcrun notarytool store-credentials <PROFILE_NAME> \
  --apple-id <APPLE_ID> \
  --team-id <TEAM_ID> \

Store credentials with notarytoolWith values, it should look like this:

xcrun notarytool store-credentials "KC_PROFILE" \
  --apple-id \
  --team-id  X4MF6H9XZ6 \
  --password qini-xppm-lzvl-buwq

You should see:

This process stores your credentials securely in the Keychain. You reference these credentials later using a profile name.

Validating your credentials...
Success. Credentials validated.
Credentials saved to Keychain.
To use them, specify `--keychain-profile "KC_PROFILE"

Notarize application

Now it is time to notarize our application. If the build you did on setup succeeds appropriately, you should find the disk image at: dist/MyMacOSApp-0.1.1-Darwin.dmg

We’ll use these for the next steps.

Let’s submit the dmg:

xcrun notarytool submit ./dist/MyMacOSApp-0.1.1-Darwin.dmg \
		--keychain-profile "KC_PROFILE" \

Submit the disk image to the Notary service with the keychain profileHere we submitted ./dist/MyMacOSApp-0.1.1-Darwin.dmg with the keychain profile "KC_PROFILE".

You probably noticed the --wait option. The purpose of this option is to wait until the submission is finished. This is convenient, primarily if you used the atool command (previous notary command) where you had to poll the Apple server to check the status of your submission.

Oh, but it failed:

Conducting pre-submission checks for MyMacOSApp-0.1.1-Darwin.dmg and initiating connection to the Apple notary service...
Submission ID received
  id: e415f471-6352-4ac5-9711-bc7d50730360
Upload progress: 100,00 % (33,5 KB of 33,5 KB)
Successfully uploaded file
  id: e415f471-6352-4ac5-9711-bc7d50730360
  path: /Users/tonygorez/perso/codesign-macos/dist/MyMacOSApp-0.1.1-Darwin.dmg
Waiting for processing to complete.
Current status: Invalid........
Processing complete
  id: e415f471-6352-4ac5-9711-bc7d50730360
  status: Invalid

But what is the root cause? Let’s take a look at the log command.

Check for errors in logs

You can reuse the id and pass it to the log command:

xcrun notarytool log e415f471-6352-4ac5-9711-bc7d50730360 --keychain-profile "KC_PROFILE"

It will output:

  "logFormatVersion": 1,
  "jobId": "e415f471-6352-4ac5-9711-bc7d50730360",
  "status": "Invalid",
  "statusSummary": "Archive contains critical validation errors",
  "statusCode": 4000,
  "archiveFilename": "MyMacOSApp-0.1.1-Darwin.dmg",
  "uploadDate": "2023-07-18T07:36:43.926Z",
  "sha256": "e5b06cedadd3ae0e61d08f2304e8eb0cbffc4a05d8e9f98be8070bff5b4f4d2e",
  "ticketContents": null,
  "issues": [
      "severity": "error",
      "code": null,
      "path": "MyMacOSApp-0.1.1-Darwin.dmg/",
      "message": "The signature does not include a secure timestamp.",
      "docUrl": "",
      "architecture": "arm64"
      "severity": "error",
      "code": null,
      "path": "MyMacOSApp-0.1.1-Darwin.dmg/",
      "message": "The executable does not have the hardened runtime enabled.",
      "docUrl": "",
      "architecture": "arm64"
      "severity": "error",
      "code": null,
      "path": "MyMacOSApp-0.1.1-Darwin.dmg/",
      "message": "The executable requests the entitlement.",
      "docUrl": "",
      "architecture": "arm64"

Look at the issues property! It is an array of objects, each containing the payload of an error. What I like is that Apple provides a docUrl property that links to documentation for the specific error rather than just a message.

I won’t detail each error. We’ll fix them quickly by adding two properties into the CMakeLists.txt:

set_target_properties(${APP_NAME} PROPERTIES
    # Add these properties
    XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--timestamp=  --options=runtime,library"

Now, let’s build the app again:

TEAM_ID="<your developer ID>" make

And replay the notarytool submit command:

Conducting pre-submission checks for MyMacOSApp-0.1.1-Darwin.dmg and initiating connection to the Apple notary service...
Submission ID received
  id: 004d775c-3409-4967-b1b4-f3748a7eacb6
Upload progress: 100,00 % (33,6 KB of 33,6 KB)
Successfully uploaded file
  id: 004d775c-3409-4967-b1b4-f3748a7eacb6
  path: /Users/tonygorez/perso/codesign-macos/dist/MyMacOSApp-0.1.1-Darwin.dmg
Waiting for processing to complete.
Current status: Accepted........
Processing complete
  id: 004d775c-3409-4967-b1b4-f3748a7eacb6
  status: Accepted

🎉 Notarization finally happened! But it is not over …

Staple the ticket

Gatekeeper will perform a check for a notarization ticket online. If it can’t reach the server (due to no internet connection, for example), and if the ticket isn’t stapled to the app, macOS will prevent the app from running because it can’t verify that it is notarized.

It’s why we staple the disk image (it also works with a .pkg or an .app) with the stapler command:

xcrun stapler staple ./dist/MyMacOSApp-0.1.1-Darwin.dmg

It should output:

Processing: /Users/tonygorez/perso/codesign-macos/dist/MyMacOSApp-0.1.1-Darwin.dmg
Processing: /Users/tonygorez/perso/codesign-macos/dist/MyMacOSApp-0.1.1-Darwin.dmg
The staple and validate action worked!

Verify notarization

spctl is the command line interface to manage and control the system policy security (Gatekeeper).

We’ll use it to check that the notarization process is okay:

xcrun spctl --assess \
  --type open \
  --context context:primary-signature \
  --ignore-cache \
  --verbose=2 \

Let’s untangle the bunch of options we have here:

  • --assess: This option tells spctl to assess the specified object. In this context, it checks if the .dmg file conforms to the system’s security policy.
  • --type open: This option is used to determine the type of assessment to perform. The open type is generally used for files that result in some form of opened UI when they are run.
  • --context context:primary-signature: It assesses the primary signature of the application (in some cases, we could have additional, nested code signatures, for instance, a signed app that contains a signed framework).
  • --ignore-cache: This option tells spctl to ignore any cached assessment results and reassess the specified object.
  • --verbose=2: This sets the verbosity level of the output.

Once run successfully, the command outputs:

./dist/MyMacOSApp-0.1.1-Darwin.dmg: accepted
source=Notarized Developer ID

Congratulations 🎉! You finally managed to get it done!

If you want to have a pointer to all of these commands, visit this repo on the main branch: