github sshnet/SSH.NET 2024.1.0

latest release: 2024.2.0
4 months ago

New features:

  • Add support for aes*-gcm@openssh.com cipher algorithms on .NET 6+
  • Add cancellation of SshCommand via signals
  • Add SshCommand.ExecuteAsync
  • Add support for zlib@openssh.com compression algorithm on .NET 6+

Breaking changes:

  • SshCommand.ExitStatus was changed in #1423 from returning int to returning int? to reflect the fact that an exit status may not always be returned.
  • PipeStream (which provides the implementation of SshCommand.OutputStream and ExtendedOutputStream) was rewritten in #1399 to fix a number of bugs and become more "stream-like". As such:
    • It may now block where previously it may have returned 0 prematurely
    • It may now return partial data where previously it may have blocked until a certain amount of data was available.
    • The properties BlockLastReadBuffer and MaxBufferLength have been removed.
  • CommandAsyncResult was deleted in #1426
  • RsaCipher, AsymmetricCipher and CipherDigitalSignature were deleted in #1373
  • Encrypt/DecryptBlock were moved down from SymmetricCipher to BlockCipher in #1369
  • The previously nonfunctional ZlibStream was deleted and the API of Compressor was changed in #1326
  • SftpFileSytemInformation was renamed to SftpFileSystemInformation in #1425
  • See the full API diff at the end

What's Changed

New Contributors

Full Changelog: 2024.0.0...2024.1.0

API diff
namespace Renci.SshNet
{
     public abstract class BaseClient : Renci.SshNet.IBaseClient, System.IDisposable
     {
-        public bool IsConnected { get; }
+        public virtual bool IsConnected { get; }
-        public event System.EventHandler<Renci.SshNet.Common.ExceptionEventArgs> ErrorOccurred;
+        public event System.EventHandler<Renci.SshNet.Common.ExceptionEventArgs>? ErrorOccurred;
-        public event System.EventHandler<Renci.SshNet.Common.HostKeyEventArgs> HostKeyReceived;
+        public event System.EventHandler<Renci.SshNet.Common.HostKeyEventArgs>? HostKeyReceived;
-        public event System.EventHandler<Renci.SshNet.Common.SshIdentificationEventArgs> ServerIdentificationReceived;
+        public event System.EventHandler<Renci.SshNet.Common.SshIdentificationEventArgs>? ServerIdentificationReceived;
     }

     public class CipherInfo
     {
-        public CipherInfo(int keySize, System.Func<byte[], byte[], Renci.SshNet.Security.Cryptography.Cipher> cipher) { }
+        public CipherInfo(int keySize, System.Func<byte[], byte[], Renci.SshNet.Security.Cryptography.Cipher> cipher, bool isAead = false) { }
+        public bool IsAead { get; }
     }

-    public class CommandAsyncResult : System.IAsyncResult
-    {
-        public object AsyncState { get; }
-        public System.Threading.WaitHandle AsyncWaitHandle { get; }
-        public int BytesReceived { get; set; }
-        public int BytesSent { get; set; }
-        public bool CompletedSynchronously { get; }
-        public bool IsCompleted { get; }
-    }

     public interface ISftpClient : Renci.SshNet.IBaseClient, System.IDisposable
     {
-        System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback asyncCallback);
+        System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback? asyncCallback);
-        System.IAsyncResult BeginListDirectory(string path, System.AsyncCallback asyncCallback, object state, System.Action<int> listCallback = null);
+        System.IAsyncResult BeginListDirectory(string path, System.AsyncCallback? asyncCallback, object? state, System.Action<int>? listCallback = null);
-        System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback asyncCallback, object state, System.Action<ulong> downloadCallback = null);
+        System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback? asyncCallback, object? state, System.Action<ulong>? downloadCallback = null);
-        System.IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, System.AsyncCallback asyncCallback, object state);
+        System.IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, System.AsyncCallback? asyncCallback, object? state);
-        System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback asyncCallback);
+        System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback? asyncCallback);
-        System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback asyncCallback, object state, System.Action<ulong> uploadCallback = null);
+        System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback? asyncCallback, object? state, System.Action<ulong>? uploadCallback = null);
-        System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, bool canOverride, System.AsyncCallback asyncCallback, object state, System.Action<ulong> uploadCallback = null);
+        System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, bool canOverride, System.AsyncCallback? asyncCallback, object? state, System.Action<ulong>? uploadCallback = null);
-        void DownloadFile(string path, System.IO.Stream output, System.Action<ulong> downloadCallback = null);
+        void DownloadFile(string path, System.IO.Stream output, System.Action<ulong>? downloadCallback = null);
-        Renci.SshNet.Sftp.SftpFileSytemInformation GetStatus(string path);
+        Renci.SshNet.Sftp.SftpFileSystemInformation GetStatus(string path);
-        System.Threading.Tasks.Task<Renci.SshNet.Sftp.SftpFileSytemInformation> GetStatusAsync(string path, System.Threading.CancellationToken cancellationToken);
+        System.Threading.Tasks.Task<Renci.SshNet.Sftp.SftpFileSystemInformation> GetStatusAsync(string path, System.Threading.CancellationToken cancellationToken);
-        System.Collections.Generic.IEnumerable<Renci.SshNet.Sftp.ISftpFile> ListDirectory(string path, System.Action<int> listCallback = null);
+        System.Collections.Generic.IEnumerable<Renci.SshNet.Sftp.ISftpFile> ListDirectory(string path, System.Action<int>? listCallback = null);
-        void UploadFile(System.IO.Stream input, string path, System.Action<ulong> uploadCallback = null);
+        void UploadFile(System.IO.Stream input, string path, System.Action<ulong>? uploadCallback = null);
-        void UploadFile(System.IO.Stream input, string path, bool canOverride, System.Action<ulong> uploadCallback = null);
+        void UploadFile(System.IO.Stream input, string path, bool canOverride, System.Action<ulong>? uploadCallback = null);
     }

     public class ScpClient : Renci.SshNet.BaseClient
     {
-        public event System.EventHandler<Renci.SshNet.Common.ScpDownloadEventArgs> Downloading;
+        public event System.EventHandler<Renci.SshNet.Common.ScpDownloadEventArgs>? Downloading;
-        public event System.EventHandler<Renci.SshNet.Common.ScpUploadEventArgs> Uploading;
+        public event System.EventHandler<Renci.SshNet.Common.ScpUploadEventArgs>? Uploading;
     }

     public class SftpClient : Renci.SshNet.BaseClient, Renci.SshNet.IBaseClient, Renci.SshNet.ISftpClient, System.IDisposable
     {
+        public override bool IsConnected { get; }
-        public System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback asyncCallback) { }
+        public System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback? asyncCallback) { }
-        public System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback asyncCallback, object state, System.Action<ulong> downloadCallback = null) { }
+        public System.IAsyncResult BeginDownloadFile(string path, System.IO.Stream output, System.AsyncCallback? asyncCallback, object? state, System.Action<ulong>? downloadCallback = null) { }
-        public System.IAsyncResult BeginListDirectory(string path, System.AsyncCallback asyncCallback, object state, System.Action<int> listCallback = null) { }
+        public System.IAsyncResult BeginListDirectory(string path, System.AsyncCallback? asyncCallback, object? state, System.Action<int>? listCallback = null) { }
-        public System.IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, System.AsyncCallback asyncCallback, object state) { }
+        public System.IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, System.AsyncCallback? asyncCallback, object? state) { }
-        public System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback asyncCallback) { }
+        public System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback? asyncCallback) { }
-        public System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback asyncCallback, object state, System.Action<ulong> uploadCallback = null) { }
+        public System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, System.AsyncCallback? asyncCallback, object? state, System.Action<ulong>? uploadCallback = null) { }
-        public System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, bool canOverride, System.AsyncCallback asyncCallback, object state, System.Action<ulong> uploadCallback = null) { }
+        public System.IAsyncResult BeginUploadFile(System.IO.Stream input, string path, bool canOverride, System.AsyncCallback? asyncCallback, object? state, System.Action<ulong>? uploadCallback = null) { }
-        public void DownloadFile(string path, System.IO.Stream output, System.Action<ulong> downloadCallback = null) { }
+        public void DownloadFile(string path, System.IO.Stream output, System.Action<ulong>? downloadCallback = null) { }
-        public Renci.SshNet.Sftp.SftpFileSytemInformation GetStatus(string path) { }
+        public Renci.SshNet.Sftp.SftpFileSystemInformation GetStatus(string path) { }
-        public System.Threading.Tasks.Task<Renci.SshNet.Sftp.SftpFileSytemInformation> GetStatusAsync(string path, System.Threading.CancellationToken cancellationToken) { }
+        public System.Threading.Tasks.Task<Renci.SshNet.Sftp.SftpFileSystemInformation> GetStatusAsync(string path, System.Threading.CancellationToken cancellationToken) { }
-        public System.Collections.Generic.IEnumerable<Renci.SshNet.Sftp.ISftpFile> ListDirectory(string path, System.Action<int> listCallback = null) { }
+        public System.Collections.Generic.IEnumerable<Renci.SshNet.Sftp.ISftpFile> ListDirectory(string path, System.Action<int>? listCallback = null) { }
-        public void UploadFile(System.IO.Stream input, string path, System.Action<ulong> uploadCallback = null) { }
+        public void UploadFile(System.IO.Stream input, string path, System.Action<ulong>? uploadCallback = null) { }
-        public void UploadFile(System.IO.Stream input, string path, bool canOverride, System.Action<ulong> uploadCallback = null) { }
+        public void UploadFile(System.IO.Stream input, string path, bool canOverride, System.Action<ulong>? uploadCallback = null) { }
     }

     public class SshClient : Renci.SshNet.BaseClient
     {
-        public Renci.SshNet.Shell CreateShell(System.IO.Stream input, System.IO.Stream output, System.IO.Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, System.Collections.Generic.IDictionary<Renci.SshNet.Common.TerminalModes, uint> terminalModes, int bufferSize) { }
+        public Renci.SshNet.Shell CreateShell(System.IO.Stream input, System.IO.Stream output, System.IO.Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, System.Collections.Generic.IDictionary<Renci.SshNet.Common.TerminalModes, uint>? terminalModes, int bufferSize) { }
-        public Renci.SshNet.Shell CreateShell(System.Text.Encoding encoding, string input, System.IO.Stream output, System.IO.Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, System.Collections.Generic.IDictionary<Renci.SshNet.Common.TerminalModes, uint> terminalModes, int bufferSize) { }
+        public Renci.SshNet.Shell CreateShell(System.Text.Encoding encoding, string input, System.IO.Stream output, System.IO.Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, System.Collections.Generic.IDictionary<Renci.SshNet.Common.TerminalModes, uint>? terminalModes, int bufferSize) { }
+        public Renci.SshNet.Shell CreateShellNoTerminal(System.IO.Stream input, System.IO.Stream output, System.IO.Stream extendedOutput, int bufferSize = -1) { }
-        public Renci.SshNet.ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, System.Collections.Generic.IDictionary<Renci.SshNet.Common.TerminalModes, uint> terminalModeValues) { }
+        public Renci.SshNet.ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, System.Collections.Generic.IDictionary<Renci.SshNet.Common.TerminalModes, uint>? terminalModeValues) { }
+        public Renci.SshNet.ShellStream CreateShellStreamNoTerminal(int bufferSize = -1) { }
     }

     public class SshCommand : System.IDisposable
     {
-        public int ExitStatus { get; }
+        public int? ExitStatus { get; }
+        public string? ExitSignal { get; }
-        public System.IAsyncResult BeginExecute(System.AsyncCallback callback) { }
+        public System.IAsyncResult BeginExecute(System.AsyncCallback? callback) { }
-        public System.IAsyncResult BeginExecute(System.AsyncCallback callback, object state) { }
+        public System.IAsyncResult BeginExecute(System.AsyncCallback? callback, object? state) { }
-        public System.IAsyncResult BeginExecute(string commandText, System.AsyncCallback callback, object state) { }
+        public System.IAsyncResult BeginExecute(string commandText, System.AsyncCallback? callback, object? state) { }
-        public void CancelAsync() { }
+        public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500) { }
-        protected override void Finalize() { }
+        public System.Threading.Tasks.Task ExecuteAsync(System.Threading.CancellationToken cancellationToken = default) { }
     }

}
namespace Renci.SshNet.Common
{
     public class PipeStream : System.IO.Stream
     {
-        public bool BlockLastReadBuffer { get; set; }
-        public long MaxBufferLength { get; set; }
     }

}
namespace Renci.SshNet.Compression
{
-    public enum CompressionMode
-    {
-        Compress = 0,
-        Decompress = 1,
-    }

     public abstract class Compressor : Renci.SshNet.Security.Algorithm, System.IDisposable
     {
-        protected Compressor() { }
-        protected bool IsActive { get; set; }
+        protected Compressor(bool delayedCompression) { }
+        public byte[] Compress(byte[] data) { }
-        protected Renci.SshNet.Session Session { get; }
-        public virtual byte[] Compress(byte[] data) { }
-        public virtual byte[] Decompress(byte[] data) { }
+        protected abstract byte[] CompressCore(byte[] data, int offset, int length);
+        public byte[] Decompress(byte[] data) { }
+        protected abstract byte[] DecompressCore(byte[] data, int offset, int length);
     }

-    public class ZlibOpenSsh : Renci.SshNet.Compression.Compressor
-    {
-        public ZlibOpenSsh() { }
-        public override string Name { get; }
-        public override void Init(Renci.SshNet.Session session) { }
-    }

}
namespace Renci.SshNet.Connection
{
-    public class ZlibStream
-    {
-        public ZlibStream(System.IO.Stream stream, Renci.SshNet.Compression.CompressionMode mode) { }
-        public void Write(byte[] buffer, int offset, int count) { }
-    }

}
namespace Renci.SshNet.Security
{
     public interface IKeyExchange : System.IDisposable
     {
-        Renci.SshNet.Security.Cryptography.Cipher CreateClientCipher();
+        Renci.SshNet.Security.Cryptography.Cipher CreateClientCipher(out bool isAead);
-        Renci.SshNet.Security.Cryptography.Cipher CreateServerCipher();
+        Renci.SshNet.Security.Cryptography.Cipher CreateServerCipher(out bool isAead);
     }

     public abstract class KeyExchange : Renci.SshNet.Security.Algorithm, Renci.SshNet.Security.IKeyExchange, System.IDisposable
     {
-        public Renci.SshNet.Security.Cryptography.Cipher CreateClientCipher() { }
+        public Renci.SshNet.Security.Cryptography.Cipher CreateClientCipher(out bool isAead) { }
-        public Renci.SshNet.Security.Cryptography.Cipher CreateServerCipher() { }
+        public Renci.SshNet.Security.Cryptography.Cipher CreateServerCipher(out bool isAead) { }
     }

     public class RsaKey : Renci.SshNet.Security.Key, System.IDisposable
     {
-        protected override void Finalize() { }
     }

}
namespace Renci.SshNet.Security.Cryptography
{
-    public abstract class AsymmetricCipher : Renci.SshNet.Security.Cryptography.Cipher
-    {
-        protected AsymmetricCipher() { }
-        public override byte MinimumSize { get; }
-    }

     public abstract class BlockCipher : Renci.SshNet.Security.Cryptography.SymmetricCipher
     {
-        public override byte[] Decrypt(byte[] input) { }
+        public abstract int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
+        public abstract int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
     }

     public abstract class Cipher
     {
-        public abstract byte[] Decrypt(byte[] input);
+        public virtual int TagSize { get; }
+        public byte[] Decrypt(byte[] input) { }
     }

-    public abstract class CipherDigitalSignature : Renci.SshNet.Security.Cryptography.DigitalSignature
-    {
-        protected CipherDigitalSignature(Renci.SshNet.Common.ObjectIdentifier oid, Renci.SshNet.Security.Cryptography.AsymmetricCipher cipher) { }
-        protected byte[] DerEncode(byte[] hashData) { }
-        protected abstract byte[] Hash(byte[] input);
-        public override byte[] Sign(byte[] input) { }
-        public override bool Verify(byte[] input, byte[] signature) { }
-    }

-    public class RsaDigitalSignature : Renci.SshNet.Security.Cryptography.CipherDigitalSignature, System.IDisposable
+    public class RsaDigitalSignature : Renci.SshNet.Security.Cryptography.DigitalSignature, System.IDisposable
     {
-        protected override byte[] Hash(byte[] input) { }
+        public override byte[] Sign(byte[] input) { }
+        public override bool Verify(byte[] input, byte[] signature) { }
     }

     public abstract class SymmetricCipher : Renci.SshNet.Security.Cryptography.Cipher
     {
-        public abstract int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
-        public abstract int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
     }

}
namespace Renci.SshNet.Security.Cryptography.Ciphers
{
     public sealed class Arc4Cipher : Renci.SshNet.Security.Cryptography.StreamCipher
     {
-        public override byte[] Decrypt(byte[] input) { }
-        public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { }
-        public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { }
     }

-    public class RsaCipher : Renci.SshNet.Security.Cryptography.AsymmetricCipher
-    {
-        public RsaCipher(Renci.SshNet.Security.RsaKey key) { }
-        public override byte[] Decrypt(byte[] input) { }
-        public override byte[] Decrypt(byte[] input, int offset, int length) { }
-        public override byte[] Encrypt(byte[] input, int offset, int length) { }
-    }

}
namespace Renci.SshNet.Sftp
{
-    public class SftpFileSytemInformation
+    public class SftpFileSystemInformation
     {
     }

}

Don't miss a new SSH.NET release

NewReleases is sending notifications on new releases.