Customizing and Code-Signing a Mobile App

Lyndsey Ferguson
5 min readNov 3, 2020

--

Background

As we saw in “Bring Customers Joy With Automation”, Appian builds custom versions of the mobile Appian application for its customers. To do so, our old process was to apply the customer’s desired settings and images to our mobile code base, run the build, and then sign the application as the customer.

The Problem

Our process worked, but it did take around 20 minutes to build. That required a build machine, which wasted money and time. We don’t like unnecessary waste.

Here is an example of what we had before — a fastlane Fastfile with two lanes:

  1. customize_build: Configures the background color and welcome message of the application.
  2. build_custom_app: Takes the arguments provided to fastlane, calls the customize_build lane, and then builds the iOS application. The example doesn’t take 20 minutes, but imagine if it did! 👎

The Original Build

The fastlane lanes customize_build and customize_build

Now, 20 minutes may not seem like a lot, but we have a lot of customers, each with a custom version of their application. For every customer, we have to manually start a Jenkins job with Vault token for each of these requests. When you think about it, we really shouldn’t have to build the application again — it was already built when we released the mobile application to Apple’s App Store and Google’s Play Store. The only differences were some settings files, some images, and the fact that it needed to be code signed using the customer’s code signing assets.

The Idea

We initially tried finding the latest released application from the iOS App Store and the Android Google Play Store, downloading the application, changing the settings files to match what the customer wants, swapping out the images in the build, and finally re-signing the application.

That seems pretty simple, right?

The Implementation

First, we had to write the code to download the already built and published application. We quickly found out that there was no programmatic way to download the latest released version of the iOS application. That threw a wrench into our plan.

Instead, for each release of our iOS and Android application, we decided to archive the application along with the image catalog and download that build each time the customer wants a custom version of the application. In the example below, I simulate that by putting the built applications in the GitHub Releases page.

Downloading the Latest Release

The fastlane lane download_latest_release

The fastlane lane download_latest_release

Once the application is downloaded, it needs to be unarchived, customized, resigned, and archived. Fortunately, both Android and iOS applications are zip archives, so it’s simple to unzip them to access the application contents.

In the case of the iOS application, we are archiving both the iOS “ipa” and the image assets in a parent zip folder. We need to include the same image assets that were associated with a particular release to ensure that we are not missing any images.

Unarchiving

We need to download the latest release and then unarchive the ipa and image assets bundle into a temporary directory. Then, we unarchive the ipa into its own temporary directory so that we can work on it.

The skeleton code that downloads the latest release and unarchives it

Customizing Settings

Now that we have the application downloaded and unarchived, we can start customizing it. First, let’s customize the settings by adding the following code underneath the TODO section above.

Code that customizes the build settings of the unarchived iOS app

This code loads a YAML file that has been configured with the customer’s settings. It then calls the method we declared earlier to customize the background color and the welcome message. It’s worth noting that plist files inside of an iOS application are stored as binaries to save space, so they have to be converted to text files, updated, and then converted back to binaries.

Customizing Images

Next, we need to write the code that customizes the icons using what the customer gave us. Let’s append the following to the code snippet above:

Note how we have to first put the customer’s images into the iconset bundle and then use the xcrun actool to compile that iconset into an Assets.car file to put into the application bundle.

Code-Signing

Now that we have the customized application, we need to code-sign it with the customer’s certificate. Continuing in the customize_built_app method where we left off with compile_images:

This code uses the Vault token role custom-mobile-apps-read-policy (as seen in “Automatically Secure Code Signing Assets”) to retrieve the keychain and password in preparation for code signing. After unlocking the keychain, it proceeds to get the keychain’s certificate ID to sign any frameworks inside the app bundle as well as the app bundle itself.

Take note that before code signing the application itself, we first have to retrieve the entitlements that were embedded into the application binary, update them to contain the bundle ID and application ID that were in the customer’s mobileprovision file, and re-embed those entitlements back into the application binary. This happens in the prepare_entitlements method and its various helper methods.

Validations

Finally, we also added code to ensure that what we built was correct (not shown here). This code

  • Removes transparencies from any launch icons, as apps that have them will be rejected from App Stores.
  • Fails and alerts us if images were not of the correct size.
  • Fails and alerts us when certificates or provisioning profiles were expired or missing.
  • Fails and alerts us if the customizations were invalid.

The Result

With these changes, we no longer have to build the application each time. Instead, we can apply some basic customizations in a few minutes rather than waiting 20 minutes for the build.

I’ve written up the example code for both an Android and an iOS application, and you can find it at this GitHub repo. If you have other ideas of how to make this process faster, or if this helps you with your own automation, I’d love to hear from you!

Afterword

This leads me to think: what other ways can we all benefit from this knowledge? There may be other instances where we need to make a change to an application, but the code or project doesn’t actually have to change. For example, a React Native application that loads a bundled JavaScript file could take advantage of this for code changes in only JS files.

If you find this kind of work interesting and want to work with creative, passionate, and smart people, there are plenty of other projects to work on like this at Appian. Apply today!

--

--

Lyndsey Ferguson

Born in Canada. Software Engineer. I love reading, films, music, scuba diving, strength training, and travelling.