How to get a persistent reference to a keychain item for VPN usage?

TobeTobe DEMember ✭✭
edited April 2016 in Xamarin.iOS

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;
            }
        });
    }
}

Answers

  • MichaelLukas.0449MichaelLukas.0449 HKUniversity

    I was also working on this and didn't find a solution. Xamarin should really give an answer because it looks like there is bug in the VPNManager thingy.

  • TobeTobe DEMember ✭✭

    @MichaelLukas.0449
    Did you get VPN via IPSec working? It also needs a reference to the Keychain for the Shared Secret, which i can't get to work either. Again I get the "IPC failed" exception when I try to save the VPN settings.

    Code:
    var _secret = new SecRecord (SecKind.GenericPassword) {
    ValueData = NSData.FromString (THE_SHARED_SECRET, NSStringEncoding.UTF8),
    Generic = NSData.FromString ("secret", NSStringEncoding.UTF8),
    };

    var querySecret = new SecRecord (SecKind.GenericPassword) {
        Generic = NSData.FromString ("secret", NSStringEncoding.UTF8),
    };
    
    err = SecKeyChain.Add (_secret);
    if (err != SecStatusCode.Success && err != SecStatusCode.DuplicateItem) {    
        Console.WriteLine ("Error: Secret could not be added to Keychain");
    }
    
    var match = SecKeyChain.QueryAsData (querySecret, true, out res);
    if (res == SecStatusCode.Success) {
        Console.WriteLine ("Secret set to settings");
        ipSec.SharedSecretReference = match;
        } else {
            Console.WriteLine ("Could not add secret to settings");
            Console.WriteLine (res);
        }
    }
    
  • MichaelLukas.0449MichaelLukas.0449 HKUniversity

    @Tobe Nope, didn't get it to work. First problem was that I didn't have a persistent reference which is needed. After aquiring one (just like you did) the reference didn't work. So I think theres a bug with Xamarin not returning correct references.

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    @MichaelLukas.0449
    Did you get VPN via IPSec working? It also needs a reference to the Keychain for the Shared Secret, which i can't get to work either. Again I get the "IPC failed" exception when I try to save the VPN settings.

    Code:
    var _secret = new SecRecord (SecKind.GenericPassword) {
    ValueData = NSData.FromString (THE_SHARED_SECRET, NSStringEncoding.UTF8),
    Generic = NSData.FromString ("secret", NSStringEncoding.UTF8),
    };

    var querySecret = new SecRecord (SecKind.GenericPassword) {
    Generic = NSData.FromString ("secret", NSStringEncoding.UTF8),
    };

    err = SecKeyChain.Add (_secret);
    if (err != SecStatusCode.Success && err != SecStatusCode.DuplicateItem) {
    Console.WriteLine ("Error: Secret could not be added to Keychain");
    }

    var match = SecKeyChain.QueryAsData (querySecret, true, out res);
    if (res == SecStatusCode.Success) {
    Console.WriteLine ("Secret set to settings");
    ipSec.SharedSecretReference = match;
    } else {
    Console.WriteLine ("Could not add secret to settings");
    Console.WriteLine (res);
    }
    }

    You can run? I can not run properly

  • TobeTobe DEMember ✭✭
    edited June 2016

    Yes I can run that code, but as stated above it crashes because I can't get a persistent reference to the keychain.
    I don't need VPN support anymore for my app, so I stopped working on this.

    You can check out this Xamarin.Forms test application I created:
    https://github.com/TobiasB2/VPN-Test

    Of course IPSec and saving passwords does not work because of the keychain problem, but the rest works fine.
    This does not work on a simulator, so you need to run this on an actual device.
    Don't forget to change to ikev2 in VPNServiceIOS.cs (lines 121-123):
    // Set the protocol to IKEv2 or ipSec // p = ike2; p = ipSec;
    And of course change username, password etc.
    // Set Accountname, Servername and description p.Username = "MY_ACCOUNT"; p.ServerAddress = "free-nl.hide.me"; manager.LocalizedDescription = "hide.me VPN";

    Android Support is not implemented, this was only a small test for iOS.

    Edit: And don't forget to create a new provisioning profile that allows VPN :-)

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    Yes I can run that code, but as stated above it crashes because I can't get a persistent reference to the keychain.
    I don't need VPN support anymore for my app, so I stopped working on this.

    You can check out this Xamarin.Forms test application I created:
    https://github.com/TobiasB2/VPN-Test

    Of course IPSec and saving passwords does not work because of the keychain problem, but the rest works fine.
    This does not work on a simulator, so you need to run this on an actual device.
    Don't forget to change to ikev2 in VPNServiceIOS.cs (lines 121-123):
    // Set the protocol to IKEv2 or ipSec // p = ike2; p = ipSec;
    And of course change username, password etc.
    // Set Accountname, Servername and description p.Username = "MY_ACCOUNT"; p.ServerAddress = "free-nl.hide.me"; manager.LocalizedDescription = "hide.me VPN";

    Android Support is not implemented, this was only a small test for iOS.

    Edit: And don't forget to create a new provisioning profile that allows VPN :-)

    Why can't I run here?
    image

  • TobeTobe DEMember ✭✭

    Did you enable VPN support in your entitlements.plist? (see attached screenshot) Did you change the protocol to iKev2?

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    Yes I can run that code, but as stated above it crashes because I can't get a persistent reference to the keychain.
    I don't need VPN support anymore for my app, so I stopped working on this.

    You can check out this Xamarin.Forms test application I created:
    https://github.com/TobiasB2/VPN-Test

    Of course IPSec and saving passwords does not work because of the keychain problem, but the rest works fine.
    This does not work on a simulator, so you need to run this on an actual device.
    Don't forget to change to ikev2 in VPNServiceIOS.cs (lines 121-123):
    // Set the protocol to IKEv2 or ipSec // p = ike2; p = ipSec;
    And of course change username, password etc.
    // Set Accountname, Servername and description p.Username = "MY_ACCOUNT"; p.ServerAddress = "free-nl.hide.me"; manager.LocalizedDescription = "hide.me VPN";

    Android Support is not implemented, this was only a small test for iOS.

    Edit: And don't forget to create a new provisioning profile that allows VPN :-)

    using System.Threading;
using NetworkExtension;
using Security;
using Foundation;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using UIKit;


/
Sources:
Establish VPN connection:
http://ramezanpour.net/post/2014/08/03/configure-and-manage-vpn-connections-programmatically-in-ios-8/
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/

/

namespace phonevpn
{
 public class VpnServiceIOS 
 {
 private NEVpnManager manager;


 public VpnServiceIOS ()
 {
 manager = NEVpnManager.SharedManager;
 manager.LoadFromPreferences (error => {
 if (error != null) {
 Console.WriteLine ("Error loading VPN preferences: ");
 Console.WriteLine (error);
 }
 } );
 }

 public void AddConfig ()
 {

 // The password for the VPN connection, add this to Keychain
 var password = new SecRecord (SecKind.GenericPassword) {
 Service = "Password Service",
 ValueData = NSData.FromString ("889955", NSStringEncoding.UTF8),
 Generic = NSData.FromString ("VPNPas", NSStringEncoding.UTF8),
 };

 // The query for the VPN password. Use this to find the password in Keychain
 var queryPassword = new SecRecord (SecKind.GenericPassword) {
 Service = "Password Service",
 Generic = NSData.FromString ("VPNPas", NSStringEncoding.UTF8),
 };

 // The shared secret for the VPN connection, add this to Keychain
 var secret = new SecRecord (SecKind.GenericPassword) {
 Service = "Secret Service",
 ValueData = NSData.FromString ("666888", NSStringEncoding.UTF8),
 Generic = NSData.FromString ("secret", NSStringEncoding.UTF8),
 };

 // The query for the VPN shared secret. Use this to find the shared secret in Keychain
 var querySecret = new SecRecord (SecKind.GenericPassword) {
 Service = "Secret Service",
 Generic = NSData.FromString ("secret", NSStringEncoding.UTF8),
 };


 // First remove old Keychain entries, then add the new ones
 // Just for testing purposes: this is to make sure the keychain entries are correct
 var err = SecKeyChain.Remove (queryPassword);
 Console.WriteLine ("Password remove: " + err);

 err = SecKeyChain.Remove (querySecret);
 Console.WriteLine ("Secret remove: " + err);

 err = SecKeyChain.Add (password);
 Console.WriteLine ("Password add: " + err);

 err = SecKeyChain.Add (secret);
 Console.WriteLine ("Secret add: " + err);


 manager.LoadFromPreferences (error => {
 if (error != null) {
 Console.WriteLine ("Error loading preferences: ");
 Console.WriteLine (error);
 } else {


 NEVpnProtocolIpSec ipSec = new NEVpnProtocolIpSec ();
 ipSec.AuthenticationMethod = NEVpnIkeAuthenticationMethod.SharedSecret;
 ipSec.UseExtendedAuthentication = true;
 ipSec.DisconnectOnSleep = false;
 ipSec.Username = "889955";
 ipSec.ServerAddress = "106.186.121.87";

 ipSec.LocalIdentifier = "666888";
 ipSec.RemoteIdentifier = "666888";
 SecStatusCode res;

 
 var match = SecKeyChain.QueryAsData (querySecret, true, out res);
 if (res == SecStatusCode.Success) {
 Console.WriteLine ("Secret found, setting secret...");
 ipSec.SharedSecretReference = match;
 } else {
 Console.WriteLine ("Could not set secret:");
 Console.WriteLine (res);
 }

 
 
 
 match = SecKeyChain.QueryAsData (queryPassword, true, out res);
 if (res == SecStatusCode.Success) {
 Console.WriteLine ("Password found, setting password...");
 ipSec.PasswordReference = match;
 } else {
 Console.WriteLine (res);
 }
 manager.ProtocolConfiguration = ipSec;

 manager.LocalizedDescription = "binbin";

 manager.OnDemandEnabled = false;
 manager.Enabled = true;
 manager.SaveToPreferences (error2 => {
 if (error2 != null) {
 Console.WriteLine ("Could not save VPN preferences");
 Console.WriteLine (error2.DebugDescription);
 }
 } );
 }
 } );
 }

 // Start the connection, make sure to load the preferences first
 public void StartVPNConnection ()
 {
 manager.LoadFromPreferences (error => {
 if (error != null) {
 Console.WriteLine ("Could not load preferences: ");
 Console.WriteLine (error.ToString ());
 }
 } );
 NSError error2;
 manager.Connection.StartVpnTunnel (out error2);
 if (error2 != null) {
 Console.WriteLine ("Could not establish connection:");
 Console.WriteLine (error2.ToString ());
 }
 }


 // Stop current VPN connection
 public void StopVPNConnection ()
 {
 manager.Connection.StopVpnTunnel ();
 }

 // remove the custom VPN connection from the iOS settings
 public void RemoveVPNConnection ()
 {
 manager.RemoveFromPreferences ((NSError error) => {
 if (error != null) {
 Console.WriteLine ("Cannot delete VPN preferences:");
 Console.WriteLine (error.ToString ());
 }
 } );
 }
 }
}



  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    Yes I can run that code, but as stated above it crashes because I can't get a persistent reference to the keychain.
    I don't need VPN support anymore for my app, so I stopped working on this.

    You can check out this Xamarin.Forms test application I created:
    https://github.com/TobiasB2/VPN-Test

    Of course IPSec and saving passwords does not work because of the keychain problem, but the rest works fine.
    This does not work on a simulator, so you need to run this on an actual device.
    Don't forget to change to ikev2 in VPNServiceIOS.cs (lines 121-123):
    // Set the protocol to IKEv2 or ipSec // p = ike2; p = ipSec;
    And of course change username, password etc.
    // Set Accountname, Servername and description p.Username = "MY_ACCOUNT"; p.ServerAddress = "free-nl.hide.me"; manager.LocalizedDescription = "hide.me VPN";

    Android Support is not implemented, this was only a small test for iOS.

    Edit: And don't forget to create a new provisioning profile that allows VPN :-)

    image
    image
    image
    image

    1.jpg 385.1K
    2.jpg 417.5K
    3.jpg 256.1K
    4.jpg 185.2K
  • TobeTobe DEMember ✭✭

    In that code you are using the ipSec protocol which requires a persistent reference to the iOS keychain to retrieve the shared secret. You can't save the connections to settings because the persistent reference to keychain does not work.
    I have no idea how to get this to work, that's why I created this topic in the first place :-)
    Please read the very first post of this thread were I explain this in detail.
    If you need a VPN connection with Xamarin for iOS, the only possibility I know and that worked on my testing devices is a VPN connection via iKev2, I never got ipSec to work because of the keychain problem.

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    In that code you are using the ipSec protocol which requires a persistent reference to the iOS keychain to retrieve the shared secret. You can't save the connections to settings because the persistent reference to keychain does not work.
    I have no idea how to get this to work, that's why I created this topic in the first place :-)
    Please read the very first post of this thread were I explain this in detail.
    If you need a VPN connection with Xamarin for iOS, the only possibility I know and that worked on my testing devices is a VPN connection via iKev2, I never got ipSec to work because of the keychain problem.

    No solution yet? I have a Xcode project that can run normally. Can you help me to change it?

    ipsec ip 106.186.121.87
    user name 889955
    password 889955

    secret 666888

  • TobeTobe DEMember ✭✭

    ipSec does NOT WORK with Xamarin iOS.

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    ipSec does NOT WORK with Xamarin iOS.

    Is it bug's xamarin.ios?

  • TobeTobe DEMember ✭✭

    I don't know, but probably it's a bug.
    References to keychain must be PERSISTENT. That is not working for me (maybe bug).

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    I don't know, but probably it's a bug.
    References to keychain must be PERSISTENT. That is not working for me (maybe bug).

    I have a Xcode project, can be connected, you need to refer to it?

  • TobeTobe DEMember ✭✭

    I don't know how XCode projects work, sorry. I'm completely new to iOS development.

    NEVpnProtocol.PasswordReference needs a PERSISTENT REFERENCE TO KEYCHAIN.

    You need: SecKeychain.QueryAsData(SecRecord query, Boolean wantPersistentReference, out SecStatusCode).
    That is not working with Xamarin as it seems.

  • binbin.9006binbin.9006 CNMember ✭✭

    @Tobe said:
    I don't know how XCode projects work, sorry. I'm completely new to iOS development.

    NEVpnProtocol.PasswordReference needs a PERSISTENT REFERENCE TO KEYCHAIN.

    You need: SecKeychain.QueryAsData(SecRecord query, Boolean wantPersistentReference, out SecStatusCode).
    That is not working with Xamarin as it seems.

    How much is your mailbox, I sent a xcode project to you, help me find it.

  • TobeTobe DEMember ✭✭

    Dude I have no idea how that works... sorry... Please contact Xamarin support.

  • Filip_MazurFilip_Mazur USMember

    I have the exact same problem as Tobe. No persistent reference or IPC failed when saving. Did anyone learn anything from Xamarin? Clear statement that it does not work at least.

  • Any updates regarding this issue? I have the same problem.

  • KevMooreKevMoore GBMember

    Hi Group.

    I am having issues with this with latest Xamarin and XCODE. The problem I get is IPC Failed on LoadFromPreferences() when using the emulator, and "Permission Denied" when attempting to use a real device.

    Has anyone followed this up with Xamarin yet? Is there a way to achieve IPSec connectivity using Xamarin?

    cheers

    Kev

  • TobeTobe DEMember ✭✭

    You can't use it with the emulator as far as i know. Make sure you have permissions for VPN and keychain access for the app. You need a new provisioning profile for the VPN access.
    I didn't have to pursue this anymore, so no idea if it works or if there's still the password problem.
    The only thing I got to work was ikev2 with a password prompt (the code of my first post). But maybe they fixed the bug with the persistent keychain reference. Good luck trying :smile:

  • PeterChanPeterChan CNMember
    edited March 2017

    I have same issue... IPC failed

  • PeterChanPeterChan CNMember

    I try whole day to get persistent reference of keychain items using Xarmarin.iOS. At the end, I gave up. I create a native library. And call the object-c native code to get the persistent reference.

Sign In or Register to comment.