sveinbjornt / Platypus

Create native Mac applications from command line scripts.
https://sveinbjorn.org/platypus
BSD 3-Clause "New" or "Revised" License
2.8k stars 170 forks source link

Notarization #169

Open hohno-panopto opened 4 years ago

hohno-panopto commented 4 years ago

This is not an issue, but I wanted to share my experience with Platypus users.

I was able to get Apple's notarization to my application which was generated by Platypus. As the result, I was able to put my app on the public website and let people download and run. Without notarization, macOS 10.15 rejects the execution of downloaded app and there is no way to override it.

I referred this article as a starting point. Thank you, @dpid

Prerequisite

Steps

All of them are done from command line (Terminal). Please read this document for more details about notarization from command line tools.

Let's assume your app is generated as ./Example.app and bundle name is com.example.app. Also assume the name of Developer ID Application certificate as "Developer ID Application: Example Company (A1BC234DEF)".

  1. Sign your application.

    chmod -R a+xr ./Example.app
    codesign --deep --force --verify --verbose --timestamp --options runtime --sign "Developer ID Application: Example Company (A1BC234DEF)" ./Example.app
  2. Create archive ZIP for notarization. This is different from the final product.

    ditto -c -k --keepParent ./Example.app ./Example.archive.zip
  3. Submitting notarization. You might need --asc-provider option if your Apple ID is associated to two or more teams.

    xcrun altool --notarize-app -f ./Example.archive.zip --primary-bundle-id "com.example.app" -u apple-id@example.com -p PassWordForAppleId
  4. You should see RequestUUID as the result of previous step. Note that UUID. We assume 12345678-1234-5678-90ab-cdef12345678 here. Type the command periodically until the Status becomes 'success'.

    xcrun altool --notarization-info 12345678-1234-5678-90ab-cdef12345678 -u apple-id@example.com  -p PassWordForAppleId
  5. After notarization succeeds, staple the ticket to the package.

    xcrun stapler staple ./Example.app
  6. Zip the app as final product. This Example.zip may be put on the public web server, and people may download and run it.

    zip -r ./Example.zip ./Example.app

Notes

sveinbjornt commented 4 years ago

This is great, thank you very much.

JayBrown commented 4 years ago

I only have a free Apple Development certificate which is not suitable for distribution, but I still give my Platypus apps a valid code signature with my own third-party certificate.

In those Platypus apps meant for distribution, the script has a test for path randomization (app translocation).

You could also have your app open an info text file or an info webpage on your domain which then explains how to remove the quarantine extended attribute to the user, instead of just displaying a generic prompt & quitting.

uiprocess="app_name"
process="process_name"
mypath="$0"
myname=$(basename "$mypath")
[[ $myname == "script" ]] && myname="$process"  
scr_parent=$(dirname "$mypath") # Resources (in Contents)

_quit-app () {
    printf "QUITAPP\n"
}

_error () {
    afplay "$scr_parent/error.aif" &>/dev/null
}

_syswarning () {
    _error &
    osascript &>/dev/null << EOT
display alert "$1" message "$2" as critical buttons {"Quit"} default button "Quit" giving up after 180
EOT
    _quit-app
    exit 1
}

if echo "$mypath" | grep -q "/AppTranslocation/" &>/dev/null ; then
    echo "ERROR[04]: application $uiprocess ($myname) has been translocated"
    _syswarning "Internal error [04]: AppTranslocation" "Please quit $uiprocess ($myname), dequarantine the app, and try again!"
fi
dipterix commented 3 years ago

This looks great. However, I got this issue when trying to validate the notarization

>$ spctl -vvv --assess --type exec "$TFILE"
lol.app: rejected (the code is valid but does not seem to be an app)
origin=Developer ID Application: XXXXXX

Looks like the Gatekeeper still fails the app even though it's properly signed?

I found this thread, not sure if it's relevant

JayBrown commented 3 years ago

For some reason, the correct command is spctl -vvv --assess --type install <filepath> or short-form spctl -vvv -a -t install <filepath>, even though an app is not an installer.

EDIT 1: you should also check the app and its nested code with codesign --verify --deep --strict --verbose=4 <filepath>

EDIT 2: and another check, only for the stapled notarization ticket: xcrun stapler validate <filepath>

dipterix commented 3 years ago

Uh, I see... Then it looks everything got passed. But my apps still cannot execute on other machines once I upload them to Github (dmg, zipped or not zipped).

Began to doubt my life as I only got problems but don't know where it went wrong...

JayBrown commented 3 years ago

That's weird. If your app is notarized, it should run just fine.

dipterix commented 3 years ago

Yup, that's what I was struggling about. I got green lights all the way to staple and verified each step, but still unable to download from another computer.

JayBrown commented 3 years ago

Three questions:

(1) Does the main script at ./Contents/Resources/script have the executable bit set? You can check with e.g. stat -x script. It should read something like rwxr-xr-x. You can also safely change those even after code-signing with the usual chmod +x script command. (I'm asking, because maybe some x-bits got flipped by Apple's notarization service.)

(2) Are you using any additional nested code or scripts besides ./Contents/MacOS/<Executable> and ./Contents/Resources/script? Other executables would probably need to be notarized/stapled too. (Not scripts, though.)

(3) EDIT: did you use macOS' built-in /bin/zsh as the script's interpreter in the shebang?

JayBrown commented 3 years ago

PS: maybe there's another problem… if you use additional code/scripts, it should be in one of the default directories assigned by Apple for signing nested code:

./Contents/Frameworks
./Contents/SharedFrameworks
./Contents/PlugIns
./Contents/Plug-ins
./Contents/XPCServices
./Contents/Helpers
./Contents/MacOS
./Contents/Library/Automator
./Contents/Library/Spotlight
./Contents/Library/LoginItems

…and maybe that's also causing the problem with the main script.

Many developers tend to nest code in irregular subpaths like ./Contents/Resources, which can lead to problems. I also asked the Platypus dev to change this, i.e. move script from ./Contents/Resources/script to e.g. ./Contents/Helpers/bin/script or ./Contents/MacOS/script.

So you could maybe try to move your script into one of the default directories, e.g. Helpers or MacOS, and then recreate ./Contents/Resources/script as a symbolic link to the new location. (Would need to be a relative path symlink, e.g. ../Helpers/script.)

lastlink commented 3 years ago

If you are not notarized, e.g. demoing w/out a cert the solution is the user must have admin access from https://apple.stackexchange.com/a/253943/222813.

running sudo xattr -r -d com.apple.quarantine /Applications/Some.app will unquarantine the app

JayBrown commented 3 years ago

You normally don't need sudo. If the app is a drag & drop installation, the installing user has ownership & control, at the very least in ~/Applications, so privilege escalation isn't necessary a lot of the time.

AdrienLF commented 3 years ago

Thank you all for the info, I followed it, however I have an issue when I try to sign the app. Here's what I'm doing: I have an .app created by pyinstaller that doesn't work on its own, I need a shell script to launch it.

So, with platypus, I create an .app from the shell script, and put the pyinstaller created .app in the ressources folder. This launches with no issues.

However, platypus converts my shell script to a file named "script" without extension, in the Resources folder. This causes codesign to give me this error:

"resource fork, Finder information, or similar detritus not allowed"

This is caused by that file that doesn't have an extension. If I remove the "script" file of the bundle, I can sign with no issue... but of course, the app then doesn't find the script, and crashes. Did anybody find a solution to this? I don't see how you can sign an app if platypus created a file with no extension.

Thank you

JayBrown commented 3 years ago

Always run xattr -cr /Applications/<yourPlatypus>.app before codesigning. And always codesign at the very end, after you have finished modifying the bundle. If you have another bundle nested within your Platypus app, you have to sign that first, and then the main bundle at the end.

Additionally, try not to use Finder when navigating the nested contents of a bundle including app bundles. Finder is a catastrophe in these scenarios.

PS: executing code should not be in ./Contents/Resources. Only non-Mach-O scripts go there. I don't know what kind of an executable the pyinstaller app has… if it's a script, it's fine in Resources… otherwise put it e.g. in ./Contents/MacOS or /Contents/Helpers

See e.g. here s.v. "Nested Code": https://developer.apple.com/library/archive/technotes/tn2206/_index.html

Mudasir441 commented 7 months ago

Thanks @hohno-panopto for this great share. The PassWordForAppleId should be App specific password I guess, as normal password of Apple ID didn't worked for me. Then I created App specific password at https://appleid.apple.com/account/home which worked.