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 returningint
to returningint?
to reflect the fact that an exit status may not always be returned.PipeStream
(which provides the implementation ofSshCommand.OutputStream
andExtendedOutputStream
) 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
andMaxBufferLength
have been removed.
CommandAsyncResult
was deleted in #1426RsaCipher
,AsymmetricCipher
andCipherDigitalSignature
were deleted in #1373Encrypt/DecryptBlock
were moved down fromSymmetricCipher
toBlockCipher
in #1369- The previously nonfunctional
ZlibStream
was deleted and the API ofCompressor
was changed in #1326 SftpFileSytemInformation
was renamed toSftpFileSystemInformation
in #1425- See the full API diff at the end
What's Changed
- Fix ShellStream when receiving data larger than buffer length by @Rob-Hague in #1337
- fix link to tests in README by @mus65 in #1338
- Remove feature binary serialization by @scott-xu in #1325
- Changed Private Key Regex to be more tolerant regarding new lines at end of key file by @staecthSICKAG in #1344
- Use latest .NET 8 SDK in Ubuntu build by @Rob-Hague in #1352
- Use BCL Timeout by @scott-xu in #1353
- Fix CancelAsync Cause Deadlock by @zeotuan in #1345
- Cleanup and muting of analyzer warnings by @jscarle in #1357
- SftpClient: handle the SFTP session being closed by the server by @mus65 in #1362
- Handle unknown channel messages correctly by @mus65 in #1363
- Remove enormous array allocations in tests causing instability in CI by @Rob-Hague in #1367
- Add Rob-Hague to CODEOWNERS by @WojciechNagorski in #1376
- enable nullable on BaseClient and SftpClient by @mus65 in #1339
- Added support for GitHub pages using docfx by @jscarle in #1358
- Add support for Zlib compression (.NET 6.0 onward only) by @scott-xu in #1326
- git: checkout cs files with crlf by @mus65 in #1372
- Fix nullable error in build by @Rob-Hague in #1377
- Add support for AEAD AES 128/256 GCM Ciphers (.NET 6.0 onward only) by @scott-xu in #1369
- Add an AOT compatibility test app by @Rob-Hague in #1378
- Implement OpenSSH strict key exchange extension by @scott-xu in #1366
- Fix potential side-channel timing attack issue by @scott-xu in #1375
- Handle timeout correctly on Socks5 Proxy by @BoronBGP in #1342
- Fix ArgumentException usage in BlockCipher by @IgorMilavec in #818
- Fix SocketAsyncEventArgsAwaitable calling continuation multiple times by @IgorMilavec in #918
- gitattributes: set diff=csharp on cs files by @mus65 in #1393
- drop obsolete #if directives by @mus65 in #1394
- Use System.Security.Cryptography for RSA by @Rob-Hague in #1373
- Tweak AsyncSocketListener in tests by @Rob-Hague in #1382
- Documentation updates by @Rob-Hague in #1395
- Cleanup formatting and style and enforce it in CI by @mus65 in #1380
- fix flaky Connectivity Tests by @mus65 in #1403
- fix flaky ReceiveOnServerSocketShouldReturnZero test by @mus65 in #1404
- fix flaky Sftp_BeginUploadFile test by @mus65 in #1402
- More AsyncSocketListener patchwork by @Rob-Hague in #1408
- use Regex Source Generator for .NET 7+ by @mus65 in #1401
- Enable nullable on NetConf/Scp/SshClient by @mus65 in #1392
- make ConnectShouldActivateKeepAliveIfSessionIs test less flaky by @mus65 in #1410
- Delete CountdownEventTests by @Rob-Hague in #1409
- dotnet pack in CI by @Rob-Hague in #1400
- Relax the RSA/DSA decision for SSH2 keys by @Rob-Hague in #1190
- Fix a few issues with PipeStream by @Rob-Hague in #1399
- fix flaky SFTP file time tests by @mus65 in #1414
- doc: Update examples.md by @lupaulus in #1421
- A couple of changes/fixes in SshCommand by @Rob-Hague in #1423
- Rename SftpFileSytemInformation to SftpFileSystemInformation by @fndejan in #1425
- Support creating Shell(Stream) without PTY by @scott-xu in #1419
- fix build with .NET 9.0 SDK by @mus65 in #1427
- Updated NETCONF framing protocol detection to check both client & server capabilities by @declspec in #639
- Fix netconf framing protocol by @schaveyt in #946
- On SOCKS5 proxy: set hostname, not always IP by @jjvaca in #1072
- Remove unnecessary DNS lookup in Connect by @Rob-Hague in #1412
- Add SshCommand.ExecuteAsync by @Rob-Hague in #1426
- Set version to 2024.1.0 by @Rob-Hague in #1429
New Contributors
- @staecthSICKAG made their first contribution in #1344
- @zeotuan made their first contribution in #1345
- @BoronBGP made their first contribution in #1342
- @lupaulus made their first contribution in #1421
- @fndejan made their first contribution in #1425
- @declspec made their first contribution in #639
- @schaveyt made their first contribution in #946
- @jjvaca made their first contribution in #1072
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
{
}
}