Pageant integration?

Feb 26, 2013 at 11:17 PM
Has anyone attempted to get ssh.net integrated with the PuTTY Pageant tool for managing keys?
Coordinator
Mar 2, 2013 at 3:01 PM
Hi,

I have somebody submitted it as a patch to be included in the library but unfortunately I could not include it since this solution is application specific.
If you interested you can download this patch here and see if it works for you.

Also, if you like and can create of this some klind of add on solution I could post it here as a seperate file that people can add or use if they would like pageant support.

Hope it helps.

Thanks,
Oleg
Mar 8, 2013 at 3:32 PM
Unfortunately, that code uses the .net 4+ MemoryMappedFile. I'm stuck with .net3.5 (For various reasons I'm not too happy with). There isn't a heavy reliance on that feature though so it may be fairly easy to replace.

Thanks,

Robert
Mar 8, 2013 at 6:16 PM
FYI, this patch works great under .net 4 without modification. Given that Pageant is the ubiquitous SSH agent for windows, it's a shame that it can't be included. It's as simple as:
        string host = "myhost";
        string username = "joeschmo";
        var agent = new PageantProtocol();
        var conn = new AgentConnectionInfo(host,username,agent);
        var client = new SshClient(conn));

        (Insert appropriate exception handling of course...).
Robert
Coordinator
Mar 9, 2013 at 3:09 PM
Hi,

The reason I cannot include it in the library is due to native method.
For example:
        [DllImport("user32.dll", EntryPoint = "SendMessageA", CallingConvention = CallingConvention.StdCall,
            ExactSpelling = true)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int dwMsg, IntPtr wParam, ref COPYDATASTRUCT lParam);
Which is problematic in my view.
I would like to keep this library as open as possible and as pluggable as possible,


For this reason I keep ConnectionInfo class public that allows you to implement any additional connection info class like AgentConnectionInfo and use it.

Also AgentConnectionInfo can be implemented in your project without changing SshNet.

Hope it explains the reason behind this decision.

Thanks,
Oleg
Mar 11, 2013 at 3:30 PM
It does. That method struck me as distinctly non-standard for C#/.net. GetProcessByName might be more suitable for searching for the Pageant process. Thanks.
Apr 8, 2013 at 4:12 PM
Oleg,

This Pageant patch appears to work perfectly for Windows 7. But when I run it through Windows server 2008 R2, the authentication always fails. It appears to be something in the way the patch communicates with the Pageant API via SendMessageA. I don't know what the difference might be that would cause a change between Windows 7 and server 2008 though.

If you know who provided the patch, perhaps they would know?

Thanks,

Robert
Coordinator
Apr 8, 2013 at 4:32 PM
Hi,

You can look on source code tab, patches.
It was part of 12705 patch and its author is mladjenovic

Thanks,
Oleg
Apr 8, 2013 at 5:50 PM
Edited Apr 8, 2013 at 5:51 PM
Hi, sorry for posting here.
I had to use SendMessage winapi call because that's what putty uses internally . There is no official api for pageant. About you problem I can't help you because I do not have windows server 2008 environment to test it. It might be the problem with stricter uac in server edition. Are both you processes running with same elevation level? Here is the link with some fixes that uses new (at leat was new when I wrote it )AuthenticationMethod api. link
                                                                                                                                                                                                                 Best regards.
Apr 8, 2013 at 8:39 PM
Thanks for the update! I'll take a look and try it out. UAC is exactly the path I'm heading down at the moment so this is good timing. Both pageant and my application are running under the same user (Administrator) so it is a bit baffling. If I get it working, I'll let you know.
Apr 8, 2013 at 10:16 PM
I took a look in the WINPGNTC.C PuTTY source file and noticed:
    /*
     * Make the file mapping we create for communication with
     * Pageant owned by the user SID rather than the default. This
     * should make communication between processes with slightly
     * different contexts more reliable: in particular, command
     * prompts launched as administrator should still be able to
     * run PSFTPs which refer back to the owning user's
     * unprivileged Pageant.
     */
    usersid = get_user_sid();
So I went back to the PageantProtocol.cs file and added:
            ...
            using (var accessor = mmFile.CreateViewAccessor())
            {
                var security = mmFile.GetAccessControl();
                security.SetOwner(System.Security.Principal.WindowsIdentity.GetCurrent().User);
                mmFile.SetAccessControl(security);
            ...
to both the GetIdentities and SignData methods. Now it works like a charm on both Windows 7 and Server 2008. I don't know when PuTTY added the use of the SID, but it seems server 2008 is more strict about this than Windows 7.

Thanks for the clues!

Robert
May 21, 2013 at 6:34 PM
I tried to create a 64bit build of my application with SshNet+Pageant, but could only get the Pageant API to work when building as a 32 bit application. Not knowing the details of the Pageant protocol, I suspect the Pageant API is arch dependent.

Are there any suggestions for detecting which Pageant build is running and adapting to the API appropriately for this patch?
Jan 3 at 4:36 AM
I think pageant integration is amazing sadly the latest patch is 404'ed does anyone have it?
Feb 3 at 1:26 AM
Sorry to double post but no one has a copy of this patch?
Feb 3 at 3:42 PM
I believe this is the patch you are looking for. I use this in some sshnet tools. I did have to tweak one file to deal with the security differences in windows 7 (pageantprotocol.cs) so I've attached that as well. I did not attempt to use the sftp portion so I can't say if that works or not.
Feb 3 at 3:46 PM
I guess my attachments didn't post correctly from my mobile. I will get these posted this evening.

Feb 3 at 4:33 PM
Thank you for doing that xl600! I noticed your changes above so was going to do those anyway. If you removed the SFTP portion in your patch if you have the original that would be great as I plan to try and use both.

Thank you again
Feb 4 at 2:58 PM
I had to upload the old patch files as a new patchid (Which will most certainly be declined). It is available here. The modification for Windows 7 to pageantprotocol.cs follows:
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Net;
using System.Text;
using Renci.SshNet.Common;
using System.Security.AccessControl;

namespace Renci.SshNet.Pageant
{
   public class PageantProtocol:IAgentProtocol
    {

        #region  Constants

        private const int WM_COPYDATA = 0x004A;

        private const int AGENT_COPYDATA_ID = unchecked((int)0x804e50ba);

        private const int AGENT_MAX_MSGLEN = 8192;

        public const byte SSH2_AGENTC_REQUEST_IDENTITIES = 11;

        public const byte SSH2_AGENT_IDENTITIES_ANSWER = 12;

        public const byte SSH2_AGENTC_SIGN_REQUEST = 13;

        public const byte SSH2_AGENT_SIGN_RESPONSE = 14;

        #endregion


       public static bool IsRunning
       {
           get
           {
               var hWnd = NativeMethods.FindWindow("Pageant", "Pageant");

               return hWnd != IntPtr.Zero;
           }
       }



        public PageantProtocol()
        {
            var hWnd = NativeMethods.FindWindow("Pageant", "Pageant");

            if (hWnd == IntPtr.Zero)
            {
                throw new SshException("Pageant not running");
            }
            
        }


        #region Implementation of IAgentProtocol

        IEnumerable<IdentityReference> IAgentProtocol.GetIdentities()
        {
            var hWnd = NativeMethods.FindWindow("Pageant", "Pageant");

            if (hWnd == IntPtr.Zero)
            {
                yield break;
            }

            string mmFileName = Path.GetRandomFileName();
            
            using (var mmFile = MemoryMappedFile.CreateNew(
                mmFileName,
                AGENT_MAX_MSGLEN))
            {
                var security = mmFile.GetAccessControl();
                security.SetOwner(System.Security.Principal.WindowsIdentity.GetCurrent().User);
                mmFile.SetAccessControl(security);

                using (var accessor = mmFile.CreateViewAccessor())
                {
                    accessor.Write(0, IPAddress.NetworkToHostOrder(AGENT_MAX_MSGLEN - 4));
                    accessor.Write(4, SSH2_AGENTC_REQUEST_IDENTITIES);

                    var copy = new COPYDATASTRUCT(AGENT_COPYDATA_ID, mmFileName);

                    if (NativeMethods.SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ref copy) == IntPtr.Zero)
                    {
                        accessor.Dispose();
                        mmFile.Dispose();
                        File.Delete(mmFileName);
                        yield break;
                    }

                    if (accessor.ReadByte(4) != SSH2_AGENT_IDENTITIES_ANSWER)
                    {
                        accessor.Dispose();
                        mmFile.Dispose();
                        File.Delete(mmFileName);
                        yield break;
                    }

                    int numberOfIdentities = IPAddress.HostToNetworkOrder(accessor.ReadInt32(5));

                    if (numberOfIdentities == 0)
                    {
                        accessor.Dispose();
                        mmFile.Dispose();
                        File.Delete(mmFileName);
                        yield break;
                    }

                    int position = 9;
                    for (int i = 0; i < numberOfIdentities; i++)
                    {
                        int blobSize = IPAddress.HostToNetworkOrder(accessor.ReadInt32(position));
                        position += 4;

                        var blob = new byte[blobSize];

                        accessor.ReadArray(position, blob, 0, blobSize);
                        position += blobSize;
                        int commnetLenght = IPAddress.HostToNetworkOrder(accessor.ReadInt32(position));
                        position += 4;
                        var commentChars = new byte[commnetLenght];
                        accessor.ReadArray(position, commentChars, 0, commnetLenght);
                        position += commnetLenght;

                        string comment = Encoding.ASCII.GetString(commentChars);
                        string type = Encoding.ASCII.GetString(blob, 4, 7);// needs more testing kind of hack

                        accessor.Dispose();
                        mmFile.Dispose();
                        File.Delete(mmFileName);

                        yield return new IdentityReference(type,blob,comment);

                    }

                    accessor.Dispose();
                    mmFile.Dispose();
                    File.Delete(mmFileName);
                }
            }
        }
        
        byte[] IAgentProtocol.SignData(IdentityReference identity, byte[] data)
        {
            var hWnd = NativeMethods.FindWindow("Pageant", "Pageant");

            if (hWnd == IntPtr.Zero)
            {
                return new byte[0];
            }

            string mmFileName = Path.GetRandomFileName();

            using (var mmFile = MemoryMappedFile.CreateNew(mmFileName,AGENT_MAX_MSGLEN))
            {
                using (var accessor = mmFile.CreateViewAccessor())
                {
                    var security = mmFile.GetAccessControl();
                    security.SetOwner(System.Security.Principal.WindowsIdentity.GetCurrent().User);
                    mmFile.SetAccessControl(security);

                    accessor.Write(0, IPAddress.NetworkToHostOrder(AGENT_MAX_MSGLEN - 4));
                    accessor.Write(4, SSH2_AGENTC_SIGN_REQUEST);
                    accessor.Write(5, IPAddress.NetworkToHostOrder(identity.Blob.Length));
                    accessor.WriteArray(9, identity.Blob, 0, identity.Blob.Length);
                    accessor.Write(9 + identity.Blob.Length, IPAddress.NetworkToHostOrder(data.Length));
                    accessor.WriteArray(13 + identity.Blob.Length, data, 0, data.Length);

                    var copy = new COPYDATASTRUCT(AGENT_COPYDATA_ID, mmFileName);

                    if (NativeMethods.SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ref copy) == IntPtr.Zero)
                    {
                        accessor.Dispose();
                        mmFile.Dispose();
                        File.Delete(mmFileName);
                        return new byte[0];
                    }

                    if (accessor.ReadByte(4) != SSH2_AGENT_SIGN_RESPONSE)
                    {
                        accessor.Dispose();
                        mmFile.Dispose();
                        File.Delete(mmFileName);
                        return new byte[0];
                    }

                    int size = IPAddress.HostToNetworkOrder(accessor.ReadInt32(5));
                    var ret = new byte[size];
                    accessor.ReadArray(9, ret, 0, size);

                    accessor.Dispose();
                    mmFile.Dispose();
                    File.Delete(mmFileName);

                    return ret;
                }
            }
        }

        #endregion
    }
}
Feb 8 at 12:49 AM
Thanks sorry the original patchset is available still I was asking about the updated patch set that was mentioned above (http://wikisend.com/download/770862/Pageant.zip) that is dead. The original patches don't apply cleaning to the current source (they override files rather than patch them anyway and don't compile).

One could pull the original sources from around the time of the original patch set, apply them, make proper patches but it seems:
https://github.com/dimov-cz/win-sshfs
integrates atleast some level of the patches so using that may be easiest for anyone else trying to get paegent support.