LibSsh2CS 0.1.0-dev
dotnet add package LibSsh2CS --version 0.1.0-dev
NuGet\Install-Package LibSsh2CS -Version 0.1.0-dev
<PackageReference Include="LibSsh2CS" Version="0.1.0-dev" />
<PackageVersion Include="LibSsh2CS" Version="0.1.0-dev" />
<PackageReference Include="LibSsh2CS" />
paket add LibSsh2CS --version 0.1.0-dev
#r "nuget: LibSsh2CS, 0.1.0-dev"
#:package LibSsh2CS@0.1.0-dev
#addin nuget:?package=LibSsh2CS&version=0.1.0-dev&prerelease
#tool nuget:?package=LibSsh2CS&version=0.1.0-dev&prerelease
LibSsh2CS
A standalone managed C# port of libssh2 for .NET 11. LibSsh2CS provides asynchronous SSH-2 sessions, authentication, command execution, and forwarding without requiring the native libssh2 library. The library and tests were migrated from LibGit2CS and have no dependency on it.
Installation
The library targets .NET 11.
dotnet add package LibSsh2CS
Quick start
Connect to an SSH server, verify its host key, and run a command. Replace the
host and username, set SSH_PASSWORD, and set SSH_HOST_FINGERPRINT to the
server's SHA-256 fingerprint obtained through a trusted channel (in
SHA256:... format, without Base64 padding).
using System.Net.Sockets;
using System.Security.Cryptography;
using LibSsh2CS;
const string host = "ssh.example.com";
const int port = 22;
string password = Environment.GetEnvironmentVariable("SSH_PASSWORD")
?? throw new InvalidOperationException("Set SSH_PASSWORD.");
string trustedFingerprint = Environment.GetEnvironmentVariable("SSH_HOST_FINGERPRINT")
?? throw new InvalidOperationException("Set SSH_HOST_FINGERPRINT.");
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
CancellationToken ct = timeout.Token;
using var client = new TcpClient();
await client.ConnectAsync(host, port, ct);
await using var session = new SshSession();
await session.HandshakeAsync(client.GetStream(), (hostKey, _, _) =>
{
string fingerprint = "SHA256:" + Convert.ToBase64String(SHA256.HashData(hostKey))
.TrimEnd('=');
return Task.FromResult(fingerprint == trustedFingerprint);
}, ct);
await session.AuthenticateWithPasswordAsync("alice", password, cancellationToken: ct);
await using SshChannel channel = await session.OpenSessionAsync(ct);
await channel.SetExtendedDataModeAsync(SshExtendedDataMode.Merge, ct);
await channel.ExecAsync("echo hello", ct);
await channel.SendEofAsync(ct);
using Stream output = Console.OpenStandardOutput();
byte[] buffer = new byte[4096];
int count;
while ((count = await channel.ReadAsync(buffer, ct)) != 0)
{
await output.WriteAsync(buffer.AsMemory(0, count), ct);
}
int exitCode = await channel.GetExitStatusAsync(ct);
Console.WriteLine($"Exit status: {exitCode}");
The examples below reuse session and ct from this setup. Authentication
examples replace the password call and run after the handshake, before opening
channels. Other channel examples use an authenticated session.
Verify a known host
HandshakeAsync requires a host-trust callback. The library verifies the
server's exchange signature first; the callback decides whether to trust the
host key. Returning false aborts the handshake.
As an alternative to fingerprint pinning, load a previously trusted OpenSSH
known_hosts file. This example restricts negotiation to Ed25519 so the key
type passed to Check matches the negotiated key. Use it in place of the
handshake above:
using var knownHosts = new SshKnownHosts();
await knownHosts.ReadFileAsync("/path/to/trusted/known_hosts", cancellationToken: ct);
session[SshMethodType.HostKey] = "ssh-ed25519";
await session.HandshakeAsync(client.GetStream(), (hostKey, _, _) =>
{
SshKnownHostCheckResult result = knownHosts.Check(
host, port, hostKey, SshKnownHostKeyType.Ed25519);
return Task.FromResult(result.Status == SshKnownHostCheckStatus.Match);
}, ct);
Only Match is accepted; a missing or mismatched entry is rejected. Load the
file explicitly: the session does not automatically read ~/.ssh/known_hosts.
The callback applies to the initial handshake; it is not called again during
rekeying.
Authenticate with a private key
Read the private key into memory and let the library derive its public key. The parser supports OpenSSH private keys and supported PEM formats, including RSA, ECDSA, and Ed25519 keys.
byte[] privateKeyData = await File.ReadAllBytesAsync("/path/to/id_ed25519", ct);
await session.AuthenticateWithPublicKeyAsync(
"alice",
publicKeyBlob: null,
privateKeyData: privateKeyData,
passphrase: Environment.GetEnvironmentVariable("SSH_KEY_PASSPHRASE"),
cancellationToken: ct);
For signing outside the library, use the public-key overload that accepts a
PublicKeySignCallback. Password-change and keyboard-interactive callbacks are
also available through SshUserAuth.
Authenticate through an SSH agent
The default agent connection uses the Unix socket named by SSH_AUTH_SOCK.
You can also pass a socket path to the SshAgent constructor.
using LibSsh2CS.Agent;
await using var agent = new SshAgent();
await agent.ConnectAsync(ct);
IReadOnlyList<SshAgentIdentity> identities = await agent.ListIdentitiesAsync(ct);
if (identities.Count == 0)
{
throw new InvalidOperationException("The SSH agent has no loaded identities.");
}
await agent.AuthenticateWithIdentityAsync(session, "alice", identities[0], ct);
This example selects the first identity; choose the identity authorized for your account. The agent performs the signing. Pageant and Windows named-pipe agent transports are not implemented.
Read command output and send input
ReadAsync reads stdout and ReadStderrAsync reads stderr by default. Drain both
streams while a command runs so buffered output does not exhaust the channel
window. Alternatively, set SshExtendedDataMode.Merge before starting the
command, as in the quick start, or use Ignore to discard stderr.
In merge mode, stdout is drained before buffered stderr; their original interleaving is not preserved. Reads return bytes, so use a streaming decoder if you need to decode text across read boundaries.
Send input with WriteAsync and call SendEofAsync when finished:
await using SshChannel command = await session.OpenSessionAsync(ct);
await command.SetExtendedDataModeAsync(SshExtendedDataMode.Merge, ct);
await command.ExecAsync("wc -c", ct);
await command.WriteAsync("hello\n"u8.ToArray(), ct);
await command.SendEofAsync(ct);
using Stream destination = Console.OpenStandardOutput();
byte[] bytes = new byte[4096];
int received;
while ((received = await command.ReadAsync(bytes, ct)) != 0)
{
await destination.WriteAsync(bytes.AsMemory(0, received), ct);
}
int status = await command.GetExitStatusAsync(ct);
Console.WriteLine($"Exit status: {status}");
Drain output before waiting for exit status. GetExitStatusAsync waits for exit
information or channel closure; it returns zero if the server closes without
sending a status. ExitSignal exposes a received termination signal.
For interactive sessions, request a PTY with RequestPtyAsync before calling
ShellAsync. Use SetEnvAsync before starting the process; the server decides
which environment variables to accept.
Open a forwarded connection
Ask the SSH server to connect to a TCP endpoint reachable from that server:
await using SshChannel tunnel = await session.OpenDirectTcpIpAsync(
"127.0.0.1", 5432, cancellationToken: ct);
// Exchange the destination protocol's bytes using tunnel.ReadAsync and WriteAsync.
ListenForwardAsync requests remote TCP forwarding and returns an SshListener
for accepting forwarded channels. OpenDirectStreamLocalAsync connects to a Unix
socket on the server. Forwarding must be permitted by the server; applications
supply their own local listeners and byte-copy loops when building tunnels.
Lifecycle and scope
The caller owns the transport passed to HandshakeAsync; disposing a session
does not close that stream or socket. Dispose channels before the session, then
dispose the transport. Use await using for sessions, channels, and agents, and
pass cancellation tokens to network operations.
Set algorithm preferences through the session indexer before the handshake.
ReadTimeout controls transport reads, and RekeyPolicy configures automatic
rekey thresholds. Time-based rekey checks run during channel activity.
ConfigureKeepAlive sets keepalive behavior, but the application must schedule
calls to SendKeepAliveAsync; the library does not start a keepalive timer.
LibSsh2CS exposes SSH transport and channel primitives. SFTP and SCP clients are
not implemented. Opening SubsystemAsync("sftp") still requires the caller to
implement the SFTP protocol over the channel.
See the repository README for build commands and test prerequisites, and the source XML comments for API contracts.
License and attribution
LibSsh2CS is licensed under the BSD 3-Clause License, with applicable upstream terms. It includes code translated from libssh2 and Ed25519 arithmetic translated from libsodium. See THIRD-PARTY-NOTICES.md for upstream attribution, permissions, and file-specific terms.
LICENSE and THIRD-PARTY-NOTICES.md are included when packing the library and
must accompany binary redistributions. No upstream endorsement is implied.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-dev | 53 | 9/19/2026 |