Create VPN connection - swift

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()))")
}
}

Related

Swift, Extracting Glyphs from a Font by Name

I am attempting to glyphs from the SF Symbol front using the code sample below provided by an extract https://github.com/davedelong/sfsymbols. I have isolated the code that I can't get to work with values extracted from a run time.
I have condensed the code down to only the statements required to produce the problem.
This issue seems to be the name I am providing to the final statement in CTFontGetGlyphWithName, Every combination of values I have attempted returns 0.
Please note that you have to have the SF Symbols application installed for it to acquire the initial bundle.
Please excuse the code quality, this is just to create the shortest possible example of code to reproduce the problem.
thanks.
I have followed quite a few snipped and tutorials but there doesn't seem to be a great deal of practical examples of these more advanced functions available.
public class StackOverflow {
public func Test() {
let maybeCFURLs = LSCopyApplicationURLsForBundleIdentifier("com.apple.SFSymbols" as CFString, nil)?.takeRetainedValue()
let cfURLs = maybeCFURLs as? Array<URL>
let fontFile = cfURLs!.compactMap { url -> URL? in
guard let appBundle = Bundle(url: url) else { return nil }
return appBundle.url(forResource: "SFSymbolsFallback", withExtension: "ttf")
}
let attributes = [
kCTFontTraitsAttribute: [
kCTFontWeightTrait: NSFont.Weight.regular.rawValue
]
]
let provider = CGDataProvider(url: fontFile.first! as CFURL)
let cgFont = CGFont(provider!)
let ctAttributes = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
let font = CTFontCreateWithGraphicsFont(cgFont!, 32, nil, ctAttributes)
// let name = "uni.plus.square.fill.medium" as CFString - Does not work
//var name = "square.fill.medium" as CFString - Does not work
let glyphName = "plus.square.fill.medium" as CFString
var glyph = CTFontGetGlyphWithName(font, glyphName )
}
}

Keychain references in Swift used in NEVPNManager

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.

SyncCredentials Don't exist in Realm Swift

Im trying to connect my app to a Realm Object Server. The documentation says, to do this you use the below:
// create a configuration object
let realmUrl = URL(string: "realms://example.com:9000/~/settings")!
let realmUser = SyncCredentials.usernamePassword(username: username, password: password)
let config = Realm.Configuration(user: realmUser, realmURL: realmUrl)
// open the Realm with the configuration object
let settingsRealm = try! Realm(configuration: config)
However for SyncCredentials.usernamePassword, XCode says SyncCredentials doesn't exist. From the looks you need to set SyncConfiguration on in Realm.configuration (or the only file I can find RealmConfiguration.swift)
Now I'm in that file theres no option to use SyncCredentials
My question is, how do I simply connect my app with a Realm Object Database using SyncCredentials (or however you're supposed to do it). Been pulling my hair out all day over this, surely it can't be that hard :-(
There seem some mistakes.
SyncCredentials is auth info that is used to log in. It is not user object. Realm.Configuration doesn't receive user and realmURL parameters in the initializer. You need to use SyncConfiguration instead.
The example code for logging in or instantiating Realm with existing user is the following.
let syncServerURL = URL(string: "realm://example.com:9080/~/settings")!
let syncAuthURL = URL(string: "http://example.com:9080")!
if let user = SyncUser.current {
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
let realm = try! Realm(configuration: config)
...
} else {
let username = ...
let password = ...
let credentials = SyncCredentials.usernamePassword(username: username, password: password)
SyncUser.logIn(with: credentials, server: syncAuthURL) { user, error in
DispatchQueue.main.async {
if let user = user {
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
let realm = try! Realm(configuration: config)
...
} else {
// Handle error
}
}
}
}
Please read our documentation again https://realm.io/docs/swift/latest/#sync
and also our RealmTasks sample project helps you to understand interacting Realm Object Server. https://github.com/realm-demos/realm-tasks
If you still see SyncCredentials not found error in above code, probably you didn't setup RealmSwift framework correctly. Please add more info that how did you set up Realm.
Swift Package Manager doesn't load SyncCredentials or SyncUser properly.
I was using the SPM to install RealmSwift as a dependency from IceCream. I removed Realm and IceCream from SPM. Then installed using Carthage to solve the issue.

Import p12 certificate with full access to private key for my application (OS X)

I'm accessing the https webserver with certificate authentication from mac app, so I need to handle authentication and provide my certificate (URLSession -> NSURLAuthenticationMethodClientCertificate -> call SecPKCS12Import and extract identity from imported certificate -> create NSURLCredential from identity and provide it in completionHandler to the server) .
But after each https request the dialog box "MYAPP wants to sign using "privateKey" in your keychain" is displayed:
I want to avoid this message. My app is signed correctly. I think I need to set access for the certificate while importing (full access for my app), I'm trying to do it using SecAccessCreate and SecPKCS12Import options:
func extractIdentity(certData:NSData, certPassword:String) -> IdentityAndTrust {
var identityAndTrust:IdentityAndTrust!
var securityError:OSStatus = errSecSuccess
var items:CFArray?
//let certOptions:CFDictionary = [ kSecImportExportPassphrase.takeRetainedValue() as String: certPassword ];
let index: CFIndex = 2
let passwordKey = kSecImportExportPassphrase as String;
let passwordValue: CFString = "PASSWORD";
let accessKey = kSecImportExportAccess as String;
var access:SecAccessRef? = nil;
SecAccessCreate("CERTIFICATE_NAME", nil, &access);
var keys = [unsafeAddressOf(accessKey), unsafeAddressOf(passwordKey)]
var values = [unsafeAddressOf(access!), unsafeAddressOf(passwordValue)]
var keyCallbacks = kCFTypeDictionaryKeyCallBacks
var valueCallbacks = kCFTypeDictionaryValueCallBacks
let options = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, index, &keyCallbacks, &valueCallbacks)
// import certificate to read its entries
securityError = SecPKCS12Import(certData, options, &items);
if securityError == errSecSuccess {
let certItems:CFArray = items as CFArray!;
let certItemsArray:Array = certItems as Array
let dict:AnyObject? = certItemsArray.first;
if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {
// grab the identity
let identityPointer:AnyObject? = certEntry["identity"];
let secIdentityRef:SecIdentityRef = identityPointer as! SecIdentityRef!;
// grab the trust
let trustPointer:AnyObject? = certEntry["trust"];
let trustRef:SecTrustRef = trustPointer as! SecTrustRef;
// grab the certificate chain
var certRef:SecCertificate?
SecIdentityCopyCertificate(secIdentityRef, &certRef);
let certArray:NSMutableArray = NSMutableArray();
certArray.addObject(certRef as SecCertificateRef!);
identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: certArray);
}
}
return identityAndTrust;
}
Anyway it doesn't work. How can I avoid this dialog box?
This thread How do I add authorizations to code sign an app from new keychain without any human interaction is related to importing the certificate using "security" command, and suggestion was to use -A or -T flags while importing the certificate, but can I do it programmatically without console commands?
You're probably building and running the application multiple times, which means that the cert was added to the keychain the first time, and the executable that did it was authorized to use the private key. However when you made some changes and rebuilt the project, the executable was replaced and the new executable doesn't have access to the private key (this is also an issue when your users have to update your software or reinstall for any reason).
What I found I had to do was remove the cert from the keychain after I used it, and re-add it before every use. However I've read that you can grant permission to an app identifier so that may work in your case too.
I have a code sample in my question's answer: How do I kill the popup?

How to invoke an AWS Lambda function in Swift

I can't find any documentation or examples on how to invoke a Lambda function in Swift but I've tried to extrapolate from the documentation using Objective-C and I'm still getting errors:
"Error in myFunction: ValidationException: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes"
It appears that I'm not passing in the parameters to the function correctly when I invoke the lambda function from swift because the script tries to write to DynamoDB but one of the parameters is empty (this lambda script works when I invoke it in javascript/node).
let lambda = AWSLambda.defaultLambda()
let request = AWSLambdaInvocationRequest()
var context = [String: String]()
let jsonString = "{\"email\":\"example#example.com\",\"name\":\"example\"}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
request.clientContext = jsonData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
request.functionName = "myFunction"
lambda.invoke(request).continueWithBlock( {
(currentTask: AWSTask!) -> AWSTask in
if (currentTask.error != nil) {
// failed to execute.
print("Error executing: ", currentTask.error)
task.setError(currentTask.error)
} else {
print("token: ", currentTask.result)
task.setResult(currentTask.result)
}
return currentTask
})
You need to set the payload parameter to a map containing the data you want to pass.
let invocationRequest = AWSLambdaInvokerInvocationRequest()
invocationRequest.functionName = "myFunction"
invocationRequest.invocationType = AWSLambdaInvocationType.RequestResponse
invocationRequest.payload = ["email" : "example#example.com", "name" : "example"]
let lambdaInvoker = AWSLambdaInvoker.defaultLambdaInvoker()
let task = lambdaInvoker.invoke(invocationRequest).continueWithSuccessBlock() { (task) -> AWSTask! in
print("response: ", task.result)
}
Ryan Fitzgerald's answer gives me multiple compile-time errors, but I've had success with this version:
First, I have an initialization function with access credentials. Note that this is not the recommended secure access method for production code, but it is fine for testing and other purposes. It also assumes you have a Constants.swift file where you define the listed constants:
func initializeLambda() {
let credentialsProvider = AWSStaticCredentialsProvider.init(accessKey:Constants.AWS_ACCESS_KEY, secretKey: Constants.AWS_SECRET_KEY)
let defaultServiceConfiguration = AWSServiceConfiguration(region: Constants.AWS_REGION, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
}
For the remainder we can provide a version similar to the previous version. I removed the 'let task' because 'task' is not used in his example. Additionally, I've included the logical outline of some JSON parsing that you are likely to be doing inside the invoke task. Finally, I've changed to a continueWithBlock(). If you use a continueWithSuccessBlock() you will not enter this block when Amazon Lambda reaches its timeout window or if something else goes wrong with the request and typically you do want these situations to be handled here.
self.initializeLambda() //Call our previously written initialization function
let invocationRequest = AWSLambdaInvokerInvocationRequest()
invocationRequest.functionName = "functionName"
invocationRequest.invocationType = AWSLambdaInvocationType.RequestResponse
invocationRequest.payload = ["key1" : "value1", "key2" : "value2"]
let lambdaInvoker = AWSLambdaInvoker.defaultLambdaInvoker()
lambdaInvoker.invoke(invocationRequest).continueWithBlock() { (task: AWSTask) -> AWSTask in
print("response: ", task.result)
//In here you'll likely be parsing a JSON payload
if let payload: AnyObject = task.result?.payload {
if let error: AnyObject = payload.objectForKey("error") {
//If there is an error key in the JSON dictionary...
} else {
//If the JSON dictionary has no error key...
}
return task;
}
}
Tested and verified as functional on Swift 2.2 in Xcode 7.3.
The answers from both the Ryan's were great and useful and I just want to add a couple of additional thoughts.
In most cases, before you can invoke a Lambda, you might need to authenticate, so the errors you get might not necessarily be because of your Lambda calls but due to failing authentication. With AWS, however, there are several different ways to authenticate and this will change based on the credentials you have.
Ryan Davis shows you one way where your backend team has set up an AWS Access Key and an AWS Secret Key.
In my case, I had to authenticate using AWS Cognito Identity Pools and there are also User Pool authentication so you need to figure out what credentials your team has given you and read the appropriate authentication documentation.
Since I needed to use using AWS Cognito Identity Pools, all I had was the region and the identity pool id so in Swift 5 authentication for AWS Cognito Identity Pools
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: Constants.AWS_REGION,
identityPoolId: Constants.AWS_REGION.AWS_IDENTITY_POOL_ID)
let serviceConfiguration = AWSServiceConfiguration(region: Constants.AWS_REGION,
credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
And then the Lambda invocation more or less stays the same but just with slightly updated Swift 5 syntax:
if let invocationRequest = AWSLambdaInvokerInvocationRequest() {
invocationRequest.functionName = "function_name"
invocationRequest.invocationType = AWSLambdaInvocationType.requestResponse
invocationRequest.payload = ["key_1": "value_1"]
let lambdaInvoker = AWSLambdaInvoker.default()
lambdaInvoker.invoke(invocationRequest) { (awsLambdaInvokerInvocationResponse, error) in
guard let payload = awsLambdaInvokerInvocationResponse?.payload as? [String: String] else {
// Handle error here
return
}
if let userId = payload["message"] {
print("USR Id: \(userId)")
}
}
}
You will need to adjust your handling based on the structure of your payload returned to you by the Lambda, in my case it was:
{
"message": "user-id-8868-8475-8757"
}
Finally, remember to import the required libraries for your use case, for my above case I needed:
import AWSCore
import AWSLambda