Since the release of Swift, I’ve been looking for any excuse to do some work in Swift. My regular work does not afford me as much opportunity as I have hope. Months after the announcement and release of Swift, I still consider myself a noob. Recently, I have landed a gig with a client who develops in-house iOS apps with their Enterprise account but they are still very entrenched into Objective-C. It didn’t looked like I have the opportunity to advance my adventure in Swift; that is until I was looking at their certificate situation. Suffice to say, it’s in a mess. I’m tasked to straighten it out so to avoid the situation where suddenly apps stop working due expired certificate and the inevitable scramble to create app bundle signed with new certificates follow by the frantic release to the users.
I’ve started to unpack the IPA files to verify the apps bundles are signed with the correct certs and fix the bundles that are signed by an older cert that’s about to expire. Furthermore, I need to verify every new bundle before releasing to the users. I realized quickly that I would be doing the same thing often enough to be annoying but not enough to form the muscle-memory for remembering the commands and it’s flag to be effortless.
The verification task involves unzipping the IPAs to get to the contents of the app package, using the codesign tool to get the certificate name, and decrypt the provisioning profile within the app bundle to get the signing provisioning profile info. All of the commands I need to complete the task are command line tools, so I burst into my usual mode of solving such problem, which is to write a bash script. Midway through, I ask myself why do I need to write the parser to convert provisioning profile format (a property list) to a usable key-value pairing. That’s when I realized I should write a Swift script to do the conversion from plist to an NSDictionary for easy extracting of the values. This is the perfect excuse to dip into some Swift time.
The point to all this is to say there are many opportunities to hone one’s skill and where they come from may surprise you.
Now if you are still here for the after show (the real show), let’s go into some nerdy stuff.
Ultimately the bash script was still necessary because there are some things that are much easier done with a shell script than a language like Swift. Besides, I don’t want to spend time to reverse engineer codesign, and the decrypting tool, which are provided by Apple and readily installed, on my Mac. By combining the best part of shell script with the best part of a language like Swift, I finish a primitive (read fragile) set of two script is a relatively short amount of time.
Behold, the “provisioninfo” Swift script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#!/usr/bin/env xcrun swift // -*- Swift -*- // // provisioninfo // import Foundation // Expect the unencrypted provisioning profile is piped into the script. let standardInput = NSFileHandle.fileHandleWithStandardInput() // Only drawback with using the method availableData is when there's no input, // the method is blocking and the script will hang. Yet to find a good // alternative let input = standardInput.availableData var error: NSError? // Parse property list into NSDictionary let provision : AnyObject! = NSPropertyListSerialization.propertyListWithData(input, options: 0, format: nil, error: &error) // Print out elements that are of interest. if provision != nil { // Check to ensure input is indeed the provisioning profile and in // unencrypted plist format. if let value = provision["Name"] as? String { println("Provisioning Profile Name: \(value)") } else { println("No name") } if let value = provision["UUID"] as? String { println("UUID: \(value)") } else { println("No UUID") } } |
The only function of the Swift script is reading the provisioning profile in the form of an unencrypted plist from the stdin, convert to NSDictionary, and print out elements that are of interest.
1 |
#!/usr/bin/env xcrun swift |
The first line would look very familiar to those that are like me that have a background in shell scripting. The proper name for the line is shebang. If you are not familiar, it’s just the mechanism in Unix shell (bash in this case) to tell the shell how to execute the rest of the file. When the shell executes the script, the shebang line would not be passed into xcrun, therefore will not cause a syntax error.
1 |
chmod +x provisioninfo |
If still unsure, just know that it is the very line that allows a Swift script be executed on the command line just like any compiled executable. That and of course with the file mode executable bit turned on as above.
2 |
// -*- Swift -*- |
The second strange looking line is a sign that the person who wrote the script is an emacs user. It’s a line that would inform emacs which editing mode to use when the file is loaded. It is normally not necessary if the filename includes the “.swift” extension but when I write scripts, I don’t like to have the extension included so visually, it’s the same with any executable. The problem is when there’s no filename extension, emacs could not figure out the file type and hence would not be able to auto-load the editing mode associated with the file type (having to manually set the editing mode, BAD!). In comes the file variable line. Normally, this file variable would be on the first line of the file but the shebang really need to be on the first line or the shell will not be able to pick it up. Fortunately, emacs is smart enough to pick it up even if the file variable line is located on the second.
Lastly, here’s the bash script that ties the whole operation together (note it’s shebang):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/bin/bash # # appsigninfo # # Assume the IPA files are in the current directory for now BASEDIR=$PWD TMPDIR=/tmp find "${BASEDIR}" -name "*.ipa" -maxdepth 1 | while read file; do fname=${file##*/} echo "File: ${fname}" UNZIP_DEST="${TMPDIR}/${fname%.ipa}" PAYLOAD_DIR="${UNZIP_DEST}/Payload" # Cleanup old leftovers if exist if [ -e "${UNZIP_DEST}" ]; then rm -rf "${UNZIP_DEST}" fi unzip -qq "${file}" -d "${UNZIP_DEST}" # The <name>.app does not necessary match with the IPA filename. # "codesign" does not expend path with wildcards, so use "find" to get the # <name>. APPDIR="$(find "${PAYLOAD_DIR}" -name *.app -maxdepth 1)" codesign -dvvv "${APPDIR}" 2>&1 | \ sed -n -e '/^Authority=/s/^Authority=iPhone Distribution:/Cert:/p' \ -e '/^Signed Time=/s/^Signed Time=/Signed Time: /p' # decrypt the provisioning profile and pipe to "profisioninfo" to extract # lines of interest. NOTE: "profisioninfo" THE Swift script. security cms -D -i "${APPDIR}/embedded.mobileprovision" | provisioninfo rm -rf "${UNZIP_DEST}" echo done |
As I’ve said, both the script was written with minimal effort and a little fragile. I’ve probably made a few mistakes or did not use a better way to perform the task. Regardless, it’s a short but yet quality Swift time I’ve just spent. I’m satisfied with my day.
—Swiftloc signing off.