11import Foundation
22
3- public final class CertRevokeChecker {
4- public static func checkRevocation( folderName: String ) async -> String {
5- let certDir = CertificateFileManager . shared. certificatesDirectory. appendingPathComponent ( folderName)
6- let p12URL = certDir. appendingPathComponent ( " certificate.p12 " )
7- let provURL = certDir. appendingPathComponent ( " profile.mobileprovision " )
8- let passwordURL = certDir. appendingPathComponent ( " password.txt " )
9-
10- var password = " "
11- if let pwData = try ? Data ( contentsOf: passwordURL) , let pw = String ( data: pwData, encoding: . utf8) {
12- password = pw
13- }
14-
15- guard let p12Data = try ? Data ( contentsOf: p12URL) ,
16- let provData = try ? Data ( contentsOf: provURL) else {
17- return " Unknown "
18- }
19-
20- guard let url = URL ( string: " https://tools.nezushub.vip/cert-ios-checker/api/ " ) else {
21- return " Unknown "
3+ class CertChecker {
4+ static let baseURL = URL ( string: " https://check-p12.applep12.com/ " ) !
5+
6+ static func getToken( ) async throws -> String {
7+ let ( data, _) = try await URLSession . shared. data ( from: baseURL)
8+ let html = String ( data: data, encoding: . utf8) ?? " "
9+ let pattern = " (?i)<input \\ s+name= \" __RequestVerificationToken \" \\ s+type= \" hidden \" \\ s+value= \" ([^ \" ]+) \" "
10+ let regex = try ? NSRegularExpression ( pattern: pattern)
11+ if let match = regex? . firstMatch ( in: html, range: NSRange ( location: 0 , length: html. utf16. count) ) ,
12+ let range = Range ( match. range ( at: 1 ) , in: html) {
13+ return String ( html [ range] )
2214 }
23-
24- var request = URLRequest ( url: url)
15+ throw NSError ( domain: " Token not found " , code: 0 )
16+ }
17+
18+ static func submit( token: String , p12Data: Data , p12Filename: String , mpData: Data , mpFilename: String , password: String ) async throws -> String {
19+ var request = URLRequest ( url: baseURL)
2520 request. httpMethod = " POST "
26-
2721 let boundary = " Boundary- \( UUID ( ) . uuidString) "
2822 request. setValue ( " multipart/form-data; boundary= \( boundary) " , forHTTPHeaderField: " Content-Type " )
29-
23+ request. setValue ( " Mozilla/5.0 (Windows NT 10.0; Win64; x64) " , forHTTPHeaderField: " User-Agent " )
24+ request. setValue ( baseURL. absoluteString, forHTTPHeaderField: " Referer " )
25+ request. setValue ( " https://check-p12.applep12.com " , forHTTPHeaderField: " Origin " )
26+
3027 var body = Data ( )
31-
32- // Add p12 file
33- body. append ( " -- \( boundary) \r \n " . data ( using: . utf8) !)
34- body. append ( " Content-Disposition: form-data; name= \" file \" ; filename= \" certificate.p12 \" \r \n " . data ( using: . utf8) !)
35- body. append ( " Content-Type: application/octet-stream \r \n \r \n " . data ( using: . utf8) !)
36- body. append ( p12Data)
37- body. append ( " \r \n " . data ( using: . utf8) !)
38-
39- // Add mobileprovision file
40- body. append ( " -- \( boundary) \r \n " . data ( using: . utf8) !)
41- body. append ( " Content-Disposition: form-data; name= \" secondary_file \" ; filename= \" profile.mobileprovision \" \r \n " . data ( using: . utf8) !)
42- body. append ( " Content-Type: application/octet-stream \r \n \r \n " . data ( using: . utf8) !)
43- body. append ( provData)
44- body. append ( " \r \n " . data ( using: . utf8) !)
45-
46- // Add password
47- body. append ( " -- \( boundary) \r \n " . data ( using: . utf8) !)
48- body. append ( " Content-Disposition: form-data; name= \" password \" \r \n \r \n " . data ( using: . utf8) !)
49- body. append ( password. data ( using: . utf8) !)
50- body. append ( " \r \n " . data ( using: . utf8) !)
51-
52- // End boundary
28+
29+ func addPart( name: String , value: String ? = nil , filename: String ? = nil , contentType: String ? = nil , data: Data ? = nil ) {
30+ body. append ( " -- \( boundary) \r \n " . data ( using: . utf8) !)
31+ if let filename = filename {
32+ body. append ( " Content-Disposition: form-data; name= \" \( name) \" ; filename= \" \( filename) \" \r \n " . data ( using: . utf8) !)
33+ if let contentType = contentType {
34+ body. append ( " Content-Type: \( contentType) \r \n \r \n " . data ( using: . utf8) !)
35+ }
36+ } else {
37+ body. append ( " Content-Disposition: form-data; name= \" \( name) \" \r \n \r \n " . data ( using: . utf8) !)
38+ }
39+ if let value = value {
40+ body. append ( value. data ( using: . utf8) !)
41+ } else if let data = data {
42+ body. append ( data)
43+ }
44+ body. append ( " \r \n " . data ( using: . utf8) !)
45+ }
46+
47+ addPart ( name: " P12File " , filename: p12Filename, contentType: " application/x-pkcs12 " , data: p12Data)
48+ addPart ( name: " P12PassWord " , value: password)
49+ addPart ( name: " MobileProvisionFile " , filename: mpFilename, contentType: " application/octet-stream " , data: mpData)
50+ addPart ( name: " __RequestVerificationToken " , value: token)
5351 body. append ( " -- \( boundary) -- \r \n " . data ( using: . utf8) !)
54-
52+
5553 request. httpBody = body
56-
57- do {
58- let ( data, _) = try await URLSession . shared. data ( for: request)
59- if let json = try ? JSONSerialization . jsonObject ( with: data) as? [ String : Any ] ,
60- let success = json [ " success " ] as? Bool , success,
61- let dataDict = json [ " data " ] as? [ String : Any ] ,
62- let certificate = dataDict [ " certificate " ] as? [ String : Any ] ,
63- let comparison = certificate [ " comparison_data " ] as? [ String : Any ] ,
64- let match = comparison [ " certificates_match " ] as? Bool , match,
65- let certStatus = certificate [ " certificate_status " ] as? [ String : Any ] ,
66- let status = certStatus [ " status " ] as? String {
67- return status
68- } else {
69- return " Unknown "
54+
55+ let ( data, _) = try await URLSession . shared. data ( for: request)
56+ return String ( data: data, encoding: . utf8) ?? " "
57+ }
58+
59+ static func parseHTML( html: String ) -> [ String : Any ] {
60+ let divPattern = " (?i)<div \\ s+class= \" [^ \" ]*alert[^ \" ]* \" [^>]*>(.*?)</div> "
61+ let divRegex = try ? NSRegularExpression ( pattern: divPattern, options: . dotMatchesLineSeparators)
62+ guard let match = divRegex? . firstMatch ( in: html, range: NSRange ( location: 0 , length: html. utf16. count) ) ,
63+ let range = Range ( match. range ( at: 1 ) , in: html) else {
64+ return [ " error " : " No certificate info found in response " ]
65+ }
66+
67+ var divContent = String ( html [ range] )
68+ divContent = divContent. replacingOccurrences ( of: " <br/> " , with: " \n " , options: . caseInsensitive)
69+ divContent = divContent. replacingOccurrences ( of: " <br /> " , with: " \n " , options: . caseInsensitive)
70+ let tagRegex = try ? NSRegularExpression ( pattern: " <[^>]+> " , options: [ ] )
71+ divContent = tagRegex? . stringByReplacingMatches ( in: divContent, range: NSRange ( 0 ..< divContent. utf16. count) , withTemplate: " " ) ?? divContent
72+
73+ var lines = divContent. components ( separatedBy: . newlines) . map { $0. trimmingCharacters ( in: . whitespaces) } . filter { !$0. isEmpty }
74+
75+ let emojiRegex = try ? NSRegularExpression ( pattern: " [🟢🔴] " , options: [ ] )
76+ for i in 0 ..< lines. count {
77+ lines [ i] = emojiRegex? . stringByReplacingMatches ( in: lines [ i] , range: NSRange ( 0 ..< lines [ i] . utf16. count) , withTemplate: " " ) ?? lines [ i]
78+ }
79+
80+ var data : [ String : Any ] = [
81+ " certificate " : [ String: String] ( ) ,
82+ " mobileprovision " : [ String: String] ( ) ,
83+ " binding_certificate_1 " : [ String: String] ( ) ,
84+ " permissions " : [ String: String] ( )
85+ ]
86+
87+ func findIndex( prefixes: [ String ] , start: Int = 0 ) -> Int ? {
88+ for i in start..< lines. count {
89+ for p in prefixes {
90+ if lines [ i] . lowercased ( ) . hasPrefix ( p. lowercased ( ) ) {
91+ return i
92+ }
93+ }
94+ }
95+ return nil
96+ }
97+
98+ let certIdx = findIndex ( prefixes: [ " CertName: " , " CertName: " ] )
99+ let mpIdx = findIndex ( prefixes: [ " MP Name: " , " MP Name: " ] )
100+ let bindingIdx = findIndex ( prefixes: [ " Binding Certificates: " , " Binding Certificates: " ] , start: mpIdx ?? 0 )
101+ let certMatchingIdx = findIndex ( prefixes: [ " Certificate Matching Status: " , " Certificate Matching Status: " ] , start: bindingIdx ?? 0 )
102+
103+ func splitKV( line: String ) -> ( String , String ) {
104+ let separators = [ " : " , " : " ]
105+ for sep in separators {
106+ if let range = line. range ( of: sep) {
107+ let k = String ( line [ ..< range. lowerBound] ) . trimmingCharacters ( in: . whitespaces)
108+ let v = String ( line [ range. upperBound... ] ) . trimmingCharacters ( in: . whitespaces)
109+ return ( k, v)
110+ }
111+ }
112+ return ( line. trimmingCharacters ( in: . whitespaces) , " " )
113+ }
114+
115+ if let certIdx = certIdx {
116+ let end = mpIdx ?? ( bindingIdx ?? lines. count)
117+ for i in certIdx..< end {
118+ let ( k, v) = splitKV ( line: lines [ i] )
119+ let lk = k. lowercased ( )
120+ var cert = data [ " certificate " ] as! [ String : String ]
121+ if lk. hasPrefix ( " certname " ) {
122+ cert [ " name " ] = v
123+ } else if lk. hasPrefix ( " effective date " ) {
124+ cert [ " effective " ] = v
125+ } else if lk. hasPrefix ( " expiration date " ) {
126+ cert [ " expiration " ] = v
127+ } else if lk. hasPrefix ( " issuer " ) {
128+ cert [ " issuer " ] = v
129+ } else if lk. hasPrefix ( " country " ) {
130+ cert [ " country " ] = v
131+ } else if lk. hasPrefix ( " organization " ) {
132+ cert [ " organization " ] = v
133+ } else if lk. hasPrefix ( " certificate number (hex) " ) {
134+ cert [ " number_hex " ] = v
135+ } else if lk. hasPrefix ( " certificate number (decimal) " ) {
136+ cert [ " number_decimal " ] = v
137+ } else if lk. hasPrefix ( " certificate status " ) {
138+ cert [ " status " ] = v
139+ }
140+ data [ " certificate " ] = cert
141+ }
142+ }
143+
144+ if let mpIdx = mpIdx {
145+ let end = bindingIdx ?? ( certMatchingIdx ?? lines. count)
146+ for i in mpIdx..< end {
147+ let ( k, v) = splitKV ( line: lines [ i] )
148+ let lk = k. lowercased ( )
149+ var mp = data [ " mobileprovision " ] as! [ String : String ]
150+ if lk. hasPrefix ( " mp name " ) {
151+ mp [ " name " ] = v
152+ } else if lk. hasPrefix ( " app id " ) {
153+ mp [ " app_id " ] = v
154+ } else if lk. hasPrefix ( " identifier " ) {
155+ mp [ " identifier " ] = v
156+ } else if lk. hasPrefix ( " platform " ) {
157+ mp [ " platform " ] = v
158+ } else if lk. hasPrefix ( " effective date " ) {
159+ if mp [ " effective " ] == nil {
160+ mp [ " effective " ] = v
161+ }
162+ } else if lk. hasPrefix ( " expiration date " ) {
163+ if mp [ " expiration " ] == nil {
164+ mp [ " expiration " ] = v
165+ }
166+ }
167+ data [ " mobileprovision " ] = mp
70168 }
71- } catch {
72- return " Unknown "
73169 }
170+
171+ if let bindingIdx = bindingIdx {
172+ let cert1Idx = findIndex ( prefixes: [ " Certificate 1: " , " Certificate 1: " , " Certificate 1 " ] , start: bindingIdx)
173+ if let cert1Idx = cert1Idx {
174+ let cert2Idx = findIndex ( prefixes: [ " Certificate 2: " , " Certificate 2: " , " Certificate 2 " ] , start: cert1Idx + 1 )
175+ let end = cert2Idx ?? ( certMatchingIdx ?? lines. count)
176+ for i in ( cert1Idx + 1 ) ..< end {
177+ let ( k, v) = splitKV ( line: lines [ i] )
178+ let lk = k. lowercased ( )
179+ var bc1 = data [ " binding_certificate_1 " ] as! [ String : String ]
180+ if lk. hasPrefix ( " certificate status " ) {
181+ bc1 [ " status " ] = v
182+ } else if lk. hasPrefix ( " certificate number (hex) " ) {
183+ bc1 [ " number_hex " ] = v
184+ } else if lk. hasPrefix ( " certificate number (decimal) " ) {
185+ bc1 [ " number_decimal " ] = v
186+ }
187+ data [ " binding_certificate_1 " ] = bc1
188+ }
189+ }
190+ }
191+
192+ let permKeys = [
193+ " Apple Push Notification Service " ,
194+ " HealthKit " ,
195+ " VPN " ,
196+ " Communication Notifications " ,
197+ " Time-sensitive Notifications "
198+ ]
199+ for line in lines {
200+ for pk in permKeys {
201+ if line. hasPrefix ( pk) {
202+ let ( _, v) = splitKV ( line: line)
203+ var perms = data [ " permissions " ] as! [ String : String ]
204+ perms [ pk] = v
205+ data [ " permissions " ] = perms
206+ }
207+ }
208+ }
209+
210+ if let certMatchingIdx = certMatchingIdx {
211+ let ( _, v) = splitKV ( line: lines [ certMatchingIdx] )
212+ data [ " certificate_matching_status " ] = v
213+ }
214+
215+ return data
216+ }
217+
218+ static func checkCert( mobileProvision: Data , mobileProvisionFilename: String = " example.mobileprovision " ,
219+ p12: Data , p12Filename: String = " example.p12 " ,
220+ password: String ) async throws -> [ String : Any ] {
221+ let token = try await getToken ( )
222+ let html = try await submit ( token: token, p12Data: p12, p12Filename: p12Filename, mpData: mobileProvision, mpFilename: mobileProvisionFilename, password: password)
223+ return parseHTML ( html: html)
74224 }
75- }
225+ }
0 commit comments