using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; using Renci.SshNet.Common; using Renci.SshNet.Compression; using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Security; using Renci.SshNet.Security.Cryptography; using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Security.Cryptography.Ciphers.Modes; namespace Renci.SshNet { /// /// Represents remote connection information class. /// /// /// This class is NOT thread-safe. Do not use the same with multiple /// client instances. /// public class ConnectionInfo : IConnectionInfoInternal { internal const int DefaultPort = 22; /// /// The default connection timeout. /// /// /// 30 seconds. /// private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); /// /// The default channel close timeout. /// /// /// 1 second. /// private static readonly TimeSpan DefaultChannelCloseTimeout = TimeSpan.FromSeconds(1); private TimeSpan _timeout; private TimeSpan _channelCloseTimeout; /// /// Gets supported key exchange algorithms for this connection. /// public IDictionary> KeyExchangeAlgorithms { get; private set; } /// /// Gets supported encryptions for this connection. /// #pragma warning disable CA1859 // Use concrete types when possible for improved performance public IDictionary Encryptions { get; private set; } #pragma warning restore CA1859 // Use concrete types when possible for improved performance /// /// Gets supported hash algorithms for this connection. /// public IDictionary HmacAlgorithms { get; private set; } /// /// Gets supported host key algorithms for this connection. /// public IDictionary> HostKeyAlgorithms { get; private set; } /// /// Gets supported authentication methods for this connection. /// public IList AuthenticationMethods { get; private set; } /// /// Gets supported compression algorithms for this connection. /// public IDictionary> CompressionAlgorithms { get; private set; } /// /// Gets the supported channel requests for this connection. /// /// /// The supported channel requests for this connection. /// public IDictionary ChannelRequests { get; private set; } /// /// Gets a value indicating whether connection is authenticated. /// /// /// if connection is authenticated; otherwise, . /// public bool IsAuthenticated { get; private set; } /// /// Gets connection host. /// /// /// The connection host. /// public string Host { get; private set; } /// /// Gets connection port. /// /// /// The connection port. The default value is 22. /// public int Port { get; private set; } /// /// Gets connection username. /// public string Username { get; private set; } /// /// Gets proxy type. /// /// /// The type of the proxy. /// public ProxyTypes ProxyType { get; private set; } /// /// Gets proxy connection host. /// public string ProxyHost { get; private set; } /// /// Gets proxy connection port. /// public int ProxyPort { get; private set; } /// /// Gets proxy connection username. /// public string ProxyUsername { get; private set; } /// /// Gets proxy connection password. /// public string ProxyPassword { get; private set; } /// /// Gets or sets connection timeout. /// /// /// The connection timeout. The default value is 30 seconds. /// public TimeSpan Timeout { get { return _timeout; } set { value.EnsureValidTimeout(nameof(Timeout)); _timeout = value; } } /// /// Gets or sets the timeout to use when waiting for a server to acknowledge closing a channel. /// /// /// The channel close timeout. The default value is 1 second. /// /// /// If a server does not send a SSH_MSG_CHANNEL_CLOSE message before the specified timeout /// elapses, the channel will be closed immediately. /// public TimeSpan ChannelCloseTimeout { get { return _channelCloseTimeout; } set { value.EnsureValidTimeout(nameof(ChannelCloseTimeout)); _channelCloseTimeout = value; } } /// /// Gets or sets the character encoding. /// /// /// The character encoding. The default is . /// public Encoding Encoding { get; set; } /// /// Gets or sets number of retry attempts when session channel creation failed. /// /// /// The number of retry attempts when session channel creation failed. The default /// value is 10. /// public int RetryAttempts { get; set; } /// /// Gets or sets maximum number of session channels to be open simultaneously. /// /// /// The maximum number of session channels to be open simultaneously. The default /// value is 10. /// public int MaxSessions { get; set; } /// /// Occurs when authentication banner is sent by the server. /// public event EventHandler AuthenticationBanner; /// /// Gets the current key exchange algorithm. /// public string CurrentKeyExchangeAlgorithm { get; internal set; } /// /// Gets the current server encryption. /// public string CurrentServerEncryption { get; internal set; } /// /// Gets the current client encryption. /// public string CurrentClientEncryption { get; internal set; } /// /// Gets the current server hash algorithm. /// public string CurrentServerHmacAlgorithm { get; internal set; } /// /// Gets the current client hash algorithm. /// public string CurrentClientHmacAlgorithm { get; internal set; } /// /// Gets the current host key algorithm. /// public string CurrentHostKeyAlgorithm { get; internal set; } /// /// Gets the current server compression algorithm. /// public string CurrentServerCompressionAlgorithm { get; internal set; } /// /// Gets the server version. /// public string ServerVersion { get; internal set; } /// /// Gets the client version. /// public string ClientVersion { get; internal set; } /// /// Gets the current client compression algorithm. /// public string CurrentClientCompressionAlgorithm { get; internal set; } /// /// Initializes a new instance of the class. /// /// The host. /// The username. /// The authentication methods. /// is . /// is a zero-length string. /// is , a zero-length string or contains only whitespace characters. /// is . /// No specified. public ConnectionInfo(string host, string username, params AuthenticationMethod[] authenticationMethods) : this(host, DefaultPort, username, ProxyTypes.None, proxyHost: null, 0, proxyUsername: null, proxyPassword: null, authenticationMethods) { } /// /// Initializes a new instance of the class. /// /// The host. /// The port. /// The username. /// The authentication methods. /// is . /// is , a zero-length string or contains only whitespace characters. /// is not within and . /// is . /// No specified. public ConnectionInfo(string host, int port, string username, params AuthenticationMethod[] authenticationMethods) : this(host, port, username, ProxyTypes.None, proxyHost: null, 0, proxyUsername: null, proxyPassword: null, authenticationMethods) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Connection username. /// Type of the proxy. /// The proxy host. /// The proxy port. /// The proxy username. /// The proxy password. /// The authentication methods. /// is . /// is , a zero-length string or contains only whitespace characters. /// is not within and . /// is not and is . /// is not and is not within and . /// is . /// No specified. public ConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params AuthenticationMethod[] authenticationMethods) { ThrowHelper.ThrowIfNull(host); port.ValidatePort(); ThrowHelper.ThrowIfNullOrWhiteSpace(username); if (proxyType != ProxyTypes.None) { ThrowHelper.ThrowIfNull(proxyHost); proxyPort.ValidatePort(); } ThrowHelper.ThrowIfNull(authenticationMethods); if (authenticationMethods.Length == 0) { throw new ArgumentException("At least one authentication method should be specified.", nameof(authenticationMethods)); } // Set default connection values Timeout = DefaultTimeout; ChannelCloseTimeout = DefaultChannelCloseTimeout; RetryAttempts = 10; MaxSessions = 10; Encoding = Encoding.UTF8; KeyExchangeAlgorithms = new Dictionary> { { "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 { { "aes128-ctr", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) }, { "aes192-ctr", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) }, { "aes256-ctr", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false)) }, { "aes128-gcm@openssh.com", new CipherInfo(128, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) }, { "aes256-gcm@openssh.com", new CipherInfo(256, (key, iv) => new AesGcmCipher(key, iv, aadLength: 4), isAead: true) }, { "chacha20-poly1305@openssh.com", new CipherInfo(512, (key, iv) => new ChaCha20Poly1305Cipher(key, aadLength: 4), isAead: true) }, { "aes128-cbc", new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) }, { "aes192-cbc", new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) }, { "aes256-cbc", new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false)) }, { "3des-cbc", new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), padding: null)) }, }; HmacAlgorithms = new Dictionary { /* Encrypt-and-MAC (encrypt-and-authenticate) variants */ { "hmac-sha2-256", new HashInfo(32*8, key => new HMACSHA256(key)) }, { "hmac-sha2-512", new HashInfo(64*8, key => new HMACSHA512(key)) }, { "hmac-sha1", new HashInfo(20*8, key => new HMACSHA1(key)) }, /* Encrypt-then-MAC variants */ { "hmac-sha2-256-etm@openssh.com", new HashInfo(32*8, key => new HMACSHA256(key), isEncryptThenMAC: true) }, { "hmac-sha2-512-etm@openssh.com", new HashInfo(64*8, key => new HMACSHA512(key), isEncryptThenMAC: true) }, { "hmac-sha1-etm@openssh.com", new HashInfo(20*8, key => new HMACSHA1(key), isEncryptThenMAC: true) }, }; #pragma warning disable SA1107 // Code should not contain multiple statements on one line var hostAlgs = new Dictionary>(); hostAlgs.Add("ssh-ed25519-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ssh-ed25519-cert-v01@openssh.com", cert, hostAlgs); }); hostAlgs.Add("ecdsa-sha2-nistp256-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com", cert, hostAlgs); }); hostAlgs.Add("ecdsa-sha2-nistp384-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com", cert, hostAlgs); }); hostAlgs.Add("ecdsa-sha2-nistp521-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com", cert, hostAlgs); }); hostAlgs.Add("rsa-sha2-512-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("rsa-sha2-512-cert-v01@openssh.com", cert, new RsaDigitalSignature((RsaKey)cert.Key, HashAlgorithmName.SHA512), hostAlgs); }); hostAlgs.Add("rsa-sha2-256-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("rsa-sha2-256-cert-v01@openssh.com", cert, new RsaDigitalSignature((RsaKey)cert.Key, HashAlgorithmName.SHA256), hostAlgs); }); hostAlgs.Add("ssh-rsa-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ssh-rsa-cert-v01@openssh.com", cert, hostAlgs); }); hostAlgs.Add("ssh-dss-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ssh-dss-cert-v01@openssh.com", cert, hostAlgs); }); hostAlgs.Add("ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(new SshKeyData(data)))); hostAlgs.Add("ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(new SshKeyData(data)))); hostAlgs.Add("ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(new SshKeyData(data)))); hostAlgs.Add("ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(new SshKeyData(data)))); hostAlgs.Add("rsa-sha2-512", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); }); hostAlgs.Add("rsa-sha2-256", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); }); hostAlgs.Add("ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data)))); hostAlgs.Add("ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data)))); #pragma warning restore SA1107 // Code should not contain multiple statements on one line HostKeyAlgorithms = hostAlgs; CompressionAlgorithms = new Dictionary> { { "none", null }, { "zlib@openssh.com", () => new ZlibOpenSsh() }, }; ChannelRequests = new Dictionary { { EnvironmentVariableRequestInfo.Name, new EnvironmentVariableRequestInfo() }, { ExecRequestInfo.Name, new ExecRequestInfo() }, { ExitSignalRequestInfo.Name, new ExitSignalRequestInfo() }, { ExitStatusRequestInfo.Name, new ExitStatusRequestInfo() }, { PseudoTerminalRequestInfo.Name, new PseudoTerminalRequestInfo() }, { ShellRequestInfo.Name, new ShellRequestInfo() }, { SignalRequestInfo.Name, new SignalRequestInfo() }, { SubsystemRequestInfo.Name, new SubsystemRequestInfo() }, { WindowChangeRequestInfo.Name, new WindowChangeRequestInfo() }, { X11ForwardingRequestInfo.Name, new X11ForwardingRequestInfo() }, { XonXoffRequestInfo.Name, new XonXoffRequestInfo() }, { EndOfWriteRequestInfo.Name, new EndOfWriteRequestInfo() }, { KeepAliveRequestInfo.Name, new KeepAliveRequestInfo() }, }; Host = host; Port = port; Username = username; ProxyType = proxyType; ProxyHost = proxyHost; ProxyPort = proxyPort; ProxyUsername = proxyUsername; ProxyPassword = proxyPassword; AuthenticationMethods = authenticationMethods; } /// /// Authenticates the specified session. /// /// The session to be authenticated. /// The factory to use for creating new services. /// is . /// is . /// No suitable authentication method found to complete authentication, or permission denied. internal void Authenticate(ISession session, IServiceFactory serviceFactory) { ThrowHelper.ThrowIfNull(serviceFactory); IsAuthenticated = false; var clientAuthentication = serviceFactory.CreateClientAuthentication(); clientAuthentication.Authenticate(this, session); IsAuthenticated = true; } /// /// Signals that an authentication banner message was received from the server. /// /// The session in which the banner message was received. /// The banner message. void IConnectionInfoInternal.UserAuthenticationBannerReceived(object sender, MessageEventArgs e) { AuthenticationBanner?.Invoke(this, new AuthenticationBannerEventArgs(Username, e.Message.Message, e.Message.Language)); } /// /// Creates a none authentication method. /// /// /// A none authentication method. /// IAuthenticationMethod IConnectionInfoInternal.CreateNoneAuthenticationMethod() { return new NoneAuthenticationMethod(Username); } /// /// Gets the supported authentication methods for this connection. /// /// /// The supported authentication methods for this connection. /// IList IConnectionInfoInternal.AuthenticationMethods { #pragma warning disable S2365 // Properties should not make collection or array copies get { return AuthenticationMethods.Cast().ToList(); } #pragma warning restore S2365 // Properties should not make collection or array copies } } }