Re: Create VPN connections
Re: Create VPN connections
- Subject: Re: Create VPN connections
- From: "Quinn \"The Eskimo!\"" <email@hidden>
- Date: Fri, 27 May 2011 16:43:20 +0100
On 27 May 2011, at 16:18, Jeff Schindler wrote:
> I started looking into using the SCNetworkConfiguration APIs, but I'd want to know first that it will allow me to create/delete services for pptp, l2tp and ipsec.
The SCNetworkConfiguration API is definitely the way to go here, and it will let you create all of these services. It's non-trivial to get this right though. I've helped a couple of developers do this as part of DTS support incidents, and I've collected together my experience in the document at the end of this email. Take a look and let me know if anything is unclear.
S+E
--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
* * *
Fundamentals
------------
To get this working you're going to need a good understanding of System Configuration (SC) framework. Before reading any further, I strongly recommend that you familiarise yourself with the overall structure of SC framework by reading "System Configuration Programming Guidelines".
<http://developer.apple.com/library/mac/#documentation/Networking/Conceptual/SystemConfigFrameworks/SC_Intro/SC_Intro.html>
You should also bookmark the "System Configuration Framework Reference" and the reference docs for the SCPreferences and SCNetworkConfiguration APIs, because you're going to be looking at them a lot.
<http://developer.apple.com/library/mac/#documentation/Networking/Reference/SysConfig/_index.html>
<http://developer.apple.com/library/mac/#documentation/Networking/Reference/SCPreferences/Reference/reference.html>
<http://developer.apple.com/library/mac/#documentation/Networking/Reference/SCNetworkConfiguration/Reference/reference.html>
Finally, I find <SystemConfiguration/SCSchemaDefinitions.h> to be very useful when it comes to figuring out what's going on with SC framework, and specifically mapping the values you see during debugging to the identifiers you use in your code.
<http://developer.apple.com/library/mac/#documentation/Networking/Reference/SCSchemaDefinitions/Reference/reference.html>
* * *
Basic Outline
-------------
Here's a basic outline of how to go about setting up a typical VPN configuration using the System Configuration framework:
1. You should start by creating a L2TP interface that's layered on top of the single global IPv4 interface.
l2tpInterface = SCNetworkInterfaceCreateWithInterface(
kSCNetworkInterfaceIPv4,
kSCNetworkInterfaceTypeL2TP
);
2. From there you create a new PPP interface on top of the L2TP interface.
pppInterface = SCNetworkInterfaceCreateWithInterface(
l2tpInterface,
kSCNetworkInterfaceTypePPP
);
3. Create a new network service based on that PPP interface.
service = SCNetworkServiceCreate(prefs, pppInterface);
4. Establish a default configuration for that service.
success = SCNetworkServiceEstablishDefaultConfiguration(service);
5. Get the PPP protocol (kSCNetworkProtocolTypePPP) within that service using SCNetworkServiceCopyProtocol and then configure it using SCNetworkProtocolGetConfiguration and SCNetworkProtocolSetConfiguration. More on the specifics of this below.
6. Configure the PPP interface using SCNetworkInterfaceGetConfiguration and SCNetworkInterfaceSetConfiguration. Also configure the IPsec (kSCEntNetIPSec) extended configuration for that interface using SCNetworkInterfaceGetExtendedConfiguration and SCNetworkInterfaceSetExtendedConfiguration.
7. If necessary configure the DNS settings for the service (SCNetworkServiceCopyProtocol with kSCNetworkProtocolTypeDNS, SCNetworkProtocolGetConfiguration and SCNetworkProtocolSetConfiguration).
8. Get the current set using SCNetworkSetCopyCurrent, and add the service to it using SCNetworkSetAddService (alternatively you can add it to a specific set by calling SCNetworkSetCopyAll to get all the sets and searching for the set you want).
In addition to this you'll have to do the standard SCPreferences tricks, namely:
o Ensure that you have appropriate privileges. You can either run as root, or acquire the "system.preferences" authorisation right.
o Create a connection to the preferences database using SCPreferencesCreate or SCPreferencesCreateWithAuthorization.
o Commit changes to the database using SCPreferencesCommitChanges and apply them to the running system using SCPreferencesApplyChanges.
Finally, you will need to mess around with the keychain, as discussed in the next section.
* * *
Keychain
--------
Secure data, like PPP passwords, are not stored in the SC preferences database directly, but are instead stored in the keychain and then referenced by the SC preferences. There are two keychain passwords that you might need to deal with:
o IPsec shared secret -- This is stored in the system keychain as a generic password item with the service name of "<serviceID>.SS". You must reference this from both PPP interface IPsec extended configuration (by setting the kSCPropNetIPSecAuthenticationMethod to kSCValNetIPSecAuthenticationMethodSharedSecret, kSCPropNetIPSecSharedSecretEncryption to kSCValNetIPSecSharedSecretEncryptionKeychain, and kSCPropNetIPSecSharedSecret to the service name) and from the IPsec basic configuration (by setting the same keys and values).
o account password -- If you're using password-based PPP authentication, you must store the password as a generic password item in the system keychain with a service name of "<serviceID>". You reference this from the PPP service configuration by setting kSCPropNetPPPAuthPasswordEncryption to kSCValNetPPPAuthPasswordEncryptionKeychain and kSCPropNetPPPAuthPassword to the service name.
You can get a reference to the system keychain by calling SecKeychainCopyDomainDefault with the kSecPreferencesDomainSystem domain. Once you have that, you can find a generic password item in the keychain using SecKeychainFindGenericPassword. If the item is there, you can modify it using SecKeychainItemModifyAttributesAndData. If the item is not there, you can add it using SecKeychainAddGenericPassword.
Finally, each keychain item must have a reasonable ACL. Specifically, it must allow UI-free access from SCHelper, System Preferences, SystemUIServer, pppd, and racoon. Setting this ACL properly is a bit challenging, so I've pasted some code below that demonstrates how to do this.
* * *
Debugging
---------
As you can see, this process is very complex. It's likely that the only way you'll be able to make this work is to manually configure the VPN as you desire, then look at that configuration, and then customise your code to create a configuration exactly the same. To that end, here's some useful debugging tips:
o The SC preferences database is stored in "/Library/Preferences/SystemConfiguration/preferences.plist". There's a relatively simple mapping from SC concepts to the structure of that file. You can grab a copy of the file before and after setting up a VPN manually, and then look at the differences to see what got changed. Similarly, you can do this before and after running your code to verify that you're making the right changes.
IMPORTANT: While it's fine to look at this file during debugging, don't write code that accesses it. Rather, always go through the SC framework API.
o On the keychain front, you should learn to love the <x-man-page://1/security> tool. Specifically, the following command will dump the system keychain, including the ACL for each item. This makes it easy to verify that your keychain manipulations are going according to plan.
$ security dump-keychain -a /Library/Keychains/System.keychain
* * *
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#import <Foundation/Foundation.h>
#include <Security/Security.h>
static const char * kAppPaths[] = {
"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/SCHelper",
"/Applications/System Preferences.app",
"/System/Library/CoreServices/SystemUIServer.app",
"/usr/sbin/pppd",
"/usr/sbin/racoon"
};
int main(int argc, char **argv)
{
#pragma unused(argc)
#pragma unused(argv)
NSAutoreleasePool * pool;
OSStatus err;
NSMutableArray * trustedApps;
SecTrustedApplicationRef trustedApp;
SecAccessRef secAccess;
size_t idx;
NSArray * keychains;
SecKeychainRef keychain;
SecKeychainSearchRef searchRef;
SecKeychainItemRef item;
NSArray * aclList;
NSArray * appList;
CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector = {0, 0};
SecAccessRef secAccess2;
pool = [[NSAutoreleasePool alloc] init];
assert(pool != nil);
fprintf(stderr, "Hello Cruel World!\n");
trustedApps = [NSMutableArray array];
assert(trustedApps != nil);
err = SecTrustedApplicationCreateFromPath(NULL, &trustedApp); //self
assert(err == errSecSuccess);
[trustedApps addObject:(id)trustedApp];
CFRelease(trustedApp);
trustedApp = NULL;
for (idx = 0; idx < (sizeof(kAppPaths) / sizeof(*kAppPaths)); idx++) {
err = SecTrustedApplicationCreateFromPath(kAppPaths[idx], &trustedApp);
if (err != errSecSuccess) {
break;
}
[trustedApps addObject:(id)trustedApp];
CFRelease(trustedApp);
trustedApp = NULL;
}
assert(err == errSecSuccess);
err = SecAccessCreate(CFSTR("Some VPN Thing"), (CFArrayRef) trustedApps, &secAccess);
assert(err == errSecSuccess);
err = SecKeychainCopySearchList( (CFArrayRef *) &keychains );
assert(err == errSecSuccess);
for (id kc in keychains) {
char kcPath[1024];
UInt32 kcPathLength;
keychain = (SecKeychainRef) kc;
kcPathLength = 1024;
err = SecKeychainGetPath(keychain, &kcPathLength, kcPath);
assert(err == errSecSuccess);
if ( [[[NSString stringWithUTF8String:kcPath] lastPathComponent] isEqual:@"Scratch.keychain"] ) {
break;
}
keychain = NULL;
}
assert(keychain != NULL);
err = SecKeychainSearchCreateFromAttributes(keychain, kSecGenericPasswordItemClass, NULL, &searchRef);
assert(err == errSecSuccess);
err = SecKeychainSearchCopyNext(searchRef, &item);
assert(err == errSecSuccess);
err = SecKeychainItemSetAccess(item, secAccess);
assert(err == errSecSuccess);
err = SecKeychainItemCopyAccess(item, &secAccess2);
assert(err == errSecSuccess);
err = SecAccessCopyACLList(secAccess2, (CFArrayRef *) &aclList);
assert(err == errSecSuccess);
for (id a in aclList) {
SecACLRef acl;
NSString * description;
acl = (SecACLRef) a;
err = SecACLCopySimpleContents(acl, (CFArrayRef *) &appList, (CFStringRef *) &description, &promptSelector);
assert(err == errSecSuccess);
NSLog(@"%@", description);
if ( [description isEqual:@"QTest"] ) {
err = SecACLRemove(acl);
assert(err == errSecSuccess);
NSLog(@" removed");
}
}
err = SecKeychainItemSetAccess(item, secAccess2);
assert(err == errSecSuccess);
fprintf(stderr, "Success!\n");
[pool drain];
return EXIT_SUCCESS;
}
* * *
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Macnetworkprog mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden