Notarize existing Java application for MacOS Catalina

Edit 12/2/2020 - there have been a lot of changes because Apple have slowly tightened the requirements for notarization. From Feb 3rd they seem to have hit the final stage, which means your app has to meet much higher requirements, including a JRE which is built against the latest SDK and with "hardened runtime" support.

So I've stripped much of the old discussion.

My first issue was setting up - you need an active Developer Programme account with Apple ID (which is easy) but then, when you follow the instructions to add a password to the keychain, use the App specific password. You also need to enable two factor auth for your Apple ID account.

Once you work out the command line calls it's pretty easy to automate in a build script. I have used jpackage to create the app and the DMG but beware - currently its approach to signing the app does not work.

In terms of scripting, here's what I'm doing to code sign the app suitable for notarization (this assumes a .app is already created):

% security unlock-keychain -p passwordhere codesigning.keychain
% find my-app.app -type f \
  -not -path "*/Contents/runtime/*" \
  -not -path "*/Contents/MacOS/my-app" \
  -not -path "*libapplauncher.dylib" \
  -exec codesign --timestamp --entitlements /tmp/bliss.entitlements -s "XXX" --prefix com.myapp. --options runtime -v --keychain /path/to/codesigning.keychain {} \;

% find my-app.app/Contents/runtime -type f \
  -not -path "*/legal/*" \
  -not -path "*/man/*" \
  -exec codesign -f --timestamp --entitlements /tmp/bliss.entitlements -s "XXX" --prefix com.myapp. --options runtime -v --keychain /path/to/codesigning.keychain {} \;

% codesign -f --timestamp --entitlements /tmp/bliss.entitlements -s "XXX" --prefix com.myapp. --options runtime -v --keychain /path/to/codesigning.keychain my-app.app/Contents/runtime

% codesign -f --timestamp --entitlements /tmp/bliss.entitlements -s "XXX" --prefix com.myapp. --options runtime -v --keychain /path/to/codesigning.keychain my-app.app

The entitlements should be:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-executable-page-protection</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
</dict>
</plist>

All tests work:

% codesign -vvv --deep --strict my-app.app/Contents/runtime 
my-app.app/Contents/runtime: valid on disk
my-app.app/Contents/runtime: satisfies its Designated Requirement
% codesign -vvv --deep --strict my-app.app/                
--prepared:/private/tmp/my-app.app/Contents/MacOS/libapplauncher.dylib
--validated:/private/tmp/my-app.app/Contents/MacOS/libapplauncher.dylib
my-app.app/: valid on disk
my-app.app/: satisfies its Designated Requirement
% spctl -a -t exec -vv my-app.app          
my-app.app: accepted
source=Developer ID
origin=XXX

At this point you should also try running your app - the code signing process can break things.

From here, you can create a DMG (again, I use jpackage) and this should pass notarization.

In summary:

  1. Build the app in the correct structure
  2. Create an entitlements file
  3. Code sign your code
  4. Code sign the files inside bundled runtime, forcing the signature
  5. Code sign the bundled runtime itself
  6. Code sign your app file
  7. Package into a DMG
  8. Notarize it
  9. Ship it

  • AFAIK, you need Java 11 (see JDK-8223671), however, recently I was told that Java 8 may also work. I haven't tried this.

  • JDK-8223671 contains some useful information. Specifically, you need to add entitlements to your code sign call:

codesign --entitlements java.entitlements --options runtime --deep -vvv -f --sign "Developer ID Application: Bla Bla (XXXX)" YourApp.app

A working sample java.entitlements file could look like this:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 
<plist version="1.0"> 
<dict> 
    <key>com.apple.security.cs.allow-jit</key> 
    <true/> 
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key> 
    <true/> 
    <key>com.apple.security.cs.disable-executable-page-protection</key> 
    <true/> 
    <key>com.apple.security.cs.disable-library-validation</key> 
    <true/> 
    <key>com.apple.security.cs.allow-dyld-environment-variables</key> 
    <true/> 
</dict> 
</plist> 
  • Bundling a jlink generated runtime is a pain, because it contains sym links (which aren't allowed during signing) and also a legal folder that contains folder names like java.xml (with a .). codesign is unfortunately a little dumb and believes such folders are unrecognized bundles and aborts. Therefore you should rename those files/folders and resolve any sim links before jlinking.

  • If you use jlink, make sure you add needed service providers, e.g., jdk.crypto.ec for HTTPS. Also note that AFAIK, in Java 11 TLSv1.3 is at least partially broken (upload of large files) and you should disable it, e.g. with -Dhttps.protocols=TLSv1.1,TLSv1.2.

  • If you use the AppBundler fork, you will need to make sure it also adheres to Apple's guidelines, i.e., is linked against macOS 10.9. I believe by default AppBundler links against 10.7, but changing it is simple.

  • If you are using Java 11 or later, make sure that you bundle libjli.dylib in /Contents/PlugIns/JAVA_PLUGIN_NAME/Contents/Home/lib/jli/libjli.dylib. Apparently that's needed by the launcher and may not be bundled by default.

  • Some of your other questions are answered in Apple's guidelines:

Notarization requires Xcode 10 or later. Building a new app for notarization requires macOS 10.13.6 or later. Stapling an app requires macOS 10.12 or later.