Connecting to VPN with Swift 3 - swift

I'm trying to connect to a VPN in an iOS app. What I already know is the VPN type (L2TP over IPSec), account name, password and shared secret. The connection works through Mac's Network settings. Although, it seems a little more complicated, when you have to use this info in code.
First, I have imported the necessary library.
import NetworkExtension
Then, I'm trying to load preferences and in case of error, I'm using my own and save them. Looks like this:
NEVPNManager.shared().loadFromPreferences { error in
// config
NEVPNManager.shared().saveToPreferences { error in
if (error == nil) {
do {
try NEVPNManager.shared().connection.startVPNTunnel()
} catch {
print("Couldn't connect")
}
} else {
print("NEVPNManager.saveToPreferencesWithCompletionHandler failed: \(error!.localizedDescription)")
}
}
}
Where you can see the "// config", my data should be passed. I'm not 100% sure, if I'm doing it right.
There's a constant let p = NEVPNProtocolIPSec() where I'm placing my data. It's like p.username = "smth".
The question is: which fields of p should be filled? Where do I put the shared secret?
--- UPDATE ---
I'm always getting the error:
NEVPNManager.saveToPreferencesWithCompletionHandler failed: The operation couldn’t be completed. (NEVPNErrorDomain error 4.)
I can't find anything specific about that.
During the config, fields .sharedSecretReference and .passwordReference require the Data? object. I'm getting it by using keychain.get("passref")?.data(using: .utf8, allowLossyConversion: true) preceded by
let keychain: KeychainSwift! = KeychainSwift()
keychain.set("<my_password>", forKey: "passref")
(class KeychainSwift comes from here)
Where am I making a mistake?

Related

Got an error when dragging files using NSEvent. (macOS)

I wanna drag files to my window and then perform actions.
I tried to use snippets below provided in this answer to distinguish whether you're dragging a file or a window.
// In my window controller
class MyWindowController: NSWindowController {
init() {
// Some initialization steps below are omitted
let win = NSWindow(...)
super.init(window: win)
let contentView = DropView(frame: win.frame)
win.contentView?.addSubview(contentView)
registerGlobalMouseEvent()
}
func registerGlobalMouseEvent() {
self.window?.acceptsMouseMovedEvents = true
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// Codes below will cause errors
let pasteBoard = NSPasteboard(name: .drag)
guard let fileNames = pasteBoard.propertyList(forType: .init(rawValue: "NSFilenamesPboardType")) as? NSArray else { return }
let changeCount = pasteBoard.changeCount
if fileNames.count > 0 && lastChangeCount != changeCount {
lastChangeCount = changeCount
// My actions when dragging
}
})
}
}
Then I ran my codes and started dragging, I got three errors:
[sandbox] Failed to get a sandbox extension
[Framework] Failed to issue sandbox extension for /Users/roy/Downloads/test.txt with error 1
[default] Failed to issue sandbox token for URL: 'file:///Users/roy/Downloads/test.txt' with error: 'Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Cannot issue a sandbox extension for file "/Users/roy/Downloads/test.txt": Operation not permitted}'
 
But when I just do
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// My actions
})
, then everything went fine.
 
The first error seems harmless since it didn't prevent my app from running.
The second and the third ones are deadly and directly caused my app to crash.
I wonder if there are any problems in his code? Any useful thoughts would be great! :)
 
You need to know about Bookmarks and Security Scoped URLs when working with sandbox . A dragged URL gives your app process permission just once to read or read/write a “user selected file” depending on how you configure entitlements.
You can save a bookmark (blob of data) to keep access over subsequent sessions as long as the file isn’t updated by another process at which point the bookmark becomes stale and you will need to encourage the user to select the file again.
Handing a URL to another process across an XPC boundary like sharing requires that you own the file so may involve a copy to your sandbox cache.
e.g:
let dragurl = url_of_dragged_file //at this point you have at-least read access
let cachepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last!
let cachedir = URL(fileURLWithPath: cachepath)
let cacheurl = cachedir
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension(dragurl.pathExtension)
try FileManager.default.copyItem(at: dragurl, to: cacheurl)
At this point you have a copy in your local sandbox cache that can be handed off to a share sheet.
So I finally got a solution for this. :)
It appears that it indeed have something to do with the snippets I mentioned above, and here's the correction:
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
let pasteboard = NSPasteboard(name: .drag)
let changeCount = pasteboard.changeCount
if lastChangeCount != changeCount {
lastChangeCount = changeCount
if pasteboard.canReadObject(forClasses: [NSURL.self], options: [:]) {
/// actions
}
}
})
In this way, I got no errors and my codes run perfectly!

Swift Realm issue in iOS 14+

------LE: We ended up removing the encryption of the database because with realm team suggestions it got worse - all we could do was to remove the database and loose all stored info. Now we encrypt in keychain only the fields we need.------
I have an app released in store and after updating their iOS version to 14+, users started to complain about info not being populated from database. Not all users with iOS 14+ have this issue, it appears randomly on some devices.
The issue goes away for awhile if they reinstall the app or after they update it to another version, but after using it for a few minutes it happens again.
My database uses encryption as documented here.
The store version of my app uses Realm 5.4.8, but I tested their last version (10.0.0) and the issue is still present.
I checked this issue but it's not the case for me, I don't have a shared app group container or a share extension.
Here's how the initialisation of realm looks like:
override init() {
super.init()
do {
guard let config = getMigrationAndEncryptionConfiguration() else {
realmConfigured = try Realm()
return
}
realmConfigured = try Realm(configuration: config)
} catch let error as NSError {
// this is where I got the error:
//"Encrypted interprocess sharing is currently unsupported.DB has been opened by pid: 4848. Current pid is 5806."
}
}
func getMigrationAndEncryptionConfiguration() -> Realm.Configuration? {
let currentSchemaVersion: UInt64 = 19
if Keychain.getData(for: .realmEncryptionKey) == nil {
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
Keychain.save(data: key, for: .realmEncryptionKey)
}
guard let key = Keychain.getData(for: .realmEncryptionKey) else {
return nil
}
let fileUrl = Realm.Configuration().fileURL!.deletingLastPathComponent()
.appendingPathComponent("Explorer.realm")
var config = Realm.Configuration(fileURL: fileUrl,
encryptionKey: key,
schemaVersion: currentSchemaVersion, migrationBlock: { (migration, oldVersion) in
if oldVersion != currentSchemaVersion {
print("we need migration!")
}
})
return config
}
I had another question opened for the same issue on SO, but it was closed because I didn't have enough details. After another release of my app with more logs, I could find the error that appears at initialisation of realm:
"Encrypted interprocess sharing is currently unsupported.DB has been opened by pid: 4848. Current pid is 5806. "
This appears after the app goes to background, it gets terminated (crash or closed by the system/user) and when the users opens it again, realm fails to init.
I read all about encrypted realm not being supported in app groups or in a share extension, but I didn't implement any of that in my app, is there any other reason why this error happens?
I also removed Firebase Performance from my app because I read that this module could generate issues on realm database, but it didn't help.
I opened an issue on realm github page, but I got no answer yet.
Does anyone have any idea how to fix this or why is this happening?
Thank you.

CNContactStore execute fails

I am working out how to use the Contacts framework, however some fairly simple code to create a contact is failing with an unexpected result. This is my code:
let Store = CNContactStore()
Store.requestAccess(for: .contacts, completionHandler:{ success, error in
if success {
let Contact = CNMutableContact()
Contact.givenName = "Dave"
Contact.familyName = "Nottage"
let SaveRequest = CNSaveRequest()
SaveRequest.add(Contact, toContainerWithIdentifier: nil)
do {
try Store.execute(SaveRequest)
print("Success")
}
catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("No access")
}
})
..and this is the result:
2019-02-22 10:30:56.050344+1030 ContactsTest[30329:25254955] [default] Unable to load Info.plist exceptions (eGPUOverrides)
2019-02-22 10:30:57.973724+1030 ContactsTest[30329:25254955] Could not get real path for Address Book lock folder: open() for F_GETPATH failed.
2019-02-22 10:30:57.973954+1030 ContactsTest[30329:25254955] Unable to open file lock: <ABProcessSharedLock: 0x600001752ac0: name=(null), lockFilePath=(null), localLock=<NSRecursiveLock: 0x600002914a80>{recursion count = 0, name = nil}, fileDescriptor=-1> Error Domain=NSPOSIXErrorDomain Code=14 "Bad address" UserInfo={ABFileDescriptor=-1}
The operation couldn’t be completed. (Foundation._GenericObjCError error 0.)
Any ideas on what might be causing this?
Edit: Note also that this is being compiled for macOS 10.14 SDK, and is running on macOS 10.14.3
The answer is to check the Contacts checkbox of App Data in the App Sandbox section in Capabilities and turn on the switch for App Sandbox.
Make sure you added key NSContactsUsageDescription in Info.plist.
Please refer to link.
Important
An iOS app linked on or after iOS 10.0 must include in its Info.plist
file the usage description keys for the types of data it needs to
access or it will crash. To access Contacts data specifically, it must
include NSContactsUsageDescription.

waitForExpectations not working as expected in Swift

Trying to write a unit test in Swift 4 for an already working method and having trouble getting the correct result. My current approach is:
let expected = expectation(description: "Obtain access keys")
let keys = sut.retrieveAccessKeys()
//sleep(5)
if keys != nil {
expected.fulfill()
} else {
XCTFail("ERROR: Failed obtaining access keys")
}
waitForExpectations(timeout: 5) { error in
let myError = error as? Error
print("ERROR: \(String(describing: myError?.localizedDescription))")
}
}
The sut makes a network call via a completion handler if the values don't exist within the keychain. The outputted error I get is:
Error: The operation couldn’t be completed. (com.apple.XCTestErrorDomain error 0.)
I've tried various ways including adding in sleep(5). sut. retrieveAccessKeys() does work and the resulting keys contains an optional tuple.

How to delete system domain file(s) on OS X using Swift 2?

For a open source project we want to create a OS X Uninstaller, which should be able to delete user and system domain files. The goal is to write the Uninstaller in Swift only.
During my research I found examples using NSTask to send a rm command,
or others recommend using SMJobBless and a HelperTool for authentication. Both seems for me not the right way. I'm new to the OS X development, so it's hard for me to say which is the right way to achive the task.
I'm looking for an example in Swift 2 which allows user to authenticate and delete user and system domain files. If any one colud help me, I would really appreciate it.
After a couple hours playing around with SecurityFoundation, I found it to be too complex for my taste. Plus, the documentation is outdated. Inspired by this question. You can do it via AppleScript.
You need to divide your uninstaller into 2 parts: a default target
to delete user files and a helper to delete system files (with escalated privileges of course).
Option 1
You can write that helper as a shell script and include that as a resource:
File delete_system_file.sh:
#!/bin/bash
rm -f /usr/local/dummy.txt
In your app:
let helper = NSBundle.mainBundle().pathForResource("delete_system_file", ofType: "sh")!
let script = "do shell script \"\(helper)\" with administrator privileges"
if let appleScript = NSAppleScript(source: script) {
var error: NSDictionary? = nil
appleScript.executeAndReturnError(&error)
if error != nil {
print(error!)
} else {
print("Deleted /usr/local/dummy.txt")
}
} else {
print("Cannot create the AppleScript object")
}
Option 2
You can do this entirely in Swift by adding a new target to your project. Assuming your first target is called MyUninstaller.
Add a new target
Click File > New > Target...
Under OS X, select Application > Command Line Tool
Give your new target a name, say DeleteSystemFile
In the Project Navigator on the left, expand the DeleteSystemFile group and click on the main.swift file. To delete the dummy.txt file:
do {
try NSFileManager.defaultManager().removeItemAtPath("/usr/local/dummy.txt")
} catch {
print(error)
}
Back to the first target
Now go back to the first target (MyUninstaller) and add DeleteSystemFile as a Target Dependancy.
You can run the second target with escalated privileges by calling it through AppleScript:
let helper = NSBundle.mainBundle().pathForAuxiliaryExecutable("DeleteSystemFile")!
let script = "do shell script \"\(helper)\" with administrator privileges"
if let appleScript = NSAppleScript(source: script) {
var error: NSDictionary? = nil
appleScript.executeAndReturnError(&error)
if error != nil {
print(error!)
} else {
print("Deleted /usr/local/dummy.txt")
}
} else {
print("Cannot create the AppleScript object")
}
Option 3
Use SMJobBless, which is the Apple's recommended way of running privileged helper:
import SecurityFoundation
import ServiceManagement
// 1. Obtain an Authorization Reference
// You can do this at the beginning of the app. It has no extra rights until later
var authRef: AuthorizationRef = nil
let status = AuthorizationCreate(nil, nil, [.Defaults], &authRef)
// There's really no reason for this to fail, but we should check or completeness
guard status == errAuthorizationSuccess else {
fatalError("Cannot create AuthorizationRef: \(status)")
}
// 2. Ask user for admin privilege
var authItem = AuthorizationItem(name: kSMRightBlessPrivilegedHelper, valueLength: 0, value: nil, flags: 0)
var authRights = AuthorizationRights(count: 1, items: &authItem)
let flags: AuthorizationFlags = [.Defaults, .InteractionAllowed, .ExtendRights]
let status2 = AuthorizationCopyRights(authRef, &authRights, nil, flags, nil)
if status2 != errAuthorizationSuccess {
// Can't obtain admin privilege, handle error
print("Cannot obtain admin privilege")
}
// 3. Run the privileged helper
// This label must be globally unique and matches the product name of your helper
let label = "com.myCompany.myApp.myAppPrivilgedHelper"
var error: CFError? = nil
let result = withUnsafeMutablePointer(&error) {
SMJobBless(kSMDomainSystemLaunchd, label, authRef, UnsafeMutablePointer($0))
}
if !result {
print(error!)
}
// 4. Release the Authorization Reference
AuthorizationFree(authRef, [.Defaults])
This involves some setup, which you can read about in the documentation for SMJobBless. There's also a sample project in ObjC.
Disclaimer: I could not test this all the way as I don't have a personal signing key. I could do away with section 2 from above, but included it here for completeness--that's how the sample project does it.