// // VPNController.swift // Lockdown // // Copyright © 2018 Confirmed, Inc. All rights reserved. // import UIKit import NetworkExtension import CocoaLumberjackSwift import WidgetKit let kVPNLocalizedDescription = "Lockdown VPN" class VPNController: NSObject { static let shared = VPNController() let manager = NEVPNManager.shared() private override init() { super.init() manager.loadFromPreferences(completionHandler: {(_ error: Error?) -> Void in }) } func status() -> NEVPNStatus { return manager.connection.status } func restart() { // Don't let this affect userWantsVPNOn/Off config VPNController.shared.setEnabled(false, completion: { error in // TODO: Handle the error VPNController.shared.setEnabled(true) }) } func isConfigurationExisting(_ completion: @escaping (Bool) -> ()) { manager.loadFromPreferences { (error) in completion(self.manager.protocolConfiguration != nil) } } func setEnabled(_ enabled: Bool, completion: @escaping (_ error: Error?) -> Void = {_ in }) { DDLogInfo("VPNController set enabled: \(enabled)") setUserWantsVPNEnabled(enabled) if #available(iOSApplicationExtension 14.0, iOS 14.0, *) { WidgetCenter.shared.reloadAllTimelines() } if (enabled) { setUpAndEnableVPN { error in completion(error) } } else { manager.loadFromPreferences(completionHandler: {(_ error: Error?) -> Void in self.manager.isEnabled = false self.manager.isOnDemandEnabled = false self.manager.saveToPreferences(completionHandler: {(_ error: Error?) -> Void in // TODO: will this ever error? completion(error) }) }) } } private func setUpAndEnableVPN(completion: @escaping (_ error: Error?) -> Void) { guard let vpnCredentials = getVPNCredentials() else { // TODO: handle error return completion("No VPN credentials found while enabling VPN") } let serverAddress = getSavedVPNRegion().serverPrefix + vpnSourceID + "." + vpnDomain let localIdentifier = vpnCredentials.id let identityData = Data(base64Encoded: vpnCredentials.keyBase64) manager.loadFromPreferences(completionHandler: {(_ error: Error?) -> Void in let p = NEVPNProtocolIKEv2() p.serverAddress = serverAddress p.serverCertificateIssuerCommonName = vpnRemoteIdentifier p.remoteIdentifier = vpnRemoteIdentifier p.certificateType = NEVPNIKEv2CertificateType.ECDSA256 p.authenticationMethod = NEVPNIKEAuthenticationMethod.certificate p.localIdentifier = localIdentifier p.useExtendedAuthentication = false p.disconnectOnSleep = false p.enablePFS = true p.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group19 p.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128GCM p.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA512 p.childSecurityAssociationParameters.lifetimeMinutes = 1440 p.ikeSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group19 p.ikeSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128GCM p.ikeSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA512 p.ikeSecurityAssociationParameters.lifetimeMinutes = 1440 p.deadPeerDetectionRate = NEVPNIKEv2DeadPeerDetectionRate.medium p.identityData = identityData p.identityDataPassword = "" if #available(iOS 13.0, *) { p.enableFallback = false } self.manager.protocolConfiguration = p self.manager.isEnabled = true self.manager.isOnDemandEnabled = true var onDemandRules:[NEOnDemandRule] = []; let connectRule = NEOnDemandRuleConnect() connectRule.interfaceTypeMatch = .any onDemandRules.append(connectRule) self.manager.onDemandRules = onDemandRules DDLogInfo("VPN status before loading: \(self.manager.connection.status)") self.manager.localizedDescription! = kVPNLocalizedDescription self.manager.saveToPreferences(completionHandler: {(_ error: Error?) -> Void in if let e = error { DDLogError("Saving VPN Error \(e)") if ((e as NSError).code == 4) { // if config is stale, probably multithreading bug DDLogInfo("Stale config, trying again") self.setUpAndEnableVPN(completion: { error in completion(error) }) } else { completion(e) } } else { self.loadFromPrefenrenceAndStartVPN(completion: completion) } }) }) } private func loadFromPrefenrenceAndStartVPN(completion: @escaping (_ error: Error?) -> Void) { manager.loadFromPreferences { [weak self] error in if let error { DDLogError("Read preference error before start vpn: " + error.localizedDescription) } do { // manually activate the starting of the tunnel, and also do a dummy connect to a nonexistant, invalid URL to force enabling try self?.manager.connection.startVPNTunnel() let config = URLSessionConfiguration.default config.requestCachePolicy = .reloadIgnoringLocalCacheData config.urlCache = nil let session = URLSession.init(configuration: config) let url = URL(string: "https://nonexistant_invalid_url") let task = session.dataTask(with: url!) { (data, response, error) in return } task.resume() } catch { DDLogError("Unable to start the tunnel after saving: " + error.localizedDescription) } completion(nil) } } }