Skip to content

Instantly share code, notes, and snippets.

@coldacid
Created December 10, 2017 02:38
Show Gist options
  • Save coldacid/6e4e8306bcdc0a8954961454bc2558ee to your computer and use it in GitHub Desktop.
Save coldacid/6e4e8306bcdc0a8954961454bc2558ee to your computer and use it in GitHub Desktop.
Prototype connector between gpg-agent and Windows named pipe for Win32-OpenSSH
using System;
using System.IO;
using System.IO.Pipes;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace GpgAgentPipe
{
class Program
{
static ushort gpgSocketPort;
static byte[] gpgSocketCookie;
static string authSocketName;
// takes the path of the gpg-agent SSH socket file as the only command line argument. Uses $SSH_AUTH_SOCK if
// no argument is provided.
static void Main(string[] args)
{
authSocketName = Environment.GetEnvironmentVariable("SSH_AUTH_SOCK");
var gpgSocketPath = args.Length > 0 ? args[0] : authSocketName;
Console.WriteLine($"Using GPG Agent SSH socket file '{gpgSocketPath}' to get socket info");
GetGpgPortAndCookie(gpgSocketPath);
Console.WriteLine($"GPG Agent port: {gpgSocketPort}");
StartServer().Wait();
}
static Task StartServer()
{
return Task.Run(() =>
{
TcpClient agent;
NetworkStream sockStream;
string pipeName = authSocketName;
Console.WriteLine($"Starting pipe server on named pipe \\\\.\\pipe\\{pipeName}");
var pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut);
while (true)
{
pipe.WaitForConnection();
Console.WriteLine("Got a connection!");
Console.Write("Connecting to GPG Agent...");
try
{
agent = new TcpClient();
agent.Connect("127.0.0.1", gpgSocketPort);
sockStream = agent.GetStream();
}
catch (Exception e)
{
Console.Error.WriteLine("Couldn't connect to gpg-agent!");
Console.Error.WriteLine(e.ToString());
throw;
}
Console.WriteLine(" Done.");
Console.Write("Writing cookie to agent socket...");
sockStream.Write(gpgSocketCookie, 0, gpgSocketCookie.Length);
sockStream.Flush();
Console.WriteLine(" Done.");
// var pipeBuffer = new byte[256];
// var sockBuffer = new byte[256];
// int pipeBytesRead;
// int sockBytesRead;
try
{
Console.Write("Copying between agent and pipe...");
// PROBLEM: can't get duplex copying between the streams working. Whether using CopyToAsync
// paired like this or reading and writing back and forth like below, the program seems to get
// stuck on pipe->agent communication.
Task.WaitAny(
pipe.CopyToAsync(sockStream),
sockStream.CopyToAsync(pipe)
);
// while (pipe.IsConnected && agent.Connected)
// {
// pipeBytesRead = 256;
// while (pipeBytesRead == 256)
// {
// pipeBytesRead = pipe.Read(pipeBuffer, 0, pipeBuffer.Length);
// sockStream.Write(pipeBuffer, 0, pipeBytesRead);
// Console.WriteLine($"Read {pipeBytesRead} bytes from pipe, wrote to agent");
// }
// sockStream.Flush();
// Console.WriteLine("Flushed agent");
// sockBytesRead = 256;
// while (sockBytesRead == 256)
// {
// sockBytesRead = sockStream.Read(sockBuffer, 0, sockBuffer.Length);
// pipe.Write(sockBuffer, 0, sockBytesRead);
// Console.WriteLine($"Read {sockBytesRead} bytes from agent, wrote to pipe");
// }
// pipe.Flush();
// Console.WriteLine("Flushed pipe");
// }
Console.WriteLine(" Done.");
}
catch (IOException e)
{
Console.Error.WriteLine("IO exception occured! Did a connection break?");
Console.Error.WriteLine(e.ToString());
throw;
}
finally
{
Console.WriteLine("Pipe connection closed.");
pipe.Disconnect();
agent.Close();
}
}
});
}
static void GetGpgPortAndCookie(string socketFilePath)
{
byte[] socketBytes;
var portBytes = new byte[5];
var portBytesIndex = 0;
var cookieStartIndex = -1;
var state = 0; // 0 = pre-port, 1 = port, 2 = newline, 3 = cookie
try
{
socketBytes = File.ReadAllBytes(socketFilePath);
}
catch (Exception)
{
Console.Error.WriteLine("Can't read from GPG Agent socket file!");
throw;
}
for(var i = 0; i < socketBytes.Length; i++)
{
//Console.WriteLine($" byte {i:X2}, value = {socketBytes[i]:X2}, state = {state}");
if (state == 3)
{
//Console.WriteLine($" setting cookie start to index pos {i:X2}");
cookieStartIndex = i;
break;
}
switch (state)
{
case 0: // before the port number
{
if (0x30 <= socketBytes[i] && 0x3A > socketBytes[i])
{
//Console.WriteLine($" value inside ASCII number range, changing state = 1");
state = 1;
i--;
}
break;
}
case 1: // the port number
{
if (socketBytes[i] < 0x30 || socketBytes[i] > 0x39)
{
//Console.WriteLine($" value outside ASCII number range, changing state = 2");
state = 2;
i--;
}
else
{
//Console.WriteLine($" adding '{(char)socketBytes[i]}' to port bytes");
portBytes[portBytesIndex] = socketBytes[i];
portBytesIndex++;
if (portBytesIndex == 5)
{
//Console.WriteLine($" port bytes are now full, changing state = 2");
state = 2;
}
}
break;
}
case 2: // hunt the newline
{
if (socketBytes[i] == 0x0A)
{
//Console.WriteLine($" found the newline, changing state = 3");
state = 3;
}
break;
}
}
}
var portString = Encoding.ASCII.GetString(portBytes);
try
{
gpgSocketPort = UInt16.Parse(portString);
}
catch (Exception)
{
Console.Error.WriteLine($"Can't parse port number from GPG agent socket! Value = '{portString}'");
throw;
}
try
{
gpgSocketCookie = new byte[socketBytes.Length - cookieStartIndex];
Array.Copy(socketBytes, cookieStartIndex, gpgSocketCookie, 0, gpgSocketCookie.Length);
}
catch (Exception)
{
Console.Error.WriteLine("Can't copy cookie from socket file data!");
Console.Error.WriteLine($" Socket bytes size: {socketBytes.Length}");
Console.Error.WriteLine($" Initial cookie index: {cookieStartIndex}");
Console.Error.WriteLine($" Cookie array size: {gpgSocketCookie.Length}");
throw;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment