More fun scripting with Swift and Xcode: the signature shuffle

I have just stolen a bit more time to progress another ‘scripting’ project using Swift 3 and Xcode. This has its origins in the discovery that some brand new MacBook Pros had shipped to users with a vital part of Sierra’s security protection – SIP – turned off. It occurred to me that, without resorting to incantations in Terminal, it is not only very difficult to know whether SIP is turned on, but to check that all the other protection systems are enabled.

Classically, this would be an easy task for AppleScript, so I turned around a quick check app within half an hour, and posted it here. But always rising to a challenge which nobody had made, I decided to implement something more extensive using Swift in Xcode, as a practical test of their suitability for such tasks. Last time I snuck a little time to work on this, I hit problems with running shell commands with root privileges. I wondered what was in store this time.

Signing off or on

One thing has been bugging me about the apps which I have been producing using Swift 3 and Xcode: although they have been signed, the signatures haven’t been working properly. I have already had a couple of forays into Xcode to try to sort this out, and even checked my Apple developer account pages, but it was all too complex for me to sort in the few minutes that I had.

I use an invaluable but free tool from Objective-See called What’s Your Sign? to check app signatures using the Finder’s contextual menu. Those that I build in Xcode are reported as being “signed, but no signing authorities”.

scripting40

In contrast, the apps that I have built using Script Debugger are being signed properly. Script Debugger’s interface for code signing is really simple, as it ought to be. There’s a single menu command to set the signing options, which are either to sign using Mac developer certificates which it gleans from your keychain, or not to sign. When I save an AppleScript app with code signing active, it is properly signed. And that’s it.

scripting41

Each time that I have tried to check this out in Xcode, it doesn’t look right. For a start, Xcode is much more complex, and manages code signing in two different places. The more fundamental is in the Accounts section of its Preferences dialog. And this is where I seemed to be coming unstuck.

scripting42

I know that it has my correct and valid Developer ID, and that should include code signing for Mac development. Unfortunately when I checked this in my Apple developer account, there is no useful information to confirm that, but I do recall installing my signing certificate in my keychain. I have checked that using Keychain Access, and it is valid and looks good.

But when I look at this dialog in Xcode, against the entry Mac Development, there is just a blank line. I cannot retrieve any information about that ability, whether it is already installed and enabled, or is denied to me without obtaining another certificate, perhaps.

The next thing to check is that I have the correct Apple Intermediate certificate installed properly, so I download that and ensure that it is installed in my keychain. Still my ‘signed’ apps have no signing authorities. The next step is to go into the settings for one of the apps which is not getting signed properly.

scripting43

In the Signing section out of its 471 different options, when I first look there is no setting at all. So much for Xcode automatically using the installed developer settings. So I then set it to use my code signing identity, only to be told that it doesn’t like a manual setting, but that I should set it to its automatic setting, as shown above. Once I have done that, apps are properly signed, and What’s Your Sign? reckons that they are good to go.

I can’t recall ever having to assume that a blank line in Preferences means that a feature is enabled, and that you have to manually set anything to its automatic setting. Xcode has a perverse interface in parts.

Running commands and capturing standard output

My little single-window app needs to do two different things: one is to run shell commands and capture their (standard) output to display in its window, and the other is to check some bundle version numbers.

scripting44

I described how I have gone about running commands in the previous article. In essence, I set up variables containing the path to the command, and a list of parameter (option) strings first:
var theSIPCmd = "/usr/bin/csrutil"
var theSIPPar = ["status"]

Then when the user clicks the button to fire the actions, I create a new process and an output pipe for its standard output, launch the task using those, read the pipe back into a variable, and wait for the task to complete. I then check the termination status:
let task = Process()
task.launchPath = theSIPCmd
task.arguments = theSIPPar
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
let fileHandle = outPipe.fileHandleForReading
let data = fileHandle.readDataToEndOfFile()
task.waitUntilExit()
let status = task.terminationStatus

Provided the status comes back as 0 (success), I then read the returned output as raw UTF8 text, and insert it as a string into the text to be displayed in a text box:
theSIPOut.stringValue = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String

This is much more verbose than AppleScript, but all fairly straightforward once you know the recipe.

Getting and displaying version numbers

In addition to running three shell commands, I want to check the version numbers of the hidden protection data files which are updated silently by Apple. There are situations where the versions installed can fall well behind what is current: for example, if you perform a Combo update some weeks after that update is released, it can set those data files to be current at the time of the release of the Combo update. It can then take some hours or days before automatic updates are installed. It is also possible for the update mechanism to become dysfunctional.

Each of these data files lives in a bundle, well within SIP coverage, so what I wanted was code to discover the short version string for a bundle. A bit of looking around followed by imagination and a couple of minutes testing in a Swift playground, and this is what I came up with.

scripting45

First, set a couple of string variables to contain the key that we want, and for each bundle to be inspected, its path:
var theVerKey = "CFBundleShortVersionString"
var theXProtPath = "/System/Library/CoreServices/XProtect.bundle"

Then we initialise a bundle object for the bundle at the given path, and retrieve the short version string:
let myBundle1 = Bundle.init(path: theXProtPath)
let theVer1 = myBundle1?.object(forInfoDictionaryKey: theVerKey)

Finally we cast that otherwise amorphous data as a string, and insert it into the text box:
theXProtVerOut.stringValue = theVer1 as! String

In case you’re wondering, the as! idiom was suggested by Xcode as the best way to do this.

scripting46scripting47scripting48

 

scripting49

So what went wrong?

Once I had sorted the signature problem out, it was a breeze. It’s still more verbose than AppleScript, but it really is straightforward stuff, which should be well within the capabilities of anyone who has written their own AppleScript tools.

And I’m sure that it will catch me out next time…