How to Notarize a Mac Application with Liberica JDK using javapackager

How to Notarize a Mac Application with Liberica JDK using javapackager

How to Notarize a Mac Application with Liberica JDK using javapackager


June 10, 2020


One of the main global trends in IT is the safety of users and preventing the spread of malicious programs, but at the same time, it greatly complicates the life of developers. There are different approaches to solving the issue associated with software security. The most common is the use of an antivirus tool by scanning a computer for malware. A complementary approach was taken by Apple, which introduced the Gatekeeper software to force code signing and check downloaded applications before allowing them to run.

The trend is moving towards deanonymization, the violation of anonymity when customers of compromised software have their personal data leaked. The need for notarization arises as a counter to this negative trend by giving users the confidence that a software package was created by an identified developer and checked for malware.

Regarding security, an example is Windows Defender, which applies a signing code for verifying the compliance of a program to security requirements.

Websites use SSL (Security Sockets Layer) certificates for the purpose of ensuring privacy, authentication, and data integrity in online communications. Apple, on the other hand, introduced notarization infrastructure.

Regardless of who is notarizing software, the fact remains that it is an essential, albeit burdensome element of software development imposed by Apple as a requirement now. But there are ways to simplify and even ameliorate the process of notarization for applications in Java and JavaFX that are provided by distributors like Liberica JDK, thus making it a routine procedure, rather than an unpleasant allocation of time and resources. The main advantage of resorting to Liberica JDK is ensuring that applications bundled with the runtime as a single package will be able to pass the notarization procedure smoother and with greater ease.

What is notarization? Why is it necessary?

Notarization gives users more confidence that the Developer ID-signed software being distributed has been checked by Apple for malicious components. Notarization is not an App Review service, as the Apple notary service is an automated system that scans software for malicious content, checks for code-signing issues, and returns the results to the user quickly. If there are no issues, the notary service generates a ticket for the user to staple to their software. The notary service also publishes that ticket online where Gatekeeper can find it. When the user first installs or runs any new software, the presence of a ticket, either online or attached to the executable file, tells Gatekeeper that Apple notarized the software. Gatekeeper then places descriptive information in the initial launch dialog to help the user make an informed choice about whether to launch the app. Now, since the procedure has become mandatory beginning with MacOS Catalina 10.15, it is impossible to launch a non-notarized application.

Liberica JDK is a notarized product. What will that give its customers and users?

Liberica JDK is an alternative to Java and acts as the basis for application development and launching Java SE applications. As an officially recognized and certified application, Liberica JDK has gone through all the steps of notarization and provides full notarization compliance of its software. Liberica JDK binaries have already been notarized for a year since last summer. OpenJDK notarization is not an obvious process and requires a deep signing of all binaries and modules. And Liberica successfully passed the notarization procedure. In order to create an application and have it notarized, users need to refer to already notarized software. This is why using Liberica JDK is one of the easiest and most straightforward means of developing applications that will be recognized and notarized by the Apple notary service. By referring to the notarized Liberica JDK package as an alternative to Java, application developers will be able to receive a shortcut in having their software verified in the future. It is quite easy with Liberica JDK having a special build that facilitates the delivery of app bundles.

The Javapackager element

An important element of the application notarization procedure and an integral part of the Liberica JDK package. The Java Packager tool already comes with the JDK and allows packaging Java applications from the command line, thus being an alternative to other third-party tools. It is important to note that the Java Packager does not automatically produce a Jar file. Many other packager formats are available, including native executable ones tailored for various platforms. Liberica JDK’s Javapackager is ultimately applied during the build process, to create applications that would get easily notarized. Alternatively, it may be used as a standalone tool.

The javapackager tool gives us several options:

  • Create a macOS application image (for testing purposes)
  • Create a notarized DMG image (suitable for distribution to end users)
  • Create a notarized PKG installer (suitable for distribution to end users)

In this article, we will demonstrate how to perform all three operations.

What if you need to notarize your Java or JavaFX application? A guide

When new software is being developed and notarized, the developer will have to pass several stages for having their software comply with the requirements set by Apple. The procedure applies to all applications written in Java/JavaFX or the Liberica suite.

If you want to make an app using Liberica JDK and Java Packager, you should walk through several stages: create a jar file containing your application, generate a bundle and notarize it.

For the notarization to be successful, it is necessary to get a special build of Liberica JDK 8u252 for MacOS, which allows creating a signed application that will be notarized. In this example, it is called bellsoft-jdk8u252+9-macos-amd64-full-nosign.zip. We use a special unsigned Liberica JDK 8u252 because the javapackager cannot re-sign already signed JDK files.

Extract the unsigned Liberica JDK 8u252 bundle:

    unzip ./bellsoft-jdk8u252+9-macos-amd64-full-nosign.zip

Also, before creating notarized bundles, do not forget to unlock the developer key:

    security unlock-keychain -p "${YOUR_PASSWORD}"

We will next create installer bundles and illustrate this step with the Recaf app.

Building an application JAR file

Recaf version 2.0.0 will serve as an example since it has a better architecture to work as a standalone macOS application. First, we should build a Recaf jar file, create an application bundle and notarize it. The notary service verifies the app’s content; it goes through a jar file and checks that all native libraries in an executable file are signed. So, our job here is to create a Recaf file, check if it has a native part and sign it.

Clone Recaf to your workspace:

    git clone https://github.com/Col-E/Recaf.git

Check out the Recaf 2 branch:

    git checkout 2.0.0-redesign.10

Compile and package it using the build script or with

    mvn clean package

If your build is successful, the resulting jar file will be placed in the target directory and called recaf-2.0.0-J8-jar-with-dependencies.jar.

Java Packager takes the application main class as one of its inputs. If the application’s main class name is unknown, you can easily find it out:

  1. Type in the following command to extract the manifest from the jar file:

         ./jdk8u252.jdk-full-nosign/Contents/Home/bin/jar \
             xf ./recaf-2.0.0-J8-jar-with-dependencies.jar \
             META-INF/MANIFEST.MF
    
  2. Inspect the extracted MANIFEST.MF to find the main class:

         grep Main-Class META-INF/MANIFEST.MF
    

The output with the main class name for Recaf is:

    Main-Class: me.coley.recaf.Recaf

Signing native libraries within the application

Without signing all native libraries within an application, it will not get notarized. If your app has no native libraries, skip this step. Otherwise, you should expect a notarization error: The Apple notary service will respond with an error message indicating the presence of an unsigned library.

Our sample application, Recaf, does contain a native library libjnidispatch.jnilib inside its jar file. This means we need to sign the native library file in Recaf’s jar for further notarization. Follow these steps:

  1. Extract the library from the jar file:

         ./jdk8u252.jdk-full-nosign/Contents/Home/bin/jar \
             xf ./recaf-2.0.0-J8-jar-with-dependencies.jar \
             com/sun/jna/darwin/libjnidispatch.jnilib
    
  2. Unlock the key chain:

         security unlock-keychain -p ${YOUR_PASSWORD}
    
  3. Create an entitlements file for signing the library (we will use the same entitlements as is used in Liberica JDK, see below).

  4. Sign the library:

         codesign -o runtime -s "${IDENTITY}" \
             --prefix me.coley.recaf. \
             --entitlements ./entitlements \
             -vvvv ./com/sun/jna/darwin/libjnidispatch.jnilib
    
  5. Add the signed library to Recaf`s jar file:

         ./jdk8u252.jdk-full-nosign/Contents/Home/bin/jar \
             uf recaf-2.0.0-J8-jar-with-dependencies.jar \
             com/sun/jna/darwin/libjnidispatch.jnilib
    

Now you can use the Recaf jar to create and notarize your macOS application with bundled Liberica JDK.

Creating a Mac application image with bundled Liberica JDK

Liberica JDK's javapackager signs the application, adds the necessary entitlements, secures timestamp and the hardened runtime while creating native macOS images.

Should you need help on the options, type in the inline help command:

    ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager \
        -help <Native image type - mac.app, pkg etc.> \
        <-verbose>

Here are all the bundler parameters for Mac Application Image (mac.app):

    name - App Name - String
    arguments - Command Line Arguments - List
    mac.bundle-id-signing-prefix - Bundle Signing Prefix - String
    classpath - Main Jar Classpath - String
    mac.signing-key-developer-id-app - Apple Developer ID Application Signing Key - String
    icon.icns - .icns Icon - File
    jvmOptions - JVM Options - List
    jvmProperties - JVM System Properties - Map
    mac.category - Category - String
    mac.CFBundleIdentifier - CFBundleIdentifier - String
    mac.CFBundleName - CFBundleName - String
    mac.CFBundleVersion - CFBundleVersion - String
    runtime - JRE - RelativeFileSet
    applicationClass - Main Class - String
    mainJar - Main Jar - RelativeFileSet
    preferencesID - Preferences ID - String
    userJvmOptions - User JVM Options - Map
    appVersion - Version – String

Next step is to create a Mac native application image with the following command:

    ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager -deploy \
        -native image \
        -name Recaf \
        -outdir res_image \
        -srcfiles ./recaf-2.0.0-J8-jar-with-dependencies.jar \
        -outfile recaf \
        -appclass me.coley.recaf.Recaf \
        -Bruntime=./jdk8u252.jdk-full-nosign/Contents/Home/ \
        -BappVersion=2.0.0-liber

Besides, you may pass your entitlements file using the -Bmac.entitlements=<entitlements file path> option. If the entitlement file is not specified, the following entitlements are used: take the following ones:

    <?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>

The resulting image will be created in the res_image/bundles/Recaf.app directory.

You can ensure that your application is signed and has the necessary properties/options with the command:

    codesign -dv --entitlements :- ./res_image/bundles/Recaf.app/

It should produce the following output:

    Executable=***/res_image/bundles/Recaf.app/Contents/MacOS/Recaf
    Identifier=me.coley.recaf
    Format=app bundle with Mach-O thin (x86_64)
    CodeDirectory v=20500 size=378 flags=0x10000(runtime) hashes=3+5 location=embedded
    Signature size=8998
    Timestamp=***********************
    Info.plist entries=15
    TeamIdentifier=8LBATW8FZA
    Runtime Version=10.14.0
    Sealed Resources version=2 rules=13 files=5
    Internal requirements count=1 size=176
    <?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>

Given that application images are most suitable for testing and not typically used directly in distribution, we will now focus on creating DMG or PKG bundles tailored for end users.

Creating a DMG bundle

Choosing between DMGs and PKGs depends on your preference. While these two are perfect for end-user distribution, they possess slightly different qualities. Some developers and vendors ship software in both formats, or go for wrapping a PKG inside a DMG. DMG is effectively a disk image the end user can click on and install an application in the same way as when inserting a CD.

Issue the following command to create a DMG image with javapackager:

    ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager -deploy \
        -native dmg \
        -name Recaf \
        -outdir res_dmg \
        -srcfiles ./recaf-2.0.0-J8-jar-with-dependencies.jar \
        -outfile recaf \
        -appclass me.coley.recaf.Recaf \
        -Bruntime=./jdk8u252.jdk-full-nosign/Contents/Home/ \
        -BappVersion=2.0.0-liber

The ready DMG image will be placed in ./res_dmg/bundles/Recaf-2.0.0-liber.dmg.

Then submit the newly made DMG bundle to Apple for a standard procedure and get it notarized. Apple provides a set of tools for notarizing bundles. The two important ones are altool (to interact with the notary service) and stapler (to work with notarization results and staple the bundle afterward).

Send the DMG bundle to the Apple notary service by typing in the command:

    xcrun altool --notarize-app \
        -primary-bundle-id com.bell-sw.recaf.0 \
        -username ${USERNAME} \
        -password '${YOUR_PASSWORD}' \
        -file ./res_dmg/bundles/Recaf-2.0.0-liber.dmg

You should get the response:

    2020-04-27 08:43:58.152 altool[47259:19144203] No errors uploading 'res_dmg/bundles/Recaf-2.0.0-liber.dmg'.
    RequestUUID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

At this point, you need to wait for a response from the notary service and check the notarization request status occasionally using the provided request UUID:

    xcrun altool --notarization-info  ${RequestUUID} \
        -u ${USERNAME} \
        -p '${YOUR_PASSWORD}'

Please note that you will have to wait a while for the notarization status. If requested right away, instead of the status, an error will show up saying the request UUID is not found. This is what you will see when the service processes your bundle:

*********************** altool[47696:19145453] No errors getting notarization info.
   RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
          Date: ***
    Status: in progress
     LogFileURL: (null)

Once the bundle is verified, you will get the ‘success’ status, and the package becomes approved:

*********************** altool[88539:21766482] No errors getting notarization info.
 
   RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
          Date: ***
    Status: success
     LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/*********
   Status Code: 0
 
Status Message: Package Approved

By following the LogFileURL link, you will get a formatted JSON response:

    {
        "logFormatVersion": 1,
        "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "status": "Accepted",
        "statusSummary": "Ready for distribution",
        "statusCode": 0,
        "archiveFilename": "Recaf-2.0.0-liber.dmg",
        "uploadDate": "********************",
        "sha256": "3d742fbfbbf6252a6d35eb23114b387934dac1c0be423b357c0b257026cd1fba",
        "ticketContents": [
            {
                "path": "Recaf-2.0.0-liber.dmg",
                "digestAlgorithm": "SHA-256",
                "cdhash": "56e2fa7437a638721fed896a24880607ffca619d"
            },
            {
                "path": "Recaf-2.0.0-liber.dmg/Recaf.app",
                "digestAlgorithm": "SHA-256",
                "cdhash": "8739a51515aded06148e434b735ddf7147d4821a",
                "arch": "x86_64"
            },
            ...
        ],
        "issues": null
    }

Now that the Apple notary service has approved the bundle and generated a special ticket, staple the bundle with the command:

    xcrun stapler staple ./res_dmg/bundles/Recaf-2.0.0-liber.dmg

The output of the ticket stapling command should look like:

    Processing: *****/res_dmg/bundles/Recaf-2.0.0-liber.dmg
    Processing: *****/res_dmg/bundles/Recaf-2.0.0-liber.dmg
    The staple and validate actions worked!

Validating the DMG bundle

You can also validate your bundle separately with the command:

    xcrun stapler validate ./res_dmg/bundles/Recaf-2.0.0-liber.dmg

This should produce the following output:

    Processing: *****/res_dmg/bundles/Recaf-2.0.0-liber.dmg
    The validate action worked!

image

If the validation is successful, you should be able to install the application!

Creating a PKG bundle

As we said before, it is required to choose either a DMG or PKG file to create at this stage. You may opt for PKG as this installer format provides sophisticated means to build an application installation experience.

The instructions for making a notarized PKG file using the javapackager are very similar to those for the DMG one.

Type in this command to generate a PKG bundle for Recaf:

    ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager -deploy \
        -native pkg \
        -name Recaf \
        -outdir res_pkg \
        -srcfiles ./recaf-2.0.0-J8-jar-with-dependencies.jar \
        -outfile recaf \
        -appclass me.coley.recaf.Recaf \
        -Bruntime=./jdk8u252.jdk-full-nosign/Contents/Home/ \
        -BappVersion=2.0.0-liber

Then you should notarize it by sending to the Apple notary service:

    xcrun altool --notarize-app \
        -primary-bundle-id com.bell-sw.recaf.0 \
        -username ${USERNAME} \
        -password '${YOUR_PASSWORD}' \
        --file ./res_pkg/bundles/Recaf-2.0.0-liber.pkg

The output will look like this:

    *********************** altool[30652:19480464] No errors uploading 'res_pkg/bundles/Recaf-2.0.0-liber.pkg'.
    RequestUUID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Wait a little and check the notarization results:

    xcrun altool --notarization-info  ${RequestUUID} \
    -u ${USERNAME} \
    -p '${YOUR_PASSWORD}'

Please note that it may take some time for the Apple notary service to process the request.

When the notarization is complete, this command will return:

*********************** altool[30708:19481150] No errors getting notarization info.
   RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
          Date: ***********************
    Status: success
     LogFileURL: https://osxapps-ssl.itunes.apple.com/*****
   Status Code: 0
 
Status Message: Package Approved

You may follow the LogFileURL link to see the notarization details for your bundle:

    {
        "logFormatVersion": 1,
        "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "status": "Accepted",
        "statusSummary": "Ready for distribution",
        "statusCode": 0,
        "archiveFilename": "Recaf-2.0.0-liber.pkg",
        "uploadDate": "***********************",
        "sha256": "cf6480a8feb54236a9e0a2894a85c130b373a173bc270e661f111e2df4b65e4c",
        "ticketContents": [
            {
                "path": "Recaf-2.0.0-liber.pkg",
                "digestAlgorithm": "SHA-1",
                "cdhash": "22d41fe89a8af1bcd7e594eab94dce942f822874"
            },
            {
                "path": "Recaf-2.0.0-liber.pkg/Recaf-app.pkg Contents/Payload/Applications/Recaf.app/Contents/MacOS/libpackager.dylib",
                "digestAlgorithm": "SHA-256",
                "cdhash": "81ec29cd1ed5c30b1ac0f9aca785e8389d3b0c16",
                "arch": "x86_64"
            },
            ...
        ],
        "issues": null
    }

Having done that, staple the generated ticket to the application:

    xcrun stapler staple ./res_pkg/bundles/Recaf-2.0.0-liber.pkg

This command will produce the following output:

    Processing: ***/res_pkg/bundles/Recaf-2.0.0-liber.pkg
    Processing: ***/res_pkg/bundles/Recaf-2.0.0-liber.pkg
    The staple and validate actions worked!

Validating the PKG bundle

You can check the signature in the PKG file by clicking the lock icon in the top right corner. The popped-up dialog informs of all its details.

image

Conclusion

Notarizing an application with the Liberica JDK package and the Javapackager is a straightforward and easy process. Remember to follow the steps thoroughly and use an unsigned copy of Liberica JDK. Just contact us if you need it, and our support team will gladly help you. The resulting application will be notarized and have the necessary credentials to comply with the requirements set by Apple.

Author image

Aleksei Voitylov

BellSoft CTO

BellSoft LTD [email protected] BellSoft LTD logo Liberica Committed to Freedom 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 111 North Market Street, Suite 300 CA 95113 San Jose US +1 702 213-59-59