From 6813bbf2c678d560bcbce085bce19c75cf23ba4a Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Mon, 16 Oct 2023 19:55:02 +0200 Subject: [PATCH 1/4] Enable trim analysis and fix warnings --- src/Renci.SshNet/Common/Extensions.cs | 17 ------- src/Renci.SshNet/ConnectionInfo.cs | 31 ++++++------ src/Renci.SshNet/IServiceFactory.cs | 4 +- src/Renci.SshNet/Renci.SshNet.csproj | 8 +++- .../Cryptography/RsaDigitalSignature.cs | 47 ++++++------------- src/Renci.SshNet/Security/KeyExchange.cs | 16 +++---- src/Renci.SshNet/ServiceFactory.cs | 21 ++------- 7 files changed, 51 insertions(+), 93 deletions(-) diff --git a/src/Renci.SshNet/Common/Extensions.cs b/src/Renci.SshNet/Common/Extensions.cs index d734ffd8b..bb6194a3c 100644 --- a/src/Renci.SshNet/Common/Extensions.cs +++ b/src/Renci.SshNet/Common/Extensions.cs @@ -92,23 +92,6 @@ internal static void DebugPrint(this IEnumerable bytes) Debug.WriteLine(sb.ToString()); } - /// - /// Creates an instance of the specified type using that type's default constructor. - /// - /// The type to create. - /// Type of the instance to create. - /// A reference to the newly created object. - internal static T CreateInstance(this Type type) - where T : class - { - if (type is null) - { - return null; - } - - return Activator.CreateInstance(type) as T; - } - internal static void ValidatePort(this uint value, string argument) { if (value > IPEndPoint.MaxPort) diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index a8089900a..25b1c6a5f 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -7,6 +7,7 @@ using Renci.SshNet.Abstractions; using Renci.SshNet.Common; +using Renci.SshNet.Compression; using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Security; @@ -46,7 +47,7 @@ public class ConnectionInfo : IConnectionInfoInternal /// /// Gets supported key exchange algorithms for this connection. /// - public IDictionary KeyExchangeAlgorithms { get; private set; } + public IDictionary> KeyExchangeAlgorithms { get; private set; } /// /// Gets supported encryptions for this connection. @@ -71,7 +72,7 @@ public class ConnectionInfo : IConnectionInfoInternal /// /// Gets supported compression algorithms for this connection. /// - public IDictionary CompressionAlgorithms { get; private set; } + public IDictionary> CompressionAlgorithms { get; private set; } /// /// Gets the supported channel requests for this connection. @@ -343,19 +344,19 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy MaxSessions = 10; Encoding = Encoding.UTF8; - KeyExchangeAlgorithms = new Dictionary + KeyExchangeAlgorithms = new Dictionary> { - { "curve25519-sha256", typeof(KeyExchangeECCurve25519) }, - { "curve25519-sha256@libssh.org", typeof(KeyExchangeECCurve25519) }, - { "ecdh-sha2-nistp256", typeof(KeyExchangeECDH256) }, - { "ecdh-sha2-nistp384", typeof(KeyExchangeECDH384) }, - { "ecdh-sha2-nistp521", typeof(KeyExchangeECDH521) }, - { "diffie-hellman-group-exchange-sha256", typeof(KeyExchangeDiffieHellmanGroupExchangeSha256) }, - { "diffie-hellman-group-exchange-sha1", typeof(KeyExchangeDiffieHellmanGroupExchangeSha1) }, - { "diffie-hellman-group16-sha512", typeof(KeyExchangeDiffieHellmanGroup16Sha512) }, - { "diffie-hellman-group14-sha256", typeof(KeyExchangeDiffieHellmanGroup14Sha256) }, - { "diffie-hellman-group14-sha1", typeof(KeyExchangeDiffieHellmanGroup14Sha1) }, - { "diffie-hellman-group1-sha1", typeof(KeyExchangeDiffieHellmanGroup1Sha1) }, + { "curve25519-sha256", () => new KeyExchangeECCurve25519() }, + { "curve25519-sha256@libssh.org", () => new KeyExchangeECCurve25519() }, + { "ecdh-sha2-nistp256", () => new KeyExchangeECDH256() }, + { "ecdh-sha2-nistp384", () => new KeyExchangeECDH384() }, + { "ecdh-sha2-nistp521", () => new KeyExchangeECDH521() }, + { "diffie-hellman-group-exchange-sha256", () => new KeyExchangeDiffieHellmanGroupExchangeSha256() }, + { "diffie-hellman-group-exchange-sha1", () => new KeyExchangeDiffieHellmanGroupExchangeSha1() }, + { "diffie-hellman-group16-sha512", () => new KeyExchangeDiffieHellmanGroup16Sha512() }, + { "diffie-hellman-group14-sha256", () => new KeyExchangeDiffieHellmanGroup14Sha256() }, + { "diffie-hellman-group14-sha1", () => new KeyExchangeDiffieHellmanGroup14Sha1() }, + { "diffie-hellman-group1-sha1", () => new KeyExchangeDiffieHellmanGroup1Sha1() }, }; Encryptions = new Dictionary @@ -404,7 +405,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy { "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data) }, }; - CompressionAlgorithms = new Dictionary + CompressionAlgorithms = new Dictionary> { { "none", null }, }; diff --git a/src/Renci.SshNet/IServiceFactory.cs b/src/Renci.SshNet/IServiceFactory.cs index cb20ae064..9302c655d 100644 --- a/src/Renci.SshNet/IServiceFactory.cs +++ b/src/Renci.SshNet/IServiceFactory.cs @@ -54,7 +54,7 @@ internal partial interface IServiceFactory /// Negotiates a key exchange algorithm, and creates a for the negotiated /// algorithm. /// - /// A of the key exchange algorithms supported by the client where the key is the name of the algorithm, and the value is the type implementing this algorithm. + /// A dictionary of the key exchange algorithms supported by the client where the key is the name of the algorithm, and the value is a factory returning this algorithm. /// The names of the key exchange algorithms supported by the SSH server. /// /// A that was negotiated between client and server. @@ -62,7 +62,7 @@ internal partial interface IServiceFactory /// is null. /// is null. /// No key exchange algorithm is supported by both client and server. - IKeyExchange CreateKeyExchange(IDictionary clientAlgorithms, string[] serverAlgorithms); + IKeyExchange CreateKeyExchange(IDictionary> clientAlgorithms, string[] serverAlgorithms); ISftpFileReader CreateSftpFileReader(string fileName, ISftpSession sftpSession, uint bufferSize); diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 4ccf6c72e..d8f0e6011 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -5,11 +5,15 @@ net462;netstandard2.0;netstandard2.1;net6.0;net7.0 + + true + + FEATURE_BINARY_SERIALIZATION;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_DNS_SYNC;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_RIPEMD160 - + @@ -17,7 +21,7 @@ - + FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP diff --git a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs index 790a0da64..54c6b66fa 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; + using Renci.SshNet.Common; using Renci.SshNet.Security.Cryptography.Ciphers; @@ -10,7 +11,7 @@ namespace Renci.SshNet.Security.Cryptography /// public class RsaDigitalSignature : CipherDigitalSignature, IDisposable { - private HashAlgorithm _hash; + private readonly HashAlgorithmName _hashAlgorithmName; /// /// Initializes a new instance of the class with the SHA-1 hash algorithm. @@ -28,8 +29,7 @@ public RsaDigitalSignature(RsaKey rsaKey) public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName) : base(ObjectIdentifier.FromHashAlgorithmName(hashAlgorithmName), new RsaCipher(rsaKey)) { - _hash = CryptoConfig.CreateFromName(hashAlgorithmName.Name) as HashAlgorithm - ?? throw new ArgumentException($"Could not create {nameof(HashAlgorithm)} from `{hashAlgorithmName}`.", nameof(hashAlgorithmName)); + _hashAlgorithmName = hashAlgorithmName; } /// @@ -41,13 +41,20 @@ public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName) /// protected override byte[] Hash(byte[] input) { - return _hash.ComputeHash(input); +#if !NET462 + using var hash = IncrementalHash.CreateHash(_hashAlgorithmName); + hash.AppendData(input); + return hash.GetHashAndReset(); +#else + using var hash = CryptoConfig.CreateFromName(_hashAlgorithmName.Name) as HashAlgorithm + ?? throw new InvalidOperationException($"Could not create {nameof(HashAlgorithm)} from `{_hashAlgorithmName}`."); + + return hash.ComputeHash(input); +#endif } #region IDisposable Members - private bool _isDisposed; - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -62,33 +69,7 @@ public void Dispose() /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - - if (disposing) - { - var hash = _hash; - if (hash != null) - { - hash.Dispose(); - _hash = null; - } - - _isDisposed = true; - } - } - - /// - /// Releases unmanaged resources and performs other cleanup operations before the - /// is reclaimed by garbage collection. - /// - ~RsaDigitalSignature() - { - Dispose(disposing: false); - } + { } #endregion } diff --git a/src/Renci.SshNet/Security/KeyExchange.cs b/src/Renci.SshNet/Security/KeyExchange.cs index 96a912b66..df04a0383 100644 --- a/src/Renci.SshNet/Security/KeyExchange.cs +++ b/src/Renci.SshNet/Security/KeyExchange.cs @@ -21,8 +21,8 @@ public abstract class KeyExchange : Algorithm, IKeyExchange private CipherInfo _serverCipherInfo; private HashInfo _clientHashInfo; private HashInfo _serverHashInfo; - private Type _compressionType; - private Type _decompressionType; + private Func _compressorFactory; + private Func _decompressorFactory; /// /// Gets the session. @@ -149,8 +149,8 @@ from a in message.CompressionAlgorithmsServerToClient _serverCipherInfo = session.ConnectionInfo.Encryptions[serverDecryptionAlgorithmName]; _clientHashInfo = session.ConnectionInfo.HmacAlgorithms[clientHmacAlgorithmName]; _serverHashInfo = session.ConnectionInfo.HmacAlgorithms[serverHmacAlgorithmName]; - _compressionType = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName]; - _decompressionType = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName]; + _compressorFactory = session.ConnectionInfo.CompressionAlgorithms[compressionAlgorithmName]; + _decompressorFactory = session.ConnectionInfo.CompressionAlgorithms[decompressionAlgorithmName]; } /// @@ -260,12 +260,12 @@ public HashAlgorithm CreateClientHash() /// public Compressor CreateCompressor() { - if (_compressionType is null) + if (_compressorFactory is null) { return null; } - var compressor = _compressionType.CreateInstance(); + var compressor = _compressorFactory(); compressor.Init(Session); @@ -280,12 +280,12 @@ public Compressor CreateCompressor() /// public Compressor CreateDecompressor() { - if (_decompressionType is null) + if (_decompressorFactory is null) { return null; } - var decompressor = _decompressionType.CreateInstance(); + var decompressor = _decompressorFactory(); decompressor.Init(Session); diff --git a/src/Renci.SshNet/ServiceFactory.cs b/src/Renci.SshNet/ServiceFactory.cs index 16568faf2..c54c59306 100644 --- a/src/Renci.SshNet/ServiceFactory.cs +++ b/src/Renci.SshNet/ServiceFactory.cs @@ -78,19 +78,8 @@ public PipeStream CreatePipeStream() return new PipeStream(); } - /// - /// Negotiates a key exchange algorithm, and creates a for the negotiated - /// algorithm. - /// - /// A of the key exchange algorithms supported by the client where key is the name of the algorithm, and value is the type implementing this algorithm. - /// The names of the key exchange algorithms supported by the SSH server. - /// - /// A that was negotiated between client and server. - /// - /// is null. - /// is null. - /// No key exchange algorithms are supported by both client and server. - public IKeyExchange CreateKeyExchange(IDictionary clientAlgorithms, string[] serverAlgorithms) + /// + public IKeyExchange CreateKeyExchange(IDictionary> clientAlgorithms, string[] serverAlgorithms) { if (clientAlgorithms is null) { @@ -103,17 +92,17 @@ public IKeyExchange CreateKeyExchange(IDictionary clientAlgorithms } // find an algorithm that is supported by both client and server - var keyExchangeAlgorithmType = (from c in clientAlgorithms + var keyExchangeAlgorithmFactory = (from c in clientAlgorithms from s in serverAlgorithms where s == c.Key select c.Value).FirstOrDefault(); - if (keyExchangeAlgorithmType is null) + if (keyExchangeAlgorithmFactory is null) { throw new SshConnectionException("Failed to negotiate key exchange algorithm.", DisconnectReason.KeyExchangeFailed); } - return keyExchangeAlgorithmType.CreateInstance(); + return keyExchangeAlgorithmFactory(); } public ISftpFileReader CreateSftpFileReader(string fileName, ISftpSession sftpSession, uint bufferSize) From ea49f1338977620c4d11653873324e20f5f40a14 Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Mon, 30 Oct 2023 22:09:44 +0100 Subject: [PATCH 2/4] Use EnableTrimAnalyzer instead of IsTrimmable I don't know how IsTrimmable works with references (i.e. to SshNet.Security.Cryptography) --- src/Renci.SshNet/Renci.SshNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index d8f0e6011..2f4338ebb 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -6,7 +6,7 @@ - true + true From 9abb605c4467e9b647756ce8709dc7f281510c3d Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Thu, 7 Dec 2023 23:28:24 +0100 Subject: [PATCH 3/4] Add additional aot/trimming related analyzers --- src/Renci.SshNet/Renci.SshNet.csproj | 2 ++ src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index f5dc9ab74..58fc6ef1d 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -7,6 +7,8 @@ true + true + true diff --git a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs index 194b8cda3..5a916f65b 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs @@ -70,7 +70,8 @@ public void Dispose() /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) - { } + { + } #endregion } From a344ebe29ffa9fbef4de76921290a45b564ec4c0 Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Thu, 25 Jan 2024 23:24:57 +0100 Subject: [PATCH 4/4] Initialise the hash instance in the constructor --- .../Cryptography/RsaDigitalSignature.cs | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs index 5a916f65b..63982d112 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs @@ -11,7 +11,11 @@ namespace Renci.SshNet.Security.Cryptography /// public class RsaDigitalSignature : CipherDigitalSignature, IDisposable { - private readonly HashAlgorithmName _hashAlgorithmName; +#if NET462 + private readonly HashAlgorithm _hash; +#else + private readonly IncrementalHash _hash; +#endif /// /// Initializes a new instance of the class with the SHA-1 hash algorithm. @@ -30,7 +34,14 @@ public RsaDigitalSignature(RsaKey rsaKey) public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName) : base(ObjectIdentifier.FromHashAlgorithmName(hashAlgorithmName), new RsaCipher(rsaKey)) { - _hashAlgorithmName = hashAlgorithmName; +#if NET462 + _hash = CryptoConfig.CreateFromName(hashAlgorithmName.Name) as HashAlgorithm + ?? throw new ArgumentException($"Could not create {nameof(HashAlgorithm)} from `{hashAlgorithmName}`.", nameof(hashAlgorithmName)); +#else + // CryptoConfig.CreateFromName is a somewhat legacy API and is incompatible with trimming. + // Use IncrementalHash instead (which is also more modern and lighter-weight than HashAlgorithm). + _hash = IncrementalHash.CreateHash(hashAlgorithmName); +#endif } /// @@ -42,15 +53,11 @@ public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName) /// protected override byte[] Hash(byte[] input) { -#if !NET462 - using var hash = IncrementalHash.CreateHash(_hashAlgorithmName); - hash.AppendData(input); - return hash.GetHashAndReset(); +#if NET462 + return _hash.ComputeHash(input); #else - using var hash = CryptoConfig.CreateFromName(_hashAlgorithmName.Name) as HashAlgorithm - ?? throw new InvalidOperationException($"Could not create {nameof(HashAlgorithm)} from `{_hashAlgorithmName}`."); - - return hash.ComputeHash(input); + _hash.AppendData(input); + return _hash.GetHashAndReset(); #endif } @@ -71,6 +78,7 @@ public void Dispose() /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) { + _hash.Dispose(); } #endregion