Explorar o código

add certificate pinning and adjustment secure user defaults in MAB

alqindiirsyam123 hai 8 meses
pai
achega
e3370aed23

BIN=BIN
.DS_Store


+ 1 - 1
AppBuilder/AppBuilder/AppDelegate.swift

@@ -101,7 +101,7 @@ extension AppDelegate: ConnectDelegate, UNUserNotificationCenterDelegate {
         editorPersonalVC.hidesBottomBarWhenPushed = true
         editorPersonalVC.unique_l_pin = opposite
         editorPersonalVC.fromNotification = true
-        let onGoingCC = UserDefaults.standard.string(forKey: "onGoingCC") ?? ""
+        let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
         if !onGoingCC.isEmpty {
             let compalintId = onGoingCC.components(separatedBy: ",")[2]
             let fPinCC = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]

+ 49 - 8
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -9,6 +9,7 @@ import UIKit
 import WebKit
 import NexilisLite
 import Speech
+import CommonCrypto
 
 class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler {
     
@@ -107,10 +108,10 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     }
     
     override func viewWillAppear(_ animated: Bool) {
-        let me = UserDefaults.standard.string(forKey: "me")
+        let me = User.getMyPin()
         
         var myURL : URL?
-        let lang = UserDefaults.standard.string(forKey: "i18n_language")
+        let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
         var intLang = 0
         if lang == "id" {
             intLang = 1
@@ -469,7 +470,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             OperationQueue.main.addOperation() {
                 self.isAllowSpeech = isButtonEnabled
                 if isButtonEnabled {
-                    UserDefaults.standard.set(isButtonEnabled, forKey: "allowSpeech")
+                    SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech")
                     self.runVoice()
                 }
             }
@@ -597,12 +598,52 @@ extension FirstTabViewController: WKUIDelegate, WKNavigationDelegate {
         decisionHandler(.allow)
     }
     func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
-            let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
-            completionHandler(.useCredential, credential)
-        }
-        else {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
             completionHandler(.cancelAuthenticationChallenge, nil)
+            return
         }
+        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
+           let pinnedCertificateHash = getCertificateHash(from: serverCertificate),
+           pinnedCertificateHash == Utils.getCertificatePinningWebview() {
+            let credential = URLCredential(trust: serverTrust)
+            completionHandler(.useCredential, credential) // Certificate matches, proceed
+        } else {
+            completionHandler(.cancelAuthenticationChallenge, nil) // Certificate doesn't match, cancel
+        }
+    }
+    
+    private func getCertificateHash(from certificate: SecCertificate) -> String? {
+        guard let publicKey = getPublicKey(from: certificate) else { return nil }
+        return hashPublicKey(publicKey)
+    }
+    
+    private func getPublicKey(from certificate: SecCertificate) -> Data? {
+        var trust: SecTrust?
+        let policy = SecPolicyCreateBasicX509()
+        let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
+
+        guard status == errSecSuccess, let trust = trust else { return nil }
+
+        guard let publicKey = SecTrustCopyKey(trust) else { return nil }
+
+        var error: Unmanaged<CFError>?
+        if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) {
+            return publicKeyData as Data
+        } else {
+            print("Error extracting public key: \(String(describing: error?.takeRetainedValue()))")
+            return nil
+        }
+    }
+
+    private func hashPublicKey(_ publicKey: Data) -> String? {
+        // SHA-256 hash
+        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
+        publicKey.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKey.count), &hash)
+        }
+
+        // Base64 encode the hash
+        let base64Hash = Data(hash).base64EncodedString()
+        return "sha256/\(base64Hash)"
     }
 }

+ 17 - 18
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -48,9 +48,9 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         switchVibrateMode.onTintColor = .mainColor
         switchSaveToGallery.onTintColor = .mainColor
         switchAutoDownload.onTintColor = .mainColor
-        let vibrateMode = UserDefaults.standard.bool(forKey: "vibrateMode")
-        let saveGallery = UserDefaults.standard.bool(forKey: "saveToGallery")
-        let autoDownload = UserDefaults.standard.bool(forKey: "autoDownload")
+        let vibrateMode: Bool = SecureUserDefaults.shared.value(forKey: "vibrateMode") ?? false
+        let saveGallery: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
+        let autoDownload: Bool = SecureUserDefaults.shared.value(forKey: "autoDownload") ?? false
         if vibrateMode {
             switchVibrateMode.setOn(true, animated: false)
         }
@@ -115,7 +115,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
     }
     
     @objc func vibrateModeSwitch() {
-        UserDefaults.standard.set(switchVibrateMode.isOn, forKey: "vibrateMode")
+        SecureUserDefaults.shared.set(switchVibrateMode.isOn, forKey: "vibrateMode")
     }
     
     @objc func saveToGallerySwitch() {
@@ -123,19 +123,19 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             PHPhotoLibrary.requestAuthorization({status in
                 DispatchQueue.main.async {
                     if status == .authorized {
-                        UserDefaults.standard.set(self.switchSaveToGallery.isOn, forKey: "saveToGallery")
+                        SecureUserDefaults.shared.set(self.switchSaveToGallery.isOn, forKey: "saveToGallery")
                     } else {
                         self.switchSaveToGallery.setOn(false, animated: true)
                     }
                 }
             })
         } else {
-            UserDefaults.standard.set(self.switchSaveToGallery.isOn, forKey: "saveToGallery")
+            SecureUserDefaults.shared.set(self.switchSaveToGallery.isOn, forKey: "saveToGallery")
         }
     }
     
     @objc func autoDownloadSwitch() {
-        UserDefaults.standard.set(switchAutoDownload.isOn, forKey: "autoDownload")
+        SecureUserDefaults.shared.set(switchAutoDownload.isOn, forKey: "autoDownload")
     }
     
     func makeMenu(imageSignIn: String = ""){
@@ -151,7 +151,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             }
         } else {
             Database.shared.database?.inTransaction({ fmdb, rollback in
-                let idMe = UserDefaults.standard.string(forKey: "me") as String?
+                let idMe = User.getMyPin() as String?
                 if let cursorUser = Database.shared.getRecords(fmdb: fmdb, query: "SELECT user_type, image_id, official_account FROM BUDDY where f_pin='\(idMe!)'"), cursorUser.next() {
                     if (User.isInternal(userType: cursorUser.string(forColumnIndex: 0) ?? "") && User.isAdmin(fmdb: fmdb)) || User.isOfficial(official_account: cursorUser.string(forColumnIndex: 2) ?? "") || User.isOfficial(official_account: cursorUser.string(forColumnIndex: 2) ?? "") {
                         Item.menus["Personal"] = [
@@ -585,7 +585,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         if item.title == "Personal Information".localized() {
             if(ViewController.checkIsChangePerson()){
                 let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
-                controller.data = UserDefaults.standard.string(forKey: "me")!
+                controller.data = User.getMyPin()!
                 controller.flag = .me
                 controller.dismissImage = { image, imageName in
                     var dataImage: [AnyHashable : Any] = [:]
@@ -632,7 +632,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             pickerView.dataSource = self
             pickerView.delegate = self
             
-            let lang = UserDefaults.standard.string(forKey: "i18n_language")
+            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
             var index = 1
             if lang == "id" {
                 index = 0
@@ -653,7 +653,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             alert.addAction(UIAlertAction(title: "Select".localized(), style: .default, handler: { (UIAlertAction) in
                 let selectedIndex = pickerView.selectedRow(inComponent: 0)
                 let lang = self.language[selectedIndex].values.first
-                UserDefaults.standard.set(lang, forKey: "i18n_language")
+                SecureUserDefaults.shared.set(lang, forKey: "i18n_language")
                 self.navigationController?.navigationBar.topItem?.title = "Settings".localized();
                 self.navigationController?.navigationBar.setNeedsLayout()
                 self.makeMenu()
@@ -710,12 +710,11 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                         id = response.getBody(key: CoreMessage_TMessageKey.F_PIN, default_value: "")
                         if(!id.isEmpty){
 //                            Nexilis.changeUser(f_pin: id)
-                            UserDefaults.standard.setValue(id, forKey: "me")
+                            SecureUserDefaults.shared.set(id, forKey: "me")
                             Utils.setProfile(value: false)
-                            UserDefaults.standard.synchronize()
                             if Utils.getForceAnonymous() {
                                 self.deleteAllRecordDatabase()
-                                UserDefaults.standard.removeObject(forKey: "device_id")
+                                SecureUserDefaults.shared.removeValue(forKey: "device_id")
                                 Nexilis.destroyAll()
                                 _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: id))
                             }
@@ -1106,7 +1105,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
     
     private func signInAdmin(password: String, completion: @escaping (Bool) -> ()) {
         DispatchQueue.global().async {
-            let idMe = UserDefaults.standard.string(forKey: "me") as String?
+            let idMe = User.getMyPin() as String?
             let p_password = password
             let md5Hex = p_password
             var result: Bool = false
@@ -1141,7 +1140,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
     
     private func signInInternal(password: String, completion: @escaping (Bool) -> ()) {
         DispatchQueue.global().async {
-            let idMe = UserDefaults.standard.string(forKey: "me") as String?
+            let idMe = User.getMyPin() as String?
             let p_password = password
             let md5Hex = p_password
             var result: Bool = false
@@ -1176,7 +1175,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
     
     private func changePasswordAdmin(oldPassword: String, newPassword: String, completion: @escaping (Bool) -> ()) {
         DispatchQueue.global().async {
-            let idMe = UserDefaults.standard.string(forKey: "me") as String?
+            let idMe = User.getMyPin() as String?
             let p_password = oldPassword
             let n_password = newPassword
             let md5Hex = p_password
@@ -1213,7 +1212,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
     
     private func changePasswordInternal(oldPassword: String, newPassword: String, completion: @escaping (Bool) -> ()) {
         DispatchQueue.global().async {
-            let idMe = UserDefaults.standard.string(forKey: "me") as String?
+            let idMe = User.getMyPin() as String?
             let p_password = oldPassword
             let n_password = newPassword
             let md5Hex = p_password

+ 38 - 21
AppBuilder/AppBuilder/PrefsUtil.swift

@@ -6,6 +6,7 @@
 //
 
 import Foundation
+import NexilisLite
 
 
 class PrefsUtil {
@@ -16,7 +17,7 @@ class PrefsUtil {
     static let DEFAULT_CPAAS_MODE = CPAAS_MODE_DOCKED
     
     static func getCpaasMode() -> Int {
-        let mode = UserDefaults.standard.integer(forKey: "cpaas_mode")
+        let mode: Int! = SecureUserDefaults.shared.value(forKey: "cpaas_mode")
         if(mode != 0) {
             return mode - 1
         }
@@ -24,13 +25,13 @@ class PrefsUtil {
     }
     
     static func setCpaasMode(mode: Int){
-        UserDefaults.standard.set(mode+1, forKey: "cpaas_mode")
+        SecureUserDefaults.shared.set(mode+1, forKey: "cpaas_mode")
     }
     
     static var CUSTOM_TAB = "1,2,3,4"
     
     static func getCustomTab() -> String {
-        if let cust = UserDefaults.standard.string(forKey: "custom_tab"){
+        if let cust: String = SecureUserDefaults.shared.value(forKey: "custom_tab") {
             if(!cust.isEmpty){
                 return cust
             }
@@ -39,34 +40,43 @@ class PrefsUtil {
     }
     
     static func getURLFirstTab() -> String? {
-        return UserDefaults.standard.string(forKey: "app_builder_url_first_tab")
+        let value: String? = SecureUserDefaults.shared.value(forKey: "app_builder_url_first_tab")
+        return value
     }
     
     static func getURLThirdTab() -> String? {
-        return UserDefaults.standard.string(forKey: "app_builder_url_third_tab")
+        let value: String? = SecureUserDefaults.shared.value(forKey: "app_builder_url_third_tab")
+        return value
     }
     static func getURLBase() -> String {
-        return UserDefaults.standard.string(forKey: "app_builder_url_base") ?? "https://nexilis.io/"
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_url_base") ?? "https://nexilis.io/"
+        return value
     }
     static func getURLQMS() -> String? {
-        return UserDefaults.standard.string(forKey: "app_builder_url_qms")
+        let value: String? = SecureUserDefaults.shared.value(forKey: "app_builder_url_qms")
+        return value
     }
     static func getIconDock() -> String {
-        return UserDefaults.standard.string(forKey: "app_builder_icon_dock") ?? ""
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_icon_dock") ?? ""
+        return value
     }
     static func getIconSS() -> String? {
-        return UserDefaults.standard.string(forKey: "app_builder_icon_ss")
+        let value: String? = SecureUserDefaults.shared.value(forKey: "app_builder_icon_ss")
+        return value
     }
     static func getBackground() -> String {
-        return UserDefaults.standard.string(forKey: "app_builder_background") ?? ""
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_background") ?? ""
+        return value
     }
     
     static func getBackgroundLight() -> String {
-        UserDefaults.standard.string(forKey: "app_builder_background_light") ?? ""
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_background_light") ?? ""
+        return value
     }
 
     static func getBackgroundDark() -> String {
-        UserDefaults.standard.string(forKey: "app_builder_background_dark") ?? ""
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_background_dark") ?? ""
+        return value
     }
     
     static func getUrlSS() -> String? {
@@ -77,38 +87,45 @@ class PrefsUtil {
     }
     
     static func getURLPrivacyPolicy() -> String {
-        return UserDefaults.standard.string(forKey: "app_builder_url_privacy_policy") ?? "https://nexilis.io/privacypolicy"
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_url_privacy_policy") ?? "https://nexilis.io/privacypolicy"
+        return value
     }
     
     static func getEnablePrivacyPolicy() -> Bool {
-        return UserDefaults.standard.bool(forKey: "app_builder_enable_privacy_policy")
+        let value: Bool = SecureUserDefaults.shared.value(forKey: "app_builder_enable_privacy_policy") ?? false
+        return value
     }
     
     static func getAgreePrivacyPolicy() -> Bool {
-        return UserDefaults.standard.bool(forKey: "agree_privacy_policy")
+        let value: Bool = SecureUserDefaults.shared.value(forKey: "agree_privacy_policy") ?? false
+        return value
     }
     
     static func setAgreePrivacyPolicy(value: Bool){
-        UserDefaults.standard.set(value, forKey: "agree_privacy_policy")
+        SecureUserDefaults.shared.set(value, forKey: "agree_privacy_policy")
     }
     static func getTerms() -> Bool {
-        return UserDefaults.standard.bool(forKey: "terms_app")
+        let value: Bool = SecureUserDefaults.shared.value(forKey: "terms_app") ?? false
+        return value
     }
     
     static func setTerms(value: Bool){
-        UserDefaults.standard.set(value, forKey: "terms_app")
+        SecureUserDefaults.shared.set(value, forKey: "terms_app")
     }
     
     static func getCustomButtons() -> String {
-        UserDefaults.standard.string(forKey: "app_builder_custom_buttons") ?? ""
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_custom_buttons") ?? ""
+        return value
     }
     
     static func getCustomFBIcon() -> String {
-        UserDefaults.standard.string(forKey: "app_builder_button_icon") ?? ""
+        let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_button_icon") ?? ""
+        return value
     }
     
     static func getIconCenterAnim() -> String? {
-        UserDefaults.standard.string(forKey: "fb_icon_center_anim")
+        let value: String? = SecureUserDefaults.shared.value(forKey: "fb_icon_center_anim")
+        return value
     }
     
 }

+ 10 - 10
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -98,7 +98,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     override func viewDidLoad() {
         super.viewDidLoad()
-        let me = UserDefaults.standard.string(forKey: "me")!
+        let me = User.getMyPin()!
         Database.shared.database?.inTransaction({ fmdb, rollback in
             if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select FIRST_NAME, LAST_NAME, IMAGE_ID, USER_TYPE from BUDDY where F_PIN = '\(me)'"), cursor.next() {
                 isAdmin = cursor.string(forColumnIndex: 3) == "23" || cursor.string(forColumnIndex: 3) == "24"
@@ -228,7 +228,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
             OperationQueue.main.addOperation() {
                 self.isAllowSpeech = isButtonEnabled
                 if isButtonEnabled {
-                    UserDefaults.standard.set(isButtonEnabled, forKey: "allowSpeech")
+                    SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech")
                     self.runVoice()
                 }
             }
@@ -435,7 +435,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         textViewSearch.delegate = self
         
         tabBarController?.navigationItem.titleView = customView
-        let lang = UserDefaults.standard.string(forKey: "i18n_language")
+        let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
         speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: lang ?? "en"))
         backgroundImage.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white
         DispatchQueue.global().async {
@@ -515,7 +515,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     @objc func onReload(notification: NSNotification) {
         let data:[AnyHashable : Any] = notification.userInfo!
-        if data["member"] as? String == UserDefaults.standard.string(forKey: "me")! {
+        if data["member"] as? String == User.getMyPin()! {
             DispatchQueue.main.async {
                 self.getData()
             }
@@ -680,7 +680,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 
                 if !group.id.isEmpty {
 //                    if group.official == "1" {
-//                        let idMe = UserDefaults.standard.string(forKey: "me") as String?
+//                        let idMe = User.getMyPin() as String?
 //                        if let cursorUser = Database.shared.getRecords(fmdb: fmdb, query: "SELECT user_type FROM BUDDY where f_pin='\(idMe!)'"), cursorUser.next() {
 //                            group.childs.append(contentsOf: getGroupRecursive(fmdb: fmdb, parent: group.id))
 //                            cursorUser.close()
@@ -738,7 +738,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     private func pullBuddy() {
-        if let me = UserDefaults.standard.string(forKey: "me") {
+        if let me = User.getMyPin() {
             DispatchQueue.global().async {
                 let _ = Nexilis.write(message: CoreMessage_TMessageBank.getBatchBuddiesInfos(p_f_pin: me, last_update: 0))
             }
@@ -748,7 +748,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     private func joinOpenGroup(groupId: String, flagMember: String = "0", completion: @escaping (Bool) -> ()) {
         DispatchQueue.global().async {
             var result: Bool = false
-            let idMe = UserDefaults.standard.string(forKey: "me") as String?
+            let idMe = User.getMyPin() as String?
             if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getAddGroupMember(p_group_id: groupId, p_member_pin: idMe!, p_position: "0")), response.isOk() {
                 result = true
             }
@@ -904,7 +904,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
         }
         if group.childs.count == 0 {
             Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                let idMe = UserDefaults.standard.string(forKey: "me") as String?
+                let idMe = User.getMyPin() as String?
                 if let cursorMember = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin from GROUPZ_MEMBER where group_id = '\(group.id)' and f_pin = '\(idMe!)'"), cursorMember.next() {
                     let groupId = group.chatId.isEmpty ? group.id : group.chatId
                     if let chooser = isChooser {
@@ -1158,7 +1158,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 data.messageText = data.messageText.trimmingCharacters(in: .whitespacesAndNewlines)
             }
             let text = Utils.previewMessageText(chat: data)
-            let idMe = UserDefaults.standard.string(forKey: "me") as String?
+            let idMe = User.getMyPin() as String?
             if let attributeText = text as? NSMutableAttributedString {
                 let stringMessage = NSMutableAttributedString(string: "")
                 if data.fpin == idMe {
@@ -1235,7 +1235,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                     if day < 7 {
                         let formatter = DateFormatter()
                         formatter.dateFormat = "EEEE"
-                        let lang = UserDefaults.standard.string(forKey: "i18n_language")
+                        let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
                         if lang == "id" {
                             formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
                         }

+ 49 - 8
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -9,6 +9,7 @@ import UIKit
 import WebKit
 import NexilisLite
 import Speech
+import CommonCrypto
 
 class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKNavigationDelegate, WKScriptMessageHandler  {
 
@@ -109,10 +110,10 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
            return
         }
         ThirdTabViewController.inView = true
-        let me = UserDefaults.standard.string(forKey: "me")
+        let me = User.getMyPin()
         
         var myURL : URL?
-        let lang = UserDefaults.standard.string(forKey: "i18n_language")
+        let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
         var intLang = 0
         if lang == "id" {
             speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id"))
@@ -479,7 +480,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             OperationQueue.main.addOperation() {
                 self.isAllowSpeech = isButtonEnabled
                 if isButtonEnabled {
-                    UserDefaults.standard.set(isButtonEnabled, forKey: "allowSpeech")
+                    SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech")
                     self.runVoice()
                 }
             }
@@ -606,12 +607,52 @@ extension ThirdTabViewController: WKUIDelegate {
         decisionHandler(.allow)
     }
     func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
-            let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
-            completionHandler(.useCredential, credential)
-        }
-        else {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
             completionHandler(.cancelAuthenticationChallenge, nil)
+            return
         }
+        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
+           let pinnedCertificateHash = getCertificateHash(from: serverCertificate),
+           pinnedCertificateHash == Utils.getCertificatePinningWebview() {
+            let credential = URLCredential(trust: serverTrust)
+            completionHandler(.useCredential, credential) // Certificate matches, proceed
+        } else {
+            completionHandler(.cancelAuthenticationChallenge, nil) // Certificate doesn't match, cancel
+        }
+    }
+    
+    private func getCertificateHash(from certificate: SecCertificate) -> String? {
+        guard let publicKey = getPublicKey(from: certificate) else { return nil }
+        return hashPublicKey(publicKey)
+    }
+    
+    private func getPublicKey(from certificate: SecCertificate) -> Data? {
+        var trust: SecTrust?
+        let policy = SecPolicyCreateBasicX509()
+        let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
+
+        guard status == errSecSuccess, let trust = trust else { return nil }
+
+        guard let publicKey = SecTrustCopyKey(trust) else { return nil }
+
+        var error: Unmanaged<CFError>?
+        if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) {
+            return publicKeyData as Data
+        } else {
+            print("Error extracting public key: \(String(describing: error?.takeRetainedValue()))")
+            return nil
+        }
+    }
+
+    private func hashPublicKey(_ publicKey: Data) -> String? {
+        // SHA-256 hash
+        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
+        publicKey.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKey.count), &hash)
+        }
+
+        // Base64 encode the hash
+        let base64Hash = Data(hash).base64EncodedString()
+        return "sha256/\(base64Hash)"
     }
 }

+ 4 - 4
AppBuilder/AppBuilder/ViewController.swift

@@ -224,7 +224,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
     
     func getPrefs(key: String) -> TMessage {
         let tMessage = NexilisLite.TMessage()
-        let me = UserDefaults.standard.string(forKey: "me")
+        let me = User.getMyPin()
         tMessage.mCode = "PPR"
         tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
         tMessage.mBodies[CoreMessage_TMessageKey.F_PIN] = me
@@ -637,7 +637,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
             privacyWV.backgroundColor = UIColor.clear
             privacyWV.scrollView.backgroundColor = UIColor.clear
             //print("URL: \(PrefsUtil.getURLPrivacyPolicy())")
-            let lang = UserDefaults.standard.string(forKey: "i18n_language")
+            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
             var urlPrivacyPolicy = PrefsUtil.getURLPrivacyPolicy()
             if lang == "id" {
                 urlPrivacyPolicy = urlPrivacyPolicy.replacingOccurrences(of: "/en/", with: "/id/")
@@ -758,7 +758,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
         termVC = UIViewController()
         if let viewTerm = termVC!.view {
             let webView = WKWebView()
-            let lang = UserDefaults.standard.string(forKey: "i18n_language")
+            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
             var urlTerm = "https://nexilis.io/newuniverse-tos"
             if lang == "id" {
                 urlTerm = "https://nexilis.io/newuniverse-tos-id"
@@ -1010,7 +1010,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
             self.selectedIndex = i
             
             DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1), execute: {
-                let me = UserDefaults.standard.string(forKey: "me")
+                let me = User.getMyPin()
                 let address = "\(PrefsUtil.getURLBase())nexilis/pages/tab5-new-post?f_pin=\(me ?? "")"
                 self.thirdTab?.webView.evaluateJavaScript("{window.localStorage.setItem('currentTab','\(ViewController.tab3)')}")
                 self.thirdTab?.webView.evaluateJavaScript("window.location = '\(address)'")

BIN=BIN
NexilisLite/.DS_Store


+ 0 - 123
NexilisLite/NexilisLite.xcodeproj/project.pbxproj

@@ -7,7 +7,6 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		0E0CBC6D2A8644203AA0DB5C /* Pods_NexilisLite_NexilisLiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7278BDB36CCEF632B12110 /* Pods_NexilisLite_NexilisLiteTests.framework */; };
 		CD1E6E6D2A0B7C3600BF871F /* NexilisLite.docc in Sources */ = {isa = PBXBuildFile; fileRef = CD1E6E6C2A0B7C3600BF871F /* NexilisLite.docc */; };
 		CD1E6E732A0B7C3600BF871F /* NexilisLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD1E6E682A0B7C3600BF871F /* NexilisLite.framework */; };
 		CD1E6E782A0B7C3600BF871F /* NexilisLiteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E6E772A0B7C3600BF871F /* NexilisLiteTests.swift */; };
@@ -217,7 +216,6 @@
 		CDDF467A2A2EF0A700049A19 /* ScreenSharingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDF46792A2EF0A700049A19 /* ScreenSharingViewController.swift */; };
 		CDE3BED92C86E3A000B0BF36 /* pb_call_in.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CDE3BED62C86E3A000B0BF36 /* pb_call_in.mp3 */; };
 		CDE3BEDA2C86E3A000B0BF36 /* pb_call_out.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CDE3BED72C86E3A000B0BF36 /* pb_call_out.mp3 */; };
-		D7C555033B5169B92C3E5655 /* Pods_NexilisLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D25E4968C9AB01451C6FEA4 /* Pods_NexilisLite.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -251,11 +249,6 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		2286875318512FECCF9DD2D5 /* Pods-NexilisLite-NexilisLiteTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite-NexilisLiteTests.debug.xcconfig"; path = "Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests.debug.xcconfig"; sourceTree = "<group>"; };
-		3BF74C8A0491A2D9CFD5036F /* Pods-NexilisLite.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite.debug.xcconfig"; path = "Target Support Files/Pods-NexilisLite/Pods-NexilisLite.debug.xcconfig"; sourceTree = "<group>"; };
-		5634B1BB53E714D616AC0339 /* Pods-NexilisLite.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite.release.xcconfig"; path = "Target Support Files/Pods-NexilisLite/Pods-NexilisLite.release.xcconfig"; sourceTree = "<group>"; };
-		759E699FA5E58242CC4AD45F /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite-NexilisLiteTests.release.xcconfig"; path = "Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests.release.xcconfig"; sourceTree = "<group>"; };
-		7D25E4968C9AB01451C6FEA4 /* Pods_NexilisLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NexilisLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E6E682A0B7C3600BF871F /* NexilisLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NexilisLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E6E6B2A0B7C3600BF871F /* NexilisLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NexilisLite.h; sourceTree = "<group>"; };
 		CD1E6E6C2A0B7C3600BF871F /* NexilisLite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = NexilisLite.docc; sourceTree = "<group>"; };
@@ -476,7 +469,6 @@
 		CDDF46792A2EF0A700049A19 /* ScreenSharingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewController.swift; sourceTree = "<group>"; };
 		CDE3BED62C86E3A000B0BF36 /* pb_call_in.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = pb_call_in.mp3; sourceTree = "<group>"; };
 		CDE3BED72C86E3A000B0BF36 /* pb_call_out.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = pb_call_out.mp3; sourceTree = "<group>"; };
-		FC7278BDB36CCEF632B12110 /* Pods_NexilisLite_NexilisLiteTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NexilisLite_NexilisLiteTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -484,7 +476,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				D7C555033B5169B92C3E5655 /* Pods_NexilisLite.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -493,7 +484,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD1E6E732A0B7C3600BF871F /* NexilisLite.framework in Frameworks */,
-				0E0CBC6D2A8644203AA0DB5C /* Pods_NexilisLite_NexilisLiteTests.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -517,8 +507,6 @@
 				CD1E79142A0CA43600BF871F /* SDWebImage.framework */,
 				CD1E79152A0CA43600BF871F /* SnapKit.framework */,
 				CD1E79162A0CA43600BF871F /* Toast_Swift.framework */,
-				7D25E4968C9AB01451C6FEA4 /* Pods_NexilisLite.framework */,
-				FC7278BDB36CCEF632B12110 /* Pods_NexilisLite_NexilisLiteTests.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -527,10 +515,6 @@
 			isa = PBXGroup;
 			children = (
 				CD46A0BE2A0CE4FD009E4C87 /* NexilisLite.podspec */,
-				3BF74C8A0491A2D9CFD5036F /* Pods-NexilisLite.debug.xcconfig */,
-				5634B1BB53E714D616AC0339 /* Pods-NexilisLite.release.xcconfig */,
-				2286875318512FECCF9DD2D5 /* Pods-NexilisLite-NexilisLiteTests.debug.xcconfig */,
-				759E699FA5E58242CC4AD45F /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
@@ -916,13 +900,11 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = CD1E6E7C2A0B7C3600BF871F /* Build configuration list for PBXNativeTarget "NexilisLite" */;
 			buildPhases = (
-				04BA19F1F3328DB84CA3450C /* [CP] Check Pods Manifest.lock */,
 				CD1E6E632A0B7C3600BF871F /* Headers */,
 				CD1E6E642A0B7C3600BF871F /* Sources */,
 				CD1E6E652A0B7C3600BF871F /* Frameworks */,
 				CD1E6E662A0B7C3600BF871F /* Resources */,
 				CD469E162A0CA6C9009E4C87 /* Embed Frameworks */,
-				6CBA06F8A3EDD9E9F4E3E3F0 /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -938,12 +920,9 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = CD1E6E7F2A0B7C3600BF871F /* Build configuration list for PBXNativeTarget "NexilisLiteTests" */;
 			buildPhases = (
-				3FE251F6F0AD0D52A6241402 /* [CP] Check Pods Manifest.lock */,
 				CD1E6E6E2A0B7C3600BF871F /* Sources */,
 				CD1E6E6F2A0B7C3600BF871F /* Frameworks */,
 				CD1E6E702A0B7C3600BF871F /* Resources */,
-				8BBEF7E204C8A2B6A2ED4970 /* [CP] Embed Pods Frameworks */,
-				8DCAA429A19A02EFDE64AEEA /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -1143,104 +1122,6 @@
 		};
 /* End PBXResourcesBuildPhase section */
 
-/* Begin PBXShellScriptBuildPhase section */
-		04BA19F1F3328DB84CA3450C /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-NexilisLite-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-		3FE251F6F0AD0D52A6241402 /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-NexilisLite-NexilisLiteTests-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-		6CBA06F8A3EDD9E9F4E3E3F0 /* [CP] Copy Pods Resources */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Copy Pods Resources";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		8BBEF7E204C8A2B6A2ED4970 /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		8DCAA429A19A02EFDE64AEEA /* [CP] Copy Pods Resources */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Copy Pods Resources";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-/* End PBXShellScriptBuildPhase section */
-
 /* Begin PBXSourcesBuildPhase section */
 		CD1E6E642A0B7C3600BF871F /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
@@ -1517,7 +1398,6 @@
 		};
 		CD1E6E7D2A0B7C3600BF871F /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 3BF74C8A0491A2D9CFD5036F /* Pods-NexilisLite.debug.xcconfig */;
 			buildSettings = {
 				BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
 				CODE_SIGN_STYLE = Automatic;
@@ -1557,7 +1437,6 @@
 		};
 		CD1E6E7E2A0B7C3600BF871F /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 5634B1BB53E714D616AC0339 /* Pods-NexilisLite.release.xcconfig */;
 			buildSettings = {
 				BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
 				CODE_SIGN_STYLE = Automatic;
@@ -1597,7 +1476,6 @@
 		};
 		CD1E6E802A0B7C3600BF871F /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 2286875318512FECCF9DD2D5 /* Pods-NexilisLite-NexilisLiteTests.debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
 				CODE_SIGN_STYLE = Automatic;
@@ -1615,7 +1493,6 @@
 		};
 		CD1E6E812A0B7C3600BF871F /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 759E699FA5E58242CC4AD45F /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
 				CODE_SIGN_STYLE = Automatic;

+ 1 - 0
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -142,6 +142,7 @@ public class Nexilis: NSObject {
         do {
             try MasterKeyUtil.shared.generateAndStoreMasterKey()
             try MasterKeyUtil.shared.generateAndStorePrefsKey()
+            Utils.setCertificatePinningWebview(value: Utils.decrypt(str: "6]qaAhNXHAxGKAOsFZmoIRUIn2Hp3rdfGAOFEfcnY4SIZ"))
         } catch {
         }
         

+ 15 - 8
NexilisLite/NexilisLite/Source/Utils.swift

@@ -348,6 +348,17 @@ public final class Utils {
         return dateFormatter.string(from: todaysDate as Date)
     }
     
+    static func setCertificatePinningWebview(value: String) {
+        SecureUserDefaults.shared.set(value, forKey: "certificate_pinning_webview")
+    }
+
+    public static func getCertificatePinningWebview() -> String {
+        if let value: String = SecureUserDefaults.shared.value(forKey: "certificate_pinning_webview") {
+            return value
+        }
+        return ""
+    }
+    
 //    public static func getMD5(string: String) -> Data {
 //        let length = Int(CC_MD5_DIGEST_LENGTH)
 //        let messageData = string.data(using:.utf8)!
@@ -2507,7 +2518,7 @@ class LocationManager: NSObject, CLLocationManagerDelegate {
 }
 
 public class SecureUserDefaults {
-    static let shared = SecureUserDefaults()
+    public static let shared = SecureUserDefaults()
     private let defaults: UserDefaults
 
     init(defaults: UserDefaults = .standard) {
@@ -2515,7 +2526,7 @@ public class SecureUserDefaults {
     }
 
     // Save a value
-    func set<T: Codable>(_ value: T, forKey key: String) {
+    public func set<T: Codable>(_ value: T, forKey key: String) {
         let encoder = JSONEncoder()
         guard let encodedData = try? encoder.encode(value),
               let encryptedData = try? MasterKeyUtil.shared.encryptP(data: encodedData) else {
@@ -2526,7 +2537,7 @@ public class SecureUserDefaults {
     }
 
     // Retrieve a value
-    func value<T: Codable>(forKey key: String) -> T? {
+    public func value<T: Codable>(forKey key: String) -> T? {
         guard let encryptedData = defaults.data(forKey: key),
               let decryptedData = try? MasterKeyUtil.shared.decryptP(data: encryptedData) else {
 //            print("Failed to decrypt data \(key)")
@@ -2537,12 +2548,8 @@ public class SecureUserDefaults {
     }
 
     // Remove a value
-    func removeValue(forKey key: String) {
+    public func removeValue(forKey key: String) {
         defaults.removeObject(forKey: key)
     }
-    
-    func sync() {
-        defaults.synchronize()
-    }
 }
 

+ 46 - 5
NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift

@@ -9,6 +9,7 @@ import Foundation
 import UIKit
 import WebKit
 import Speech
+import CommonCrypto
 
 public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, SFSpeechRecognizerDelegate {
     var webView = WKWebView()
@@ -580,12 +581,52 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
     }
     
     public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
-            let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
-            completionHandler(.useCredential, credential)
-        }
-        else {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
             completionHandler(.cancelAuthenticationChallenge, nil)
+            return
+        }
+        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
+           let pinnedCertificateHash = getCertificateHash(from: serverCertificate),
+           pinnedCertificateHash == Utils.getCertificatePinningWebview() {
+            let credential = URLCredential(trust: serverTrust)
+            completionHandler(.useCredential, credential) // Certificate matches, proceed
+        } else {
+            completionHandler(.cancelAuthenticationChallenge, nil) // Certificate doesn't match, cancel
+        }
+    }
+    
+    private func getCertificateHash(from certificate: SecCertificate) -> String? {
+        guard let publicKey = getPublicKey(from: certificate) else { return nil }
+        return hashPublicKey(publicKey)
+    }
+    
+    private func getPublicKey(from certificate: SecCertificate) -> Data? {
+        var trust: SecTrust?
+        let policy = SecPolicyCreateBasicX509()
+        let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
+
+        guard status == errSecSuccess, let trust = trust else { return nil }
+
+        guard let publicKey = SecTrustCopyKey(trust) else { return nil }
+
+        var error: Unmanaged<CFError>?
+        if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) {
+            return publicKeyData as Data
+        } else {
+            print("Error extracting public key: \(String(describing: error?.takeRetainedValue()))")
+            return nil
         }
     }
+
+    private func hashPublicKey(_ publicKey: Data) -> String? {
+        // SHA-256 hash
+        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
+        publicKey.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKey.count), &hash)
+        }
+
+        // Base64 encode the hash
+        let base64Hash = Data(hash).base64EncodedString()
+        return "sha256/\(base64Hash)"
+    }
 }