diff --git a/Makefile b/Makefile index 0af75b42c293..ed96a2438e28 100644 --- a/Makefile +++ b/Makefile @@ -1247,7 +1247,7 @@ $(PKG): release-only --resources $(MACOSOUTDIR)/installer/productbuild/Resources \ --package-path $(MACOSOUTDIR)/pkgs ./$(PKG) SIGN="$(PRODUCTSIGN_CERT)" PKG="$(PKG)" sh tools/osx-productsign.sh - sh tools/osx-notarize.sh $(FULLVERSION) + sh tools/osx-notarize.sh $(PKG) .PHONY: pkg pkg: $(PKG) ## Build the macOS installer for releases. @@ -1433,6 +1433,7 @@ else endif ifeq ($(OSTYPE),darwin) SIGN="$(CODESIGN_CERT)" PKGDIR="$(BINARYNAME)" sh tools/osx-codesign.sh + sh tools/osx-notarize.sh $(BINARYNAME)/bin/node endif tar -cf $(BINARYNAME).tar $(BINARYNAME) $(RM) -r $(BINARYNAME) diff --git a/tools/osx-notarize.sh b/tools/osx-notarize.sh index 06ba801c9e81..c9b50a2f3b69 100755 --- a/tools/osx-notarize.sh +++ b/tools/osx-notarize.sh @@ -1,12 +1,107 @@ #!/bin/sh -# Notarize a generated node-.pkg file as an Apple requirement for installation on macOS Catalina and later, as validated by Gatekeeper. +# Notarize a macOS release artifact as an Apple requirement for running or +# installing on macOS Catalina and later, as validated by Gatekeeper. # Uses notarytool and requires Xcode >= 13.0. +# +# Usage: osx-notarize.sh +# +# ending in .pkg: the installer is submitted as is, assessed with +# spctl and the notarization ticket is stapled to it. +# any other : a Mach-O binary, such as bin/node from the release +# tarball. Apple only accepts zip, dmg or pkg submissions, so the binary +# is zipped for the upload and the zip is discarded afterwards. A bare +# binary cannot carry a stapled ticket; Gatekeeper fetches it online by +# the code directory hash, so the binary itself is left untouched and the +# published checksums do not change. +# +# For backwards compatibility a bare version id (for example v26.13.0) is +# accepted and resolved to node-.pkg. -pkgid="$1" +# The notarization ticket can take a while to become available from Apple's +# servers after notarytool reports "Accepted", so retry with backoff. +retry() { + delay=15 + attempt=1 + while ! "$@"; do + if [ "$attempt" -ge 6 ]; then + return 1 + fi + echo "Attempt $attempt failed, retrying in ${delay}s..." + sleep "$delay" + attempt=$((attempt + 1)) + delay=$((delay * 2)) + done +} + +submit() { + echo "Submitting $1 for notarization..." + if xcrun notarytool submit \ + --keychain-profile "NODE_RELEASE_PROFILE" \ + --wait \ + "$1"; then + echo "Notarization $1 submitted successfully." + else + echo "Notarization $1 failed." + return 1 + fi +} + +# Remove the upload zip on every exit path, including a failed ditto or an +# interrupted build, the same way the dep_updaters scripts clean their +# workspace. $zip is only set by notarize_binary. +cleanup() { + EXIT_CODE=$? + if [ -n "$zip" ]; then + rm -f "$zip" + fi + exit $EXIT_CODE +} + +notarize_pkg() { + submit "$1" || exit 1 + + if ! retry xcrun spctl --assess --type install --context context:primary-signature --ignore-cache --verbose=2 "$1"; then + echo "error: Signature will not be accepted by Gatekeeper!" 1>&2 + exit 1 + fi + echo "Verification was successful." + + if ! retry xcrun stapler staple "$1"; then + echo "error: Stapling $1 failed." 1>&2 + exit 1 + fi + echo "Stapler was successful." +} + +notarize_binary() { + # ditto preserves the code signature and resource forks, which plain zip + # does not, so it is the archiver Apple documents for notarization uploads. + # The zip only exists for the upload and is removed by cleanup. + zip="$1.notarize.zip" + rm -f "$zip" + if ! ditto -c -k --keepParent "$1" "$zip"; then + echo "error: Could not create $zip for submission." 1>&2 + exit 1 + fi + + submit "$zip" || exit 1 + rm -f "$zip" + + # There is nothing to staple to a bare binary, so the only way to confirm + # the ticket is live is to have codesign look it up. spctl cannot be used + # here: it rejects command line tools with "does not seem to be an app". + if ! retry codesign --verify --verbose=2 -R=notarized "$1"; then + echo "error: $1 will not be accepted by Gatekeeper!" 1>&2 + exit 1 + fi + echo "Verification was successful." +} -if [ -z "$pkgid" ]; then - echo "Usage: $0 " +target="$1" + +if [ -z "$target" ]; then + echo "Usage: $0 " exit 1 fi @@ -26,6 +121,16 @@ if [ -z "$NOTARIZATION_TEAM_ID" ]; then exit 0 fi +# Legacy call form: a version id instead of a path. +if [ ! -e "$target" ] && [ -e "node-$target.pkg" ]; then + target="node-$target.pkg" +fi + +if [ ! -e "$target" ]; then + echo "error: $target does not exist." 1>&2 + exit 1 +fi + echo "Notarization process is done with Notarytool." if ! command -v xcrun notarytool > /dev/null @@ -34,45 +139,9 @@ then exit 1 fi -echo "Submitting node-$pkgid.pkg for notarization..." - -xcrun notarytool submit \ - --keychain-profile "NODE_RELEASE_PROFILE" \ - --wait \ - "node-$pkgid.pkg" - -if [ $? -eq 0 ]; then - echo "Notarization node-$pkgid.pkg submitted successfully." -else - echo "Notarization node-$pkgid.pkg failed." - exit 1 -fi - -# The notarization ticket can take a while to become available from Apple's -# servers after notarytool reports "Accepted", so retry with backoff. -retry() { - delay=15 - attempt=1 - while ! "$@"; do - if [ "$attempt" -ge 6 ]; then - return 1 - fi - echo "Attempt $attempt failed, retrying in ${delay}s..." - sleep "$delay" - attempt=$((attempt + 1)) - delay=$((delay * 2)) - done -} - -if ! retry xcrun spctl --assess --type install --context context:primary-signature --ignore-cache --verbose=2 "node-$pkgid.pkg"; then - echo "error: Signature will not be accepted by Gatekeeper!" 1>&2 - exit 1 -else - echo "Verification was successful." -fi +trap cleanup INT TERM EXIT -if ! retry xcrun stapler staple "node-$pkgid.pkg"; then - echo "error: Stapling node-$pkgid.pkg failed." 1>&2 - exit 1 -fi -echo "Stapler was successful." \ No newline at end of file +case "$target" in + *.pkg) notarize_pkg "$target" ;; + *) notarize_binary "$target" ;; +esac