// OneSign Windows Credential Provider
// PipeServer.cs — Named pipe listener that receives credentials from the agent
//
// The OneSign agent writes a 4-byte little-endian length-prefixed JSON message
// to \\.\pipe\OneSignCredProvider when it wants to unlock the workstation.
// This class starts a background thread that waits for exactly one message per
// pipe connection, parses it, and fires the CredentialsReceived event.
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using System.Threading;
namespace OneSign.CredentialProvider
{
///
/// Credentials delivered by the OneSign agent over the named pipe.
///
internal sealed class AgentCredentials
{
public string Username { get; }
public string Password { get; }
public string Domain { get; }
public AgentCredentials(string username, string password, string domain)
{
Username = username;
Password = password;
Domain = domain;
}
}
///
/// Listens on \\.\pipe\OneSignCredProvider and raises CredentialsReceived
/// each time the agent delivers a JSON credential blob.
///
internal sealed class PipeServer : IDisposable
{
public const string PipeName = "OneSignCredProvider";
public event EventHandler? CredentialsReceived;
private volatile bool _running;
private Thread? _thread;
public void Start()
{
if (_running) return;
_running = true;
_thread = new Thread(ListenLoop)
{
IsBackground = true,
Name = "OneSign-PipeServer",
};
_thread.Start();
}
public void Stop()
{
_running = false;
}
public void Dispose() => Stop();
// ── Background listener ───────────────────────────────────────────────
private void ListenLoop()
{
while (_running)
{
try
{
using var pipe = new NamedPipeServerStream(
PipeName,
PipeDirection.In,
maxNumberOfServerInstances: 1,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
inBufferSize: 4096,
outBufferSize: 0
);
// Wait up to 5 seconds for a client; if no connection arrives
// loop again so we can check _running.
var connectTask = pipe.WaitForConnectionAsync();
if (!connectTask.Wait(5_000) || !_running)
continue;
using var reader = new BinaryReader(pipe, Encoding.UTF8, leaveOpen: true);
// Read 4-byte length prefix
int length = reader.ReadInt32();
if (length <= 0 || length > 65535)
continue;
byte[] buf = reader.ReadBytes(length);
if (buf.Length != length)
continue;
var creds = ParsePayload(buf);
if (creds != null)
CredentialsReceived?.Invoke(this, creds);
}
catch (IOException)
{
// Client disconnected mid-read — loop again
}
catch (Exception)
{
// Guard against unexpected errors; back off briefly
Thread.Sleep(1000);
}
}
}
private static AgentCredentials? ParsePayload(byte[] data)
{
try
{
var doc = JsonDocument.Parse(data);
var root = doc.RootElement;
string username = root.GetProperty("username").GetString() ?? "";
string password = root.GetProperty("password").GetString() ?? "";
string domain = root.TryGetProperty("domain", out var d)
? (d.GetString() ?? ".")
: ".";
if (string.IsNullOrEmpty(username))
return null;
return new AgentCredentials(username, password, domain);
}
catch
{
return null;
}
}
}
}