@@ -20,32 +20,33 @@ public enum CertificateError: Error {
2020 case unknown
2121}
2222
23- /// CertificatesManager handles cert extraction/checking and exposes the currently selected identity.
24- /// Replace `SecIdentity` with your own wrapper type if you use a custom model.
2523public final class CertificatesManager : ObservableObject {
2624 public static let shared = CertificatesManager ( )
2725 private init ( ) { }
2826
29- // REMOVED: @Published public var selectedCertificate: SecIdentity? = nil
30-
3127 /// Returns the currently selected SecIdentity by loading from the selected folder in UserDefaults
3228 public var selectedIdentity : SecIdentity ? {
3329 guard let folderName = UserDefaults . standard. string ( forKey: " selectedCertificateFolder " ) ,
3430 !folderName. isEmpty else {
3531 return nil
3632 }
3733
38- let certDir = CertificateFileManager . shared. certificatesDirectory. appendingPathComponent ( folderName)
34+ let certDir = CertificateFileManager . shared. certificatesDirectory
35+ . appendingPathComponent ( folderName)
36+
3937 let p12URL = certDir. appendingPathComponent ( " certificate.p12 " )
40- let pwURL = certDir. appendingPathComponent ( " password.txt " )
38+ let pwURL = certDir. appendingPathComponent ( " password.txt " )
4139
4240 guard let p12Data = try ? Data ( contentsOf: p12URL) ,
43- let password = try ? String ( contentsOf: pwURL, encoding: . utf8) ? . trimmingCharacters ( in : . whitespacesAndNewlines ) else {
41+ let passwordRaw = try ? String ( contentsOf: pwURL, encoding: . utf8) else {
4442 return nil
4543 }
4644
45+ let password = passwordRaw. trimmingCharacters ( in: . whitespacesAndNewlines)
46+
4747 var items : CFArray ?
4848 let options = [ kSecImportExportPassphrase as String : password] as CFDictionary
49+
4950 let status = SecPKCS12Import ( p12Data as CFData , options, & items)
5051
5152 guard status == errSecSuccess,
@@ -55,35 +56,29 @@ public final class CertificatesManager: ObservableObject {
5556 return nil
5657 }
5758
58- return identityAny as! SecIdentity
59+ return identityAny as? SecIdentity
5960 }
6061
61- // MARK: - Utility: SHA256 hex
62+ // MARK: - SHA256 hex
6263 public static func sha256Hex( _ d: Data ) -> String {
6364 let digest = SHA256 . hash ( data: d)
6465 return digest. map { String ( format: " %02x " , $0) } . joined ( )
6566 }
6667
67- // MARK: - Export public key bytes for a SecCertificate
68+ // MARK: - Public key data
6869 private static func publicKeyData( from cert: SecCertificate ) throws -> Data {
6970 guard let secKey = SecCertificateCopyKey ( cert) else {
7071 throw CertificateError . certExtractionFailed
7172 }
72-
73- var cfErr : Unmanaged < CFError > ?
74- guard let keyData = SecKeyCopyExternalRepresentation ( secKey, & cfErr) as Data ? else {
75- if let cfError = cfErr? . takeRetainedValue ( ) {
76- let code = CFErrorGetCode ( cfError)
77- throw CertificateError . publicKeyExportFailed ( OSStatus ( code) )
78- } else {
79- throw CertificateError . publicKeyExportFailed ( - 1 )
80- }
73+ var error : Unmanaged < CFError > ?
74+ guard let data = SecKeyCopyExternalRepresentation ( secKey, & error) as Data ? else {
75+ let code = error? . takeRetainedValue ( ) . map { CFErrorGetCode ( $0) as OSStatus } ?? - 1
76+ throw CertificateError . publicKeyExportFailed ( code)
8177 }
82-
83- return keyData
78+ return data
8479 }
8580
86- // MARK: - Extract certificates array from mobileprovision (PKCS7) blob
81+ // MARK: - Extract certs from mobileprovision
8782 private static func certificatesFromMobileProvision( _ data: Data ) throws -> [ SecCertificate ] {
8883 let startTag = Data ( " <plist " . utf8)
8984 let endTag = Data ( " </plist> " . utf8)
@@ -96,122 +91,79 @@ public final class CertificatesManager: ObservableObject {
9691 let plistData = data [ startRange. lowerBound..< endRange. upperBound]
9792 let parsed = try PropertyListSerialization . propertyList ( from: Data ( plistData) , options: [ ] , format: nil )
9893
99- guard let dict = parsed as? [ String : Any ] else {
100- throw CertificateError . plistExtractionFailed
94+ guard let dict = parsed as? [ String : Any ] ,
95+ let devArray = dict [ " DeveloperCertificates " ] as? [ Any ] else {
96+ throw CertificateError . noCertsInProvision
10197 }
10298
103- var resultCerts : [ SecCertificate ] = [ ]
104- if let devArray = dict [ " DeveloperCertificates " ] as? [ Any ] {
105- for item in devArray {
106- if let certData = item as? Data {
107- if let secCert = SecCertificateCreateWithData ( nil , certData as CFData ) {
108- resultCerts. append ( secCert)
109- }
110- } else if let base64String = item as? String ,
111- let certData = Data ( base64Encoded: base64String) {
112- if let secCert = SecCertificateCreateWithData ( nil , certData as CFData ) {
113- resultCerts. append ( secCert)
114- }
115- }
99+ var result : [ SecCertificate ] = [ ]
100+ for item in devArray {
101+ if let certData = item as? Data ,
102+ let cert = SecCertificateCreateWithData ( nil , certData as CFData ) {
103+ result. append ( cert)
104+ } else if let base64 = item as? String ,
105+ let certData = Data ( base64Encoded: base64) ,
106+ let cert = SecCertificateCreateWithData ( nil , certData as CFData ) {
107+ result. append ( cert)
116108 }
117109 }
118110
119- if resultCerts. isEmpty {
120- throw CertificateError . noCertsInProvision
121- }
122-
123- return resultCerts
111+ guard !result. isEmpty else { throw CertificateError . noCertsInProvision }
112+ return result
124113 }
125114
126- // MARK: - Readable display name from mobileprovision
115+ // MARK: - Display name from provision
127116 public func getCertificateName( mobileProvisionData: Data ) -> String ? {
128- // Extract the <plist>...</plist> block
129117 let startTag = Data ( " <plist " . utf8)
130118 let endTag = Data ( " </plist> " . utf8)
131119 guard let startRange = mobileProvisionData. range ( of: startTag) ,
132- let endRange = mobileProvisionData. range ( of: endTag) else {
133- return nil
134- }
135-
136- let plistDataSlice = mobileProvisionData [ startRange. lowerBound..< endRange. upperBound]
137- let plistData = Data ( plistDataSlice)
120+ let endRange = mobileProvisionData. range ( of: endTag) else { return nil }
138121
139- guard let parsed = try ? PropertyListSerialization . propertyList ( from: plistData, options: [ ] , format: nil ) ,
140- let dict = parsed as? [ String : Any ] else {
141- return nil
142- }
122+ let plistData = mobileProvisionData [ startRange. lowerBound..< endRange. upperBound]
123+ guard let parsed = try ? PropertyListSerialization . propertyList ( from: Data ( plistData) , options: [ ] , format: nil ) ,
124+ let dict = parsed as? [ String : Any ] else { return nil }
143125
144- if let teamName = dict [ " TeamName " ] as? String , !teamName. isEmpty {
145- return teamName
146- }
147- if let name = dict [ " Name " ] as? String , !name. isEmpty {
148- return name
149- }
150- return nil
126+ return ( dict [ " TeamName " ] as? String ) ?? ( dict [ " Name " ] as? String )
151127 }
152128
153- // MARK: - Top-level check: verify p12 matches one of the embedded certs in mobileprovision
154- /// Returns .success(.success) if match, .success(.noMatch) if no match, or .failure(Error)
129+ // MARK: - Check p12 ↔ mobileprovision match
155130 public static func check( p12Data: Data , password: String , mobileProvisionData: Data ) -> Result < CertificateCheckResult , Error > {
156131 let options = [ kSecImportExportPassphrase as String : password] as CFDictionary
157- var itemsCF : CFArray ?
158-
159- let importStatus = SecPKCS12Import ( p12Data as CFData , options, & itemsCF)
160-
161- if importStatus == errSecAuthFailed {
162- return . success( . incorrectPassword)
163- }
132+ var items : CFArray ?
164133
165- guard importStatus == errSecSuccess, let items = itemsCF as? [ [ String : Any ] ] , items. count > 0 else {
166- return . failure( CertificateError . p12ImportFailed ( importStatus) )
167- }
134+ let status = SecPKCS12Import ( p12Data as CFData , options, & items)
168135
169- guard let first = items. first else {
170- return . failure( CertificateError . identityExtractionFailed)
171- }
136+ if status == errSecAuthFailed { return . success( . incorrectPassword) }
172137
173- // kSecImportItemIdentity should be present
174- guard let identityAny = first [ kSecImportItemIdentity as String ] else {
175- return . failure( CertificateError . identityExtractionFailed)
138+ guard status == errSecSuccess,
139+ let itemsArray = items as? [ [ String : Any ] ] ,
140+ let identityAny = itemsArray. first ? [ kSecImportItemIdentity as String ] ,
141+ CFGetTypeID ( identityAny as CFTypeRef ) == SecIdentityGetTypeID ( ) ,
142+ let identity = identityAny as? SecIdentity else {
143+ return . failure( CertificateError . p12ImportFailed ( status) )
176144 }
177145
178- // Verify CFTypeID is SecIdentity, then force-cast
179- guard CFGetTypeID ( identityAny as CFTypeRef ) == SecIdentityGetTypeID ( ) else {
180- return . failure( CertificateError . identityExtractionFailed)
181- }
182- let identity = identityAny as! SecIdentity
183-
184146 var certRef : SecCertificate ?
185- let certStatus = SecIdentityCopyCertificate ( identity, & certRef)
186-
187- guard certStatus == errSecSuccess, let p12Cert = certRef else {
147+ guard SecIdentityCopyCertificate ( identity, & certRef) == errSecSuccess,
148+ let p12Cert = certRef else {
188149 return . failure( CertificateError . certExtractionFailed)
189150 }
190151
191152 do {
192- let p12PubKeyData = try publicKeyData ( from: p12Cert)
193- let p12Hash = sha256Hex ( p12PubKeyData)
194-
195- let embeddedCerts = try certificatesFromMobileProvision ( mobileProvisionData)
196-
197- for cert in embeddedCerts {
198- do {
199- let embPubKeyData = try publicKeyData ( from: cert)
200- let embHash = sha256Hex ( embPubKeyData)
201-
202- if embHash == p12Hash {
203- return . success( . success)
204- }
205- } catch {
206- // continue checking other embedded certs
207- continue
153+ let p12KeyData = try publicKeyData ( from: p12Cert)
154+ let p12Hash = sha256Hex ( p12KeyData)
155+
156+ let embedded = try certificatesFromMobileProvision ( mobileProvisionData)
157+
158+ for cert in embedded {
159+ let keyData = try publicKeyData ( from: cert)
160+ if sha256Hex ( keyData) == p12Hash {
161+ return . success( . success)
208162 }
209163 }
210-
211164 return . success( . noMatch)
212165 } catch {
213166 return . failure( error)
214167 }
215168 }
216- }
217-
169+ }
0 commit comments