In Development
Devlog Archives 2008-2010
Devlog Archives 2002-2007
Tutorials

Devlog

Permanent Eraser + Yosemite

29th April 2015 | Permanent Eraser

Each new version of OS X brings changes, which often prompts software makers to update their own products to ensure that things work properly on the new system. Sometimes the changes are minor and other times they are quite severe. This article will focus on two areas, Gestalt and code signing, which required some additional work to get Permanent Eraser 2.6.3 to work properly with Apple's desktop OS of 2014 — Yosemite.

Gestalt + Yosemite

There have been numerous ways to determine the operating system version on OS X, but it hasn't been until recently that Apple provided a sanctioned way in Objective-C (and Swift) with the inclusion of NSProcessInfo's operatingSystemVersion method. This is a welcome, albeit highly belated, addition to the Cocoa framework.

Before the inclusion of the operatingSystemVersion method, programmers used a variety of methods to determine the operating system's version, including a mainstay which harkens back to the pre-Cocoa days — the Gestalt Manager. Up until Yosemite (aka OS X 10.10), the following code snippet worked pretty well.

SInt32	osVersion;
Gestalt(gestaltSystemVersion, (SInt32 *) &osVersion);	// Set OS Version

if (osVersion >= 0x00001060) { // Snow Leopard (10.6) or later
	...
}

Unfortunately, with a system version like 10.10.0, we have run out of space using this old format, which results in warnings such as the following to appear:

WARNING: The Gestalt selector gestaltSystemVersion is returning 10.9.0 instead of 10.10.0. Use NSProcessInfo's operatingSystemVersion property to get correct system version number.

The problem which is encountered is when either the minor or patch numbers exceed the number 9, so the version of Mac OS X 10.4.11 would be returned as 10.4.9 or 10.10.3 would be returned as 10.9.3. Years ago, some Adobe installers encountered an error when trying to install on Mac OS X 10.4.10 or 10.4.11, thinking that it was an earlier version of Tiger (Adobe InDesign 4.0.5 does no longer start up). The problem now results in the returned OS version has run out of space to properly represent the system version for Yosemite.

The work around for this problem was to make use of a back port which will use the latest methods, but will fallback to another method for earlier versions of OS X. The replacement for the Gestalt call I used was Jake Petroules' CocoaBackports, which checks if methods like operatingSystemVersion and isOperatingSystemAtLeastVersion are available. If not, swizzle the methods and use the custom methods. The updated code now looks like this:

if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 6, 0}] == YES)	{
	...
}

Code Signing + Yosemite

A year ago I detailed the process I took to code sign Permanent Eraser to work with Gatekeeper on Mountain Lion and Mavericks. However, when developing Permanent Eraser 2.6.3, I encountered the following error when trying to launch the app on Yosemite.

So what happened?! This was working before! Let's do a quick codesign verification to see what is happening. If your app has been signed with an older version of codesign, you will see the following message if you verify the package with current versions of OS X:

codesign --verify --verbose=4 MyGreatApp.app
MyGreatApp.app: resource envelope is obsolete (version 1 signature)

If everything is good, then it might look more like this:

codesign --verify --verbose=4 MyGreatApp.app 
--prepared:MyGreatApp.app/Contents/Library/Automator/Foo.action
--validated:MyGreatApp.app/Contents/Library/Automator/Foo.action
--prepared:MyGreatApp.app/Contents/PlugIns/Bar.workflow
--validated:MyGreatApp.app/Contents/PlugIns/Bar.workflow
--prepared:MyGreatApp.app/Contents/PlugIns/Baz.workflow
--validated:MyGreatApp.app/Contents/PlugIns/Baz.workflow
MyGreatApp.app: valid on disk
MyGreatApp.app: satisfies its Designated Requirement

Or for the more succinct version:

codesign --verify -v MyGreatApp.app 
MyGreatApp.app: valid on disk
MyGreatApp.app: satisfies its Designated Requirement

If the app hasn't been signed at all, then you will see a response like this:

codesign --verify --verbose=4 MyOldApp.app
MyOldApp.app: code object is not signed at all
In architecture: ppc

The short answer is that Gatekeeper has changed in the past year. For apps to be approved by Gatekeeper, they now need to be signed in OS X 10.9.5 or later, which contains the latest signature. Due to changes like these, there's little wonder why I have sprouted at least three grey hairs due to dealing with code signing and provisioning profiles.

Apple Says: Important: For your apps to run on updated versions of OS X they must be signed on OS X version 10.9 or later and thus have a version 2 signature. They also must not contain any custom resource rules. You must sign using OS X 10.9 or later. You cannot just take the codesign utility and move it to an older version of OS X. Mac applications signed with version 2 signatures will still work on older versions of OS X.

Gatekeeper changes in OS X 10.9.5 and later

  • All packages within the app need to be signed, including Automator plug-ins and actions, frameworks, helper tools, shared libraries, etc.
  • Sign with the more modern version of codesign (v2), so when verifying with the command codesign -dvvv app_name.app, the second to last line should say "Sealed Resources version=2", instead of "version=1".
  • Resource rules are no longer supported.

What to sign

  • Frameworks
  • Automator actions
  • Plug-ins
  • Helper utilities
  • The app

How to sign

  • Sign from the inside out. Sign the inner components before signing the entire application package.
  • Let codesign guide you. It will fail if any components still need to be signed.

Start by trying to sign the main application bundle. If any enclosed subcomponents need to be signed, you will be notified.

codesign --force --sign "Developer ID Application: John Doe" MyGreatApp.app/
MyGreatApp.app/: replacing existing signature
MyGreatApp.app/: code object is not signed at all
In subcomponent: /Users/jdoe/Desktop/MyGreatApp.app/Contents/Library/Automator/Foo.action

In this example, the Automator action named Foo still needs to be signed.

codesign --force --sign "Developer ID Application: John Doe" MyGreatApp.app/Contents/Library/Automator/Foo.action

In the UNIX world, no news is good news. If you sign this bundle and nothing else comes back after the process is complete, assume everything is good. This works fine for signing Automator actions. If you are trying to sign a workflow, the process requires a little extra work. If you try and sign the top level of the workflow package, you will receive the following error:

codesign --force --sign "Developer ID Application: John Doe" MyGreatApp.app/Contents/PlugIns/Bar.workflow/
MyGreatApp.app/Contents/PlugIns/Bar.workflow/: code object is not signed at all
In subcomponent: /Users/jdoe/Desktop/MyGreatApp.app/Contents/PlugIns/Bar.workflow/Contents/document.wflow

The correct way to sign a workflow is to first sign the XML file document.wflow, then sign the enclosing workflow file.

codesign --force --sign "Developer ID Application: John Doe" MyGreatApp.app/Contents/PlugIns/Bar.workflow/Contents/document.wflow
codesign --force --sign "Developer ID Application: John Doe" MyGreatApp.app/Contents/PlugIns/Bar.workflow/

Problem Signing an Old Automator Workflow

Here is an error which occurs when trying to codesign the old Automator workflow plug-in which is intended for Leopard systems.

codesign --force -v --sign "Developer ID Application: John Doe" MyGreatApp.app/Contents/PlugIns/Baz.workflow/
MyGreatApp.app/Contents/PlugIns/Baz.workflow/: bundle format unrecognized, invalid, or unsuitable

To work around this issue, an Info.plist file needs to be included into the workflow bundle's Contents folder. An example Info.plist file is below. Replace the CFBundleName's value with the name of the workflow.

Example Info.plist:
<?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>CFBundleName</key>
	<string>Baz</string>
</dict>
</plist>

Verify

Once the application and all of its subcomponents have been properly code signed, verify using codesign and spctl to ensure that everything is good.

codesign --verify -v MyGreatApp.app 
MyGreatApp.app: valid on disk
MyGreatApp.app: satisfies its Designated Requirement


spctl -a -t exec -vv MyGreatApp.app
MyGreatApp.app: accepted
source=Developer ID
origin=Developer ID Application: John Doe

Packaging

When I was initially packaging Permanent Eraser 2.6.3 as a DMG, I used FileStorm 1.9.5. Unfortunately, this did not package the application correctly, so the app could not be verified properly by Gatekeeper. To work around this problem, I used FileStorm 2.0.2. Zipping the application from the Finder also works.

References