You are currently viewing Simple VPN app in IOS (Swift5)

Simple VPN app in IOS (Swift5)

  • Post author:
  • Post category:iOS / swift
  • Post comments:2 Comments
  • Post last modified:December 10, 2021
  • Reading time:5 mins read

Creating a VPN app in iOS using swift is pretty simple.

First you will need KeychainService

//
// KeychainService.swift
// Androvaid VPN
//
// Created by Shaikh Wasi Sadman on 18/11/21.
// Copyright (c) 2021 Androvaid. All rights reserved.
//


import Foundation
import UIKit
import Security

// 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) {
                }
                return data as Data;
            }
        }
        return "".data(using: .utf8)!;
    }
}

Then use VPNManager class and you can easily make one.

//
// VPNManager.swift
// Androvaid VPN
//
// Created by Shaikh Wasi Sadman on 30/11/21.
// Copyright (c) 2021 Androvaid. All rights reserved.
//

import Foundation
import NetworkExtension


protocol VpnDelegate: AnyObject {
    func setStatus(status: Status)
    func checkNES(status: NEVPNStatus)
}

class VPNManager {
    
    let vpnManager = NEVPNManager.shared();
    var server_address : String
    weak var vpnDelegate: VpnDelegate?
    
    
    /// The static field that controls the access to the singleton instance.
    ///
    /// This implementation let you extend the Singleton class while keeping
    /// just one instance of each subclass around.
    static var shared: VPNManager = {
        let instance = VPNManager(serverAddress: "Enter your server address")
        // ... configure the instance
        // ...
        return instance
    }()
    
    private init(serverAddress address : String) {
        self.server_address = address
    }
    
    private var vpnLoadHandler: (Error?) -> Void { return
        { (error:Error?) in
            if ((error) != nil) {
                self.vpnDelegate?.setStatus(status: Status.errorConnecting)
                print("vpnLoadHandler Could not load VPN Configurations : \(String(describing: error))")
                return;
            }
            
            let p = NEVPNProtocolIKEv2()
            //        p.username = "androvaid"
            p.serverAddress = self.server_address /// Required
            p.authenticationMethod = NEVPNIKEAuthenticationMethod.sharedSecret
            
            let kcs = KeychainService();
            kcs.save(key: "SHARED", value: "password") // Enter your password
            p.sharedSecretReference = kcs.load(key: "SHARED") // Required type is Data
            p.useExtendedAuthentication = false
            p.disconnectOnSleep = false
            p.remoteIdentifier = self.server_address /// Required
            
            p.ikeSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group2
            p.ikeSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128
            p.ikeSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA96
            p.ikeSecurityAssociationParameters.lifetimeMinutes = 1140
            
            p.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group2
            p.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128
            p.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA96
            p.childSecurityAssociationParameters.lifetimeMinutes = 1140
            
            self.vpnManager.protocolConfiguration = p
            
            self.vpnManager.localizedDescription = "Androvaid VPN" // The VPN Name saved in your setting
            self.vpnManager.isOnDemandEnabled = false
            
            /// You can set rules for On Demand Connection
            let rule1 : NEOnDemandRule = NEOnDemandRule()
            rule1.setValuesForKeys(["Action" : NEOnDemandRuleAction.connect, "InterfaceTypeMatch" : NEOnDemandRuleInterfaceType.wiFi])
            
            let rule2 : NEOnDemandRule = NEOnDemandRule()
            rule2.setValuesForKeys(["Action" : NEOnDemandRuleAction.connect, "InterfaceTypeMatch" : NEOnDemandRuleInterfaceType.cellular])
            
            let connectRule = NEOnDemandRuleConnect()
            connectRule.interfaceTypeMatch = .any
            self.vpnManager.onDemandRules = [connectRule]
            
            
            
            //        do {
            //
            //
            //        let rule1 : NEOnDemandRuleConnect = NEOnDemandRuleConnect()
            //
            //        rule1.setValue(NEOnDemandRuleAction.connect, forKeyPath: "Action")
            //        //rule1.setValue(NEOnDemandRuleAction.connect, forKey: "Action")
            //        //rule1.setValue(NEOnDemandRuleAction.connect, forKeyPath:)
            //        //rule1.setValue(NEOnDemandRuleAction.connect, forUndefinedKey: "Action")
            //
            //        //rule1.setValuesForKeys(["Action" : NEOnDemandRuleAction.connect, "InterfaceTypeMatch" : NEOnDemandRuleInterfaceType.wiFi])
            //        rule1.setValue(NEOnDemandRuleInterfaceType.wiFi, forKeyPath: "InterfaceTypeMatch")
            //
            //
            //        let rule2 : NEOnDemandRule = NEOnDemandRule()
            //        rule2.setValue(NEOnDemandRuleAction.connect, forKeyPath: "action")
            //        rule2.setValue(NEOnDemandRuleInterfaceType.cellular, forKeyPath: "InterfaceTypeMatch")
            //
            //        self.vpnManager.protocolConfiguration = p
            //        self.vpnManager.onDemandRules = [rule2, rule1]
            //
            //        } catch {
            //            Log.d(message:  "Error info: \(error)")
            //        }
            //
            self.vpnManager.isEnabled = true
            self.vpnManager.saveToPreferences(completionHandler: self.vpnSaveHandler)
        }
        
    }
    
    private var vpnSaveHandler: (Error?) -> Void { return
        { (error:Error?) in
            if (error != nil) {
                print("vpnSaveHandler Could not save VPN Configurations \(String(describing: error?.localizedDescription))")
                return
            } else {
                do {
                    print("vpnSaveHandler do")
                    try self.vpnManager.connection.startVPNTunnel()
                    
                } catch let error {
                    
                    print("Error starting VPN Connection \(error.localizedDescription)");
                }
            }
        }
        //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
        print("connectVPN")
        self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)
        NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil , queue: nil) {
            notification in
            let nevpnconn = notification.object as! NEVPNConnection
            let status = nevpnconn.status
            self.vpnDelegate?.checkNES(status: status)
        }
    }
    
    public func disconnectVPN() ->Void {
        self.vpnManager.loadFromPreferences { error in
            assert(error == nil, "Failed to load preferences: \(error!.localizedDescription)")
            self.vpnManager.connection.stopVPNTunnel()
            NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil , queue: nil) {
                notification in
                let nevpnconn = notification.object as! NEVPNConnection
                let status = nevpnconn.status
                self.vpnDelegate?.checkNES(status: status)
            }
        }
    }
}

 

You can use it like below

Initialize VPNManger => var vpn = VPNManager.shared

Connect VPN =>  self.vpn.connectVPN();

Disconnect VPN => self.vpn.disconnectVPN()

 

 

5 2 votes
Article Rating
Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
wasisadman
wasisadman
1 year ago

Great post