@@ -14,7 +14,8 @@ public enum CertificateError: Error {
1414 case certExtractionFailed
1515 case noCertsInProvision
1616 case publicKeyExportFailed( OSStatus )
17- case unsupportedPlatform
17+ case plistExtractionFailed
18+ case unknown
1819}
1920
2021public final class CertificatesManager {
@@ -33,48 +34,64 @@ public final class CertificatesManager {
3334 var cfErr : Unmanaged < CFError > ?
3435 guard let keyData = SecKeyCopyExternalRepresentation ( secKey, & cfErr) as Data ? else {
3536 if let cfError = cfErr? . takeRetainedValue ( ) {
36- let nsError = cfError as NSError
37- throw CertificateError . publicKeyExportFailed ( OSStatus ( nsError. code) )
37+ // get numeric code from CFError safely
38+ let code = CFErrorGetCode ( cfError)
39+ throw CertificateError . publicKeyExportFailed ( OSStatus ( code) )
3840 } else {
3941 throw CertificateError . publicKeyExportFailed ( - 1 )
4042 }
4143 }
4244 return keyData
4345 }
4446
45- // Parse PKCS#7 (DER) from the mobileprovision using Security's CMSDecoder ,
46- // convert to SecCertificate array (no OpenSSL required)
47+ // Extract the <plist>...</plist> portion from a . mobileprovision (PKCS7) blob ,
48+ // parse it to a dictionary and return SecCertificate objects from DeveloperCertificates.
4749 private static func certificatesFromMobileProvision( _ data: Data ) throws -> [ SecCertificate ] {
48- // Create decoder
49- var decoderOptional : CMSDecoder ?
50- var status = CMSDecoderCreate ( & decoderOptional)
51- guard status == errSecSuccess, let decoder = decoderOptional else {
52- throw CertificateError . certExtractionFailed
53- }
50+ // Find XML plist bounds inside the blob
51+ let startTag = Data ( " <plist " . utf8)
52+ let endTag = Data ( " </plist> " . utf8)
5453
55- // Feed data into decoder
56- let updateStatus : OSStatus = data. withUnsafeBytes { ( rawPtr: UnsafeRawBufferPointer ) in
57- guard let base = rawPtr. baseAddress else { return errSecParam }
58- return CMSDecoderUpdateMessage ( decoder, base. assumingMemoryBound ( to: UInt8 . self) , data. count)
54+ guard let startRange = data. range ( of: startTag) ,
55+ let endRange = data. range ( of: endTag) else {
56+ throw CertificateError . plistExtractionFailed
5957 }
60- guard updateStatus == errSecSuccess else {
61- throw CertificateError . certExtractionFailed
58+
59+ // endRange.upperBound is index after </plist>
60+ let plistData = data [ startRange. lowerBound..< endRange. upperBound]
61+
62+ // Parse plist
63+ let parsed = try PropertyListSerialization . propertyList ( from: Data ( plistData) , options: [ ] , format: nil )
64+ guard let dict = parsed as? [ String : Any ] else {
65+ throw CertificateError . plistExtractionFailed
6266 }
6367
64- // Finalize
65- status = CMSDecoderFinalizeMessage ( decoder)
66- guard status == errSecSuccess else {
67- throw CertificateError . certExtractionFailed
68+ // Typical key with embedded certs in provisioning profiles:
69+ // "DeveloperCertificates" -> [Data] (DER blobs)
70+ var resultCerts : [ SecCertificate ] = [ ]
71+
72+ if let devArray = dict [ " DeveloperCertificates " ] as? [ Any ] {
73+ for item in devArray {
74+ if let certData = item as? Data {
75+ if let secCert = SecCertificateCreateWithData ( nil , certData as CFData ) {
76+ resultCerts. append ( secCert)
77+ }
78+ } else if let base64String = item as? String ,
79+ let certData = Data ( base64Encoded: base64String) {
80+ if let secCert = SecCertificateCreateWithData ( nil , certData as CFData ) {
81+ resultCerts. append ( secCert)
82+ }
83+ } else {
84+ // ignore unknown item types
85+ continue
86+ }
87+ }
6888 }
6989
70- // Extract all certificates
71- var certsCF : CFArray ?
72- status = CMSDecoderCopyAllCerts ( decoder, & certsCF)
73- guard status == errSecSuccess, let certsArray = certsCF as? [ SecCertificate ] , !certsArray. isEmpty else {
90+ if resultCerts. isEmpty {
7491 throw CertificateError . noCertsInProvision
7592 }
7693
77- return certsArray
94+ return resultCerts
7895 }
7996
8097 /// Top-level check: returns result
@@ -96,13 +113,12 @@ public final class CertificatesManager {
96113 return . failure( CertificateError . p12ImportFailed ( importStatus) )
97114 }
98115
99- // Force -cast to SecIdentity ( import guarantees this key exists for valid PKCS12 )
116+ // Grab identity (force -cast is safe because import succeeded and the dictionary contains the identity )
100117 guard let first = items. first else {
101118 return . failure( CertificateError . identityExtractionFailed)
102119 }
103- guard let identity = first [ kSecImportItemIdentity as String ] as? SecIdentity else {
104- return . failure( CertificateError . identityExtractionFailed)
105- }
120+ // use force-cast to avoid "conditional downcast ... will always succeed" warnings
121+ let identity = first [ kSecImportItemIdentity as String ] as! SecIdentity
106122
107123 // 2) extract certificate from identity
108124 var certRef : SecCertificate ?
@@ -116,7 +132,7 @@ public final class CertificatesManager {
116132 let p12PubKeyData = try publicKeyData ( from: p12Cert)
117133 let p12Hash = sha256Hex ( p12PubKeyData)
118134
119- // 4) parse mobileprovision and check embedded certs
135+ // 4) parse mobileprovision and check embedded certs (no OpenSSL)
120136 let embeddedCerts = try certificatesFromMobileProvision ( mobileProvisionData)
121137
122138 for cert in embeddedCerts {
0 commit comments