Create Personal VPN connection using NEVPNManager

2020-08-01 06:53发布

问题:

I am trying to create VPN connection in my app. I go through this link https://developer.apple.com/documentation/networkextension/nevpnmanager but did not find any official code to use NEVPNManager and even not found any tutorial to use this NEVPNManager.

I am new to VPN concept and don't know that much about it. so Can anyone give some solutions?

回答1:

Here is a IKEv2 configuration of VPN using Network Extension (Without shared key and certificate) in Swift 4.2:

final class VPNHandler {

    let vpnManager = NEVPNManager.shared()

    func initVPNTunnelProviderManager() {

        print("CALL LOAD TO PREFERENCES...")
        self.vpnManager.loadFromPreferences { (error) -> Void in

            if((error) != nil) {

                print("VPN Preferences error: 1")
            } else {

                let IKEv2Protocol = NEVPNProtocolIKEv2()

                IKEv2Protocol.username = vpnUser.username
                IKEv2Protocol.serverAddress = vpnServer.serverID //server tunneling Address
                IKEv2Protocol.remoteIdentifier = vpnServer.remoteID //Remote id
                IKEv2Protocol.localIdentifier = vpnUser.localID //Local id

                IKEv2Protocol.deadPeerDetectionRate = .low
                IKEv2Protocol.authenticationMethod = .none
                IKEv2Protocol.useExtendedAuthentication = true //if you are using sharedSecret method then make it false
                IKEv2Protocol.disconnectOnSleep = false

                //Set IKE SA (Security Association) Params...
                IKEv2Protocol.ikeSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES256
                IKEv2Protocol.ikeSecurityAssociationParameters.integrityAlgorithm = .SHA256
                IKEv2Protocol.ikeSecurityAssociationParameters.diffieHellmanGroup = .group14
                IKEv2Protocol.ikeSecurityAssociationParameters.lifetimeMinutes = 1440
                //IKEv2Protocol.ikeSecurityAssociationParameters.isProxy() = false

                //Set CHILD SA (Security Association) Params...
                IKEv2Protocol.childSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES256
                IKEv2Protocol.childSecurityAssociationParameters.integrityAlgorithm = .SHA256
                IKEv2Protocol.childSecurityAssociationParameters.diffieHellmanGroup = .group14
                IKEv2Protocol.childSecurityAssociationParameters.lifetimeMinutes = 1440

                let kcs = KeychainService()
                //Save password in keychain...
                kcs.save(key: "VPN_PASSWORD", value: vpnUser.password)
                //Load password from keychain...
                IKEv2Protocol.passwordReference = kcs.load(key: "VPN_PASSWORD")

                self.vpnManager.protocolConfiguration = IKEv2Protocol
                self.vpnManager.localizedDescription = "Safe Login Configuration"
                self.vpnManager.isEnabled = true

                self.vpnManager.isOnDemandEnabled = true
                //print(IKEv2Protocol)

                //Set rules
                var rules = [NEOnDemandRule]()
                let rule = NEOnDemandRuleConnect()
                rule.interfaceTypeMatch = .any
                rules.append(rule)

                print("SAVE TO PREFERENCES...")
                //SAVE TO PREFERENCES...
                self.vpnManager.saveToPreferences(completionHandler: { (error) -> Void in
                    if((error) != nil) {

                        print("VPN Preferences error: 2")
                    } else {

                        print("CALL LOAD TO PREFERENCES AGAIN...")
                        //CALL LOAD TO PREFERENCES AGAIN...
                        self.vpnManager.loadFromPreferences(completionHandler: { (error) in
                            if ((error) != nil) {
                                print("VPN Preferences error: 2")
                            } else {
                                var startError: NSError?
                                do {
                                    //START THE CONNECTION...
                                    try self.vpnManager.connection.startVPNTunnel()
                                } catch let error as NSError {

                                    startError = error
                                    print(startError.debugDescription)
                                } catch {

                                    print("Fatal Error")
                                    fatalError()
                                }
                                if ((startError) != nil) {
                                    print("VPN Preferences error: 3")

                                    //Show alert here
                                    print("title: Oops.., message: Something went wrong while connecting to the VPN. Please try again.")

                                    print(startError.debugDescription)
                                } else {
                                    //self.VPNStatusDidChange(nil)
                                    print("Starting VPN...")
                                }
                            }
                        })
                    }
                })
            }
        } //END OF .loadFromPreferences //

    }

    //MARK:- Connect VPN
    static func connectVPN() {
        VPNHandler().initVPNTunnelProviderManager()
    }

    //MARK:- Disconnect VPN
    static func disconnectVPN() {
        VPNHandler().vpnManager.connection.stopVPNTunnel()
    }

    //MARK:- check connection staatus
    static func checkStatus() {

        let status = VPNHandler().vpnManager.connection.status
        print("VPN connection status = \(status.rawValue)")

        switch status {
        case NEVPNStatus.connected:

            print("Connected")

        case NEVPNStatus.invalid, NEVPNStatus.disconnected :

            print("Disconnected")

        case NEVPNStatus.connecting , NEVPNStatus.reasserting:

            print("Connecting")

        case NEVPNStatus.disconnecting:

            print("Disconnecting")

        default:
            print("Unknown VPN connection status")
        }
    }
}

Code for keychain:

//MARK:- Variables for keychain access

// Identifiers
let serviceIdentifier = "MySerivice"
let userAccount = "authenticatedUser"
let accessGroup = "MySerivice"

// Arguments for the keychain queries
var kSecAttrAccessGroupSwift = NSString(format: kSecClass)

let kSecClassValue = kSecClass as CFString
let kSecAttrAccountValue = kSecAttrAccount as CFString
let kSecValueDataValue = kSecValueData as CFString
let kSecClassGenericPasswordValue = kSecClassGenericPassword as CFString
let kSecAttrServiceValue = kSecAttrService as CFString
let kSecMatchLimitValue = kSecMatchLimit as CFString
let kSecReturnDataValue = kSecReturnData as CFString
let kSecMatchLimitOneValue = kSecMatchLimitOne as CFString
let kSecAttrGenericValue = kSecAttrGeneric as CFString
let kSecAttrAccessibleValue = kSecAttrAccessible as CFString


class KeychainService: NSObject {

    func save(key:String, value:String) {

        let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!
        let valueData: Data = value.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!

        let keychainQuery = NSMutableDictionary();

        keychainQuery[kSecClassValue as! NSCopying] = kSecClassGenericPasswordValue
        keychainQuery[kSecAttrGenericValue as! NSCopying] = keyData
        keychainQuery[kSecAttrAccountValue as! NSCopying] = keyData
        keychainQuery[kSecAttrServiceValue as! NSCopying] = "VPN"
        keychainQuery[kSecAttrAccessibleValue as! NSCopying] = kSecAttrAccessibleAlwaysThisDeviceOnly
        keychainQuery[kSecValueData as! NSCopying] = valueData;

        // Delete any existing items
        SecItemDelete(keychainQuery as CFDictionary)

        SecItemAdd(keychainQuery as CFDictionary, nil)
    }

    func load(key: String)->Data {

        let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)!

        let keychainQuery = NSMutableDictionary();

        keychainQuery[kSecClassValue as! NSCopying] = kSecClassGenericPasswordValue
        keychainQuery[kSecAttrGenericValue as! NSCopying] = keyData
        keychainQuery[kSecAttrAccountValue as! NSCopying] = keyData
        keychainQuery[kSecAttrServiceValue as! NSCopying] = "VPN"
        keychainQuery[kSecAttrAccessibleValue as! NSCopying] = kSecAttrAccessibleAlwaysThisDeviceOnly
        keychainQuery[kSecMatchLimit] = kSecMatchLimitOne
        keychainQuery[kSecReturnPersistentRef] = kCFBooleanTrue

        var result: AnyObject?

        let status = withUnsafeMutablePointer(to: &result) { SecItemCopyMatching(keychainQuery, UnsafeMutablePointer($0)) }

        if status == errSecSuccess {

            if let data = result as! NSData? {

                if let value = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) {
                    //print(value)
                }
                return data as Data;
            }
        }
        return "".data(using: .utf8)!;
    }
}


回答2:

This tutorial help me to create VPN connection.

http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/

VPN file for connection and disconnection

import Foundation
import NetworkExtension

  // MARK: - NEVPNManager
    // MARK: -


    private var vpnLoadHandler: (Error?) -> Void { return
    { (error:Error?) in
        if ((error) != nil) {
            print("Could not load VPN Configurations")
            self.removeToast()
            return;
        }

        self.showToast(msg: STRINGVALUES.kCreatingConnection)


        //VPN connection via Username password
        let p = NEVPNProtocolIPSec()
        let kcs = KeychainService()
        p.authenticationMethod = NEVPNIKEAuthenticationMethod.sharedSecret

        //For the security purpose added word xyz in password .so it should be remove while connecting


        if self.selectedSever != nil{
            self.selectedSever?.password = (self.selectedSever?.password.replacingOccurrences(of: "xyz", with: ""))!
            p.username = self.selectedSever?.userName
            p.serverAddress = self.selectedSever?.serverAddress
            kcs.save(key: "SHARED", value: (self.selectedSever?.password)!)

            kcs.save(key: "VPN_PASSWORD", value: (self.selectedSever?.password)!)
            p.sharedSecretReference = kcs.load(key: STRINGVALUES.kShared)
            p.passwordReference = kcs.load(key: STRINGVALUES.kVPN_Pswd)
            p.useExtendedAuthentication = true
            p.disconnectOnSleep = false

            // Check for free subscriber
            if self.selectedSever?.serverType == STRINGVALUES.VIP.lowercased() && !Singleton.checkForPaidReciept(){

                self.disconnectVPN()
                Helper.showAlert(sender: self, title: STRINGVALUES.AppName, message: AlertMessage.kValidateSubscription)
                return

            }


            self.vpnManager.protocolConfiguration = p
            self.vpnManager.localizedDescription = STRINGVALUES.AppName
            self.vpnManager.isEnabled = true

            self.vpnManager.saveToPreferences(completionHandler: self.vpnSaveHandler)
        }else{

        }


        }

    }


    private var vpnSaveHandler: (Error?) -> Void { return
    { (error:Error?) in
        if (error != nil) {
            print("Could not save VPN Configurations")
            self.removeToast()
            return
        } else {
            do {
                try self.vpnManager.connection.startVPNTunnel()
            } catch let error {
                print("Error starting VPN Connection \(error.localizedDescription)");
                self.removeToast()
            }
        }
        }
        //self.vpnlock = false
    }



    public func connectVPN() {
        //For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works
        do {
            try self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)

        } catch let error {
            print("Could not start VPN Connection: \(error.localizedDescription)" )
            self.removeToast()
        }
    }

    public func disconnectVPN() ->Void {
        vpnManager.connection.stopVPNTunnel()


    }

    func vpnConnectionStatusChanged(){

       let status = self.vpnManager.connection.status
        print("VPN connection status = \(status)")

        switch status {
        case NEVPNStatus.connected:

            showToast(msg: STRINGVALUES.kConnected)




        case NEVPNStatus.invalid, NEVPNStatus.disconnected :

            showToast(msg: STRINGVALUES.kDisconnected)


        case NEVPNStatus.connecting , NEVPNStatus.reasserting:

            showToast(msg: STRINGVALUES.kConnecting)


        case NEVPNStatus.disconnecting:
            showToast(msg: STRINGVALUES.kDisconnecting)

        default:
            print("Unknown VPN connection status")
        }

    }