Last active
August 20, 2024 14:39
-
-
Save perlmunger/8318538a02166ab4c275789a9feb8992 to your computer and use it in GitHub Desktop.
A wrapper around SecCertificate to simplify accessing iOS app certificate properties found in the embedded.mobileprovision file inside the bundle payload. The WrapperImplementation file shows how to extract both the provisioning profile and the details of each certificate. Works on iOS 12, iOS 13, and newer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct SecCertificateWrapper : Comparable { | |
var data:Data | |
var cert:SecCertificate | |
// Initialize with a data object from the "DeveloperCertificates" | |
// array (see WrapperImplementation.swift) | |
init(data:Data) { | |
self.cert = SecCertificateCreateWithData(nil, data as CFData)! | |
// Use this later for parsing the date details from the cert | |
self.data = data | |
} | |
// The certificate name | |
var name : String { | |
return SecCertificateCopySubjectSummary(cert)! as String | |
} | |
// Return a tuple with both notValidBefore and notValidAfter dates | |
var validDates : (notValidBefore:Date, notValidAfter:Date)? { | |
guard let decodedString = String( data: self.data, encoding: .ascii ) else { return nil } | |
var foundWWDRCA = false | |
var notValidBeforeDate = "" | |
var notValidAfterDate = "" | |
decodedString.enumerateLines { (line, _) in | |
if foundWWDRCA && (notValidBeforeDate.isEmpty || notValidAfterDate.isEmpty) { | |
let certificateData = line.prefix(13) | |
if notValidBeforeDate.isEmpty && !certificateData.isEmpty { | |
notValidBeforeDate = String(certificateData) | |
} else if notValidAfterDate.isEmpty && !certificateData.isEmpty { | |
notValidAfterDate = String(certificateData) | |
} | |
} | |
if line.contains("Apple Worldwide Developer Relations Certification Authority") { foundWWDRCA = true } | |
} | |
return (self.format(notValidBeforeDate), self.format(notValidAfterDate)) | |
} | |
// Some convenience properties for access to the notValidBefore and notValidAfter | |
// dates in various formats | |
var notValidAfterUnixDate : Double { | |
return self.validDates?.notValidAfter.timeIntervalSince1970 ?? 0 | |
} | |
var notValidAfterUnixDateAsString : String { | |
return String(self.notValidAfterUnixDate) | |
} | |
var notValidBeforeUnixDate : Double { | |
return self.validDates?.notValidBefore.timeIntervalSince1970 ?? 0 | |
} | |
var notValidBeforeUnixDateAsString : String { | |
return String(self.notValidBeforeUnixDate) | |
} | |
// MARK: - Utilities | |
// Implement comparable by providing a less than operator | |
static func < (lhs: SecCertificateWrapper, rhs: SecCertificateWrapper) -> Bool { | |
guard let lhsDate = lhs.validDates?.notValidAfter, let rhsDate = rhs.validDates?.notValidAfter else { return true } | |
return lhsDate < rhsDate | |
} | |
// Create a static data formatter just for convenience | |
static let certificateDateFormatter : DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.dateFormat = "yyMMddHHmmssZ" | |
return formatter | |
}() | |
// Provide a format function to help shorten the code where date | |
// formatting is performed | |
func format(_ date:String) -> Date { | |
return SecCertificateWrapper.certificateDateFormatter.date(from: date)! | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This implementation file shows how to extract details for the provisioning profile | |
// but also demonstrates how to grab the certificate info from the DeveloperCertificates | |
// arry found in the plist | |
guard let url = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision"), let data = try? Data(contentsOf: url), let string = String(data: data, encoding: String.Encoding.isoLatin1) else { | |
return | |
} | |
// Extract the relevant part of the data string from the start of the opening plist tag | |
// to the ending one. | |
let scanner = Scanner(string: string) | |
if scanner.scanUpTo("<plist", into: nil) { | |
var plistString:NSString? = "" | |
if scanner.scanUpTo("</plist>", into: &plistString) { | |
plistString = (plistString! as String) + "</plist>" as NSString | |
if let plistData = plistString?.data(using: String.Encoding.isoLatin1.rawValue) { | |
// Load the property list into a dictionary | |
if let mobileProvision = try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String:Any] { | |
// Grab all of the profile properties | |
let appName = mobileProvision["AppIDName"] as! String | |
let creationDate = mobileProvision["CreationDate"] as! Date | |
let expirationDate = mobileProvision["ExpirationDate"] as! Date | |
let teamName = mobileProvision["TeamName"] as! String | |
let ttl = mobileProvision["TimeToLive"] as! Int | |
let name = mobileProvision["Name"] as! String | |
// Grab the nested entitlements dictionary and relevant properties | |
let entitlements = mobileProvision["Entitlements"] as! [String:Any] | |
var taskAllow = entitlements["get-task-allow"] as? Bool ?? false | |
let appId = entitlements["application-identifier"] as! String | |
let teamId = entitlements["com.apple.developer.team-identifier"] as! String | |
// Iterate through the DeveloperCertificates array sorted by notValidAfter date | |
// (see Comparable implementation in the SecCertificateWrapper.swift file) and | |
// create and print SecCertificateWrapper objects | |
if let certData = mobileProvision["DeveloperCertificates"] as? [Data] { | |
for cert in certData.map( { SecCertificateWrapper(data: $0) }).sorted() { | |
debugPrint("Name: \(cert.name), Not Valid After Date: \(cert.notValidAfterUnixDateAsString)") | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment