Hello,
I'm currently trying to establish a custom VPN connection via the Network Extension Library (NEVpnManager).
After reading and trying a lot, it works like a charm with one big issue I can't get fixed:
The NEVpnProtocol can receive a reference to a password that is stored in the apps keychain so that the user doesn't have to enter the password every time he logs in. No matter what I do, the user has to enter a password, or I can't save the NEVpnManager Settings.
As you can see here NEVpnProtocol.PasswordReference needs a persistent reference to work, otherwise it won't work.
Sadly I don't completely understand the objectC Code behind this link, that's probably why I can't get this to work with Xamarin.
In every Keychain example for Xamarin I found SecKeyChain.QueryAsRecord is used, however I found out that there's also the SecKeyChain.QueryAsData method, which can receive a parameter "wantPersistentReference". That should probably do the trick, but when I use this method I can't save the NEVpnManager settings:
Error Domain=NEVPNErrorDomain Code=5 "IPC failed" UserInfo={NSLocalizedDescription=IPC failed}
IPC probably means Inter Process Communication, allowing different processes to communicate, so I think my problem is that the NEVpnManager can't read the password because it is "persistent".
Is it possible to store and receive the log-in details in a way so that this works like intended ( = user won't be asked for a password)?
Any help is appreaciated, thank you in advance.
The sources I used to create this code:
Establish VPN Connection:
http://ramezanpour.net/post/2014/08/03/configure-and-manage-vpn-connections-programmatically-in-ios-8/
Apple KeyChain Concept:
https://developer.apple.com/library/mac/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html
Xamarin KeyChain Example:
https://github.com/xamarin/monotouch-samples/tree/master/Keychain
Persistent KeyChain iOS:
http://ramezanpour.net/post/2014/09/26/how-to-get-persistent-references-to-keychain-items-in-ios/
Finally, here my complete code, with the code not working ("IPC failed error") and the code working but with a password prompt (in comments).
Please note that in this stage of the coding process the user can't choose a username / password.
Usage of the code:
1) Create VPNServiceIOS object
2) SaveVPNConfig
3) StartVPNConnection
4) StopVPNConnection()
public class VPNServiceIOS : IVPNService
{
private NEVpnManager manager;
public VPNServiceIOS ()
{
manager = NEVpnManager.SharedManager;
var s = new SecRecord (SecKind.GenericPassword) {
Label = "VPNTest",
Description = "Item description",
Account = "MYACCOUNTNAME",
Service = "VPNService",
Comment = "Your comment here",
ValueData = NSData.FromString ("MYPASSWORD", NSStringEncoding.UTF8),
Generic = NSData.FromString ("VPNPas", NSStringEncoding.UTF8),
};
var err = SecKeyChain.Add (s);
if (err != SecStatusCode.Success && err != SecStatusCode.DuplicateItem) {
Console.WriteLine ("Password not in Keychain:");
Console.WriteLine (err.ToString ());
}
}
public void StartVPNConnection ()
{
manager.LoadFromPreferences (error => {
if (error != null) {
Console.WriteLine ("Cant load VPN settings: ");
Console.WriteLine (error.ToString ());
}
});
NSError error2 = new NSError ();
manager.Connection.StartVpnTunnel (out error2);
if (error2 != null) {
Console.WriteLine ("Cant establish connection:");
Console.WriteLine (error2.ToString ());
}
}
public void StopVPNConnection ()
{
manager.Connection.StopVpnTunnel ();
}
public void SaveVPNConfig (string type, string description, string server, string remoteIdentifier, string localIdentifier, string username)
{
manager.LoadFromPreferences (error => {
if (error != null) {
Console.WriteLine ("Cant load VPN Settings: ");
Console.WriteLine (error);
} else {
NEVpnProtocol p = null;
switch (type) {
case "IKEv2":
NEVpnProtocolIke2 ike2 = new NEVpnProtocolIke2 ();
ike2.AuthenticationMethod = NEVpnIkeAuthenticationMethod.None;
ike2.LocalIdentifier = localIdentifier;
ike2.RemoteIdentifier = remoteIdentifier;
ike2.UseExtendedAuthentication = true;
ike2.DisconnectOnSleep = false;
p = ike2;
break;
default:
Console.WriteLine ("unknown Protocol!");
break;
}
p.Username = username;
p.ServerAddress = server;
manager.LocalizedDescription = description;
var s = new SecRecord (SecKind.GenericPassword) {
Account = "MYACCOUNTNAME",
Generic = NSData.FromString ("VPNPas", NSStringEncoding.UTF8)
};
// CODE NOT WORKING:
SecStatusCode res;
var match = SecKeyChain.QueryAsData(s, true, out res);
if (res == SecStatusCode.Success){
p.PasswordReference = match;
}
else {
Console.WriteLine (res);
}
// CODE WORKING but only with password prompt:
// SecStatusCode res;
// var match = SecKeyChain.QueryAsRecord(s, out res);
// if (res == SecStatusCode.Success){
// p.PasswordReference = match.ValueData;
// }
// else {
// Console.WriteLine (res);
// }
manager.ProtocolConfiguration = p;
manager.OnDemandEnabled = false;
manager.SaveToPreferences (error2 => {
if (error2 != null) {
Console.WriteLine ("Cant save manager settings:");
Console.WriteLine (error2.Code);
Console.WriteLine (error2.DebugDescription);
Console.WriteLine (error2.Description);
Console.WriteLine (error2.Domain);
Console.WriteLine (error2.HelpAnchor);
Console.WriteLine (error2.LocalizedDescription);
Console.WriteLine (error2.LocalizedFailureReason);
Console.WriteLine (error2.LocalizedRecoverySuggestion);
}
});
}
});
}
public void DeleteVPNConfig ()
{
manager.RemoveFromPreferences ((NSError error) => {
if (error != null) {
Console.WriteLine ("Cant delete VPN Config:");
Console.WriteLine (error.ToString ());
} else {
isConnected = false;
}
});
}
}