I'm trying to connect to a VPN using Swift in Xcode. I'm using KeychainSwift to keep keychain references. My code looks like this:
private func connectVPN(completion: #escaping () -> Void) {
let keychain = KeychainSwift()
keychain.set("<mypassword>", forKey: "passref")
keychain.set("<sharedsecretpassword>", forKey: "secretref")
NEVPNManager.shared().loadFromPreferences { error in
let vpnhost = "<11.11.11.11>"
let username = "<myusername>"
let p = NEVPNProtocolIPSec()
p.username = username
p.localIdentifier = username
p.serverAddress = vpnhost
p.remoteIdentifier = vpnhost
p.authenticationMethod = .sharedSecret
p.disconnectOnSleep = false
p.sharedSecretReference = keychain.getData("secretref")
p.passwordReference = keychain.getData("passref")
var rules = [NEOnDemandRule]()
let rule = NEOnDemandRuleConnect()
rule.interfaceTypeMatch = .any
rules.append(rule)
NEVPNManager.shared().localizedDescription = "My VPN"
NEVPNManager.shared().protocolConfiguration = p
NEVPNManager.shared().onDemandRules = rules
NEVPNManager.shared().isOnDemandEnabled = true
NEVPNManager.shared().isEnabled = true
NEVPNManager.shared().saveToPreferences { error in
if (error != nil) {
print(error!)
} else {
do {
try NEVPNManager.shared().connection.startVPNTunnel()
completion()
} catch {
print("can't connect VPN'")
}
}
}
}
}
I'm using keychain.getData("secretref"), because this field needs
A persistent keychain reference to a keychain item containing the IKE
shared secret.
What's more,
The persistent keychain reference must refer to a keychain item of
class kSecClassGenericPassword.
I'm not really sure, if I'm doing it right. I didn't subclass kSecClassGenericPassword or use it in any way.
When I'm using this function in code, a window shows with information, that there is no shared secret for this VPN. I think it means that this keychain doesn't work as it's supposed to.
In iPhone settings, it tries to connect, moves switch towards green and instantly the switch goes back to "off" state. When I put the same data as in code manually, the connection works.
What am I doing wrong? What should I correct?
Okay, I have the answer. In the query for the SecItemCopyMatching, I had to choose kSecReturnPersistentRef with kCFBooleanTrue - not kSecReturnData.
Related
I am making an app that activates a VPN connection based on OpenVPN, retrieves a certificate from the database, and opens a tunnel using NEPacketTunnelProvider and NetworkExtension.
I used the following repository, and now my VPN is working fine.
But the problem is that I want to allow only one app to use this VPN when enabled (WhatsApp precisely), and I want to restrict all other apps of using it.
On Android it's possible by giving the bundle identifier of the allowed apps to the PackageManager.
Can you please help me?
This is my PacketTunnelProvider class:
import NetworkExtension
import OpenVPNAdapter
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
print("started!")
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
// configuration.settings = [
// // Additional parameters as key:value pairs may be provided here
// ]
// Uncomment this line if you want to keep TUN interface active during pauses or reconnections
// configuration.tunPersist = true
// Apply OpenVPN configuration
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
// Provide credentials if needed
if !evaluation.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else {
fatalError()
}
// Retrieve a password from the keychain
// guard let password: String = ... {
// fatalError()
// }
let credentials = OpenVPNCredentials()
credentials.username = username
// credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect(using: packetFlow)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (Error?) -> Void) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
print(logMessage)
}
}
This is the function used in my VPN View Model to start a tunnel:
func configureVPN(serverAddress: String, username: String, password: String) {
var configData:Data = Data.init()
self.getCertificate{certificate in
configData = certificate!
guard
//If we want to read from a file
// let configData = self.readFile(name: "vtest2"),
let providerManager = self.providerManager
else {
return
}
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = self.providerId // bundle id of the network extension target
tunnelProtocol.providerConfiguration = ["ovpn": configData]
tunnelProtocol.disconnectOnSleep = false
providerManager.protocolConfiguration = tunnelProtocol
providerManager.localizedDescription = "Slyfone Guard" // the title of the VPN profile which will appear on Settings
providerManager.isEnabled = true
providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try providerManager.connection.startVPNTunnel(options: nil) // starts the VPN tunnel.
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
}
As an engineer from Apple said:
The way to do it is to use Per-App VPN. See the Per-App VPN On Demand section in the NETunnelProviderManager documentation.
With NEPacketTunnelProvider on macOS (as of 10.15.4) you can set this up yourself with NEAppRule. A very generic example of setting up Safari to trigger the VPN would be:
var perAppManager = NETunnelProviderManager.forPerAppVPN()
/* ... */
NETunnelProviderManager.forPerAppVPN().loadFromPreferences(completionHandler: { error in
precondition(Thread.isMainThread)
/* ... */
let proto = (perAppManager.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol()
proto.serverAddress = "server.vpn.com"
proto.providerBundleIdentifier = "com.perapp-vpn.macOSPacketTunnel.PacketTunnelTest"
var appRules = [NEAppRule]()
let appRule = NEAppRule(signingIdentifier: "com.apple.Safari", designatedRequirement: "identifier \"com.apple.Safari\" and anchor apple")
appRule.matchDomains = ["example.com"]
appRules.append(appRule)
perAppManager.appRules = appRules
perAppManager.isOnDemandEnabled = true
perAppManager.protocolConfiguration = proto
perAppManager.isEnabled = true
perAppManager.localizedDescription = "Testing Per-App VPN"
self.perAppManager.saveToPreferences { saveError in
/* Proceed to connect */
}
})
That was a very generic case and forPerAppVPN() is only available on macOS. A more real-world case world case for iOS would be to create this process through MDM. That entire flow is explained in the documentation I mentioned previously. I would start by just creating a configuration profile in Configurator 2 and testing it out.
No idea if it works on OpenVPN
I am trying to connect programmatically to a VPN. I got the code below on this website: http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/
I am trying to develop a command line application for macOS to connect programatically to a VPN.
import Foundation
import NetworkExtension
class VPN {
let vpnManager = NEVPNManager.shared();
let userName: String;
let passString: String;
let secret: String;
let server: String;
init (_ user: String, _ pass: String, _ secret: String, _ server: String){
userName = user
passString = pass
self.secret = secret
self.server = server
}
private var vpnLoadHandler: (Error?) -> Void { return
{ (error:Error?) in
if ((error) != nil) {
print("Could not load VPN Configurations")
return;
}
print ("me! me! ")
let p = NEVPNProtocolIPSec()
p.username = self.userName
p.serverAddress = self.server
p.authenticationMethod = NEVPNIKEAuthenticationMethod.sharedSecret
let kcs = KeychainService();
kcs.save(key: "SHARED", value: self.secret )
kcs.save(key: "VPN_PASSWORD", value: self.passString)
p.sharedSecretReference = kcs.load(key: "SHARED")
p.passwordReference = kcs.load(key: "VPN_PASSWORD")
p.useExtendedAuthentication = true
p.disconnectOnSleep = false
p.localIdentifier = "uio"
self.vpnManager.protocolConfiguration = p
self.vpnManager.localizedDescription = "Contensi"
self.vpnManager.isEnabled = true
self.vpnManager.saveToPreferences(completionHandler: self.vpnSaveHandler)
} }
private var vpnSaveHandler: (Error?) -> Void { return
{ (error:Error?) in
if (error != nil) {
print("Could not save VPN Configurations")
return
} else {
do {
try self.vpnManager.connection.startVPNTunnel()
} catch let error {
print("Error starting VPN Connection \(error.localizedDescription)");
}
}
}
}
public func connectVPN() {
//For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works
do {
self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)
// self.vpnManager.saveToPreferences(completionHandler: nil)
}
}
public func disconnectVPN() ->Void {
self.vpnManager.connection.stopVPNTunnel()
}
public func status() -> NEVPNStatus {
return vpnManager.connection.status
}
}
My problem is that I my completion handlers are not working. Take the function connectVPN(), for instance. It's completion handler is vpnLoadHandler. However, I never get that handler executed. See the print statement on the handler? Nothing ever gets printed.
Reading Apple's documentation, I know that I have to configure XCode with an entitlement in order to allow VPN - I haven't got the "capabilities"-tab, so I don't know how to add that entitlement. I am not sure if that's the problem, since the app compiles file and executes. I just don't get the completion handler to execute.
So, basically:
why isn't the handler being called? And what can I do to debug?
Could this be due the lack an entitlement to allow the configuration of a personal vpn in xcode? If so, how can I configure that?
Best,
Francis
UPDATE I actually managed to create the entitlements file, and I guess it has the proper settings to allow a personal vpn. So I guess that part is solved. But why isn't the completion handler called?
I'm completely new to macos-development and swift so please bear with me...
I'm trying to create a VPN connection in a Cocoa app. I've based the code on macosvpn: https://github.com/halo/macosvpn/blob/master/macosvpn/Classes/VPNServiceCreator.swift
This is what I have so far:
func createVPNService() {
let options = ["" : ""]
let prefs = SCPreferencesCreate(nil, "TheVPN" as CFString, nil)
// These variables will hold references to our new interfaces
let initialTopInterface: SCNetworkInterface!
let initialBottomInterface: SCNetworkInterface!
// L2TP on top of IPv4
initialBottomInterface = SCNetworkInterfaceCreateWithInterface(kSCNetworkInterfaceIPv4, kSCNetworkInterfaceTypeL2TP)
// PPP on top of L2TP
initialTopInterface = SCNetworkInterfaceCreateWithInterface(initialBottomInterface!, kSCNetworkInterfaceTypePPP)
let service = SCNetworkServiceCreate(prefs!, initialTopInterface!)
SCNetworkServiceSetName(service!, ("Service Name" as CFString))
// Because, if we would like to modify the interface, we first need to freshly fetch it from the service
// See https://lists.apple.com/archives/macnetworkprog/2013/Apr/msg00016.html
let topInterface = SCNetworkServiceGetInterface(service!)
SCNetworkInterfaceSetConfiguration(topInterface!, options as CFDictionary)
// Now let's apply the shared secret to the IPSec part of the L2TP/IPSec Interface
let thingy:CFString = "IPSec" as CFString
SCNetworkInterfaceSetExtendedConfiguration(topInterface!, thingy, options as CFDictionary)
SCNetworkServiceEstablishDefaultConfiguration(service!)
let networkSet = SCNetworkSetCopyCurrent(prefs!)
let serviceProtocol = SCNetworkServiceCopyProtocol(service!, kSCNetworkProtocolTypeIPv4)
SCNetworkProtocolSetConfiguration(serviceProtocol!, options as CFDictionary)
SCNetworkSetAddService(networkSet!, service!)
if !SCPreferencesCommitChanges(prefs!) {
print("Error: Could not commit preferences. (Code \(SCError()))")
}
if !SCPreferencesApplyChanges(prefs!) {
print("Error: Could not apply changes. (Code \(SCError()))")
}
}
No network service is created when I'm running this.
I have no idea what options and prefs should be really? They are, more or less, empty now.
Also, SCPreferencesCommitChanges and SCPreferencesApplyChanges fails with a 1003-code. I suppose they need root-privileges to work and I haven't been able to figure out how to get root-privileges.
Thanks for any help!
I finally figured out how to create the VPN connection/service. But I still have to figure out how options and prefs should be used.
SCPreferencesCreate was replaced with SCPreferencesCreateWithAuthorization and some Authorization code before. This is the updated code.
func createVPNConnection() {
let flags : AuthorizationFlags = [.interactionAllowed, .extendRights, .preAuthorize]
var authRef: AuthorizationRef?
AuthorizationCreate(nil, nil, flags, &authRef)
let options = ["" : ""]
let prefs = SCPreferencesCreateWithAuthorization(nil, "TheVPN" as CFString, nil, authRef)
// These variables will hold references to our new interfaces
let initialTopInterface: SCNetworkInterface!
let initialBottomInterface: SCNetworkInterface!
// L2TP on top of IPv4
initialBottomInterface = SCNetworkInterfaceCreateWithInterface(kSCNetworkInterfaceIPv4, kSCNetworkInterfaceTypeL2TP)
// PPP on top of L2TP
initialTopInterface = SCNetworkInterfaceCreateWithInterface(initialBottomInterface!, kSCNetworkInterfaceTypePPP)
let service = SCNetworkServiceCreate(prefs!, initialTopInterface!)
SCNetworkServiceSetName(service!, ("Test Service Name" as CFString))
// Because, if we would like to modify the interface, we first need to freshly fetch it from the service
// See https://lists.apple.com/archives/macnetworkprog/2013/Apr/msg00016.html
let topInterface = SCNetworkServiceGetInterface(service!)
// Let's apply all configuration to the PPP interface
// Specifically, the servername, account username and password
SCNetworkInterfaceSetConfiguration(topInterface!, options as CFDictionary)
// Now let's apply the shared secret to the IPSec part of the L2TP/IPSec Interface
let thingy:CFString = "IPSec" as CFString
SCNetworkInterfaceSetExtendedConfiguration(topInterface!, thingy, options as CFDictionary)
SCNetworkServiceEstablishDefaultConfiguration(service!)
let networkSet = SCNetworkSetCopyCurrent(prefs!)
let serviceProtocol = SCNetworkServiceCopyProtocol(service!, kSCNetworkProtocolTypeIPv4)
SCNetworkProtocolSetConfiguration(serviceProtocol!, options as CFDictionary)
SCNetworkSetAddService(networkSet!, service!)
if !SCPreferencesCommitChanges(prefs!) {
print("Error: Could not commit preferences. (Code \(SCError()))")
}
if !SCPreferencesApplyChanges(prefs!) {
print("Error: Could not apply changes. (Code \(SCError()))")
}
}
Sorry, my subject isn't very specific.
I'm dealing with managing multiple users in my realm and having some problems. I have a user register with their email and name, then when realm logs in and creates a SyncUser, I create a new YpbUser object with the email, name, and SyncUser.current.identity in YpbUser.id.
When a user logs in, I want to use their email to look up whether there's an existing YpbUser for their email. I'm doing it this way so in the future if someone uses username/password and then uses Google auth (not yet implemented), those SyncUsers can get to the same YpbUser.
Below is my code for checking for an existing YpbUser, along with my realm setup code. The problem I'm having is that it usually, but not always, fails to find a YpbUser even if that user exists (I'm looking at the YpbUser list in realm studio and the user with that email is definitely there!)
Inspecting from within existingUser(for:in:), users is usually 0, but it sometimes non-zero.
I assume the issue lies somewhere in the fact that I'm pretty much just guessing on how to use SyncSubscription.observe.
Please help?
fileprivate func openRealmWithUser(user: SyncUser) {
DispatchQueue.main.async { [weak self] in
let config = user.configuration(realmURL: RealmConstants.realmURL, fullSynchronization: false, enableSSLValidation: true, urlPrefix: nil)
self?.realm = try! Realm(configuration: config)
let songSub = self?.realm?.objects(Song.self).subscribe()
let usersSub = self?.realm?.objects(YpbUser.self).subscribe()
self?.usersToken = usersSub?.observe(\.state, options: .initial) { state in
if !(self?.proposedUser.email.isEmpty)! {
self?.findYpbUser(in: (self?.realm)!)
}
}
self?.performSegue(withIdentifier: Storyboard.LoginToNewRequestSegue, sender: nil)
}
}
fileprivate func findYpbUser(in realm: Realm) {
if proposedUser != YpbUser() { // self.proposedUser.email gets set from the login method
let existingUser = YpbUser.existingUser(for: proposedUser, in: realm)
guard existingUser == nil else { // If we find the YpbUser, set as current:
try! realm.write {
pr("YpbUser found: \(existingUser!)")
YpbUser.current = existingUser }
return
}
pr("YpbUser not found.") // i.e., SyncUser set, but no YpbUser found. i.e., creating a new YpbUser
createNewYpbUser(for: proposedUser, in: realm)
}
}
extension YpbUser {
class func existingUser (for proposedUser: YpbUser, in realm: Realm) -> YpbUser? {
let users = realm.objects(YpbUser.self)
let usersWithThisEmail = users.filter("email = %#", proposedUser.email)
if let emailUser = usersWithThisEmail.first {
return emailUser
}
return nil
}
}
So I made a chatroom and when someone sends a message they also add a Subscription in my cloud kit database but the problem is there cant be more then one of the same name that is a subscription and I want them to be able to set more subscriptions then one. Here is some code:
func setupCloudKitSubscription () {
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.boolForKey("subscribed") == false {
let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)
let subscription = CKSubscription(recordType: "Extra1", predicate: predicate, options: CKSubscriptionOptions.FiresOnRecordCreation)
let notificationInfo = CKNotificationInfo()
notificationInfo.alertLocalizationKey = "New Sweet"
notificationInfo.shouldBadge = true
subscription.notificationInfo = notificationInfo
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveSubscription(subscription) { (subscription:CKSubscription?, error:NSError?) -> Void in
if error != nil {
print(error?.localizedDescription)
}else{
userDefaults.setBool(true, forKey: "subscribed")
userDefaults.synchronize()
You see how it says recordType: "Extra1" how can I made the "Extra1" different text every time someone makes a subscription? Thanks!
Your question is not completely clear. I think what you wanted to ask is that you want the subscription to send you a different message with each notification.
You could set it to display one or more fields of the record. For doing that you should use something like this:
notificationInfo.alertLocalizationKey = "Response: %1$#"
notificationInfo.alertLocalizationArgs = ["responseField"]
Then you also need this in your Localization.Strings file.
"Response: %1$#" = "Response: %1$#";