InstallBuilder Home
Contact Support Download

Appendix I: Notarizer

notarizer

The notarizer tool allows notarizing DMG and .app bundles without depending on Apple built-in tools (such as notarytool, altool and the stapler), allowing submiting your software from macOS, Linux and Windows.

Setting up the tool

The easiest way to set it up is to invoke the setup command:

$> notarizer setup --help
setup writes the required configuration file to be able to use the notarizer

Usage:
  notarizer setup [flags]

Flags:
  -h, --help                      help for setup
      --issuer-id string          App Store Connect API issuer ID
      --key-id string             App Store Connect API key ID
      --password string           Apple app-specific password (app-specific)
      --private-key-file string   Path to the App Store Connect API private key file
      --team-id string            Apple Developer Team ID (app-specific)
      --username string           Apple ID username (app-specific)
  -y, --yes                       Automatically answer yes to all overwrite and create questions

Global Flags:
      --config-file string   Configuration file
      --log-level string     Log Level: (trace, debug, info, warn, error, fatal, panic) (default "info")
      --no-color             Do not use colors in the output messages
      --plain-output         Do not use colors nor animations in the output messages

The notarizer is able to use either app-spacific passwords or App Store Connect API keys. The tool will ask which of them do you have, but if you manually create the config.json file or you provide both set of credentials using flags, the former (app-specific passwords) will take precedence.

Saving the configuration to a custom file

By default, the tool will try setup the configuration file under home directory but you can override the location using the --config-file flag:

$>  notarizer setup --config-file custom.json
Configuration file "custom.json" does not exist. Do you want to create it? [Y/n]: y
...
Configuration file "custom.json" successfully created
Configuring app-specific passwors

Then, run the setup process:

### Using app-specific passwords
$>  notarizer setup
Configuration file "/Users/backstaff/.installbuilder/notarizer/etc/config.json" does not exist. Do you want to create it? [Y/n]:
Do you have an Apple app-specific password? [y/n]: y
Enter your Apple ID username: testuser
Enter you Apple app-specific password: *****
Enter your Apple Developer Team ID: My Team
Configuration file "/Users/backstaff/.installbuilder/notarizer/etc/config.json" successfully created

Or all at once:

$> notarizer setup --config-file config.json --username testuser --password 'secret!' --team-id "My Team" --yes
Configuration file "config.json" successfully created
$> cat foo.json
{
	"Auth": {
		"Username": "testuser",
		"Password": "secret!",
		"TeamID": "My Team",
		"KeyID": "",
		"IssuerID": "",
		"PrivateKey": ""
	}
}
Configuring App Store Connect API keys

First, get your App Store Connect API keys

Then, run the setup process:

### Using App Store Connect API keys
$>  notarizer setup
Configuration file "/Users/backstaff/.installbuilder/notarizer/etc/config.json" does not exist. Do you want to create it? [Y/n]:
Do you have an Apple app-specific password? [y/n]: n
Do you have a App Store Connect API key? [y/n]: y
Enter your App Store Connect API key ID: SFAZX65J1N
Enter your App Store Connect API issuer ID: 24d44e78-9e92-a808-2cf779d390d3
Enter your App Store Connect API key Text (in base64): ***********************...***********
Configuration file "custom.json" successfully created

Providing the API key in this way is a little annoying, so it is better to provide it via command line flags:

$> notarizer setup --config-file config.json --key-id SFAZX65J1N --issuer-id 24d44e78-9e92-a808-2cf779d390d3 --private-key-file AuthKey_SFAZX65J1N.p8 --yes
Configuration file "config.json" successfully created

$> cat config.json
{
	"Auth": {
		"Username": "",
		"Password": "",
		"TeamID": "",
		"KeyID": "SFAZX65J1N",
		"IssuerID": "24d44e78-9e92-a808-2cf779d390d3",
		"PrivateKey": "bPFsu6VcN8gFt+GBJfw...F2AsM7k81ZIcnhjOMsFC1EcUnfL/aqCLYu/x0QM7XKJcWcY8ClhSc3RsQeOukK"
	}
}

Usage

The tool supports different operations to perform:

Send DMG or APP files to be notarized

This subcommand allows sending signed DMGs or .app bundles to the Apple notarization servers:

$> notarizer upload --help
upload sends a signed package for notarization

Usage:
  notarizer upload [file] [flags]

Aliases:
  upload, submit

Flags:
  -h, --help                  help for upload
      --interval int          Wait interval between checks in seconds (default 10)
      --staple                Staple the resulting asset (implies wait)
      --summary-file string   Summary File in JSON format
      --timeout int           Wait timeout (default 1200)
      --wait                  Wait for the upload to be processed

Global Flags:
      --config-file string   Configuration file
      --log-level string     Log Level: (trace, debug, info, warn, error, fatal, panic) (default "info")
      --no-color             Do not use colors in the output messages
      --plain-output         Do not use colors nor animations in the output messages

The process sends the provided DMG file to be notarized and prints the associated request id, that can be used to later query for the status (make sure the DMG and its bundled app is signed):

$> notarizer upload sample-1.0-osx-installer.dmg
 »  Notarizing "./sample-1.0-osx-installer.dmg"
    »  Uploading "./sample-1.0-osx-installer.dmg" for notarization
      ✔  Uploaded with id "9da7b0ce-b23d-4661-a6c6-bbccbac63c72" [In Progress]
Success!

Using this id, you will be able to check for its status (see the next subcommand section). Alternatively, you can also instruct the notarizer tool to wait for the analysis process to finish:

$> notarizer upload sample-1.0-osx-installer.dmg --wait
 »  Notarizing "./sample-1.0-osx-installer.dmg"
    »  Uploading "./sample-1.0-osx-installer.dmg" for notarization
      ✔  Uploaded with id "9da7b0ce-b23d-4661-a6c6-bbccbac63c72" [In Progress]
    ✔  Finished waiting for id "9da7b0ce-b23d-4661-a6c6-bbccbac63c72": Accepted
 Success!
Waiting for a notarization submission status

If you submitted an asset to be notarized wihout specifying the --wait flag, you can wait for it using its submission id:

$> notarized wait
./bin/notarizer wait 9da7b0ce-b23d-4661-a6c6-bbccbac63c72
Notarization sucessful for "9da7b0ce-b23d-4661-a6c6-bbccbac63c72" [Accepted]

If you did not take note of the submission id, you can list your latest submission and get the id from there:

$> notarizer list --output-format json --last 1| jq '.[].id'
"9da7b0ce-b23d-4661-a6c6-bbccbac63c72"
Query a notarization submission status

After a DMG file is sent for notatization, Apple server will take care of analyzing it and if it passes its validation, it will publish a notarization ticket so Gatekeeper can verify it later on.

You can refer to your notarization tickets by the id returned by the upload command.

A ticket being processed would look like:

$> notarizer status 934941aa-c8ab-40dc-af39-f68e84e1d3d1
{
  "id": "934941aa-c8ab-40dc-af39-f68e84e1d3d1",
  "type": "submissions",
  "attributes": {
    "status": "Accepted",
    "name": "sample-1.0-osx-installer.dmg",
    "createdDate": "2024-06-17T07:21:30.891Z"
  }
}

In the above log, we managed to successfully notarize the DMG, but it is also possible to get a rejection:

 $> notarizer status 52371698-c890-42cc-84c6-1a75170deca0
 {
   "id": "52371698-c890-42cc-84c6-1a75170deca0",
   "type": "submissions",
   "attributes": {
     "status": "Invalid",
     "name": "sample-1.0-osx-installer.dmg",
     "createdDate": "2024-09-27T11:46:06.621Z"
   }
 }

And you can get more info through the log command:

$> notarizer log -output-format json 00ebf941-af71-4720-bf38-58f818f37847
{
  "logFormatVersion": 1,
  "jobId": "00ebf941-af71-4720-bf38-58f818f37847",
  "status": "Invalid",
  "statusSummary": "Archive contains critical validation errors",
  "statusCode": 4000,
  "archiveFilename": "sample-1.0-osx-installer.dmg",
  "uploadDate": "2024-09-27T11:46:02.981Z",
  "sha256": "ea8e0b633a4fea0de41b91f0495bc2bf3d233f5443e02cca36f02d97f30c7a53",
  "ticketContents": null,
  "issues": [
  {
    "severity": "error",
    "code": null,
    "path": "sample-1.0-osx-installer.dmg/sample-1.0-osx-installer.app/Contents/MacOS/osx-x86_64",
    "message": "The binary is not signed with a valid Developer ID certificate.",
    "docUrl": null,
    "architecture": "x86_64"
  }
  ]
}

It is also possible to list the submissions. By default it will show all the history, but you can control the amount of tickets using the --last flag:

$> notarizer list --last 5
┌───┬─────────────┬──────────────────────────────────────┬──────────────┬───────────────┐
│ # │ STATUS      │ ID                                   │ NAME         │ CREATEDDATE   │
├───┼─────────────┼──────────────────────────────────────┼──────────────┼───────────────┤
│ 0 │ Accepted    │ 69f4c22f-679e-4f56-80e9-30b78d366ae6 │ sample-5-dmg │ 2024-06-17T07 │
│ 1 │ In Progress │ 53758b06-a445-451c-bca2-9a43dda82256 │ sample-4.dmg │ 2024-06-17T3Z │
│ 2 │ Accepted    │ 51b61552-95e0-433b-bc86-1d18d3a43268 │ sample-3.dmg │ 2024-06-15T2Z │
│ 3 │ Accepted    │ 73c0e332-fdef-4e74-97d0-c59972924333 │ sample-2.dmg │ 2024-06-15T7Z │
│ 4 │ Accepted    │ 547f605b-4035-4996-9ede-a8bb1d219771 │ sample-1.dmg │ 2024-06-15T6Z │
└───┴─────────────┴──────────────────────────────────────┴──────────────┴───────────────┘

Or in json format:

$> notarizer list --output-format json --last 5
[
  {
    "id": "69f4c22f-679e-4f56-80e9-30b78d366ae6",
    "type": "submissions",
    "attributes": {
      "status": "Accepted",
      "name": "sample-1.0-osx-installer.dmg",
      "createdDate": "2024-06-17T07:21:30.891Z"
    }
  },
  {
    "id": "53758b06-a445-451c-bca2-9a43dda82256",
    "type": "submissions",
    "attributes": {
      "status": "In Progress",
      "name": "sample-1.0-osx-installer.dmg",
      "createdDate": "2024-06-17T07:21:22.803Z"
    }
  },
  {
    "id": "51b61552-95e0-433b-bc86-1d18d3a43268",
    "type": "submissions",
    "attributes": {
      "status": "Accepted",
      "name": "sample-1.0-osx-installer.dmg",
      "createdDate": "2024-06-15T09:26:53.152Z"
    }
  },
  {
    "id": "73c0e332-fdef-4e74-97d0-c59972924333",
    "type": "submissions",
    "attributes": {
      "status": "Accepted",
      "name": "sample-1.0-osx-installer.dmg",
      "createdDate": "2024-06-15T09:10:46.177Z"
    }
  },
  {
    "id": "547f605b-4035-4996-9ede-a8bb1d219771",
    "type": "submissions",
    "attributes": {
      "status": "Accepted",
      "name": "sample-1.0-osx-installer.dmg",
      "createdDate": "2024-06-15T09:08:12.986Z"
    }
  }
]
Stappling the ticket back to the asset

Once an asset (DMG or .app) is notarized, when it gets downloaded to a user computer and opened, its Gatekeeper will check Apple servers to query for it notarization status. It will then find the associated ticket, and confirm it was notarized. This process however requires Internet connection (the ticket is cached locally, so this only happens the first time).

To make the process work withpout Internet, it is also possible to attach the notarization ticket to the asset:

$> notarizer staple attach sample.dmg
┌───┬────────────┬──────────────────────────────────────────────┬───────────┬──────────┬───────┐
│ # │ FILE       │ TICKET                                       │ NOTARIZED │ STAPPLED │ ERROR │
├───┼────────────┼──────────────────────────────────────────────┼───────────┼──────────┼───────┤
│ 0 │ sample.dmg │ 2/2/e011f3dc33ccc66f97f52303c5d988ff82debb4a │ true      │ true     │       │
└───┴────────────┴──────────────────────────────────────────────┴───────────┴──────────┴───────┘

It will modify the asset with the notarization ticket. You can verify it on macOS using the stappler command:

$> stapler validate sample.dmg
Processing: sample.dmg
The validate action worked!

If you are not running the tool on macOS, you can also use the validate command to check if the asset is stappled:

$> notarizer staple validate ./sample.dmg
┌───┬────────────┬──────────────────────────────────────────────┬───────────┬─────────┬───────┐
│ # │ FILE       │ TICKET                                       │ NOTARIZED │ STAPLED │ ERROR │
├───┼────────────┼──────────────────────────────────────────────┼───────────┼─────────┼───────┤
│ 0 │ sample.dmg │ 2/2/e011f3dc33ccc66f97f52303c5d988ff82debb4a │ true      │ true    │       │
└───┴────────────┴──────────────────────────────────────────────┴───────────┴─────────┴───────┘