Secure (xc)config for iOS apps
Tags: ios, security, opensource, mobile
Published: 2020-07-26 03:52:12

## App configuration dilemma :worried:
Developers often have to set some app config (API secrets for Staging, hard-coded users/passwords) locally that they don't want to **ever** be committed to their git repo, **especially** if that repo is open source. But people (and developers are people too) can, and do, make mistakes. So you may end up having to delete PRs, clean git histories, and ask GitHub support staff - who are super helpful actually - to delete PRs and run git garbage collection etc., which is all stuff that takes time away from more enjoyable tasks like feature development or *writing blog posts*.

It would be very nice if the app config could be set in a way that it's impossible to accidentally push that supposed-to-be-local temporary confidential information to git. We have a full implementation of secure configuration in our [Remote Config iOS SDK](https://github.com/rakutentech/ios-remote-config/commit/28f63b14bc80eabecf0dc22836ffc2e278045e59). In this post I will introduce the general approach. 

## plists and xcconfig :gear:
Many SDKs (including ours) require app developers to add key-value configs to their `Info.plist` and the SDKs read those values at runtime. 

Instead of using hard-coded values in the plist we can make them variables e.g. `$(API_KEY)` that reference the real value stored in an xcconfig file. This allows the app to easily have different configs for their different environments. But, importantly, it also gives us a mechanism to remove secrets from the Info plist. Thanks are due to my colleague Pierre from the Mini App team for initiating this idea.

To understand the ins and outs of xcconfig files I followed this excellent [Thoughtbot blog post](https://thoughtbot.com/blog/let-s-setup-your-ios-environments) which describes how to use xcconfig files to configure your app for different environments. Although I don't want to cover the same stuff I want to highlight this point from the author
> Since these files will potentially contain secure information, such as API_KEY, I’d recommend not checking them into version control and instead using a secure file storage system like 1Password

An alternative approach is to use a *hidden-from-git* "secrets" xcconfig file.

## Make it secure :lock:
You can make the approach secure by following these steps:

* Create a hidden-from-git `secrets.xcconfig` (or any name you prefer). The secrets xcconfig is hidden from git by adding it to your `.gitignore` file - the secrets xcconfig must **never** be committed/pushed to git.
* Add your secrets to `secrets.xcconfig` as key-value pairs, for example `MY_SECRET=my real secret`
* In your `<configuration type>.xcconfig` file import `secrets.xcconfig`
* In your app `Info.plist` create a key `MySecret` set to value `$(MY_SECRET)`

At build time `my real secret` will be injected into the plist as the value of `MY_SECRET`.

Your file snippets should look similar to:

#### `secrets.xcconfig` 
```bash
MY_SECRET = my real secret
```

#### `<configuration type>.xcconfig`
```obj-c
#include "secrets.xcconfig"
```

#### `Info.plist`
```xml
<?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>MySecret</key>
	<string>$(MY_SECRET)</string>
</dict>
</plist>
```
*Note: The above plist snippet only shows one key for clarity*

## Automate! :robot:
The above approach works pretty well but it would be a pain to maintain. Our team deploy and integrate our SDKs using CocoaPods so I wanted to find a way to automate secrets management that fits our process. So what I did was add a CocoaPods [post_install hook](https://guides.cocoapods.org/syntax/podfile.html#post_install) to our app `Podfile` that runs a custom shell script that [generates](https://github.com/rakutentech/ios-remote-config/blob/master/scripts/configure-secrets.sh#L21-L36) the secrets xcconfig file from local environment variables. When the script runs during `pod install` we print a warning/error message if a required environment variable is not set. 

## Are there limitations? :thinking:

### Security
This approach only prevents secrets in Info.plist files from being inadvertently committed to your git repo. It **does not** provide any run-time protection for your secrets and it should be noted that extracting secrets from an iOS ipa is as simple as [unzipping the binary package and opening the plist](https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06b-basic-security-testing#exploring-the-app-package) in a text editor. If your secrets also need run-time protection you must consider an alternative approach.

### Usability
The steps to add a new secret plist variable are not that obvious and it would be better to have a proper CocoaPods plugin. It could be based on the [cocoapods-keys plugin](https://github.com/orta/cocoapods-keys) (which stores secrets in the macos keychain and writes obfuscated secrets to generated source files that can be imported and used by app source files). I think making a plugin would be the logical next step for our secure xcconfig approach and I hope to explore building a plugin for this in future.