Apple developer centre – organized and automate
Published: 2021-04-16 14:04:04

Code signing goes hand in hand with iOS development, whether you wish to build and upload your app to your device, or you just want to upload it to the App Store. If you're new to iOS development and don't want to deal with it right from the start, you can enable automatically managed code signing, which is fine for the time being, but in a team of 50, it becomes rather ineffective. When someone removes their device and invalidates a wildcard development provisioning profile, or accidentally invalidates a distribution certificate, your pipeline will fail out of nowhere, and the robustness of continuous integration and/or deployment suffers as a consequence.

The right approach for getting rid of human-error in any process is to remove humans from the equation. Don't worry, in this case, it just means to remove their access to the developer centre. But how do you keep people able to develop their apps on real devices and distribute apps to the AppStore?

##It's a Match! Fastlane Match##

Fastlane and its match don’t need much introduction in the iOS community. It's a handy tool that ensures everyone has access to all development and distribution certificates, as well as profiles, without having access to the dev centre, as match uses git as storage for encrypted files. It offers a `read-only` switch that makes sure nothing gets generated and invalidated accidentally. There are two roles in this approach - one for the admin and the developer. The developer uses match to install whatever is needed at the time of development and sets up CI/CD. He only needs access to the match git repository, not the developer centre. That's where the admin comes in - he is the one responsible for setting up all the devices, provisioning profiles, certificates, and git repository, where all the match magic happens. It's good to have at least two admins in case something goes awry while one of them is out of office.

##Match setup (admin perspective)##

The idea behind match is pretty simple, you don't have to deal with the developer centre as much, and you can instead focus on having a private git repository set up with all your certificates and provisioning profiles, all properly encrypted, of course. It supports developer and distribution certificates, a single repository can even handle multiple accounts. Match expects a specific folder structure in order to automatically find the matching type of certificates and profiles, but it's pretty straightforward:

`-certs
|--development
|--distribution
|-profiles
|--appstore
|--development`

The certs folder contains a private key and a public certificate, both are encrypted. Profiles contain encrypted provisioning profiles. Match works with `AES-256-CBC`, so to encrypt the provisioning profile you can use `openssl`, which comes pre-installed on macOS.

##Certificate encryption##

First, you create a certificate in the dev centre. The certificate’s key is then exported from the keychain to the p12 container, and the certificate itself is exported to the cert file. Match expects the key and the certificate to be in separate files, so don't export them both from the keychain to a single p12 container. You need to pick a passphrase that is used to encrypt and later decrypt certificates and profiles. It's recommended to distribute the passphrase to others in some independent way, storing it in the repository (even though private) would make the encryption useless.

To encrypt key, run:

`openssl aes-256-cbc -k "my_secret_password" -in private_key.p12 -out encrypted_key.p12 -a`

To encrypt the certificate:

`openssl aes-256-cbc -k "my_secret_password" -in public_cert.cer -out encrypted_cert.cer -a`

You can have multiple certificates of the same kind (developer or distribution) under one account. To assign a provisioning profile to its certificate you need to use a unique identifier generated and linked to the certificate in the developer centre. The following Ruby script lists all the certificates with their generated identifiers. The identifier is used as a name for the key and for the certificate:

```
require 'spaceship'

Spaceship.login('john.doe@company.com')
Spaceship.select_team

Spaceship.certificate.all.each do |cert|
  cert_type = Spaceship::Portal::Certificate::CERTIFICATE_TYPE_IDS[cert.type_display_id].to_s.split("::")[-1]
  puts "Cert id: #{cert.id}, name: #{cert.name}, expires: #{cert.expires.strftime("%Y-%m-%d")}, type: #{cert_type}"
end
```

##Provisioning profiles encryption##

Provisioning profiles are encrypted in the same way as the certificates:

`openssl aes-256-cbc -k "my_secret_password" -in profile.mobileprovision -out encrypted_profile.mobileprovision -a`

Naming is a bit easier: Bundle identifier is prefixed with the type for the provisioning profile like this:

`AppStore_bundle.id.mobileprovision
Development_bundle.id.mobileprovision`

##Good orphans##

The typical git branching model doesn't make much sense in this scenario. Git repository is used as storage for provisioning profiles and certificates, rather than for its ability for merging one branch into another. It's no exception to having access to multiple dev centres, for instance, one for the company account, one for the enterprise account, and multiple accounts for the companies you develop and deploy apps for. You can use branches for each of those accounts. As those branches have no ambition of merging into each other, you can create orphan branches to keep them clearly separated. Then just use the `git_branch` parameter to address them (for both development and distribution):

`fastlane match --readonly --git_branch "company" ...
fastlane match --readonly --git_branch "enterprise" ...
fastlane match --readonly --git_branch "banking_company" ...`

##With great power...##

As the admin of a team without access to the dev centre, you're going to get a lot of questions on how to install certificates and profiles. It's helpful to set up a README in your codesigning repository that describes which apps are stored under which branches, and even includes match documentation and fastlane's code signing guides. It's also super cool of you to set up an installation script for each project, and put it under version control of the said project. Then when a new member joins the team and asks how to set stuff up, you just point them to run `./install_profiles.sh`.

##Match usage (developer perspective)##

As a developer, you don't have access to the dev centre. You only need access to the git repository and a few commands to download profiles and install them on your machine. You also need to have your device registered in the account and assigned to the provisioning profile you'd like to use. But since you don't have access, you need to ask admins to set it up for you, which is a price paid by the admins for the sake of order and clarity. After that, you're all set and can run the commands to install whatever is necessary. The developer gets asked the passphrase the first time the command is run. You can choose to store it in the keychain if you'd like to skip entering it next time.

##Development profiles##

There are but a few inputs to match command: `git_branch` reflects which account the app is registered in, `app_identifier` is a bundle identifier of the app, and the others are also quite self-explanatory. If you're not sure which branch to use, you can go one by one and browse the profiles folder to see if the bundle identifier is listed there; it is unique across all accounts, so it should only be in one branch.

For instance, to install a development profile with certificate for the bundle id `my.company.weatherApp` you'd run:

`fastlane match --readonly --git_branch "company" --git_url "https://github.com/company/private_repository" --app_identifier "my.company.weatherApp" --type development`

You can also store a wildcard profile in the match repository, even if it does not have any real bundle identifier. In such a case you can just choose any identifier and use that, for instance `my.company.*`:

`fastlane match --readonly --git_branch "company" --git_url "https://github.com/company/private_repository" --app_identifier "my.company.*" --type development`

##Distribution profiles##

Distribution of the app to the App Store is basically the same as installing developer profiles, just change the `type` from `development` to `appstore`:

`fastlane match --readonly --git_branch "company" --git_url "https://github.com/company/private_repository" --app_identifier "my.company.weatherApp" --type appstore`

Distribution to the App Store is usually scripted in a Fastfile script, which consists of many different actions in addition to match. That is outside the scope of this post and is well explained in other posts on the Internet.

##Conclusion##

You can clean up your dev centre and avoid certificates/profiles being revoked accidentally by switching the responsibility to git versioned repository using match. You can trick match to think that the wildcard provisioning profile is just some made-up bundle id in order to store it in git. You can have multiple branches for multiple types of dev centre accounts for an extra level of tidiness. On top of all that, you save your development team a lot of time by distributing the scripts to install whatever they need, and you can make life a bit easier for newcomers as well.