Categories
Uncategorized

The pain of publishing Electron apps on macOS

This is the 2nd part of a 2-article series on publishing Electron apps on macOS. The 1st one, an ultimate guide on publishing desktop apps, is here.

Starting with Mojave 10.14, Apple has introduced serious changes in app publication. They are about signing and notarizing the apps. If your application is bad in the eyes of Apple, final users will see threatening messages upon the first launch. The messages would ask the users to delete the app, which is not really good for customers, right?

This is the 2nd part of the two-part article on signing and notarizing macOS Electron apps. You can find the 1st part here.

The author is an ElectronJS app developer himself, Around a year ago, faced the problem of having to notarize the app on Apple servers besides signing it. It means that you check whether the app has malware in it.

To notarize, you have to send the app, wait for 10 minutes tops, get your results, and be happy. For those who are not proficient in it, there is an article on our website. There we discuss basic concepts of publishing apps to macOS.

I began notarizing my apps and kept getting the Package Approved notification over and over and was pretty happy about it, until recently, when I noticed that the situation repeats itself. All right, I thought, let’s just notarize it once again. 

In the end, the status had over 9000 authorization errors. Invalid certificate for some files, absence of hardened runtime even an absence of executable library files from NPM.

Sounds scary, doesn’t it? That’s right, Apple is making their system tighter, more secure, and doesn’t allow publishing a threatening, from their PoV, application.

I started discovering solutions. In the end, I was able to solve the problems and understand the rules of the game with Apple deeper. Actually, there are solutions in the internet but they are spread too wide. I’ve spent a considerable amount of time amassing experience in the field and now am willing to share it with my fellow developers so that their Electron apps are notarized faster.

I faced these problems when the app was using the ElectronJS library 5.0.0. I used electron-packager 13.1.1 to build an app and exelctron-osx-sign 0.4.15 to sign it.

To sign an app, we have a certificate Developer ID Application: <TeamName> (<UniqueID>) on the Apple site. A certificate like this allows releasing apps through any online service. We use the release server Electron to do that. You are going to need another certificate to publish apps to Mac App Store.

So where’s the pain?

In errors. I’ve split all of them into subparts and showed the solution which will make you more successful in the matter. 

What Apple doesn’t like

The binary is not signed. Absence of certificates and timestamps for internal executable files

The notarization log from Apple for many internal app files had The binary is not signed error. Every executable file was a part of this, for instance, built FFmpeg and echopring-codegen libraries. which are used externally by the app. Some executable files from NPM libraries were there, too.

The solution here is simple. While signing apps, it’s important to sign all files that Apple doesn’t like with this certificate, not just the .app one.

If you use a codesign utility, it’s mandatory to list those files with a space. Use the electron-osx-sign library, activate binaries to create paths to binary files that need to be signed with this certificate.

It’s worth mentioning that this solution is also usable for The signature does not include a secure timestamp error. It’s because the certificate puts its own timestamp while signing a file.

The signature of the binary is invalid. Wrong routes in the general hierarchy of an Electron app

This error is, in my opinion. an Apple mistake. Whenever an .app file is sent to notarization, it automatically  is packed into a .zip archive. Because of that the hierarchy of some paths corrupts, thus the certificate becomes invalid for some files.

The solution is packing the file by yourself into the .zip archive, using the integrated utility ditto. It’s important to use a special flag – -keepParent, which allows saving the hierarchy of all paths during packing. So, this is the command I used for packing:

ditto -c -k --keepParent “<APP_NAME>.app” “<APP_NAME>.zip”

After this, you can send the archive to notarization.

I use electron-notarize for notarization. When the notarization is successful, the library tries to return an archive of the app, so that you unpack it and swap an .app file with a notarized .app file. However, please note that XCode spectool can’t work with archives.

The executable does not have the hardened runtime enabled.

Now let’s discuss the most difficult and interesting notarization problem. In the latest macOS versions (10.15 Catalina and 11.0 Big Sur), Apple requires that the hardened runtime is enabled to launch apps. It protects the app from malware injections, DLL attacks, and process memory space tampering. Apple wishes to protect their users, therefore this is an important requirement.

To go with this requirement, you have to use a flag –options=runtime if codesign is used or mention a field hardenedRuntime: true if you’re going with electron-osx-sign. So, I took this action and found out that using this flag completely breaks an Electron app: it either doesn’t launch or fails with a system error.

One way or another, after having spent lots of time googling, I found a set of requirements. Completing those requirements helps building in building and signing an Electron app with hardened runtime

How to build and sign an Electron app?

First of all, forget about Electron v. 5

As sad as it may sound, you have to forget about Electron v. 5 and update it to v. 6 at the very least. As the source says, hardened runtime support was introduced in Electron v. 6, so any attempts to enable it in the predecessor are doomed to fail. Besides, if you’re using electron-packager, you must update it to v. 14.0.4 for the apps to be build correctly.

Testers will probably not be happy to hear about it but there’s so much we can do. Apple sets the rules of the game, and it will be necessary to complete a full regression test of the app with a newer Electron version.

Second, create a parameter file entitlements.plist

This practice often comes in handy when creating apps in XCode entironment, but when it comes to Electron apps – not so much. Electron builder will automatically enable a deafult file with a standard parameter set. The file is an XML code and it lists system prohibitions and permissions on using different resources of the OS. This file now has to contain 2 important parameters.

<?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-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
...
</dict>
</plist>

The first one, allow-unsigned-executable-memory, allows the app using raw memory. The second one, allow-dyid-environment-variables, allows using environment variables. Every JS developer knows what they are, but these variables are not exactly the same as those we use in the app. This parameter permits the Electron framework using the environment variables he needs for correct work. For example, a path to the system library libffmpeg.dylib. If this parameter isn’t in the file entitlements.plist, the app will fail on the first launch, and will show an error of that library not being where it’s supposed to, despite it actually being there.

Third, activate entitlements.plist correctly

The file must be connected during signing a built Electron app with a certificate. If you’re using codesign directly, type in a flag –entitlements, then space, then show the path to the parameter file. However, it might not work right away, and codesign will have an error unrecognized option for that flag. To deal with it, use the utility codesign not from XCode Developer Tools but from the full XCode environment with a more expanded version of the utility. Run these commands to do that:

xcode-select -print-path
xcode-select -switch /path/to/SDK

There is a simpler way, too: use electron-osx-sign. However, it’s important to note that you have to enable the parameter file in entitlements and entitlements-inherit fields, otherwise nothing will work. It’s because entitlements-inherit is responsible for the parameters mentioned for distributive frameworks, and our app actually does work on Electron framework. This framework needs environment variables with paths to system files.

Easier now?

If you develop desktop apps for macOS on XCode, you likely won’t face these issues. Apple adapts its environment rapidly. However, as it turns out, Electron apps are out there, too. It’s possible to take care of the notarization problems, and the users can be absolutely happy without seeing the warning about potentially dangerous software.

I hope that this article was useful to you. If you have any other questions on the topic, do not hesitate to contact us via the contact form!

We have also launched Instagram, so feel free to check us out there, too 🙂

Categories
Uncategorized

How to Publish Desktop Apps on macOS? Ultimate Guide

ios-developer-meme

Developing a program code always brings joy. You get the instant outcome, you may check it out and see how neat the user experience is.

However, there are some formalities that act as a buzzkill here. Some of them are about publishing an app. Windows and macOS protect their users from malicious software, therefore publishing anything and everything isn’t possible. It’s imperative that the OS knows that the software comes from the trusted developer (signing), and its code doesn’t contain threats to the user (notarizing). Windows has only signing requirements, while Apple has been checking notarization since the macOS 10.14 Mojave was released.

This article came about after having to face some troubles while publishing an ElectronJS-app for macOS, and I would like to share my experience. We will begin with some starting information.

A short introduction to ElectronJS

ElectronJS is a tool to easily create a cross-platform app to launch it on all popular operational systems. The library uses the Chrome V8 engine and emulates a different browser window, where you can apply your HTML code, written with ReactJS. Electron uses its own backend to interact with an operational system, for example, to show system windows. Communication between what the user sees on the screen and the Electron backend happens with events.

Building an application is also fairly simple. Electron daughter libraries are in charge here, such as electron-builder, electron-packager. As an outcome, we have a ready binary file – .exe for Windows, .app for macOS, or executive binary file for Linux

You wrote and built an app. Ready to publish and send to the customer?

Unfortunately, not. For the users to launch an app, a developer certificate is necessary. This is a so-called digital signature of the app. It protects the program by mentioning who the author is. Apps with digital signatures are verified and are less-provocative to the system, antivirus programs, and firewalls. Software like that rarely ends up in quarantine.

If the app isn’t signed, macOS will politely ask to move the file to the bin.

macos notification unverified app
MacOS user warning about unverified apps

Windows will notify the user that the developer is unknown and you’re at risk.

Windows notification unverified app
Window user warning about unverified apps

Do not scare your user with these messages, better go and get that developer certificate! Add it to the keychain of the OS you are making a signature for. Later, use the certificate to sign the app. XCode Developer Tools on macOS provides codesign to do that. Electron-builder and electron-packager libraries supply you with their wraps to sign an app. You just have to let them know that you’re willing to sign an app with that particular name after the building is completed. More than that, macOS has one more way to do so: a separate wrap library electron-osx-sign.

Got the certificate, signed the app. Anything else?

Yep. Notarizing is the next step. It means that you have to check the code on the Apple servers to verify that there is no malware in it. Otherwise, the user will see this upon the first launch:

MacOS notification for non-notarizated
MacOS user warning for non-notarizated applications can scare users away

To send an app for notarization, Apple ID is necessary. That’s your login email to developer.apple.com and an Apple ID password from appleid.apple.com.

XCode altool provides utility for notarization. Unfortunately, it’s not in the XCode Developer Tools pack, so we have to install the full XCode. That moment when you don’t use XCode, like, ever, but you need it so you kiss goodbye to around 30 Gb of memory 🙂

We send the application using the command:

xcrun altool --notarize-app --primary-bundle-id "<id>" -u "<appleid>" -p "<app-specific password>"

Anything works as primary-bundle-id. It doesn’t affect notarization at all. For instance, mention com.electron.<appName>.

Notarization takes about 5-10 minutes. Apple will give you a unique RequestUUID. Use it to check on your notarization status. For that, use the command:

xcrun altool --notarization-info <RequestUUID> -u "<appleid>" -p "<app-specific password>"

The whole history can be checked with:

xcrun altool --notarization-history -u "<appleid>" -p "<app-specific password>"

Electron developers have expanded electron-builder and electron-packager libraries by adding a notarization process into the general workflow. There is also a separate wrap library electron-notarize which does exactly the same things. Basically, you need for things ready: built and signed .app application, appleId, appleIdPassword, appBundleId.

If a notarization tool doesn’t stumble upon anything bad in the app, your status will turn to Package Approved with a status code 0. Otherwise, Apple will give you the Package Invalid status and a status code 2. Fear not, friends, as every “bad” notarization has a log attached. All problems are mentioned there, as well as the directories of the files that stopped you from publishing right away.

If notarization is successful, congratulations! You’re ready to release your app.

When you have that last piece of the jigsaw, everything will, I hope, be clear (Albus Dumbledore)

When starting developing a desktop app on ElectronJS, think about the certificate to sign the app in advance. Don’t forget about the notarization, either. If you skip these parts, the potential amount of software users will drastically reduce because of conflicts with an operational system.

Check out the second part of this text. This time we touched on the problems one might face when notarizing an app on macOS. See you then!