'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 1:[1]

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 [email protected] -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 [email protected] -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 [email protected] -p aaaa-aaaa-aaaa-aaa -v FFFFFFFF"
   echo "       .dmg   sh $0 --notarize -a MyApp.dmg  -b com.company.myapp  -u [email protected] -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 [email protected] -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

Solution 2:[2]

I found this tutorial which explains all in detail: https://blog.pakkly.com/how-to-sign-qt-apps-on-mac-for-release/

Its quite brief and simple. Following this step by step, i was able to create a notarized DMG file.

Some more hints:

  1. The certificate used can not be used on a different machine, even if both use the same apple ID account.
  2. When building the DMG with a jenkins job i was stopped with the following (obviously well known) message:

codesign --deep --timestamp --options runtime -s xxxx my.app

my.app: errSecInternalComponent

This can easily be solved by adding the following line before:

security unlock-keychain -p “<pwd>”

(in my case the certificate was in the login section)

  1. Also note this very important post: Error when trying to obtain a certificate: The specified item could not be found in the keychain

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rendy Del Rosario
Solution 2 RED SOFT ADAIR