Skip to content

eclipse-cbi/macos-notarization-service

macOS Notarization Service

Build Status GitHub release GitHub license SLSA Build Level

This is a web service that runs on macOS and offers a REST API to notarize signed application bundles or signed DMG. You can read more about notarization on the Apple developer website.

Getting started

Requirements

brew install temurin17

Build

$ ./mvnw clean package

It produces the self-contained application in /target/quarkus-app.

You can run the application using: java -jar target/quarkus-app/quarkus-run.jar

See below for advanced startup method.

Installation

To download a release and perform verification whether the downloaded artifact has been produced by the project, you should use the download-github-release.sh script (supported since v1.2.0):

$ ./download-github-release.sh -v 1.3.0

This will download the 1.3.0 release together with the provenance and perform verification (requires that the slsa-verifier tool is installe):

$ ./download-github-release.sh -v 1.3.0
REPO = eclipse-cbi/macos-notarization-service
VERSION = 1.3.0
ARTIFACT = macos-notarization-service
Downloaded artifact 'macos-notarization-service-1.3.0.zip'
Downloaded provenance 'macos-notarization-service-1.3.0-attestation.intoto.build.slsa'
Verifying artifact 'macos-notarization-service-1.3.0.zip' using provenance 'macos-notarization-service-1.3.0-attestation.intoto.build.slsa':

Verified build using builder "https://github.com/jreleaser/release-action/.github/workflows/builder_slsa3.yml@refs/tags/v1.1.0-java" at commit 5325c11c611568f5e043d934185183783f228c0a
Verifying artifact macos-notarization-service-1.3.0.zip: PASSED

PASSED: Verified SLSA provenance

Run

Configure NOTARIZATION_APPLEID_USERNAME, NOTARIZATION_APPLEID_PASSWORD and NOTARIZATION_APPLEID_TEAMID with the proper values for your Apple developer ID account in file start.sh. Then, just run

./start.sh

On production system, it is advised to run this service as a system daemon with launchd. The service will then be started automatically at boot time or if the program crash. You can find a sample file to edit and put in /Library/LaunchDaemons in src/main/launchd/org.eclipse.cbi.macos-notarization-service.plist. To load (or unload) the service, just do

sudo launchctl load -w /Library/LaunchDaemons/org.eclipse.cbi.macos-notarization-service.plist

or

sudo launchctl unload -w /Library/LaunchDaemons/org.eclipse.cbi.macos-notarization-service.plist

See Apple documentation about daemons and agents for more options.

Documentation

The service is Quarkus application exposing a simple REST API with 3 endpoints. See Quarkus documentation for all of its configuration options.

The following script calls the 3 endpoints successively to notarize a signed DMG file. It assumes that jq is installed on the system running the script. The notarization service is expected to run on IP 10.0.0.1 on port 8383.

DMG="/path/to/myApp.dmg"

RESPONSE=\
$(curl -s -X POST \
  -F file=@${DMG} \
  -F 'options={"primaryBundleId": "my-primary-bundle-id", "staple": true};type=application/json' \
  http://10.0.0.1:8383/macos-notarization-service/notarize)
  
UUID=$(echo ${RESPONSE} | jq -r '.uuid')

STATUS=$(echo ${RESPONSE} | jq -r '.notarizationStatus.status')

while [[ ${STATUS} == 'IN_PROGRESS' ]]; do
  sleep 1m
  RESPONSE=$(curl -s http://10.0.0.1:8383/macos-notarization-service/${UUID}/status)
  STATUS=$(echo ${RESPONSE} | jq -r '.notarizationStatus.status')
done

if [[ ${STATUS} != 'COMPLETE' ]]; then
  echo "Notarization failed: ${RESPONSE}"
  exit 1
fi

mv "${DMG}" "unnotarized-${DMG}"

curl -JO http://10.0.0.1:8383/macos-notarization-service/${UUID}/download

We first upload our DMG to the service endpoint macos-notarization-service/notarize in a form part named file along with a set of options in JSON format in a form part named options. Only two options are available for now:

  • primaryBundleId (required): the primary bundle ID that will be sent to the notarization service by xcrun altool. The value you give doesn’t need to match the bundle identifier of the submitted app or have any particular value. It only needs to make sense to you. See Apple documentation for more information.
  • staple: a boolean to specify wether or not the notarization ticket should be stapled to the notarized binary at the end of the process. Default is false. We advise you always set it to true. This ensures that Gatekeeper can find the notarization ticket even when a network connection isn’t available.

Once the upload to the notarization is complete, you will receive (the $RESPONSE variable in the script above) a JSON file with a content similar to

{ 
  "uuid":"e68713e3-2ee7-4ebd-8672-20949a9ecdb9",
  "notarizationStatus": {
    "status":"IN_PROGRESS",
    "message":"Uploading' file to Apple notarization 'service"
  }
}

The uuid field is very important as it will be the one that will let you poll the service to know the status of the notarization process for your file and to download the results in the end. The notarizationStatus object contains the current status.

The $STATUS will change from ÌN_PROGRESS to either COMPLETE or ERROR depending on the outcome of the process. Here the script polls the service every minute to check if the process is done via the second endpoint macos-notarization-service/$UUID/status.

Once the process is done, you can download the notarized DMG with the endpoint macos-notarization-service/${UUID}/download. Note that this is unnecessary if you did not asked for the notarization ticket to be stapled to the binary to be notarized. Indeed, the notarization itself is side effect free for binaries if you don't staple the ticket.

Trademarks

  • Eclipse® is a Trademark of the Eclipse Foundation, Inc.
  • Eclipse Foundation is a Trademark of the Eclipse Foundation, Inc.

Copyright and license

Copyright 2019 the Eclipse Foundation, Inc. and others. Code released under the Eclipse Public License Version 2.0 (EPL-2.0).

SPDX-License-Identifier: EPL-2.0