Skip to content

Commit 1876a8e

Browse files
authored
Merge branch 'develop' into bcl-ciphermode
2 parents 599718c + 7c07b10 commit 1876a8e

26 files changed

+723
-550
lines changed

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,11 @@ The tests always log to the console. See the [Logging documentation](https://ssh
4141
### Wireshark
4242

4343
Wireshark is able to dissect initial connection packets, such as key exchange, before encryption happens. Enter "ssh" as the display filter. See https://wiki.wireshark.org/SSH.md for more information.
44+
45+
The Debug build of SSH.NET has helpers to also allow dissection of the encrypted traffic by dumping the session keys in a format that Wireshark understands. Set a value for `SshNetLoggingConfiguration.WiresharkKeyLogFilePath` before connecting, and supply the same value to Wireshark in Edit -> Preferences -> Protocols -> SSH -> "Key log filename".
46+
47+
```c#
48+
using Renci.SshNet;
49+
50+
SshNetLoggingConfiguration.WiresharkKeyLogFilePath = @"C:\tmp\sshkeylogfile.txt";
51+
```

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<SignAssembly>true</SignAssembly>
1010
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Renci.SshNet.snk</AssemblyOriginatorKeyFile>
1111
<GenerateDocumentationFile>true</GenerateDocumentationFile>
12+
<EnablePackageValidation>true</EnablePackageValidation>
1213
<LangVersion>latest</LangVersion>
1314
<WarningLevel>9999</WarningLevel>
1415
</PropertyGroup>

README.md

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ SSH.NET is a Secure Shell (SSH-2) library for .NET, optimized for parallelism.
88

99
## Key Features
1010

11-
* Execution of SSH command using both synchronous and asynchronous methods
11+
* Execution of SSH commands using both synchronous and asynchronous methods
1212
* SFTP functionality for both synchronous and asynchronous operations
1313
* SCP functionality
14-
* Remote, dynamic and local port forwarding
14+
* Remote, dynamic and local port forwarding
1515
* Interactive shell/terminal implementation
16-
* Authentication via publickey, password and keyboard-interactive methods, including multi-factor
16+
* Authentication via public key, password and keyboard-interactive methods, including multi-factor
1717
* Connection via SOCKS4, SOCKS5 or HTTP proxy
1818

1919
## How to Use
@@ -52,12 +52,12 @@ using (var client = new SftpClient("sftp.foo.com", "guest", "pwd"))
5252

5353
The main types provided by this library are:
5454

55-
* Renci.SshNet.SshClient
56-
* Renci.SshNet.SftpClient
57-
* Renci.SshNet.ScpClient
58-
* Renci.SshNet.PrivateKeyFile
59-
* Renci.SshNet.SshCommand
60-
* Renci.SshNet.ShellStream
55+
* [Renci.SshNet.SshClient](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.SshClient.html)
56+
* [Renci.SshNet.SftpClient](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.SftpClient.html)
57+
* [Renci.SshNet.PrivateKeyFile](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.PrivateKeyFile.html)
58+
* [Renci.SshNet.SshCommand](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.SshCommand.html)
59+
* [Renci.SshNet.ForwardedPort](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.ForwardedPort.html)
60+
* [Renci.SshNet.ShellStream](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.ShellStream.html)
6161

6262
## Additional Documentation
6363

@@ -106,11 +106,6 @@ The main types provided by this library are:
106106
* ssh.com format ("BEGIN SSH2 ENCRYPTED PRIVATE KEY")
107107
* OpenSSH key format ("BEGIN OPENSSH PRIVATE KEY")
108108
* PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3")
109-
* DSA in
110-
* OpenSSL traditional PEM format ("BEGIN DSA PRIVATE KEY")
111-
* OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY")
112-
* ssh.com format ("BEGIN SSH2 ENCRYPTED PRIVATE KEY")
113-
* PuTTY private key format ("PuTTY-User-Key-File-2", "PuTTY-User-Key-File-3")
114109
* ECDSA 256/384/521 in
115110
* OpenSSL traditional PEM format ("BEGIN EC PRIVATE KEY")
116111
* OpenSSL PKCS#8 PEM format ("BEGIN PRIVATE KEY", "BEGIN ENCRYPTED PRIVATE KEY")
@@ -157,7 +152,8 @@ Private keys in PuTTY private key format can be encrypted using the following ci
157152
* rsa-sha2-512
158153
* rsa-sha2-256
159154
* ssh-rsa
160-
* ssh-dss
155+
156+
OpenSSH certificate authentication is supported for all of the above, e.g. ssh-ed25519-cert-v01<span></span>@openssh.com.
161157

162158
## Message Authentication Code
163159

@@ -186,17 +182,17 @@ Private keys in PuTTY private key format can be encrypted using the following ci
186182

187183
The library has no special requirements to build, other than an up-to-date .NET SDK. See also [CONTRIBUTING.md](https://github.com/sshnet/SSH.NET/blob/develop/CONTRIBUTING.md).
188184

189-
## Using Pre-Release NuGet Package
185+
## Using Pre-Release NuGet Packages
190186

191-
If you need an unreleased bugfix or feature, you can use the Pre-Release NuGet packages from the `develop` branch which are published to the [GitHub NuGet Registry](https://github.com/sshnet/SSH.NET/pkgs/nuget/SSH.NET).
192-
In order to pull packages from the registry you first have to create a Personal Access Token with the `read:packages` permissions. Then add a NuGet Source for SSH.NET:
193-
194-
Note: you may have to add `--store-password-in-clear-text` on non-Windows platforms.
187+
Pre-release NuGet packages are published from the `develop` branch to the [GitHub NuGet Registry](https://github.com/sshnet/SSH.NET/pkgs/nuget/SSH.NET).
188+
In order to pull packages from the registry, create a Personal Access Token with the `read:packages` permissions. Then add a package source for SSH.NET:
195189

196190
```
197191
dotnet nuget add source --name SSH.NET --username <username> --password <personalaccesstoken> https://nuget.pkg.github.com/sshnet/index.json
198192
```
199193

194+
Note: you may have to add `--store-password-in-clear-text` on non-Windows platforms.
195+
200196
Then you can add the the package as described [here](https://github.com/sshnet/SSH.NET/pkgs/nuget/SSH.NET).
201197

202198
## Supporting SSH.NET

docfx/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
href: examples.md
55
- name: API
66
href: api/
7+
- name: Logging
8+
href: logging.md

src/Renci.SshNet/Abstractions/StreamExtensions.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#if NETFRAMEWORK || NETSTANDARD2_0
2+
using System;
23
using System.IO;
34
using System.Threading.Tasks;
45

@@ -8,8 +9,15 @@ internal static class StreamExtensions
89
{
910
public static ValueTask DisposeAsync(this Stream stream)
1011
{
11-
stream.Dispose();
12-
return default;
12+
try
13+
{
14+
stream.Dispose();
15+
return default;
16+
}
17+
catch (Exception exc)
18+
{
19+
return new ValueTask(Task.FromException(exc));
20+
}
1321
}
1422
}
1523
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#pragma warning disable
2+
// Copied verbatim from https://github.com/dotnet/runtime/blob/d2650b6ae7023a2d9d2c74c56116f1f18472ab04/src/libraries/Common/src/System/Net/ArrayBuffer.cs
3+
4+
// Licensed to the .NET Foundation under one or more agreements.
5+
// The .NET Foundation licenses this file to you under the MIT license.
6+
7+
using System.Buffers;
8+
using System.Diagnostics;
9+
using System.Runtime.CompilerServices;
10+
using System.Runtime.InteropServices;
11+
12+
namespace System.Net
13+
{
14+
// Warning: Mutable struct!
15+
// The purpose of this struct is to simplify buffer management.
16+
// It manages a sliding buffer where bytes can be added at the end and removed at the beginning.
17+
// [ActiveSpan/Memory] contains the current buffer contents; these bytes will be preserved
18+
// (copied, if necessary) on any call to EnsureAvailableBytes.
19+
// [AvailableSpan/Memory] contains the available bytes past the end of the current content,
20+
// and can be written to in order to add data to the end of the buffer.
21+
// Commit(byteCount) will extend the ActiveSpan by [byteCount] bytes into the AvailableSpan.
22+
// Discard(byteCount) will discard [byteCount] bytes as the beginning of the ActiveSpan.
23+
24+
[StructLayout(LayoutKind.Auto)]
25+
internal struct ArrayBuffer : IDisposable
26+
{
27+
#if NET
28+
private static int ArrayMaxLength => Array.MaxLength;
29+
#else
30+
private const int ArrayMaxLength = 0X7FFFFFC7;
31+
#endif
32+
33+
private readonly bool _usePool;
34+
private byte[] _bytes;
35+
private int _activeStart;
36+
private int _availableStart;
37+
38+
// Invariants:
39+
// 0 <= _activeStart <= _availableStart <= bytes.Length
40+
41+
public ArrayBuffer(int initialSize, bool usePool = false)
42+
{
43+
Debug.Assert(initialSize > 0 || usePool);
44+
45+
_usePool = usePool;
46+
_bytes = initialSize == 0
47+
? Array.Empty<byte>()
48+
: usePool ? ArrayPool<byte>.Shared.Rent(initialSize) : new byte[initialSize];
49+
_activeStart = 0;
50+
_availableStart = 0;
51+
}
52+
53+
public ArrayBuffer(byte[] buffer)
54+
{
55+
Debug.Assert(buffer.Length > 0);
56+
57+
_usePool = false;
58+
_bytes = buffer;
59+
_activeStart = 0;
60+
_availableStart = 0;
61+
}
62+
63+
public void Dispose()
64+
{
65+
_activeStart = 0;
66+
_availableStart = 0;
67+
68+
byte[] array = _bytes;
69+
_bytes = null!;
70+
71+
if (array is not null)
72+
{
73+
ReturnBufferIfPooled(array);
74+
}
75+
}
76+
77+
// This is different from Dispose as the instance remains usable afterwards (_bytes will not be null).
78+
public void ClearAndReturnBuffer()
79+
{
80+
Debug.Assert(_usePool);
81+
Debug.Assert(_bytes is not null);
82+
83+
_activeStart = 0;
84+
_availableStart = 0;
85+
86+
byte[] bufferToReturn = _bytes;
87+
_bytes = Array.Empty<byte>();
88+
ReturnBufferIfPooled(bufferToReturn);
89+
}
90+
91+
public int ActiveLength => _availableStart - _activeStart;
92+
public Span<byte> ActiveSpan => new Span<byte>(_bytes, _activeStart, _availableStart - _activeStart);
93+
public ReadOnlySpan<byte> ActiveReadOnlySpan => new ReadOnlySpan<byte>(_bytes, _activeStart, _availableStart - _activeStart);
94+
public Memory<byte> ActiveMemory => new Memory<byte>(_bytes, _activeStart, _availableStart - _activeStart);
95+
96+
public int AvailableLength => _bytes.Length - _availableStart;
97+
public Span<byte> AvailableSpan => _bytes.AsSpan(_availableStart);
98+
public Memory<byte> AvailableMemory => _bytes.AsMemory(_availableStart);
99+
public Memory<byte> AvailableMemorySliced(int length) => new Memory<byte>(_bytes, _availableStart, length);
100+
101+
public int Capacity => _bytes.Length;
102+
public int ActiveStartOffset => _activeStart;
103+
104+
public byte[] DangerousGetUnderlyingBuffer() => _bytes;
105+
106+
public void Discard(int byteCount)
107+
{
108+
Debug.Assert(byteCount <= ActiveLength, $"Expected {byteCount} <= {ActiveLength}");
109+
_activeStart += byteCount;
110+
111+
if (_activeStart == _availableStart)
112+
{
113+
_activeStart = 0;
114+
_availableStart = 0;
115+
}
116+
}
117+
118+
public void Commit(int byteCount)
119+
{
120+
Debug.Assert(byteCount <= AvailableLength);
121+
_availableStart += byteCount;
122+
}
123+
124+
// Ensure at least [byteCount] bytes to write to.
125+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
126+
public void EnsureAvailableSpace(int byteCount)
127+
{
128+
if (byteCount > AvailableLength)
129+
{
130+
EnsureAvailableSpaceCore(byteCount);
131+
}
132+
}
133+
134+
private void EnsureAvailableSpaceCore(int byteCount)
135+
{
136+
Debug.Assert(AvailableLength < byteCount);
137+
138+
if (_bytes.Length == 0)
139+
{
140+
Debug.Assert(_usePool && _activeStart == 0 && _availableStart == 0);
141+
_bytes = ArrayPool<byte>.Shared.Rent(byteCount);
142+
return;
143+
}
144+
145+
int totalFree = _activeStart + AvailableLength;
146+
if (byteCount <= totalFree)
147+
{
148+
// We can free up enough space by just shifting the bytes down, so do so.
149+
Buffer.BlockCopy(_bytes, _activeStart, _bytes, 0, ActiveLength);
150+
_availableStart = ActiveLength;
151+
_activeStart = 0;
152+
Debug.Assert(byteCount <= AvailableLength);
153+
return;
154+
}
155+
156+
int desiredSize = ActiveLength + byteCount;
157+
158+
if ((uint)desiredSize > ArrayMaxLength)
159+
{
160+
throw new OutOfMemoryException();
161+
}
162+
163+
// Double the existing buffer size (capped at Array.MaxLength).
164+
int newSize = Math.Max(desiredSize, (int)Math.Min(ArrayMaxLength, 2 * (uint)_bytes.Length));
165+
166+
byte[] newBytes = _usePool ?
167+
ArrayPool<byte>.Shared.Rent(newSize) :
168+
new byte[newSize];
169+
byte[] oldBytes = _bytes;
170+
171+
if (ActiveLength != 0)
172+
{
173+
Buffer.BlockCopy(oldBytes, _activeStart, newBytes, 0, ActiveLength);
174+
}
175+
176+
_availableStart = ActiveLength;
177+
_activeStart = 0;
178+
179+
_bytes = newBytes;
180+
ReturnBufferIfPooled(oldBytes);
181+
182+
Debug.Assert(byteCount <= AvailableLength);
183+
}
184+
185+
public void Grow()
186+
{
187+
EnsureAvailableSpaceCore(AvailableLength + 1);
188+
}
189+
190+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
191+
private void ReturnBufferIfPooled(byte[] buffer)
192+
{
193+
// The buffer may be Array.Empty<byte>()
194+
if (_usePool && buffer.Length > 0)
195+
{
196+
ArrayPool<byte>.Shared.Return(buffer);
197+
}
198+
}
199+
}
200+
}

src/Renci.SshNet/Common/Extensions.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ namespace Renci.SshNet.Common
1919
/// </summary>
2020
internal static class Extensions
2121
{
22+
#pragma warning disable S4136 // Method overloads should be grouped together
2223
internal static byte[] ToArray(this ServiceName serviceName)
24+
#pragma warning restore S4136 // Method overloads should be grouped together
2325
{
2426
switch (serviceName)
2527
{
@@ -382,6 +384,28 @@ internal static bool Remove<TKey, TValue>(this Dictionary<TKey, TValue> dictiona
382384
value = default;
383385
return false;
384386
}
387+
388+
internal static ArraySegment<T> Slice<T>(this ArraySegment<T> arraySegment, int index)
389+
{
390+
return new ArraySegment<T>(arraySegment.Array, arraySegment.Offset + index, arraySegment.Count - index);
391+
}
392+
393+
internal static ArraySegment<T> Slice<T>(this ArraySegment<T> arraySegment, int index, int count)
394+
{
395+
return new ArraySegment<T>(arraySegment.Array, arraySegment.Offset + index, count);
396+
}
397+
398+
internal static T[] ToArray<T>(this ArraySegment<T> arraySegment)
399+
{
400+
if (arraySegment.Count == 0)
401+
{
402+
return Array.Empty<T>();
403+
}
404+
405+
var array = new T[arraySegment.Count];
406+
Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
407+
return array;
408+
}
385409
#endif
386410
}
387411
}

0 commit comments

Comments
 (0)