macosqt5macos-catalinasignnotarize

How properly add MacOS Signature & Notarization to .app built with Qt


I have an application (built with Qt / QML) that I am trying to sign and notarize for Ad hoc distribution outside the App Store (it's for internal company use). Apple confirms that my software has been notarized, and I then staple my .app - but Gatekeeper still stops the application from running and alerts users:

App Name can’t be opened because Apple cannot check it for malicious software.

What am I doing wrong?.

Here are the steps that I am following:

  1. Ran QMake / Build in Qt Creator.

  2. Modified the CFBundleIdentifier key in the generated Info.plist file to be com.myCompanyName.appName instead of com.yourcompany.appName as generated by Qt.

  3. cd to my build dir.

  4. Ran macdeployqt:
    /Users/<user>/Qt/5.13.0/clang_64/bin/macdeployqt <appName>.app -qmldir=<project source code dir>.

  5. Code Sign:
    codesign --deep -f -s "Developer ID Application: <company name> (<team id>)" --options "runtime" "<appName>.app/"
    (Where the developer id application is the name as it appears in KeychainAccess).

  6. Verified that the signature:
    codesign -dv --verbose=4 <appName>.app
    Returns:

    `Executable=<build dir>/<app name>.app/Contents/MacOS/<app name>
    Identifier=com.<company name>.<app name>
    Format=app bundle with Mach-O thin (x86_64)
    CodeDirectory v=20200 size=53750 flags=0x10000(runtime) hashes=1674+3 location=embedded
    Library validation warning=OS X SDK version before 10.9 does not support Library Validation
    VersionPlatform=1
    VersionMin=658432
    VersionSDK=0
    Hash type=sha256 size=32
    CandidateCDHash sha256=*************************************
    CandidateCDHashFull sha256=*************************************
    Hash choices=sha256
    CMSDigest=*************************************
    CMSDigestType=2
    Page size=4096
    CDHash=*************************************
    Signature size=8990
    Authority=Developer ID Application: <my company name> (<my team ID>)
    Authority=Developer ID Certification Authority
    Authority=Apple Root CA
    Timestamp=May 27, 2020 at 2:35:38 PM
    Info.plist entries=10
    TeamIdentifier=V7L2LD4Q9S
    Sealed Resources version=2 rules=13 files=1198
    Internal requirements count=1 size=184
    

    Or if I look at a (chosen at random) executable from the Qt framework inside the app bundle:
    codesign -dv --verbose=4 <appName>.app/Contents/Frameworks/QtCore.framework/Versions/5/QtCore
    Returns:

    Executable=<build dir>/<app name>.app/Contents/Frameworks/QtCore.framework/Versions/5/QtCore
    Identifier=org.qt-project.QtCore
    Format=bundle with Mach-O thin (x86_64)
    CodeDirectory v=20500 size=49057 flags=0x10000(runtime) hashes=1526+3 location=embedded
    VersionPlatform=1
    VersionMin=658432
    VersionSDK=658944
    Hash type=sha256 size=32
    CandidateCDHash sha256=*************************************
    CandidateCDHashFull sha256=*************************************
    Hash choices=sha256
    CMSDigest=*************************************
    CMSDigestType=2
    Page size=4096
    CDHash=*************************************
    Signature size=8990
    Authority=Developer ID Application: <my company name> (<my team ID>)
    Authority=Developer ID Certification Authority
    Authority=Apple Root CA
    Timestamp=May 27, 2020 at 2:35:37 PM
    Info.plist entries=8
    TeamIdentifier=V7L2LD4Q9S
    Runtime Version=10.14.0
    Sealed Resources version=2 rules=13 files=1
    Internal requirements count=1 size=184
    
  7. zip: ditto -ck --rsrc --sequesterRsrc "<appName>.app" "<appName>.zip"

  8. Upload for notarization:
    xcrun altool --notarize-app -t osx -f <appName>.zip --primary-bundle-id="com.<my company name>.<appName>" -u "<develpper ID @ company name> -p "<app specific password>"

  9. Receive an email that "Your Mac software was successfully notarized."

  10. Staple: xcrun stapler staple "<appName>.app"
    Returns: The staple and validate action worked!

  11. Distribute:

    1. Create a blank sparse bundle disk image.
    2. Copy the appName.app bundle to it (along with a shortcut to /Applications).
    3. Convert to read only.
    4. Distribute to users.


If I look in Console.app - I can see in the logs that the app is being denied to open:

default 15:10:56.549137-0400    runningboardd   Invalidating assertion 294-139-3156 (target:executable<<appName>(501)>) from originator 139
default 15:11:01.061942-0400    runningboardd   [executable<<appName>(501)>:4893] Death sentinel fired!

There is nothing in the console with XprotectService as this SO answer suggests.


If I try to use spctl to check the signature, it fails:

spctl -v -a -t open --context context:primary-signature <appName>.app
Returns:

opal.app: rejected
source=Unnotarized Developer ID

spctl -a -t exec -vv <appName>.app produces
Returns:

<appName>.app: rejected
source=Unnotarized Developer ID
origin=Developer ID Application: <my company name> (<team ID>)
produces: invalid API object reference


However, if I use stapler to validate the app, it passes:
stapler validate <appName>.app
Returns:

Processing: /<build dir>/<appName>.app
The validate action worked!


About my environment:

MacOS 10.15.3
Qt 5.13
Xcode 11.4


Solution

  • The problem is that since you are distributing using a .dmg you need to notarize the .dmg as well. Please follow these steps:

    1) Add your notarized and stapled app to the .dmg.

    2) Notarize your .dmg file.

    3) Staple the notarization to the .dmg file.

    You can use the following bash script I created which allows you to create a notarization request, check notarization request staus and staple notarization:

    #!/bin/bash
    
    usage()
    {
        # Display Help
    
       echo "*********************************************************************************************************************************************************"
       echo "  MacOS Application Notarization Script"
       echo "*********************************************************************************************************************************************************"
       echo
       echo "   Requirements"
       echo "    - XCode Installed"
       echo "    - Apple Id Account app-specific Password (https://support.apple.com/en-us/HT204397)"
       echo "    - Apple Developer ID Application Certificate created and installed in keychain (https://developer.apple.com/support/developer-id/)"
       echo
       echo "   Instructions"
       echo "    1. Run notarize option to code sign application and create notarization request"
       echo "    2. Run check option with the request UUID of the previous step to check the notarization status"
       echo "    3. Run staple option only if the notarization status was successful and package was approved"
       echo "    4. You are now ready to distribute, if you want to create an installer you can use this option https://github.com/sindresorhus/create-dmg."
       echo "       Note that if you distribute your app in a .dmg, follow these steps:"
       echo
       echo "      - Add your notarized and stapled app to the DMG."
       echo "      - Notarize your .dmg file."
       echo "             Example: sh $0 --notarize -a MyApp.dmg  -b com.company.myapp  -u myappleaccount@gmail.com -p aaaa-aaaa-aaaa-aaa -v FFFFFFFF)"
       echo "      - Staple the notarization to the .dmg file: xcrun stapler staple MyApp.dmg."
       echo "             Example: sh $0 --staple --file MyApp.dmg"
       echo "________________________________________________________________________________________________________________________________________________________"
       echo
       echo "  Usage"
       echo "   $0 [-n|s|c] [ -a APP_NAME ] [ -i SIGNING_IDENTITY ] [ -e ENTITLEMENTS ]  [ -b BUNDLE_ID ] [ -u USERNAME ] [ -p PASSWORD ] [ -v PROVIDER ] [ -k UUID ]"
       echo
       echo "________________________________________________________________________________________________________________________________________________________"
       echo
       echo "  Options:"
       echo
       notarizeHelp
       checkHelp
       stapleHelp
    
      return
    }
    notarizeHelp()
    {
       echo "    ======================================================================="
       echo "    -n | --notarize  Notarize file"
       echo "    ======================================================================="
       echo "    Syntax:"
       echo "              [ -n | --notarize ] [ -a | --file APP_NAME ] [ -i SIGNING_IDENTITY ] [ -e ENTITLEMENTS ]  [ -b BUNDLE_ID ] [ -u USERNAME ] [ -p PASSWORD ] [ -v PROVIDER ]"
       echo "    Parameters:"
       echo "              [ -a | --file  ]        - File name"
       echo "              [ -i ]                  - Apple Signing identity"
       echo "              [ -e ]                  - Application entitlements file"
       echo "              [ -b ]                  - Application Bundle identifier"
       echo "              [ -u ]                  - Apple Developer ID Username"
       echo "              [ -p ]                  - Application Specific password"
       echo "              [ -v ]                  - Access Provider"
       echo "    Example:"
       echo "       .app   sh $0 --notarize -a MyApp.app  -b com.company.myapp  -u myappleaccount@gmail.com -p aaaa-aaaa-aaaa-aaa -v FFFFFFFF -e App.entitlements -i \"Developer ID Application: COMPANY\""
       echo "       .zip   sh $0 --notarize -a MyApp.app.zip  -b com.company.myapp  -u myappleaccount@gmail.com -p aaaa-aaaa-aaaa-aaa -v FFFFFFFF"
       echo "       .dmg   sh $0 --notarize -a MyApp.dmg  -b com.company.myapp  -u myappleaccount@gmail.com -p aaaa-aaaa-aaaa-aaa -v FFFFFFFF"
       echo
    }
    checkHelp()
    {
       echo "    ======================================================================="
       echo "    -c | --check     Check notarization status"
       echo "    ======================================================================="
       echo "    Syntax:"
       echo "              [ -c | --check ] [ -u USERNAME ] [ -p PASSWORD ] [ -k UUID ]"
       echo "    Parameters:"
       echo "              [ -u ]                  - Apple Developer ID Username"
       echo "              [ -p ]                  - Application Specific password"
       echo "              [ -k ]                  - Notarization Request UUID"
       echo "    Example:"
       echo "              sh $0 --check  -u myappleaccount@gmail.com -p aaaa-aaaa-aaaa-aaa -k ffff-ffffff-ffffff-ffffffffff"
       echo
    }
    stapleHelp()
    {
       echo "    ======================================================================="
       echo "    -s | --staple    Staple file"
       echo "    ======================================================================="
       echo "    Syntax:"
       echo "              [ -s | --staple ] [ -a | --file APP_NAME ]"
       echo "    Parameters:"
       echo "              [ -a | --file  ]        - File name"
       echo "    Example:"
       echo "              sh $0 --staple --file MyApp.app"
       echo
    }
    
    #Help Dictionary 
    helpFunction()
    {
    
       echo ""
       usage
       exit 1 
    }
    
    # Transform long options to short ones
    for arg in "$@"; do
      shift
      case "$arg" in
        "--notarize") set -- "$@" "-n" ;;
        "--staple")   set -- "$@" "-s" ;;
        "--check")    set -- "$@" "-c" ;;
        "--file")     set -- "$@" "-a" ;;
        *)            set -- "$@" "$arg"
      esac
    done
    
    
    while getopts "nsca:i:e:b:v:u:k:p:" option
    do
    case "${option}"
    in
    n) ACTION=NOTARIZE;;
    s) ACTION=STAPLE;;
    c) ACTION=CHECK;;
    a) APP_NAME=${OPTARG};;
    i) SIGNING_IDENTITY=${OPTARG};;
    e) ENTITLEMENTS=${OPTARG};;
    b) BUNDLE_ID=${OPTARG};;
    p) PASSWORD=${OPTARG};;
    v) PROVIDER=${OPTARG};;
    u) USERNAME=${OPTARG};;
    k) UUID=${OPTARG};;
    ?) helpFunction ;;
    esac
    done
    
    
    do_check()
    {
        echo "$UUID"
        if [ -z "${UUID}" ]; then
            echo "[Error] Didn't specify notarization request UUID";
        fi
    
        if [ -z "${USERNAME}" ]; then
            echo "[Error] Apple ID username is required";
        fi
    
        if [ -z "${PASSWORD}" ]; then
            echo "[Error] App Specific password is required";
        fi
    
        if [ -z "${UUID}" ] || [ -z "${USERNAME}" ] || [ -z "${PASSWORD}" ]; then
            echo
            checkHelp
            exit 1 
        fi
        echo "[INFO] Checking Notarization status for $UUID"
        xcrun altool --notarization-info "$UUID" -u "$USERNAME" -p "$PASSWORD" --output-format xml
    
        exit 1
    }
    
    
    
    sign()
    {
        if [ -z "${APP_NAME}" ]; then
            echo "[Error] Didn't specify a filename";
        fi
        if [ -z "${SIGNING_IDENTITY}" ]; then
            echo "[Error] Didn't specify signing identity";
        fi
        if [ -z "${ENTITLEMENTS}" ]; then
            echo "[Error] Didn't specify entitlements file";
        fi
        if [ -z "${BUNDLE_ID}" ]; then
            echo "[Error] Didn't specify bundle identifier";
        fi
        if [ -z "${PROVIDER}" ]; then
            echo "[Error] Didn't specify access provider";
        fi
        if [ -z "${USERNAME}" ]; then
            echo "[Error] Apple ID username is required";
        fi
        if [ -z "${PASSWORD}" ]; then
            echo "[Error] App Specific password is required";
        fi
    
        if  [ -z "${APP_NAME}" ] || [ -z "${USERNAME}" ] || [ -z "${PASSWORD}" ] || [ -z "${PROVIDER}" ]  || [ -z "${ENTITLEMENTS}" ] || [ -z "${SIGNING_IDENTITY}" ]; then
            echo
            notarizeHelp
            exit 1 
        fi
    
        echo "[INFO] Signing app contents"
        find "$APP_NAME/Contents"|while read fname; do
            if [[ -f $fname ]]; then
                echo "[INFO] Signing $fname"
                codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" $fname
            fi
        done
    
        echo "[INFO] Signing app file"
    
    
        codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$APP_NAME"
    
        echo "[INFO] Verifying Code Sign"
    
        codesign --verify --verbose "$APP_NAME"
    
        echo "[INFO] Zipping $APP_NAME to ${APP_NAME}.zip"
    
        ditto -c -k --rsrc --keepParent "$APP_NAME" "${APP_NAME}.zip"
    
        #echo "[INFO] Uploading $APP_NAME for notarization"
    
        #xcrun altool --notarize-app -t osx -f "${APP_NAME}.zip" --primary-bundle-id "$BUNDLE_ID" -u "$USERNAME" -p "$PASSWORD" --asc-provider "$PROVIDER"  --output-format xml
    
        notarizationUpload "${APP_NAME}.zip"
    }
    
    notarize()
    {
    
        if [ -z "${APP_NAME}" ]; then
            echo "[Error] Didn't specify a filename";
        fi
        if [ -z "${BUNDLE_ID}" ]; then
            echo "[Error] Didn't specify bundle identifier";
        fi
        if [ -z "${PROVIDER}" ]; then
            echo "[Error] Didn't specify access provider";
        fi
        if [ -z "${USERNAME}" ]; then
            echo "[Error] Apple ID username is required";
        fi
        if [ -z "${PASSWORD}" ]; then
            echo "[Error] App Specific password is required";
        fi
    
        if  [ -z "${APP_NAME}" ] || [ -z "${USERNAME}" ] || [ -z "${BUNDLE_ID}" ] || [ -z "${PASSWORD}" ] || [ -z "${PROVIDER}" ]; then
            echo
            notarizeHelp
            exit 1 
        fi
    
    
    case "$APP_NAME" in
        *.app)  sign;;
        *.zip)  notarizationUpload "$APP_NAME";;
        *.dmg)  notarizationUpload "$APP_NAME";;
    esac
    
    
    }
    
    notarizationUpload()
    {
        echo "[INFO] Uploading $APP_NAME for notarization"
        xcrun altool --notarize-app -t osx -f "$1" --primary-bundle-id "$BUNDLE_ID" -u "$USERNAME" -p "$PASSWORD" --asc-provider "$PROVIDER"  --output-format xml
    
    }
    
    do_staple()
    {
    
        if [ -z "${APP_NAME}" ]; then
            echo "[Error] Didn't specify a filename";
            echo
            stapleHelp
            exit 1 
        fi
    
        echo "[INFO] Stapling $APP_NAME"
        xcrun stapler staple "$APP_NAME"
        echo "[INFO] Validating Staple for $APP_NAME"
        xcrun stapler validate "$APP_NAME"
    }
    
    
    
    #Excute Action base on the option  -s -n -c
    case $ACTION in
    STAPLE) do_staple;;
    CHECK) do_check;;
    NOTARIZE) notarize;;
    *) helpFunction;
    esac
    
    
    
    unset APP_NAME ACTION SIGNING_IDENTITY BUNDLE_ID ENTITLEMENTS USERNAME PASSWORD PROVIDER UUID