I don't have access to Xcode 8 / Swift 3.2, but here is the code needed to do what you want in Swift 4. I've tested it on a couple of profiles / certs I have available to me, and it gets the information you are requesting.
Provisioning Profile
func getProvisioningProfileExpirationDate() -> Date?
{
self.getCertificateExpirationDate()
let profilePath: String? = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision")
if( profilePath != nil )
{
let plistData = NSData(contentsOfFile: profilePath!)
let plistDataString = String(format: "%@", plistData!)
var plistString: String = extractPlist(fromMobileProvisionDataString:plistDataString)
let pattern = "<key>ExpirationDate</key>.*<date>(.*)</date>"
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let textCheckingResult : NSTextCheckingResult = regex.firstMatch(in: plistString, options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)), range: NSMakeRange(0, plistString.characters.count))!
let matchRange : NSRange = textCheckingResult.range(at: 1)
let expirationDateString : String = (plistString as NSString).substring(with: matchRange)
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
print( "Profile expires: (dateFormatter.date(from: expirationDateString)!)" )
return dateFormatter.date(from: expirationDateString)!
}
return nil
}
We need to do some manipulation, since the embedded.mobileprovision file is not readable without converting it from hex, and then pulling out just the stuff between the plist tags.
func extractPlist( fromMobileProvisionDataString:String ) -> String
{
// Remove brackets at beginning and end
var range = Range(NSMakeRange(0, 1), in: fromMobileProvisionDataString)
var plistDataString = fromMobileProvisionDataString.replacingCharacters(in:range!, with: "")
range = Range(NSMakeRange(plistDataString.count-1, 1), in: plistDataString)
plistDataString.replaceSubrange(range!, with: "")
// Remove spaces
plistDataString = plistDataString.replacingOccurrences(of: " ", with: "")
// convert hex to ascii
let profileText = hexStringtoAscii( plistDataString )
// I tried using regular expressions and normal NSString operations to get this, but it simply wouldn't work, so I went with this ugly method.
// return extractPlistText(fromProfileString:profileText)
// Remove whitespaces and new lines characters and splits into individual lines.
let profileWords = profileText.components(separatedBy: CharacterSet.newlines)
var plistString = "";
var inPlist = false;
for word in profileWords
{
if( word.contains("<plist") ) { inPlist = true }
if( inPlist ) { plistString.append(" "); plistString.append( word ) }
if (word.contains("</plist")) { inPlist = false }
}
return plistString;
}
func hexStringtoAscii(_ hexString : String) -> String {
let pattern = "(0x)?([0-9a-f]{2})"
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let nsString = hexString as NSString
let matches = regex.matches(in: hexString, options: [], range: NSMakeRange(0, nsString.length))
let characters = matches.map {
Character(UnicodeScalar(UInt32(nsString.substring(with: $0.range(at: 2)), radix: 16)!)!)
}
return String(characters)
}
I've verified this works to pull out the expiration date from the embedded.mobileprovision file on a physical device. It would be trivial to pull out other elements from the profile plist data.
Certificate:
To get the certificate information, I was able to get it to work using the following:
func getCertificateExpirationDate() -> Date?
{
let profilePath: String? = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision")
if( profilePath != nil )
{
let plistData = NSData(contentsOfFile: profilePath!)
let plistDataString = String(format: "%@", plistData!)
var plistString: String = extractPlist(fromMobileProvisionDataString:plistDataString)
// Trying to extract thecert information aswell, but haven't gotten it to work.
let certPattern = "<key>DeveloperCertificates</key>\s*<array>\s*<data>([^<]*)</data>"
let certRegex = try! NSRegularExpression(pattern: certPattern, options: .caseInsensitive)
let certCheckingResult : NSTextCheckingResult = certRegex.firstMatch(in: plistString, options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)), range: NSMakeRange(0, plistString.characters.count))!
let certMatchRange : NSRange = certCheckingResult.range(at: 1)
let certDataString : String = (plistString as NSString).substring(with: certMatchRange)
let decodedData = Data(base64Encoded: certDataString, options: [])
let decodedString = String( data: decodedData!, encoding: .ascii )
let cfData = decodedData as! CFData
let certificate: SecCertificate = SecCertificateCreateWithData(nil, cfData)!
var description: CFString = SecCertificateCopySubjectSummary(certificate)!
print( "Certificate name: (description)")
let certDate = self.extractCertExpirationDate(fromDecodedCertDataString: decodedString!)
print( "Certificate expires: (certDate)")
let certOrg = self.extractCertOrg(fromDecodedCertDataString: decodedString!)
print( "Certificate organization: (certOrg)")
return certDate
}
return nil
}
func extractCertExpirationDate( fromDecodedCertDataString: String ) -> Date
{
// Remove new lines characters and split into individual lines.
let certWords = fromDecodedCertDataString.components(separatedBy: CharacterSet.newlines)
var foundWWDRCA = false;
var certStartDate = ""
var certEndDate = ""
var certOrg = ""
for word in certWords
{
if( foundWWDRCA && (certStartDate.isEmpty || certEndDate.isEmpty))
{
var certData = word.prefix(13)
if( certStartDate.isEmpty && !certData.isEmpty )
{
certStartDate = String( certData );
}
else if( certEndDate.isEmpty && !certData.isEmpty )
{
certEndDate = String( certData );
}
}
if( word.contains("Apple Worldwide Developer Relations Certification Authority") ) { foundWWDRCA = true }
}
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.dateFormat = "yyMMddHHmmssZ"
return dateFormatter.date(from: certEndDate)!
}
func extractCertOrg( fromDecodedCertDataString: String ) -> String
{
// Remove new lines characters and split into individual lines.
let certWords = fromDecodedCertDataString.components(separatedBy: CharacterSet.newlines)
var foundWWDRCA = false;
var certStartDate = ""
var certEndDate = ""
var certOrg = ""
for word in certWords
{
if( foundWWDRCA && (certStartDate.isEmpty || certEndDate.isEmpty))
{
var certData = word.prefix(13)
if( certStartDate.isEmpty && !certData.isEmpty )
{
certStartDate = String( certData );
}
else if( certEndDate.isEmpty && !certData.isEmpty )
{
certEndDate = String( certData );
}
}
else if( foundWWDRCA && word.contains("u{17}") && certOrg.isEmpty)
{
var orgString = word.suffix(word.count-1)
certOrg = String( orgString.prefix(orgString.count - 1))
}
if( word.contains("Apple Worldwide Developer Relations Certification Authority") ) { foundWWDRCA = true }
}
return certOrg
}
Note that this only checks the provisioning profile / cert that is bundled with the app when installed. It will not check other, potentially valid, profiles on the device. So even if the embedded profile has expired, there is a chance the app could still run, if there are other mechanisms for installing profiles on the device that are used (device management, installing another app with newer wildcard provisioning profile, etc. ). However, if the certificate used to sign the app has expired, it will not run, even if a newer provisioning profile exists on the device.
For the certificate information, I still think the safest way would be to use the openssl library to decript the DER encoded x509 certificate, but the parsing I was able to do after base64 decoding the certificate data seems to pull the information you need.