Skip to content

Instantly share code, notes, and snippets.

@IhorYachmenov
Created October 13, 2020 07:32
Show Gist options
  • Save IhorYachmenov/fb63fd19f30541780950b6670e8a2865 to your computer and use it in GitHub Desktop.
Save IhorYachmenov/fb63fd19f30541780950b6670e8a2865 to your computer and use it in GitHub Desktop.
Example how configure VPN profile based on IKEv2 protocol use NEVPNManager with certificate authentication, example was be tested on real devices, server use AlgoVPN.
import Foundation
import NetworkExtension
class VPNIKEv2Setup {
static let shared = VPNIKEv2Setup()
let vpnManager = NEVPNManager.shared()
func initVPNTunnelProviderManager() {
print("CALL LOAD TO PREFERENCES...")
self.vpnManager.loadFromPreferences { [self] (error) -> Void in
if((error) != nil) {
print("VPN Preferences error: 1 - \(String(describing: error))")
} else {
let IKEv2Protocol = NEVPNProtocolIKEv2()
IKEv2Protocol.authenticationMethod = .certificate
IKEv2Protocol.serverAddress = VPNServerSettings.shared.vpnServerAddress
IKEv2Protocol.remoteIdentifier = VPNServerSettings.shared.vpnRemoteIdentifier
IKEv2Protocol.localIdentifier = VPNServerSettings.shared.vpnLocalIdentifier
IKEv2Protocol.useExtendedAuthentication = false
IKEv2Protocol.ikeSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES256GCM
IKEv2Protocol.ikeSecurityAssociationParameters.diffieHellmanGroup = .group20
IKEv2Protocol.ikeSecurityAssociationParameters.integrityAlgorithm = .SHA512
IKEv2Protocol.ikeSecurityAssociationParameters.lifetimeMinutes = 1440
IKEv2Protocol.childSecurityAssociationParameters.encryptionAlgorithm = .algorithmAES256GCM
IKEv2Protocol.childSecurityAssociationParameters.diffieHellmanGroup = .group20
IKEv2Protocol.childSecurityAssociationParameters.integrityAlgorithm = .SHA512
IKEv2Protocol.childSecurityAssociationParameters.lifetimeMinutes = 1440
IKEv2Protocol.deadPeerDetectionRate = .medium
IKEv2Protocol.disableRedirect = true
IKEv2Protocol.disableMOBIKE = false
IKEv2Protocol.enableRevocationCheck = false
IKEv2Protocol.enablePFS = true
IKEv2Protocol.useConfigurationAttributeInternalIPSubnet = false
IKEv2Protocol.serverCertificateIssuerCommonName = VPNServerSettings.shared.vpnServerCertificateIssuerCommonName
IKEv2Protocol.disconnectOnSleep = false
IKEv2Protocol.certificateType = .ECDSA384
IKEv2Protocol.identityDataPassword = VPNServerSettings.shared.p12Password
IKEv2Protocol.identityData = self.dataFromFile()
self.vpnManager.protocolConfiguration = IKEv2Protocol
self.vpnManager.localizedDescription = "Venom VPN"
self.vpnManager.isEnabled = true
self.vpnManager.isOnDemandEnabled = false
//Set rules
var rules = [NEOnDemandRule]()
let rule = NEOnDemandRuleConnect()
rule.interfaceTypeMatch = .any
rules.append(rule)
// self.vpnManager.onDemandRules = rules
print("SAVE TO PREFERENCES...")
//SAVE TO PREFERENCES...
self.vpnManager.saveToPreferences(completionHandler: { (error) -> Void in
if((error) != nil) {
print("VPN Preferences error: 2 - \(String(describing: error))")
} 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 - \(String(describing: error))")
} 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 - \(String(describing: error))")
//Show alert here
print("title: Oops.., message: Something went wrong while connecting to the VPN. Please try again.")
print(startError.debugDescription)
} else {
print("Starting VPN...")
}
}
})
}
})
}
}
}
//MARK:- Connect VPN
static func connectVPN() {
VPNIKEv2Setup().initVPNTunnelProviderManager()
}
//MARK:- Disconnect VPN
static func disconnectVPN() {
VPNIKEv2Setup().vpnManager.connection.stopVPNTunnel()
}
//MARK:- Disconnect VPN
static func testConnect() {
do {
try VPNIKEv2Setup().vpnManager.connection.startVPNTunnel()
} catch let error {
print(error)
}
}
//MARK:- check connection staatus
static func checkStatus() {
let status = VPNIKEv2Setup().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")
}
}
func dataFromFile() -> Data? {
let rootCertPath = Bundle.main.url(forResource: "phone", withExtension: "p12")
print(rootCertPath?.absoluteURL as Any)
return try? Data(contentsOf: rootCertPath!.absoluteURL)
}
}
class VPNServerSettings: NSObject {
static let shared = VPNServerSettings()
let p12Password = "*****" // password from file certificate "****.p12"
let vpnServerAddress = "******"
let vpnRemoteIdentifier = "*******" // In my case same like vpn server address
let vpnLocalIdentifier = "phone@caf1e9*******.algo"
let vpnServerCertificateIssuerCommonName = "*******" // In my case same like vpn server address
}
@Dearimranullah
Copy link

not working for me it give this error
CALL LOAD TO PREFERENCES...
2023-11-06 20:14:16.584739+0500 MagicChatApp[1058:203441] [] Failed to load configurations: Error Domain=NEConfigurationErrorDomain Code=10 "permission denied" UserInfo={NSLocalizedDescription=permission denied}
2023-11-06 20:14:16.891598+0500 MagicChatApp[1058:203172] [] Failed to load the configuration: Error Domain=NEVPNErrorDomain Code=5 "permission denied" UserInfo={NSLocalizedDescription=permission denied}
VPN Preferences error: 1 - Optional(Error Domain=NEVPNErrorDomain Code=5 "permission denied" UserInfo={NSLocalizedDescription=permission denied})

@IhorYachmenov
Copy link
Author

IhorYachmenov commented Nov 6, 2023

@Dearimranullah It's possible this code worked well in 2019 not sure about it right now, in your case I guess the problem with "permission denied" this functionality requires VPN permission from the user, if you forgot about that this code wouldn't work absolutely, also, your App Store account should have a Company license if your account has another type of license you will not pass the App Store Review Process, rule number 5.4 "Apps offering VPN services must utilize the NEVPNManager API and may only be offered by developers enrolled as an organization."

@Dearimranullah
Copy link

Dearimranullah commented Nov 6, 2023 via email

@IhorYachmenov
Copy link
Author

IhorYachmenov commented Nov 6, 2023

@Dearimranullah select your Target than tab "Signing & Capabilities" and you should enable capability "Network Extension -> and check Packet Tunnel" and add another one capability "Personal VPN", and in your entitlements file add "Outgoing Network Connections" - > YES, maybe last step will added automatically can't recall that. Then when you will call loadFromPreferences you should see alert with permission access

@Dearimranullah
Copy link

Dearimranullah commented Nov 6, 2023 via email

@Rayan25062011
Copy link

How do I use it in content view? Can you give me an example?

@IhorYachmenov
Copy link
Author

@Rayan25062011 Hi, in this realization VPNIKEv2Setup is Singelton because you can call any non-static function such as VPNIKEv2Setup.initVPNTunnelProviderManager.initVPNTunnelProviderManager(), and after that in any comfortable place use VPNIKEv2Setup. connectVPN() and disconnectVPN() etc., or rewrite realization for your requirements

@Rayan25062011
Copy link

@Rayan25062011 Hi, in this realization VPNIKEv2Setup is Singelton because you can call any non-static function such as VPNIKEv2Setup.initVPNTunnelProviderManager.initVPNTunnelProviderManager(), and after that in any comfortable place use VPNIKEv2Setup. connectVPN() and disconnectVPN() etc., or rewrite realization for your requirements

Thank you very much @IhorYachmenov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment