Here at Runway we've written about the wonderful fastlane set of tools in the past. It's great! One of the best tools fastlane offers is match. Match interfaces with the App Store Connect API to sync your provisioning profiles and code signing credentials to a storage location of your choosing (usually a GitHub repo) so they can be accessed from a centralized place. fastlane match can also automatically renew credentials for your apps using the App Store Connect API. You can learn more about how to use fastlane match to automate iOS code signing tasks on our blog.
But fastlane is also built in Ruby, which carries along some requirements of at least a little Ruby knowledge to build your workflows and the ability to keep up a Ruby environment. What if you wanted a centralized way to manage signing certificates but wanted to avoid Ruby and fastlane match? Luckily, everything that’s needed to accomplish this is available in the App Store Connect API, and by leveraging our OpenAPI Swift client we can build a lot of this functionality in Swift relatively easily. If you haven’t gone through our tutorial for setting up an OpenAPI client for the App Store Connect API yet, we highly recommend that as a first step before continuing.
To get started, we’ll look at how to use the App Store Connect API to fetch provisioning profiles and signing certificates that we can later store in a location of our choosing. In Part 2 of this post, we’ll see how we can again leverage the App Store Connect API via our Swift client to regenerate provisioning profiles and signing certificates when needed.
Finding the profiles
The App Store Connect API documentation has a handy sidebar which lists out different sections of topics. Among them is a group called Provisioning, with sub-sections for Certificates and Profiles. Digging into these sections will give us the endpoints and models we'll need to interact with.
Let's start with profiles. The <code>v1/profiles<code> documentation endpoint will return all the profiles for our App Store Connect account. The return values include <code>data<code> of type <code>[Profile]<code> (an array of provisioning profiles) and paging information to get the next set of profiles until they are all downloaded.
We build a <code>Profile<code> type which contains much of the contents from the schema object being returned to us (which is a Profile resource) and extract from it the things we want.
- The profile's name as it appears in the developer portal.
- The platform that the profile supports. Surprisingly this only includes options for iOS and macOS. This is likely because Apple's other platforms – tvOS, watchOS, visionOS – are other iOS variants and use the same platform code. macOS is a very different animal. Because the schema lists the API's return type as BundleIDPlatform we'll create our own corresponding type, which will come in handy later.
- The actual content of the profile – which will get serialized to disk and saved out for Xcode to read in.
- Whether or not the profile is active. The API call returns to us all profiles on your developer account so we'll use this to keep only the active ones.
The next thing to do is add the endpoint to our OpenAPI configuration so the generator will build out the code we need:
Here we now have the <code>v1/profiles<code> path generating the needed code for the endpoint. Taking this, let's expand on our <code>APIClient<code> that we started in a prior post and add fetching of profiles:
This API call looks really similar to the ones we've made before. The generated code gives us the `client.profiles_hyphen_get_collection()` code and we'll extract from an ok response the JSON body and create our array of profiles. We added a helper static function on `Profile` to parse the individual schemas from the whole response.
A date detour
If we try to run this code, we'll find that it doesn't work. There's an error coming from the decoding of the payload from the API, and the stack trace will point to the <code>expirationDate<code> property of a profile. The problem here is that our OpenAPI Runtime package uses an <code>ISO8601DateFormatter<code> by default, and the string coming back from the API doesn't match what that type can handle. To fix this we'll need to add our own formatter as a fallback in case the default fails. Thankfully this is pretty straightforward.
In our <code>APIClient<code> initializer, we'll need to specify a custom <code>Configuration<code> to the generated <code>Client<code> class:
The manager gets created with an API client, calls our new method to fetch the profiles, and can take a profile and write it to disk in the directory Xcode looks for. This is great!
Signing the deal
Provisioning profiles are but only one part of the signing process. We also need a certificate which cryptographically signs the profiles and signs our apps too. Let's fetch some certificates!
We'll start again with our model (which is a Certificate resource) and the key information is in its <code>attributes<code> property. Notice the <code>platform<code> attribute of the certificate is our <code>BundleIDPlatform<code> that we built out earlier!
We've now got a <code>Certificate<code> type with the necessary details picked out from the returned schema. We reused our <code>BundleID.Platform<code> type from earlier and made a new enum for the different types of certificates that could be returned.
Let's build the API client call next. The endpoint to hit is <code>v1/certificates<code> so that gets added to our OpenAPI config file paths (see the configuration above). Next we'll build the <code>APIClient<code> code.
Like we did with profiles and apps, we can build out a <code>CertificateManager<code> to help fetch the certificates and write them to the keychain.
The Keychain APIs are difficult to work with. If you have a favorite package already for handling them then it should be able to plug in here just fine, otherwise check out the KeychainServices documentation to fill that in. It's a bit out of scope for this post.
That's a wrap
In this article you've seen how we can use the App Store Connect API to fetch provisioning profiles and signing certificates, just like fastlane match does! There's a lot more the API can do, like creating and deleting/revoking those items. This is the foundation for a very powerful set of tools that can help you centralize your org’s code signing identities so you can integrate them into your workflows precisely how you want to.
‍