Code Signing Plugins

Apple’s code signing restrictions have become much more concrete with the release of Catalina. When you use Xcode to build your plugin, it will by default code sign only your local copy of the plugin. If you transfer the plugin (without an installer) to another computer with a different Apple ID associated with it, you will get the dreaded message from the OS that the product can not be used because it is not from a trusted source. While you may always circumvent this process in the Privacy and Security -> Firewall page in Preferences (google if you don’t know how) the proper method is to code sign and notarize your plugin. If you create an installer .pkg file, you will need to code sign it, and notarizing it also notarizes the plugin inside, allowing you to skip that step above.

Initially, this process can be very frustrating – rip out your arm-hair frustrating – and it takes some time to get everything exactly correct, but once you have a pattern established, it is not so bad — in addition, it is documented heavily on the internet so there is plenty of information out there. My advice is to test these operations with very small plugins and/or packages (because the notarization process requires an upload to Apple and this is generally very slow).

VST3 plugins for MacOS must have their main bundles code signed; the bundle is in the form (PLUGIN NAME).vst3 and you may use “Show Package Contents” to see what’s inside, if you are curious.

AU plugins (MacOS only) are actually two bundles: the Cocoa UI is a bundle that is embedded within the plugin, and the plugin itself is a bundle packaged as a .component. When you code sign an AU plugin packaged this way, you must first code sign the internal Cocoa UI Bundle, and then code sign the outer container .component bundle. If you do not code sign in that order, the notarization will fail later, even though the code signing produced no errors.

Apple Developer Account

You first need to establish an Apple Developer account. If you are in a University or other educational facility, you may be able to do this for free, but most of us will need to pay a yearly fee (currently $99 USD). As a consultant, I don’t have a problem paying this fee as it is a legitimate business expense. There are numerous websites that explain how to register as an Apple Developer. You do not need to use the same email as your Apple ID. You may find instructions here: Apple Developer Account Setup

Certificates

You will next need to generate two certificates and add them to your keychain. The first certificate “Developer ID Application” is for code signing the plugin. The second is for code signing the installer that you will deliver to you customer/user.

  • Developer ID Application: the certificate for code signing the plugin bundles
  • Developer ID Installer: the certificate for code signing the installer .pkg file

Go to your Certificates page after you login with your Apple Developer account and generate two of them as noted above. Use the “Learn More…” link to get an instant step-by-step guide on creating and downloading your disk based certificate, and then double-clicking on them to add to your Apple Keychain. Each certificate will have a string of 10 numbers connected, after your name such as: Joe Schmoe (DX778HXD2D) and you will need to enter this string during the signing process.

Code Sign the Plugin

When you use Xcode to build your plugin, it will be automatically code signed to run on your local computer only. You will then need to sign it again from Terminal in order to apply a timestamp that is now required. The easiest thing is to navigate to the folder that contains your plugin bundle, open Terminal and cd (change directory) into the location. Otherwise, you will need to provide a complete path in the signing routines. In both cases below, you will get a message “yourplugin.vst3: replacing existing signature” or “yourplugin.component: replacing existing signature” etc…

VST3

You only need to code sign the outer bundle. The syntax will look like this (note the -timestamp and -f which forces the overwriting of the code signing that Xcode did for you) where “yourplugin.vst3” is the path to your plugin.

codesign -s "Developer ID Application: Joe Schmoe (DX778HXD2D)" "yourplugin.vst3"--timestamp -f

AU

For AU, you have to code sign twice, one for the inner Cocoa UI bundle and the other for the component where “yourplugin” is the name of the plugin proper:

codesign -s "Developer ID Application: Joe Schmoe (DX778HXD2D)" "yourplugin.component/Contents/Resources/yourplugin.bundle" --timestamp -f

The next is for the component itself:

codesign -s "Developer ID Application: Joe Schmoe (DX778HXD2D)" "yourplugin.component" --timestamp -f

Notarizing

You will need to notarize the plugin after code signing. This is the step where your plugin is uploaded to Apple’s computers that check for the proper code signatures. This process can take a few minutes or several hours depending on the size of the package that needs notarizing. If you are NOT using an installer, you will need to zip up the plugin and notarize the zipped file. If you are using an installer, then all of its contents may be notarized at once, when you notarize the installer. Notarizing the installers is widely documented (see links below) but there is a nice tutorial on notarizing an installer as well as a ZIP file a this KVR link. The notarization terminal command will be in a form like this (the thing_to_be_notarized will be either a ZIP or PKG).

xcrun altool --notarize-app -f "thing_to_be_notarized" --primary-bundle-id "bundle_id_in_plugin" --username "youremail.com" --password "app_specific_password"

The bundle_id is found in the Xcode project settings. If using ASPiK, you can find it in the plugindescription.h file (there are separate bundle IDs for VST3 and AU).

The app specific password is an alternate password you get from your Apple Developer page. It is not associated directly with any particular app (though the misleading name implies that) but is to be used for anything not involving Apple, such as ZIPs and installers. You can request as many as you like, just be sure to keep track of the password. It will be in a compact UID form like this: “csdc-vken-wikf-ppso”

When the process is complete, you will first get a message (that only verifies the upload, and not the notarization) and UUID like this:

No errors uploading 'thing_to_be_notarized'.
RequestUUID = fa53cd59-15af-4640-b70d-50e7aa7c286e

A while later you will get an email stating whether the notarization passed or failed. If the notarization failed, you may use the UUID above and a command to get more information, including a gigantic web link that will only be active for 24 hours, that explains what failed. With that weblink, you can then go back and figure out where your mistake lies.

xcrun altool --notarization-info "fa53cd59-15af-4640-b70d-50e7aa7c286e"  --username "youremail.com" --password "app_specific_password"  

Package Installer

There are multiple MacOS installer packages you may choose from. I use Packages which is free and heavily documented. Regardless of your packager software, you will need to code sign and then notarize the package. There is a fantastic tutorial at the appcoda website which explains both Packages development as well as the product signing (which is different from code signing) and notarization, so I will not attempt to reproduce that here. Once the package is notarized, you then staple it. You are actually stapling a ticket to the package in case the user is offline when they try to open it. You can find more tutorials and instructions at both the appcoda site as well as the KVR page here. Note that these steps will involve your second certificate for the “Developer ID Installer.”

There is a bit of misinformation regarding testing the final stapled package out there on the internet. The staple operation and the verification code are shown below for the current SynthLab installer package:

xcrun stapler staple "SynthLabInstaller2.0.pkg"

// --- output from this command is:
Processing: /a_long_path/SynthLabInstaller2.0.pkg
Processing: /a_long_path/SynthLabInstaller2.0.pkg
The staple and validate action worked!

// --- the validation is:
spctl -a -t install --context context:primary-signature -v SynthLabInstaller2.0.pkg

// --- output from validation
SynthLabInstaller2.0.pkg: accepted
source=Notarized Developer ID