Home > AutoPkg, Mac administration, macOS > Adding installer package code-signing to AutoPkg workflows

Adding installer package code-signing to AutoPkg workflows

As part of building an AutoPkg workflow to create installer packages, one of the requirements I was given was that any packages that weren’t already signed by the vendor needed to be signed using a Developer ID Installer signing certificate.

Screen Shot 2017 11 09 at 9 58 53 PM

Signing installer package is not usually an outcome of most AutoPkg workflows, since code signature verification can be used at the download end to make sure that the application is what it is supposed to be. However, there were several good reasons for adding a package signing step to the workflow, including:

  1. It is now necessary to sign packages before you’ll be able to use them as part of NetInstall sets
  2. The InstallApplication MDM command requires that macOS installer packages be signed with an appropriate certificate

After some research and testing, I was able to incorporate installer package signing into my AutoPkg workflow and am now able to automatically sign installer package as they’re generated by my package creation workflows. For more details, see below the jump.

To enable AutoPkg to sign the packages it creates, I’m using an AutoPkg processer named PkgSigner to add the needed code signing. PkgSigner was written by Paul Suh and it uses Apple’s productsign tool to access a Developer ID Installer certificate stored in the login keychain. For those who need it, I have a copy of the PkgSigner processor available via the link below:

https://github.com/rtrouton/AutoPkg_Processors/tree/master/PkgSigner

Preparing to use the PkgSigner processor

Before the PkgSigner processor can be used, the Developer ID Installer certificate needs to be added to the login keychain of the account being used to run AutoPkg. For more information about adding Apple Developer certificates to an account, please see the link below:

https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html

It is also necessary to run Apple’s productsign tool manually once before using the PkgSigner processor. The reason for this is so that you can be prompted to always allow productsign access to the Developer ID Installer certificate stored in the login keychain.

Screen Shot 2017 11 09 at 9 56 42 PM

You can do this by running a command similar to the one shown below to sign an unsigned package.

productsign --sign 'Developer ID Installer: Developer Name Here (756DF992DDB6)' "/path/to/unsigned_package_here.pkg" "/path/to/signed_package_here.pkg"

Using the PkgSigner processor in AutoPkg recipes

Once you’ve prepared productsign to be able to access the Developer ID Installer certificate stored in the login keychain, you should be ready to start using the PkgSigner AutoPkg processor by placing a copy of the PkgSigner.py file in the same directory as your AutoPkg .pkg recipe.

Screen Shot 2017 11 09 at 10 27 47 PM

As an example of how the PkgSigner processor can be used, I’ve written an AutoPkg recipe for Google Chrome which generates an installer package. In this recipe, a variable called SIGNINGCERTIFICATE is being defined as being where the certificate name is being referenced.

Screen Shot 2017 11 09 at 9 52 04 PM

Because we don’t want to include the actual signing certificate name in the recipe, a placeholder value is there. The actual certificate name should be added via an AutoPkg override. The SIGNINGCERTIFICATE variable in the override should look like this:

Screen Shot 2017 11 09 at 10 02 46 PM

The SIGNINGCERTIFICATE variable is then called later in the recipe by the PkgSigner processor, to sign the referenced package using Apple’s productsign tool. The way it should be set up in the recipe is to first generate the package using a name defined by variables elsewhere in the recipe:

Screen Shot 2017 11 09 at 10 11 09 PM

Next, the PkgSigner processor will locate the package by its location and do the following:

  1. Rename the unsigned package from /path/to/package_name_here.pkg to /path/to/package_name_here-unsigned.pkg
  2. Sign the package
  3. Save the signed package as /path/to/package_name_here.pkg, so that the name matches the original package. Renaming the signed package to match the original unsigned package’s name allows AutoPkg to continue to work with the now-signed installer package.

Screen Shot 2017 11 09 at 10 11 10 PM

Certificate ID formatting

When referencing the Developer ID Installer certificate in an AutoPkg recipe, special characters should be replaced by their percent encoding equivalent. For example, my personal Developer ID Installer certificate is referenced by productsign by using the following identifier:

Developer ID Installer: Rich Trouton (XF95CST45F)

AutoPkg recipes are written in XML though, which uses colons and parentheses for its own purposes. Instead, the colons and parantheses should be translated into their percent encoded equivalents:


Developer ID Installer: Rich Trouton (XF95CST45F)

view raw

gistfile1.txt

hosted with ❤ by GitHub

Please see below for a link to a table of percent encoding codes and the special characters they map to:

https://www.dvteclipse.com/documentation/svlinter/How_to_use_special_characters_in_XML.3F.html

For those who want to examine the example AutoPkg recipe and override, they’re available below:

Google Chrome .pkg recipe:


<?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"&gt;
<plist version="1.0">
<dict>
<key>Description</key>
<string>Downloads latest Google Chrome disk image and builds a package.</string>
<key>Identifier</key>
<string>com.company.pkg.GoogleChrome</string>
<key>Input</key>
<dict>
<key>NAME</key>
<string>Google Chrome</string>
<key>PACKAGER</key>
<string>Company Name</string>
<key>VENDOR</key>
<string>Google</string>
<key>SOFTWARETITLE</key>
<string>Chrome</string>
<key>SIGNINGCERTIFICATE</key>
<string>Put_Signing_Certificate_into_AutoPkg_recipe_override</string>
</dict>
<key>ParentRecipe</key>
<string>com.github.autopkg.download.googlechrome</string>
<key>Process</key>
<array>
<dict>
<key>Arguments</key>
<dict>
<key>predicate</key>
<string>SIGNINGCERTIFICATE == "Put_Signing_Certificate_into_AutoPkg_recipe_override"</string>
</dict>
<key>Processor</key>
<string>StopProcessingIf</string>
</dict>
<dict>
<key>Processor</key>
<string>AppDmgVersioner</string>
<key>Arguments</key>
<dict>
<key>dmg_path</key>
<string>%pathname%</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgRootCreator</string>
<key>Arguments</key>
<dict>
<key>pkgroot</key>
<string>%RECIPE_CACHE_DIR%/%NAME%</string>
<key>pkgdirs</key>
<dict>
<key>Applications</key>
<string>0775</string>
</dict>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>Copier</string>
<key>Arguments</key>
<dict>
<key>source_path</key>
<string>%pathname%/%app_name%</string>
<key>destination_path</key>
<string>%pkgroot%/Applications/%app_name%</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgCreator</string>
<key>Arguments</key>
<dict>
<key>pkg_request</key>
<dict>
<key>pkgname</key>
<string>%PACKAGER%_%VENDOR%_%SOFTWARETITLE%_%version%</string>
<key>version</key>
<string>%version%</string>
<key>id</key>
<string>%bundleid%</string>
<key>options</key>
<string>purge_ds_store</string>
<key>chown</key>
<array>
<dict>
<key>path</key>
<string>Applications</string>
<key>user</key>
<string>root</string>
<key>group</key>
<string>admin</string>
</dict>
</array>
</dict>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgSigner</string>
<key>Arguments</key>
<dict>
<key>pkg_path</key>
<string>%RECIPE_CACHE_DIR%/%PACKAGER%_%VENDOR%_%SOFTWARETITLE%_%version%.pkg</string>
<key>signing_cert</key>
<string>%SIGNINGCERTIFICATE%</string>
</dict>
</dict>
</array>
</dict>
</plist>

view raw

gistfile1.txt

hosted with ❤ by GitHub

Google Chrome .pkg recipe override:


<?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"&gt;
<plist version="1.0">
<dict>
<key>Identifier</key>
<string>local.pkg.GoogleChrome</string>
<key>Input</key>
<dict>
<key>DOWNLOAD_URL</key>
<string>https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg</string&gt;
<key>NAME</key>
<string>Google Chrome</string>
<key>PACKAGER</key>
<string>Company Name</string>
<key>SIGNINGCERTIFICATE</key>
<string>Developer ID Installer&#58; Rich Trouton &#40;XF95CST45F&#41;</string>
<key>SOFTWARETITLE</key>
<string>Chrome</string>
<key>VENDOR</key>
<string>Google</string>
</dict>
<key>ParentRecipe</key>
<string>com.company.pkg.GoogleChrome</string>
<key>ParentRecipeTrustInfo</key>
<dict>
<key>non_core_processors</key>
<dict>
<key>PkgSigner</key>
<dict>
<key>path</key>
<string>/Users/username/Library/AutoPkg/RecipeRepos/com.company.autopkg.recipes/GoogleChrome/PkgSigner.py</string>
<key>sha256_hash</key>
<string>475e5a7ffaa8145bf3f4c103e504719594072363ce9faf003c7ace8a3f05e109</string>
</dict>
</dict>
<key>parent_recipes</key>
<dict>
<key>com.company.pkg.GoogleChrome</key>
<dict>
<key>path</key>
<string>/Users/username/Library/AutoPkg/RecipeRepos/com.company.autopkg.recipes/GoogleChrome/GoogleChrome.pkg.recipe</string>
<key>sha256_hash</key>
<string>91c39577df611d211b9499a685dcf2dc337d5444ed7c9840921dd4cd5a5ad979</string>
</dict>
<key>com.github.autopkg.download.googlechrome</key>
<dict>
<key>git_hash</key>
<string>f29f399c54adee7122e9fdc3c1ea7ed4c67388a4</string>
<key>path</key>
<string>/Users/username/Library/AutoPkg/RecipeRepos/com.github.autopkg.recipes/GoogleChrome/GoogleChrome.download.recipe</string>
<key>sha256_hash</key>
<string>880dbbc39342ba18bb4609a4746be75fa4b41667840201876c0f936aef7599e6</string>
</dict>
</dict>
</dict>
</dict>
</plist>

view raw

gistfile1.txt

hosted with ❤ by GitHub

  1. November 10, 2017 at 3:54 am

    That’s hot.

  2. berazooka
    February 25, 2020 at 8:55 am

    Hey Richard! Have you came across the issue, when using this processor, that from time to time you have packages that are ~ 207 KB in size?

    There’s no failure or error but of course the package is wrong; however, if I run the same recipe again (no changes made) I get it with the expected (several MB) size.

    Thanks!

  1. No trackbacks yet.

Leave a comment