diff --git a/ios/Podfile b/ios/Podfile index 1e8c3c9..4e07110 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -32,6 +32,8 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + pod 'CryptoSwift', '~> 1.2.0' end post_install do |installer| diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a8c663b..cd8ae0c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,5 @@ PODS: + - CryptoSwift (1.2.0) - Flutter (1.0.0) - image_picker_ios (0.0.1): - Flutter @@ -6,10 +7,15 @@ PODS: - Flutter DEPENDENCIES: + - CryptoSwift (~> 1.2.0) - Flutter (from `Flutter`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) +SPEC REPOS: + trunk: + - CryptoSwift + EXTERNAL SOURCES: Flutter: :path: Flutter @@ -19,10 +25,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_ios/ios" SPEC CHECKSUMS: + CryptoSwift: 40e374e45291d8dceedcb0d6184da94533eaabdf Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: 28949384d1a9817c5c0092fcf6ffc3a836337eb0 COCOAPODS: 1.11.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7f30107..144c77c 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -68,7 +68,6 @@ 81B81E118F31476FA4045A87 /* Pods-Runner.release.xcconfig */, A4FEFEB72D9AAEB411E9065F /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..5e8222e 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,13 @@ import UIKit import Flutter +import CryptoSwift + +private extension String { + static let CRYPTO_CHANNEL = "com.hoc.node_auth/crypto" + static let CRYPTO_ERROR_CODE = "com.hoc.node_auth/crypto_error" + static let ENCRYPT_METHOD = "encrypt" + static let DECRYPT_METHOD = "decrypt" +} @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -7,7 +15,142 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + let flutterVC = window?.rootViewController as! FlutterViewController + + let cryptoChannel = FlutterMethodChannel( + name: .CRYPTO_CHANNEL, + binaryMessenger: flutterVC.binaryMessenger + ) + cryptoChannel.setMethodCallHandler { call, result in + switch call.method { + case .ENCRYPT_METHOD: encrypt(call: call, result: result) + case .DECRYPT_METHOD: decrypt(call: call, result: result) + default: + result(FlutterMethodNotImplemented) + } + } + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } + +private enum AESConfig { + static let iv: [UInt8] = "_hoc081098_auth_".bytes + static let key: [UInt8] = "__hoc081098_nodejs_auth_rxdart__".bytes + + static let backgroundQueue = DispatchQueue.global(qos: .userInitiated) + + static func gcm() -> GCM { GCM(iv: AESConfig.iv, mode: .combined) } +} + +private func complete(result: @escaping FlutterResult, with error: Error) { + NSLog("\n[NODE_AUTH] Error: \(error)") + + executeOnMain { + result( + FlutterError( + code: .CRYPTO_ERROR_CODE, + message: error.localizedDescription, + details: nil + ) + ) + } +} + +private func executeOnMain(block: @escaping () -> Void) { + if Thread.isMainThread { + block() + } else { + DispatchQueue.main.async { + block() + } + } +} + +private func useAES( + input: String, + result: @escaping FlutterResult, + inputToBytes: (String) -> [UInt8]?, + bytesToString: @escaping ([UInt8]) -> String?, + block: @escaping (AES, [UInt8]) throws -> [UInt8] +) { + guard let inputBytes = inputToBytes(input) else { + NSLog("\n[NODE_AUTH] Error: inputToBytes returns nil") + + executeOnMain { + result( + FlutterError( + code: .CRYPTO_ERROR_CODE, + message: "An unexpected error occurred!", + details: nil + ) + ) + } + return + } + + AESConfig.backgroundQueue.async { + let start = DispatchTime.now() + + do { + let aes = try AES( + key: AESConfig.key, + blockMode: AESConfig.gcm(), + padding: .noPadding + ) + + let outputBytes = try block(aes, inputBytes) + guard let stringResult = bytesToString(outputBytes) else { + NSLog("\n[NODE_AUTH] Error: bytesToString returns nil") + + executeOnMain { + result( + FlutterError( + code: .CRYPTO_ERROR_CODE, + message: "An unexpected error occurred!", + details: nil + ) + ) + } + return + } + + let end = DispatchTime.now() + let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds + let millisTime = Double(nanoTime) / 1_000_000 + NSLog("\n[NODE_AUTH] Time: \(millisTime) ms") + + executeOnMain { result(stringResult) } + } catch { + complete(result: result, with: error) + } + } +} + +private func encrypt(call: FlutterMethodCall, result: @escaping FlutterResult) { + useAES( + input: call.arguments as! String, + result: result, + inputToBytes: { $0.bytes }, + bytesToString: base64Encode(bytes:) + ) { aes, bytes in try aes.encrypt(bytes) } +} + + +private func decrypt(call: FlutterMethodCall, result: @escaping FlutterResult) { + useAES( + input: call.arguments as! String, + result: result, + inputToBytes: base64Decode(s:), + bytesToString: { .init(bytes: $0, encoding: .utf8) } + ) { aes, bytes in try aes.decrypt(bytes) } +} + +func base64Decode(s: String) -> [UInt8]? { + Data(base64Encoded: s)?.bytes +} + +func base64Encode(bytes: [UInt8]) -> String { + Data(bytes).base64EncodedString() +}