Created
December 10, 2017 02:38
-
-
Save coldacid/6e4e8306bcdc0a8954961454bc2558ee to your computer and use it in GitHub Desktop.
Prototype connector between gpg-agent and Windows named pipe for Win32-OpenSSH
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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