Reading iOS Provisioning Profile in your Swift App

In this short post, I will describe how to a read provisioning profile from iOS mobile app to discover some apps metadata.

Mickaël Rémond
· 3 min read
Send by email

The mobile provisioning profile is a file embedded by XCode when you build and package your application. It contains several pieces of information that can be useful for your app. For example, you can get the Apple push environment to use, either sandbox for development apps or production for store or Testflight production builds.

We can help you build your next Swift app in weeks rather than months
Read more about ProcessOne and Swift »

Principles

Getting and reading the mobile provisioning file

The mobile provision file is embedded in the app. You can read it from the main bundle. You first need to read the path of that file from the main bundle:

let profilePath: String? = Bundle.main.path(forResource: "embedded",
                                            ofType: "mobileprovision")

And then you can try reading the file into an string:

let profileString = try? NSString.init(contentsOfFile: profilePath,
                                       encoding: String.Encoding.isoLatin1.rawValue)

Extracting the metadata plist

The provisioning profile contains a plist file that exposes some app metadata. However, the plist string is embedded in a larger binary file. The easiest way to extract the plist is to read only the XML part of the file. This can be done as follows:

// Skip binary part at the start of the mobile provisionning profile
let scanner = Scanner(string: profileString as String)
guard scanner.scanUpTo("<plist", into: nil) != false else { return nil }

// ... and extract plist until end of plist payload (skip the end binary part.
var extractedPlist: NSString?
guard scanner.scanUpTo("</plist>", into: &extractedPlist) != false else { return nil }

guard let plist = extractedPlist?.appending("</plist>").data(using: .isoLatin1) else { return nil }

Parsing the plist string

You can finally parse the plist strong to get the plist content into a MobileProvision struct, using Swift decodable protocol, introduced in Swift 4:

let decoder = PropertyListDecoder()
do {
   let provision = try decoder.decode(MobileProvision.self, from: plist)
   // Do something with the data
} catch let err {
   // Handle parsing error
}

Putting it all together

Here is the full code with the MobileProvision struct implementing the Decodable protocol:

//
// MobileProvision.swift
// Fluux.io
//
// Created by Mickaël Rémond on 03/11/2018.
// Copyright © 2018 ProcessOne.
// Distributed under Apache License v2
//
import Foundation
/* Decode mobileprovision plist file
Usage:
1. To get mobileprovision data as embedded in your app:
MobileProvision.read()
2. To get mobile provision data from a file on disk:
MobileProvision.read(from: "my.mobileprovision")
*/
struct MobileProvision: Decodable {
var name: String
var appIDName: String
var platform: [String]
var isXcodeManaged: Bool? = false
var creationDate: Date
var expirationDate: Date
var entitlements: Entitlements
private enum CodingKeys : String, CodingKey {
case name = "Name"
case appIDName = "AppIDName"
case platform = "Platform"
case isXcodeManaged = "IsXcodeManaged"
case creationDate = "CreationDate"
case expirationDate = "ExpirationDate"
case entitlements = "Entitlements"
}
// Sublevel: decode entitlements informations
struct Entitlements: Decodable {
let keychainAccessGroups: [String]
let getTaskAllow: Bool
let apsEnvironment: Environment
private enum CodingKeys: String, CodingKey {
case keychainAccessGroups = "keychain-access-groups"
case getTaskAllow = "get-task-allow"
case apsEnvironment = "aps-environment"
}
enum Environment: String, Decodable {
case development, production, disabled
}
init(keychainAccessGroups: Array<String>, getTaskAllow: Bool, apsEnvironment: Environment) {
self.keychainAccessGroups = keychainAccessGroups
self.getTaskAllow = getTaskAllow
self.apsEnvironment = apsEnvironment
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let keychainAccessGroups: [String] = (try? container.decode([String].self, forKey: .keychainAccessGroups)) ?? []
let getTaskAllow: Bool = (try? container.decode(Bool.self, forKey: .getTaskAllow)) ?? false
let apsEnvironment: Environment = (try? container.decode(Environment.self, forKey: .apsEnvironment)) ?? .disabled
self.init(keychainAccessGroups: keychainAccessGroups, getTaskAllow: getTaskAllow, apsEnvironment: apsEnvironment)
}
}
}
// Factory methods
extension MobileProvision {
// Read mobileprovision file embedded in app.
static func read() -> MobileProvision? {
let profilePath: String? = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision")
guard let path = profilePath else { return nil }
return read(from: path)
}
// Read a .mobileprovision file on disk
static func read(from profilePath: String) -> MobileProvision? {
guard let plistDataString = try? NSString.init(contentsOfFile: profilePath,
encoding: String.Encoding.isoLatin1.rawValue) else { return nil }
// Skip binary part at the start of the mobile provisionning profile
let scanner = Scanner(string: plistDataString as String)
guard scanner.scanUpTo("<plist", into: nil) != false else { return nil }
// ... and extract plist until end of plist payload (skip the end binary part.
var extractedPlist: NSString?
guard scanner.scanUpTo("</plist>", into: &extractedPlist) != false else { return nil }
guard let plist = extractedPlist?.appending("</plist>").data(using: .isoLatin1) else { return nil }
let decoder = PropertyListDecoder()
do {
let provision = try decoder.decode(MobileProvision.self, from: plist)
return provision
} catch {
// TODO: log / handle error
return nil
}
}
}

MobileProvision.swift hosted by GitHub

You can use the code from your mobile app as follows:

var sandbox: Bool
if let provision = MobileProvision.read() {
   print("We need to use Sandbox")
   sandbox = provision.entitlements.apsEnvironment == .development ? true : false
}

That’s it. Your application is now able to discover if it should use APNS in development or production mode automatically, without having to rely on defining a DEBUG build environment variable.