Apple packages (.pkgs) are opened by the GUI Installer.app or the command line installer command. If a package is unsigned and gets a quarantine flag (from being transferred over a network), the GUI Installer will refuse to run it. We can get around that with a right-click -> Open, but we shouldn’t be training computer users to ignore security warnings like this.
If you are creating your own packages, and users or techs may run them manually, then you really should be signing them. Even if you are deploying them in a way that a person won’t see a warning, signing packages can be very easy and provide a check that nothing changed since you created it. See below the break for how to easily automate signing packages.
I use WhiteBox Packages to create most of my packages. It has a built in feature to sign packages it builds, but recent versions have had some problems with it and when it works it still has to be set per package project.
The Apple provided tool to sign our packages is
productsign. This works, but to make things easier on myself, I have wrapped that up in an Automator application that I can use as a droplet.
The basic format for using productsign is
productsign [options] --sign identity input-product-path output-product-path
Interestingly, the man page for productsign doesn’t show any available options. So next we want to
identity. This is our Developer ID Installer certificate from Apple’s developer program. This does require a paid membership, but if you work for an organization supporting Macs, there is plenty of benefit to offset the $99 or $399/year (signing identities, early access to OS builds for testing, etc). To sign packages we need a Developer ID Installer (see Apple’s documentation)
Once you have your Dev ID Installer certificate installed in your keychain, then we can get the appropriate reference for it. Open /Applications/Utilities/Keychain Access. Search for Developer ID and select the appropriate certificate. Then you can highlight and copy the name. If you are a part of more than one developer program, select the “right” one here. You can create several Automator applications if you want to have one for each ID.
So now we can write our command
productsign --sign 'Developer ID Installer: OrgName (Code)' in_pkg out_pkg.
productsign --sign 'Developer ID Installer: OrgName (Code)' ~/Development/Unsigned.pkg ~/Development/Signed.pkg. This will prompt to allow productsign access to your keychain. Say Allow or Always Allow.
$ productsign --sign 'Developer ID Installer: OrgName (Code)' ~/Desktop/Unsigned/build/Unsigned.pkg ~/Desktop/Unsigned/build/Signed.pkg productsign: using timestamp authority for signature productsign: signing product with identity "Developer ID Installer: OrgName (Code)" from keychain /Users/username/Library/Keychains/login.keychain-db productsign: adding certificate "Developer ID Certification Authority" productsign: adding certificate "Apple Root CA" productsign: Wrote signed product archive to /Users/username/Desktop/Unsigned/build/Signed.pkg
Now when we run the installer by double clicking it, the installer opens. Notice the lock in the upper right corner. Click the clock and we can examine the certificate chain back to Apple’s Root.
The idea when we started was to make this easy to do since we might need to sign a lot of packages over time. To automate this, open /Applications/Automator.app. Choose to create a New Document. Then choose Application for the type and click Choose. Search for shell in the Actions name search field. Drag a run shell script action to the workflow. First adjust the Pass input: pop up to as arguments. That will fill in the template for us. Then enter our productsign command with some slight differences. The
identity will be the same, but now for the input and output, we need to make this work with whatever is dropped on the application. The packages will be passed as arguments and the for loop will work on each one in turn. These will be assigned to the pkg variable, so that becomes our input file. Then for the output, I settled on packagename.signed.pkg to differentiate the output from the input. This
$(sed 's/.pkg$/.signed.pkg/g' <<< $pkg) will find the last ‘.pkg’ in the filename and replace it with ‘.signed.pkg’, while keeping the name portion the same (see note at the end for how this works) . Notice I also changed the
pkg just to make it clear what the variable represents.
for pkg in "$@" do productsign --sign 'Developer ID Installer: OrgName (Code)' "$pkg" "$(sed 's/.pkg$/.signed.pkg/g' <<< $pkg)" done
The Automator workflow will then look like:
Save this somewhere easily accessible or save it and put it in your dock, and now you can drag groups of unsigned packages to it. Signed copies will be made in the location that the unsigned packages came from.
You could also do the same thing and make it a folder action if you keep all your packages in a single location. You would likely have to do some testing to see if a package was already .signed.pkg so you don’t try to re-sign everything in that folder.
* sed note –
$(sed 's/.pkg$/.signed.pkg/g' <<< $pkg) This sed command will search the string passed to it ($pkg in this case) for ‘.pkg’. The $ means that the string must end with that ‘.pkg’. Once found, the ‘.pkg’ will be replaced by ‘.signed.pkg’ without changing anything else in the string. Since this is the output file name, we taking in name.pkg and outputting name.signed.pkg. The <<< is a redirect to make sed work on the variable $pkg which is the package currently passed into the for loop.