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.
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 🙂